diff --git a/app/Console/Commands/BadgesPrintCommand.php b/app/Console/Commands/BadgesPrintCommand.php index 34033577..49200351 100644 --- a/app/Console/Commands/BadgesPrintCommand.php +++ b/app/Console/Commands/BadgesPrintCommand.php @@ -3,7 +3,7 @@ namespace App\Console\Commands; use App\Models\Badge\Badge; -use App\Models\Badge\States\Printed; +use App\Models\Badge\State_Fulfillment\Printed; use Illuminate\Console\Command; class BadgesPrintCommand extends Command @@ -16,7 +16,7 @@ public function handle(): void { // Prints all badges Badge::whereNull('printed_at')->update([ - 'status' => Printed::$name, + 'status_fulfillment' => Printed::$name, 'printed_at' => now(), ]); } diff --git a/app/Console/Commands/BadgesUnprintCommand.php b/app/Console/Commands/BadgesUnprintCommand.php index 68973086..375cfe2f 100644 --- a/app/Console/Commands/BadgesUnprintCommand.php +++ b/app/Console/Commands/BadgesUnprintCommand.php @@ -3,7 +3,7 @@ namespace App\Console\Commands; use App\Models\Badge\Badge; -use App\Models\Badge\States\Pending; +use App\Models\Badge\State_Fulfillment\Pending; use Illuminate\Console\Command; class BadgesUnprintCommand extends Command @@ -16,7 +16,7 @@ public function handle(): void { // Unprint all badges Badge::whereNotNull('printed_at')->update([ - 'status' => Pending::$name, + 'status_fulfillment' => Pending::$name, 'printed_at' => null, ]); } diff --git a/app/Domain/Checkout/Models/Checkout/States/Active.php b/app/Domain/Checkout/Models/Checkout/States/Active.php index 16867cd1..1a04e7f8 100644 --- a/app/Domain/Checkout/Models/Checkout/States/Active.php +++ b/app/Domain/Checkout/Models/Checkout/States/Active.php @@ -2,13 +2,12 @@ namespace App\Domain\Checkout\Models\Checkout\States; -use App\Models\Badge\States\BadgeStatusState; class Active extends CheckoutStatusState { - public static string $name = 'ACTIVE'; - public function color(): string - { - // TODO: Implement color() method. - } + public static string $name = 'ACTIVE'; + public function color(): string + { + // TODO: Implement color() method. + } } diff --git a/app/Domain/Checkout/Models/Checkout/States/Cancelled.php b/app/Domain/Checkout/Models/Checkout/States/Cancelled.php index af9d2019..92952f61 100644 --- a/app/Domain/Checkout/Models/Checkout/States/Cancelled.php +++ b/app/Domain/Checkout/Models/Checkout/States/Cancelled.php @@ -2,13 +2,11 @@ namespace App\Domain\Checkout\Models\Checkout\States; -use App\Models\Badge\States\BadgeStatusState; - class Cancelled extends CheckoutStatusState { - public static string $name = 'CANCELLED'; - public function color(): string - { - // TODO: Implement color() method. - } + public static string $name = 'CANCELLED'; + public function color(): string + { + // TODO: Implement color() method. + } } diff --git a/app/Domain/Checkout/Models/Checkout/States/Finished.php b/app/Domain/Checkout/Models/Checkout/States/Finished.php index aec62a14..83b95e0c 100644 --- a/app/Domain/Checkout/Models/Checkout/States/Finished.php +++ b/app/Domain/Checkout/Models/Checkout/States/Finished.php @@ -2,13 +2,11 @@ namespace App\Domain\Checkout\Models\Checkout\States; -use App\Models\Badge\States\BadgeStatusState; - class Finished extends CheckoutStatusState { - public static string $name = 'FINISHED'; - public function color(): string - { - // TODO: Implement color() method. - } + public static string $name = 'FINISHED'; + public function color(): string + { + // TODO: Implement color() method. + } } diff --git a/app/Domain/Checkout/Models/Checkout/Transitions/ToFinished.php b/app/Domain/Checkout/Models/Checkout/Transitions/ToFinished.php index b126528f..73680091 100644 --- a/app/Domain/Checkout/Models/Checkout/Transitions/ToFinished.php +++ b/app/Domain/Checkout/Models/Checkout/Transitions/ToFinished.php @@ -6,7 +6,8 @@ use App\Domain\Checkout\Models\Checkout\States\Cancelled; use App\Domain\Checkout\Models\Checkout\States\Finished; use App\Domain\Checkout\Services\FiskalyService; -use App\Models\Badge\States\ReadyForPickup; +use App\Models\Badge\State_Fulfillment\ReadyForPickup; +use App\Models\Badge\State_Payment\Paid; use Illuminate\Support\Facades\DB; use Spatie\ModelStates\Transition; @@ -26,8 +27,8 @@ public function handle() $fiskalyService->updateOrCreateTransaction($this->checkout); $this->checkout->items->each(function ($item) { - if ($item->payable->status->canTransitionTo(ReadyForPickup::class)) { - $item->payable->status->transitionTo(ReadyForPickup::class); + if ($item->payable->status_payment->canTransitionTo(Paid::class)) { + $item->payable->status_payment->transitionTo(Paid::class); } }); diff --git a/app/Filament/Resources/BadgeResource.php b/app/Filament/Resources/BadgeResource.php index 3f37bf0a..e7958524 100644 --- a/app/Filament/Resources/BadgeResource.php +++ b/app/Filament/Resources/BadgeResource.php @@ -5,11 +5,14 @@ use App\Filament\Resources\BadgeResource\Pages; use App\Jobs\Printing\PrintBadgeJob; use App\Models\Badge\Badge; -use App\Models\Badge\States\BadgeStatusState; -use App\Models\Badge\States\Pending; -use App\Models\Badge\States\PickedUp; -use App\Models\Badge\States\Printed; -use App\Models\Badge\States\ReadyForPickup; +use App\Models\Badge\State_Fulfillment\BadgeFulfillmentStatusState; +use App\Models\Badge\State_Fulfillment\Pending; +use App\Models\Badge\State_Fulfillment\PickedUp; +use App\Models\Badge\State_Fulfillment\Printed; +use App\Models\Badge\State_Fulfillment\ReadyForPickup; +use App\Models\Badge\State_Payment\BadgePaymentStatusState; +use App\Models\Badge\State_Payment\Paid; +use App\Models\Badge\State_Payment\Unpaid; use App\Models\Fursuit\States\FursuitStatusState; use App\Models\Machine; use Filament\Forms; @@ -37,9 +40,13 @@ public static function form(Form $form): Form ->relationship('fursuit', 'name') ->required(), // Status - Forms\Components\Select::make('status') - ->label('Status') - ->options(BadgeStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [$key => ucfirst($key)])) + Forms\Components\Select::make('status_fulfillment') + ->label('status_fulfillment') + ->options(BadgeFulfillmentStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [$key => ucfirst($key)])) + ->required(), + Forms\Components\Select::make('status_payment') + ->label('status_payment') + ->options(BadgePaymentStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [$key => ucfirst($key)])) ->required(), Forms\Components\Group::make([ // Total @@ -64,25 +71,32 @@ public static function table(Table $table): Table Tables\Columns\TextColumn::make('custom_id') ->sortable() ->searchable() - ->label('Fursuit'), + ->label('Custom ID'), Tables\Columns\TextColumn::make('fursuit.user.attendee_id') ->sortable() ->searchable() - ->label('Fursuit'), + ->label('Attendee ID'), Tables\Columns\TextColumn::make('fursuit.name') ->searchable() ->label('Fursuit'), - Tables\Columns\TextColumn::make('status')->badge()->colors([ + Tables\Columns\TextColumn::make('status_fulfillment')->badge()->colors([ Pending::$name => 'default', Printed::$name => 'success', ReadyForPickup::$name => 'success', PickedUp::$name => 'warning', ]), + Tables\Columns\TextColumn::make('status_payment')->badge()->colors([ + Unpaid::$name => 'warning', + Paid::$name => 'success', + ]), ]) ->filters([ - Tables\Filters\SelectFilter::make('status') - ->options(BadgeStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [ucfirst($key) => $key])) - ->label('Badge Status'), + Tables\Filters\SelectFilter::make('status_fulfillment') + ->options(BadgeFulfillmentStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [ucfirst($key) => $key])) + ->label('Badge Fulfillment Status'), + Tables\Filters\SelectFilter::make('status_payment') + ->options(BadgePaymentStatusState::getStateMapping()->keys()->mapWithKeys(fn($key) => [ucfirst($key) => $key])) + ->label('Badge Payment Status'), // Duplex Bool Filter Tables\Filters\TernaryFilter::make('dual_side_print') ->label('Double Sided'), @@ -114,10 +128,10 @@ public static function table(Table $table): Table ->defaultSort('fursuit.user.attendee_id', 'asc'); } - public static function printBadge(Badge $badge,$mass = 0): Badge + public static function printBadge(Badge $badge, $mass = 0): Badge { - if ($badge->status !== Printed::class && $badge->status->canTransitionTo(Printed::class)) { - $badge->status->transitionTo(Printed::class); + if ($badge->status_fulfillment->canTransitionTo(Printed::class)) { + $badge->status_fulfillment->transitionTo(Printed::class); } // Add delay for mass printing so they are generated in order PrintBadgeJob::dispatch($badge)->delay(now()->addSeconds($mass * 15)); diff --git a/app/Http/Controllers/BadgeController.php b/app/Http/Controllers/BadgeController.php index eef4756e..38df1b92 100644 --- a/app/Http/Controllers/BadgeController.php +++ b/app/Http/Controllers/BadgeController.php @@ -5,12 +5,12 @@ use App\Http\Requests\BadgeCreateRequest; use App\Http\Requests\BadgeUpdateRequest; use App\Models\Badge\Badge; +use App\Models\Badge\State_Payment\Paid; +use App\Models\Badge\State_Payment\Unpaid; use App\Models\Event; -use App\Models\Fursuit\States\Pending; use App\Models\Species; use App\Models\User; use App\Notifications\BadgeCreatedNotification; -use App\Notifications\FursuitApprovedNotification; use App\Services\BadgeCalculationService; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -56,7 +56,7 @@ public function store(BadgeCreateRequest $request) $validated = $request->validated(); // Create Fursuit $fursuit = $request->user()->fursuits()->create([ - 'status' => Pending::$name, + 'status' => \App\Models\Fursuit\States\Pending::$name, 'event_id' => $event->id, 'species_id' => Species::firstOrCreate([ 'name' => $validated['species'], @@ -80,11 +80,12 @@ public function store(BadgeCreateRequest $request) ); // Tax is 19% in Germany - $subtotal = round($total / 1.19,); + $subtotal = round($total / 1.19, ); $tax = round($total - $subtotal); $badge = $fursuit->badges()->create([ - 'status' => \App\Models\Badge\States\Pending::$name, + 'status_fulfillment' => \App\Models\Badge\State_Fulfillment\Pending::$name, + 'status_payment' => $total === 0 ? Paid::$name : Unpaid::class, 'subtotal' => round($subtotal), 'tax_rate' => 0.19, 'tax' => round($tax), @@ -92,6 +93,7 @@ public function store(BadgeCreateRequest $request) 'dual_side_print' => true, 'is_free_badge' => $isFreeBadge, 'apply_late_fee' => $event->preorder_ends_at->isPast(), + 'paid_at' => $total === 0 ? now() : null, ]); // Pay for Badge (force pay as we allow negative balance) $request->user()->forcePay($badge); @@ -105,9 +107,12 @@ public function store(BadgeCreateRequest $request) $clone->subtotal = round($total / 1.19); $clone->tax = round($clone->total - $clone->subtotal); $clone->extra_copy_of = $badge->id; + $clone->status_payment = Unpaid::class; + $clone->paid_at = null; // Spare copies are not paid immediately $clone->save(); $request->user()->forcePay($clone->fresh()); } + return $badge; }); @@ -158,7 +163,7 @@ public function update(BadgeUpdateRequest $request, Badge $badge) } // if species_id, name or image changed, status goes back to pending review if ($fursuit->isDirty(['species_id', 'name', 'image'])) { - $fursuit->status = Pending::$name; + $fursuit->status = \App\Models\Badge\State_Fulfillment\Pending::$name; } $fursuit->save(); /** @@ -169,7 +174,7 @@ public function update(BadgeUpdateRequest $request, Badge $badge) isFreeBadge: $badge->is_free_badge, isLate: $badge->apply_late_fee, ); - if($previousTotal !== $total) { + if ($previousTotal !== $total) { $badge->fursuit->user->refund($badge); } $badge->total = round($total); diff --git a/app/Http/Controllers/POS/BadgeController.php b/app/Http/Controllers/POS/BadgeController.php index 98d093f4..e019a23e 100644 --- a/app/Http/Controllers/POS/BadgeController.php +++ b/app/Http/Controllers/POS/BadgeController.php @@ -4,8 +4,8 @@ use App\Http\Controllers\Controller; use App\Models\Badge\Badge; -use App\Models\Badge\States\PickedUp; -use App\Models\Badge\States\ReadyForPickup; +use App\Models\Badge\State_Fulfillment\PickedUp; +use App\Models\Badge\State_Fulfillment\ReadyForPickup; use Illuminate\Http\Request; use function Symfony\Component\String\b; @@ -17,8 +17,8 @@ public function handoutBulk(Request $request) $badgeIds = $request->input('badge_ids'); $badges = Badge::whereIn('id', $badgeIds)->get(); $badges->each(function ($badge) { - if($badge->status->canTransitionTo(PickedUp::class)) { - $badge->status->transitionTo(PickedUp::class); + if($badge->status_fulfillment->canTransitionTo(PickedUp::class)) { + $badge->status_fulfillment->transitionTo(PickedUp::class); } else { $someError = true; } @@ -30,8 +30,8 @@ public function handoutBulk(Request $request) } public function handout(Badge $badge) { - if($badge->status->canTransitionTo(PickedUp::class)) { - $badge->status->transitionTo(PickedUp::class); + if($badge->status_fulfillment->canTransitionTo(PickedUp::class)) { + $badge->status_fulfillment->transitionTo(PickedUp::class); return back()->with('success', 'Badge handed out'); } else { return back()->with('error', 'Badge cannot be handed out'); @@ -40,8 +40,8 @@ public function handout(Badge $badge) public function handoutUndo(Badge $badge) { - if($badge->status->canTransitionTo(ReadyForPickup::class)) { - $badge->status->transitionTo(ReadyForPickup::class); + if($badge->status_fulfillment->canTransitionTo(ReadyForPickup::class)) { + $badge->status_fulfillment->transitionTo(ReadyForPickup::class); return back()->with('success', 'Badge handout undone'); } return back()->with('error', 'Badge handout cannot be undone'); diff --git a/app/Http/Controllers/POS/CheckoutController.php b/app/Http/Controllers/POS/CheckoutController.php index f1907cf6..3975ddbd 100644 --- a/app/Http/Controllers/POS/CheckoutController.php +++ b/app/Http/Controllers/POS/CheckoutController.php @@ -11,6 +11,7 @@ use App\Domain\Checkout\Services\FiskalyService; use App\Http\Controllers\Controller; use App\Models\Badge\Badge; +use App\Models\Badge\State_Payment\Unpaid; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; @@ -63,12 +64,12 @@ public function store(Request $request) if (empty($data['badge_ids'])) { $data['badge_ids'] = Badge::whereHas('fursuit.user', function ($query) use ($data) { $query->where('id', $data['user_id']); - })->where('status', 'unpaid')->pluck('id')->toArray(); + })->where('status_payment', Unpaid::$name)->pluck('id')->toArray(); } else { $data['badge_ids'] = Badge::whereHas('fursuit.user', function ($query) use ($data) { $query->where('id', $data['user_id']); - })->where('status', 'unpaid') + })->where('status_payment', Unpaid::$name) ->whereIn('id', $data['badge_ids']) ->pluck('id')->toArray(); } diff --git a/app/Http/Controllers/POS/Printing/PrintBadgeController.php b/app/Http/Controllers/POS/Printing/PrintBadgeController.php index 4bce9432..9452ab9f 100644 --- a/app/Http/Controllers/POS/Printing/PrintBadgeController.php +++ b/app/Http/Controllers/POS/Printing/PrintBadgeController.php @@ -2,23 +2,19 @@ namespace App\Http\Controllers\POS\Printing; -use App\Badges\EF28_Badge; -use App\Enum\PrintJobStatusEnum; -use App\Enum\PrintJobTypeEnum; + use App\Http\Controllers\Controller; use App\Jobs\Printing\PrintBadgeJob; use App\Models\Badge\Badge; -use App\Models\Badge\States\Printed; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Storage; +use App\Models\Badge\State_Fulfillment\Printed; class PrintBadgeController extends Controller { public function __invoke(Badge $badge) { PrintBadgeJob::dispatch($badge); - if($badge->status !== Printed::class && $badge->status->canTransitionTo(Printed::class)) { - $badge->status->transitionTo(Printed::class); + if($badge->status_fulfillment->canTransitionTo(Printed::class)) { + $badge->status_fulfillment->transitionTo(Printed::class); } // dispatch print job return redirect()->back()->with('success', 'Badge has been added to the print queue'); diff --git a/app/Jobs/Printing/PrintBadgeJob.php b/app/Jobs/Printing/PrintBadgeJob.php index 91280fad..eda4f127 100644 --- a/app/Jobs/Printing/PrintBadgeJob.php +++ b/app/Jobs/Printing/PrintBadgeJob.php @@ -7,7 +7,7 @@ use App\Enum\PrintJobStatusEnum; use App\Enum\PrintJobTypeEnum; use App\Models\Badge\Badge; -use App\Models\Badge\States\Printed; +use App\Models\Badge\State_Fulfillment\Printed; use App\Models\Machine; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -26,8 +26,8 @@ public function __construct(private readonly Badge $badge) public function handle(): void { - if($this->badge->status !== Printed::class && $this->badge->status->canTransitionTo(Printed::class)) { - $this->badge->status->transitionTo(Printed::class); + if($this->badge->status_fulfillment->canTransitionTo(Printed::class)) { + $this->badge->status_fulfillment->transitionTo(Printed::class); } diff --git a/app/Models/Badge/Badge.php b/app/Models/Badge/Badge.php index 4102bceb..9374a1f2 100644 --- a/app/Models/Badge/Badge.php +++ b/app/Models/Badge/Badge.php @@ -3,7 +3,8 @@ namespace App\Models\Badge; use App\Domain\Printing\Models\PrintJob; -use App\Models\Badge\States\BadgeStatusState; +use App\Models\Badge\State_Fulfillment\BadgeFulfillmentStatusState; +use App\Models\Badge\State_Payment\BadgePaymentStatusState; use App\Models\Fursuit\Fursuit; use Bavix\Wallet\Interfaces\Customer; use Bavix\Wallet\Interfaces\ProductInterface; @@ -22,7 +23,8 @@ class Badge extends Model implements ProductInterface protected $guarded = []; protected $casts = [ - 'status' => BadgeStatusState::class, + 'status_fulfillment' => BadgeFulfillmentStatusState::class, + 'status_payment' => BadgePaymentStatusState::class, 'extra_copy' => 'boolean', 'dual_side_print' => 'boolean', 'apply_late_fee' => 'boolean', @@ -51,14 +53,14 @@ public function getMetaProduct(): ?array { // Title Generator $features = []; - if($this->dual_side_print) { + if ($this->dual_side_print) { $features[] = 'Double Sided Print'; } - if($this->extra_copy_of) { + if ($this->extra_copy_of) { $features[] = 'Extra Copy'; } $append = ''; - if(count($features) > 0) { + if (count($features) > 0) { $append = ' with Extras (' . implode(', ', $features) . ')'; } return [ diff --git a/app/Models/Badge/State_Fulfillment/BadgeFulfillmentStatusState.php b/app/Models/Badge/State_Fulfillment/BadgeFulfillmentStatusState.php new file mode 100644 index 00000000..c11c5f6f --- /dev/null +++ b/app/Models/Badge/State_Fulfillment/BadgeFulfillmentStatusState.php @@ -0,0 +1,22 @@ +default(Pending::class) + ->allowTransition(Pending::class, Printed::class, ToPrinted::class) + ->allowTransition(Printed::class, ReadyForPickup::class) + ->allowTransition(PickedUp::class, ReadyForPickup::class) // Incase of pos user error we can revert the status + ->allowTransition(ReadyForPickup::class, PickedUp::class); + } +} diff --git a/app/Models/Badge/State_Fulfillment/Pending.php b/app/Models/Badge/State_Fulfillment/Pending.php new file mode 100644 index 00000000..435abee8 --- /dev/null +++ b/app/Models/Badge/State_Fulfillment/Pending.php @@ -0,0 +1,12 @@ +custom id should contain attendee id - int id + // int id should count up starting from one, the db field is unique so use while loop to find the next available id + // Prevent race condition by locking the table via user id + DB::transaction(function () { + $nextId = 1; + + $user = $this->badge->fursuit->user()->lockForUpdate()->first(); + do { + $customId = "{$user->attendee_id}-" . $nextId++; + } while (Badge::where('custom_id', $customId)->exists()); + $this->badge->custom_id = $customId; + + if ($this->badge->status_payment->equals(Paid::class)) { + // we will skip the printed state and go directly to ready for pickup + $this->badge->status_fulfillment = new ReadyForPickup($this->badge); + } else { + $this->badge->status_fulfillment = new Printed($this->badge); + } + + $this->badge->printed_at = now(); + $this->badge->save(); + }, 5); + + activity() + ->performedOn($this->badge) + ->log('Fursuit Printed'); + return $this->badge; + }); + } +} diff --git a/app/Models/Badge/State_Fulfillment/Transitions/ToReadyForPickup.php b/app/Models/Badge/State_Fulfillment/Transitions/ToReadyForPickup.php new file mode 100644 index 00000000..3d87efcf --- /dev/null +++ b/app/Models/Badge/State_Fulfillment/Transitions/ToReadyForPickup.php @@ -0,0 +1,29 @@ +badge->status = new ReadyForPickup($this->badge); // we will skip the printed state and go directly to ready for pickup + $this->badge->paid_at = now(); + $this->badge->save(); + + activity() + ->performedOn($this->badge) + ->log('Fursuit Badge Paid'); + return $this->badge; + }); + } +} diff --git a/app/Models/Badge/State_Payment/BadgePaymentStatusState.php b/app/Models/Badge/State_Payment/BadgePaymentStatusState.php new file mode 100644 index 00000000..d6ac899d --- /dev/null +++ b/app/Models/Badge/State_Payment/BadgePaymentStatusState.php @@ -0,0 +1,19 @@ +default(Unpaid::class) + ->allowTransition(Unpaid::class, Paid::class, ToPaid::class); + } +} diff --git a/app/Models/Badge/State_Payment/Paid.php b/app/Models/Badge/State_Payment/Paid.php new file mode 100644 index 00000000..9e93bac5 --- /dev/null +++ b/app/Models/Badge/State_Payment/Paid.php @@ -0,0 +1,13 @@ +badge->status_fulfillment->equals(Printed::class)) { + $this->badge->status_fulfillment = new ReadyForPickup($this->badge); + } + + $this->badge->status_payment = new Paid($this->badge); + + $this->badge->paid_at = now(); + $this->badge->save(); + + activity() + ->performedOn($this->badge) + ->log('Fursuit Badge Paid'); + return $this->badge; + }); + } +} diff --git a/app/Models/Badge/State_Payment/Unpaid.php b/app/Models/Badge/State_Payment/Unpaid.php new file mode 100644 index 00000000..f1cb7621 --- /dev/null +++ b/app/Models/Badge/State_Payment/Unpaid.php @@ -0,0 +1,13 @@ +default(Pending::class) - ->allowTransition(Pending::class, Printed::class, ToPrinted::class) - ->allowTransition(Printed::class, PendingPayment::class) - ->allowTransition(Printed::class, ReadyForPickup::class) - ->allowTransition(PendingPayment::class, ReadyForPickup::class) - ->allowTransition(PickedUp::class, ReadyForPickup::class) // Incase of pos user error we can revert the status - ->allowTransition(ReadyForPickup::class, PickedUp::class); - } -} diff --git a/app/Models/Badge/States/Pending.php b/app/Models/Badge/States/Pending.php deleted file mode 100644 index 68661a17..00000000 --- a/app/Models/Badge/States/Pending.php +++ /dev/null @@ -1,14 +0,0 @@ -custom id should contain attendee id - int id - // int id should count up starting from one, the db field is unique so use while loop to find the next available id - // Prevent race condition by locking the table via user id - DB::transaction(function () { - $nextId = 1; - - $user = $this->badge->fursuit->user()->lockForUpdate()->first(); - do { - $customId = "{$user->attendee_id}-" . $nextId++; - } while (Badge::where('custom_id', $customId)->exists()); - $this->badge->custom_id = $customId; - - if($this->badge->total === 0) { - // we will skip the printed state and go directly to ready for pickup - $this->badge->status = new ReadyForPickup($this->badge); - } else { - $this->badge->status = new PendingPayment($this->badge); - } - - $this->badge->printed_at = now(); - $this->badge->save(); - }, 5); - - activity() - ->performedOn($this->badge) - ->log('Fursuit Printed'); - return $this->badge; - }); - } -} diff --git a/app/Models/Badge/States/Transitions/ToReadyForPickup.php b/app/Models/Badge/States/Transitions/ToReadyForPickup.php deleted file mode 100644 index ccc42c58..00000000 --- a/app/Models/Badge/States/Transitions/ToReadyForPickup.php +++ /dev/null @@ -1,31 +0,0 @@ -badge->status = new ReadyForPickup($this->badge); // we will skip the printed state and go directly to ready for pickup - $this->badge->paid_at = now(); - $this->badge->save(); - - activity() - ->performedOn($this->badge) - ->log('Fursuit Badge Paid'); - return $this->badge; - }); - } -} diff --git a/app/Policies/BadgePolicy.php b/app/Policies/BadgePolicy.php index 770e914c..22b340c0 100644 --- a/app/Policies/BadgePolicy.php +++ b/app/Policies/BadgePolicy.php @@ -2,12 +2,10 @@ namespace App\Policies; -use App\Enum\EventStateEnum; use App\Models\Badge\Badge; -use App\Models\Badge\States\Pending; +use App\Models\Badge\State_Fulfillment\Pending; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; -use Illuminate\Routing\Route; class BadgePolicy { @@ -77,7 +75,7 @@ public function delete(User $user, Badge $badge): bool return false; } // Cannot edit a badge that has already been printed - if ((string) $badge->status !== Pending::$name) { + if (!$badge->status_fulfillment->equals(Pending::class)) { return false; } diff --git a/database/factories/Badge/BadgeFactory.php b/database/factories/Badge/BadgeFactory.php index a8b38baf..45b53317 100644 --- a/database/factories/Badge/BadgeFactory.php +++ b/database/factories/Badge/BadgeFactory.php @@ -3,10 +3,12 @@ namespace Database\Factories\Badge; use App\Models\Badge\Badge; -use App\Models\Badge\States\Pending; -use App\Models\Badge\States\PickedUp; -use App\Models\Badge\States\Printed; -use App\Models\Badge\States\ReadyForPickup; +use App\Models\Badge\State_Fulfillment\Pending; +use App\Models\Badge\State_Fulfillment\PickedUp; +use App\Models\Badge\State_Fulfillment\Printed; +use App\Models\Badge\State_Fulfillment\ReadyForPickup; +use App\Models\Badge\State_Payment\Paid; +use App\Models\Badge\State_Payment\Unpaid; use App\Models\Fursuit\Fursuit; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Carbon; @@ -20,12 +22,16 @@ public function definition(): array return [ 'is_free_badge' => $this->faker->boolean(), 'extra_copy_of' => null, - 'status' => $this->faker->randomElement([ + 'status_fulfillment' => $this->faker->randomElement([ Pending::$name, Printed::$name, ReadyForPickup::$name, PickedUp::$name ]), + 'status_payment' => $this->faker->randomElement([ + Paid::$name, + Unpaid::$name + ]), 'dual_side_print' => $this->faker->boolean(), 'extra_copy' => $this->faker->boolean(), 'apply_late_fee' => $this->faker->boolean(), diff --git a/database/migrations/2025_07_15_193314_split_status_in_badges_table.php b/database/migrations/2025_07_15_193314_split_status_in_badges_table.php new file mode 100644 index 00000000..3a086728 --- /dev/null +++ b/database/migrations/2025_07_15_193314_split_status_in_badges_table.php @@ -0,0 +1,55 @@ +renameColumn('status', 'status_fulfillment'); + $table->string('status_payment', 255)->after('status_fulfillment')->nullable(); // Temporarily nullable to avoid issues with existing data + }); + + // Payment status migration + DB::statement('UPDATE badges SET status_payment = "paid" WHERE status_fulfillment IN ("ready_for_pickup", "picked_up") OR total = 0'); + DB::statement('UPDATE badges SET status_payment = "unpaid" WHERE status_payment IS NULL'); + + // Fulfillment status migration + DB::statement('UPDATE badges SET status_fulfillment = "printed" WHERE status_fulfillment IN ("printed", "unpaid")'); + // No-op updates for completeness + DB::statement('UPDATE badges SET status_fulfillment = "ready_for_pickup" WHERE status_fulfillment = "ready_for_pickup"'); + DB::statement('UPDATE badges SET status_fulfillment = "picked_up" WHERE status_fulfillment = "picked_up"'); + DB::statement('UPDATE badges SET status_fulfillment = "pending" WHERE status_fulfillment = "pending"'); + + + Schema::table('badges', function (Blueprint $table) { + $table->string('status_payment', 255)->after('status_fulfillment')->nullable(false)->change(); // Make it non-nullable after migration + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Restore kinda the original values of status + // DOES NOT RESTORE THE OLD DATA 100% + DB::statement('UPDATE badges SET status_fulfillment = "printed" WHERE status_fulfillment = "printed"'); + DB::statement('UPDATE badges SET status_fulfillment = "ready_for_pickup" WHERE status_fulfillment = "ready_for_pickup"'); + DB::statement('UPDATE badges SET status_fulfillment = "picked_up" WHERE status_fulfillment = "picked_up"'); + DB::statement('UPDATE badges SET status_fulfillment = "pending" WHERE status_fulfillment = "pending"'); + DB::statement('UPDATE badges SET status_fulfillment = "unpaid" WHERE status_payment = "unpaid"'); + + Schema::table('badges', function (Blueprint $table) { + // Restore the original structure + $table->renameColumn('status_fulfillment', 'status'); + $table->dropColumn('status_payment'); + }); + } +}; diff --git a/resources/js/Components/BadgeListItem.vue b/resources/js/Components/BadgeListItem.vue index 298bcbb1..b1af7fff 100644 --- a/resources/js/Components/BadgeListItem.vue +++ b/resources/js/Components/BadgeListItem.vue @@ -60,8 +60,6 @@ function getBadgeSeverity(status) { return 'info'; case 'ready_for_pickup': return 'success'; - case 'unpaid': - return 'error'; case 'picked_up': return 'success'; } @@ -86,24 +84,20 @@ function getBadgeTooltipText(status) {
{{ badge.fursuit.species.name }}
{{ formatEuroFromCents(badge.total) }}
Price