command: sed -n '240,520p' app/Services/OrderService.php

aggregated_output:
        return $prefix.((int) max($start - 1, $maxExisting ?? 0) + 1);
    }

    private function sequenceFromOrderNumber(string $orderNumber): ?int
    {
        $digits = preg_replace('/\D+/', '', $orderNumber);

        return $digits === '' ? null : (int) $digits;
    }

    private function createOrderLines(Checkout $checkout, Order $order): void
    {
        $this->cartLines($checkout)->each(function (CartLine $line) use ($checkout, $order): void {
            $variant = $line->variant;

            $order->lines()->create([
                'product_id' => $variant?->product_id,
                'variant_id' => $variant?->getKey(),
                'title_snapshot' => $this->titleSnapshot($variant),
                'sku_snapshot' => $variant?->sku,
                'quantity' => $line->quantity,
                'unit_price_amount' => $line->unit_price_amount,
                'total_amount' => $line->line_total_amount,
                'tax_lines_json' => [],
                'discount_allocations_json' => $this->discountAllocations($checkout, $line),
            ]);
        });
    }

    private function titleSnapshot(?ProductVariant $variant): string
    {
        $title = $variant?->product?->title ?? 'Product';
        $optionValues = $variant?->optionValues
            ->sortBy(fn (ProductOptionValue $value): int => $value->option?->position ?? $value->position)
            ->pluck('value')
            ->filter()
            ->implode(' / ');

        return $optionValues ? "{$title} - {$optionValues}" : $title;
    }

    /**
     * @return array<int, array{code: string, amount: int}>
     */
    private function discountAllocations(Checkout $checkout, CartLine $line): array
    {
        if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
            return [];
        }

        return [[
            'code' => $checkout->discount_code,
            'amount' => $line->line_discount_amount,
        ]];
    }

    private function commitReservedInventory(Checkout $checkout): void
    {
        $this->cartLines($checkout)->each(function (CartLine $line): void {
            $this->inventory->commit($this->inventoryItemForVariant($line->variant_id), $line->quantity);
        });
    }

    private function commitReservedOrderInventory(Order $order): void
    {
        $order->lines->each(function (OrderLine $line): void {
            if ($line->variant_id !== null) {
                $this->inventory->commit($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            }
        });
    }

    private function releaseReservedOrderInventory(Order $order): void
    {
        $order->lines->each(function (OrderLine $line): void {
            if ($line->variant_id !== null) {
                $this->inventory->release($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            }
        });
    }

    private function releaseFailedPaymentReservation(Checkout $checkout): void
    {
        DB::transaction(function () use ($checkout): void {
            $checkout = $this->freshCheckout($checkout);

            if ($checkout->status !== CheckoutStatus::PaymentSelected) {
                return;
            }

            $this->cartLines($checkout)->each(function (CartLine $line): void {
                $this->inventory->release($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            });

            $checkout->forceFill([
                'status' => CheckoutStatus::ShippingSelected,
                'payment_method' => null,
                'expires_at' => null,
            ])->save();
        });
    }

    private function incrementDiscountUsage(Checkout $checkout): void
    {
        $code = trim((string) $checkout->discount_code);

        if ($code === '') {
            return;
        }

        Discount::withoutGlobalScopes()
            ->where('store_id', $checkout->store_id)
            ->whereRaw('lower(code) = ?', [mb_strtolower($code)])
            ->increment('usage_count');
    }

    private function assertPendingBankTransfer(Order $order): void
    {
        if ($order->payment_method !== PaymentMethod::BankTransfer || $order->financial_status !== FinancialStatus::Pending) {
            throw InvalidOrderOperationException::because('Order is not a pending bank transfer order.');
        }
    }

    private function inventoryItemForVariant(int $variantId): InventoryItem
    {
        return InventoryItem::withoutGlobalScopes()
            ->where('variant_id', $variantId)
            ->firstOrFail();
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function cartLines(Checkout $checkout): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->with([
                'variant' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.product' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.optionValues' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.optionValues.option' => fn ($query) => $query->withoutGlobalScopes(),
            ])
            ->where('cart_id', $checkout->cart_id)
            ->orderBy('id')
            ->get();
    }
}


output:
Chunk ID: 0a227b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1313
Output:

        return $prefix.((int) max($start - 1, $maxExisting ?? 0) + 1);
    }

    private function sequenceFromOrderNumber(string $orderNumber): ?int
    {
        $digits = preg_replace('/\D+/', '', $orderNumber);

        return $digits === '' ? null : (int) $digits;
    }

    private function createOrderLines(Checkout $checkout, Order $order): void
    {
        $this->cartLines($checkout)->each(function (CartLine $line) use ($checkout, $order): void {
            $variant = $line->variant;

            $order->lines()->create([
                'product_id' => $variant?->product_id,
                'variant_id' => $variant?->getKey(),
                'title_snapshot' => $this->titleSnapshot($variant),
                'sku_snapshot' => $variant?->sku,
                'quantity' => $line->quantity,
                'unit_price_amount' => $line->unit_price_amount,
                'total_amount' => $line->line_total_amount,
                'tax_lines_json' => [],
                'discount_allocations_json' => $this->discountAllocations($checkout, $line),
            ]);
        });
    }

    private function titleSnapshot(?ProductVariant $variant): string
    {
        $title = $variant?->product?->title ?? 'Product';
        $optionValues = $variant?->optionValues
            ->sortBy(fn (ProductOptionValue $value): int => $value->option?->position ?? $value->position)
            ->pluck('value')
            ->filter()
            ->implode(' / ');

        return $optionValues ? "{$title} - {$optionValues}" : $title;
    }

    /**
     * @return array<int, array{code: string, amount: int}>
     */
    private function discountAllocations(Checkout $checkout, CartLine $line): array
    {
        if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
            return [];
        }

        return [[
            'code' => $checkout->discount_code,
            'amount' => $line->line_discount_amount,
        ]];
    }

    private function commitReservedInventory(Checkout $checkout): void
    {
        $this->cartLines($checkout)->each(function (CartLine $line): void {
            $this->inventory->commit($this->inventoryItemForVariant($line->variant_id), $line->quantity);
        });
    }

    private function commitReservedOrderInventory(Order $order): void
    {
        $order->lines->each(function (OrderLine $line): void {
            if ($line->variant_id !== null) {
                $this->inventory->commit($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            }
        });
    }

    private function releaseReservedOrderInventory(Order $order): void
    {
        $order->lines->each(function (OrderLine $line): void {
            if ($line->variant_id !== null) {
                $this->inventory->release($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            }
        });
    }

    private function releaseFailedPaymentReservation(Checkout $checkout): void
    {
        DB::transaction(function () use ($checkout): void {
            $checkout = $this->freshCheckout($checkout);

            if ($checkout->status !== CheckoutStatus::PaymentSelected) {
                return;
            }

            $this->cartLines($checkout)->each(function (CartLine $line): void {
                $this->inventory->release($this->inventoryItemForVariant($line->variant_id), $line->quantity);
            });

            $checkout->forceFill([
                'status' => CheckoutStatus::ShippingSelected,
                'payment_method' => null,
                'expires_at' => null,
            ])->save();
        });
    }

    private function incrementDiscountUsage(Checkout $checkout): void
    {
        $code = trim((string) $checkout->discount_code);

        if ($code === '') {
            return;
        }

        Discount::withoutGlobalScopes()
            ->where('store_id', $checkout->store_id)
            ->whereRaw('lower(code) = ?', [mb_strtolower($code)])
            ->increment('usage_count');
    }

    private function assertPendingBankTransfer(Order $order): void
    {
        if ($order->payment_method !== PaymentMethod::BankTransfer || $order->financial_status !== FinancialStatus::Pending) {
            throw InvalidOrderOperationException::because('Order is not a pending bank transfer order.');
        }
    }

    private function inventoryItemForVariant(int $variantId): InventoryItem
    {
        return InventoryItem::withoutGlobalScopes()
            ->where('variant_id', $variantId)
            ->firstOrFail();
    }

    /**
     * @return Collection<int, CartLine>
     */
    private function cartLines(Checkout $checkout): Collection
    {
        return CartLine::withoutGlobalScopes()
            ->with([
                'variant' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.product' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.optionValues' => fn ($query) => $query->withoutGlobalScopes(),
                'variant.optionValues.option' => fn ($query) => $query->withoutGlobalScopes(),
            ])
            ->where('cart_id', $checkout->cart_id)
            ->orderBy('id')
            ->get();
    }
}


cmd: sed -n '1,320p' app/Services/PricingEngine.php


command: sed -n '1,320p' app/Services/PricingEngine.php

PricingEngine.php

type: read

aggregated_output:
<?php

namespace App\Services;

use App\Exceptions\InvalidCheckoutTransitionException;
use App\Models\CartLine;
use App\Models\Checkout;
use App\Models\ShippingRate;
use App\Models\TaxSettings;
use App\ValueObjects\DiscountResult;
use App\ValueObjects\PricingResult;
use Illuminate\Support\Facades\DB;

class PricingEngine
{
    public function __construct(
        private readonly DiscountService $discounts,
        private readonly ShippingCalculator $shipping,
        private readonly TaxCalculator $taxes,
    ) {}

    public function calculate(Checkout $checkout): PricingResult
    {
        return DB::transaction(function () use ($checkout): PricingResult {
            $checkout = Checkout::withoutGlobalScopes()
                ->with(['cart.lines'])
                ->whereKey($checkout->getKey())
                ->firstOrFail();
            $cart = $checkout->cart;

            $this->resetLineAmounts($checkout);

            $lines = CartLine::withoutGlobalScopes()
                ->where('cart_id', $cart->getKey())
                ->orderBy('id')
                ->get();
            $subtotal = $lines->sum('line_subtotal_amount');
            $discountAmount = 0;
            $freeShipping = false;

            if ($checkout->discount_code) {
                $discount = $this->discounts->validate($checkout->discount_code, $checkout->store, $cart);
                $discountResult = $this->discounts->applyToCart($cart, $discount);
                $discountAmount += $discountResult->amount;
                $freeShipping = $freeShipping || $discountResult->freeShipping;
            }

            foreach ($this->discounts->automaticForCart($checkout->store, $cart) as $discount) {
                $discountResult = $this->discounts->applyToCart($cart, $discount);
                $discountAmount += $discountResult->amount;
                $freeShipping = $freeShipping || $discountResult->freeShipping;
            }

            $lines = CartLine::withoutGlobalScopes()
                ->where('cart_id', $cart->getKey())
                ->orderBy('id')
                ->get();
            $discountResult = new DiscountResult($discountAmount, [], $freeShipping);
            $shippingAmount = $this->shippingAmount($checkout, $discountResult);
            $taxSettings = $this->taxSettings($checkout);
            $taxResult = $this->taxes->calculateForAmounts(
                $lines->pluck('line_total_amount')->map(fn (int $amount): int => $amount)->all(),
                $shippingAmount,
                $taxSettings,
                $checkout->shipping_address_json ?? [],
            );
            $discountedSubtotal = $subtotal - $discountResult->amount;
            $total = $taxSettings->prices_include_tax
                ? $discountedSubtotal + $shippingAmount
                : $discountedSubtotal + $shippingAmount + $taxResult->totalAmount;

            $result = new PricingResult(
                subtotal: $subtotal,
                discount: $discountResult->amount,
                shipping: $shippingAmount,
                taxLines: $taxResult->taxLines,
                taxTotal: $taxResult->totalAmount,
                total: $total,
                currency: $cart->currency,
            );

            $checkout->forceFill([
                'tax_provider_snapshot_json' => $taxResult->toArray(),
                'totals_json' => $result->toArray(),
            ])->save();

            return $result;
        });
    }

    private function resetLineAmounts(Checkout $checkout): void
    {
        CartLine::withoutGlobalScopes()
            ->where('cart_id', $checkout->cart_id)
            ->get()
            ->each(function (CartLine $line): void {
                $subtotal = $line->unit_price_amount * $line->quantity;

                $line->forceFill([
                    'line_subtotal_amount' => $subtotal,
                    'line_discount_amount' => 0,
                    'line_total_amount' => $subtotal,
                ])->save();
            });
    }

    private function shippingAmount(Checkout $checkout, DiscountResult $discountResult): int
    {
        if (! $this->shipping->requiresShipping($checkout->cart)) {
            return 0;
        }

        if ($checkout->shipping_method_id === null) {
            return 0;
        }

        $rate = ShippingRate::withoutGlobalScopes()->find($checkout->shipping_method_id);

        if (! $rate instanceof ShippingRate) {
            throw InvalidCheckoutTransitionException::because('Shipping rate is not available.');
        }

        $amount = $this->shipping->calculate($rate, $checkout->cart);

        if ($amount === null) {
            throw InvalidCheckoutTransitionException::because('Shipping rate is not available for this cart.');
        }

        return $discountResult->freeShipping ? 0 : $amount;
    }

    private function taxSettings(Checkout $checkout): TaxSettings
    {
        return TaxSettings::withoutGlobalScopes()
            ->where('store_id', $checkout->store_id)
            ->first() ?? new TaxSettings([
                'store_id' => $checkout->store_id,
                'mode' => 'manual',
                'provider' => 'none',
                'prices_include_tax' => false,
                'config_json' => [
                    'default_rate_bps' => 0,
                    'shipping_taxable' => false,
                ],
            ]);
    }
}

status: completed


output:
Chunk ID: 3febdc
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1357
Output:
<?php

namespace App\Services;

use App\Exceptions\InvalidCheckoutTransitionException;
use App\Models\CartLine;
use App\Models\Checkout;
use App\Models\ShippingRate;
use App\Models\TaxSettings;
use App\ValueObjects\DiscountResult;
use App\ValueObjects\PricingResult;
use Illuminate\Support\Facades\DB;

class PricingEngine
{
    public function __construct(
        private readonly DiscountService $discounts,
        private readonly ShippingCalculator $shipping,
        private readonly TaxCalculator $taxes,
    ) {}

    public function calculate(Checkout $checkout): PricingResult
    {
        return DB::transaction(function () use ($checkout): PricingResult {
            $checkout = Checkout::withoutGlobalScopes()
                ->with(['cart.lines'])
                ->whereKey($checkout->getKey())
                ->firstOrFail();
            $cart = $checkout->cart;

            $this->resetLineAmounts($checkout);

            $lines = CartLine::withoutGlobalScopes()
                ->where('cart_id', $cart->getKey())
                ->orderBy('id')
                ->get();
            $subtotal = $lines->sum('line_subtotal_amount');
            $discountAmount = 0;
            $freeShipping = false;

            if ($checkout->discount_code) {
                $discount = $this->discounts->validate($checkout->discount_code, $checkout->store, $cart);
                $discountResult = $this->discounts->applyToCart($cart, $discount);
                $discountAmount += $discountResult->amount;
                $freeShipping = $freeShipping || $discountResult->freeShipping;
            }

            foreach ($this->discounts->automaticForCart($checkout->store, $cart) as $discount) {
                $discountResult = $this->discounts->applyToCart($cart, $discount);
                $discountAmount += $discountResult->amount;
                $freeShipping = $freeShipping || $discountResult->freeShipping;
            }

            $lines = CartLine::withoutGlobalScopes()
                ->where('cart_id', $cart->getKey())
                ->orderBy('id')
                ->get();
            $discountResult = new DiscountResult($discountAmount, [], $freeShipping);
            $shippingAmount = $this->shippingAmount($checkout, $discountResult);
            $taxSettings = $this->taxSettings($checkout);
            $taxResult = $this->taxes->calculateForAmounts(
                $lines->pluck('line_total_amount')->map(fn (int $amount): int => $amount)->all(),
                $shippingAmount,
                $taxSettings,
                $checkout->shipping_address_json ?? [],
            );
            $discountedSubtotal = $subtotal - $discountResult->amount;
            $total = $taxSettings->prices_include_tax
                ? $discountedSubtotal + $shippingAmount
                : $discountedSubtotal + $shippingAmount + $taxResult->totalAmount;

            $result = new PricingResult(
                subtotal: $subtotal,
                discount: $discountResult->amount,
                shipping: $shippingAmount,
                taxLines: $taxResult->taxLines,
                taxTotal: $taxResult->totalAmount,
                total: $total,
                currency: $cart->currency,
            );

            $checkout->forceFill([
                'tax_provider_snapshot_json' => $taxResult->toArray(),
                'totals_json' => $result->toArray(),
            ])->save();

            return $result;
        });
    }

    private function resetLineAmounts(Checkout $checkout): void
    {
        CartLine::withoutGlobalScopes()
            ->where('cart_id', $checkout->cart_id)
            ->get()
            ->each(function (CartLine $line): void {
                $subtotal = $line->unit_price_amount * $line->quantity;

                $line->forceFill([
                    'line_subtotal_amount' => $subtotal,
                    'line_discount_amount' => 0,
                    'line_total_amount' => $subtotal,
                ])->save();
            });
    }

    private function shippingAmount(Checkout $checkout, DiscountResult $discountResult): int
    {
        if (! $this->shipping->requiresShipping($checkout->cart)) {
            return 0;
        }

        if ($checkout->shipping_method_id === null) {
            return 0;
        }

        $rate = ShippingRate::withoutGlobalScopes()->find($checkout->shipping_method_id);

        if (! $rate instanceof ShippingRate) {
            throw InvalidCheckoutTransitionException::because('Shipping rate is not available.');
        }

        $amount = $this->shipping->calculate($rate, $checkout->cart);

        if ($amount === null) {
            throw InvalidCheckoutTransitionException::because('Shipping rate is not available for this cart.');
        }

        return $discountResult->freeShipping ? 0 : $amount;
    }

    private function taxSettings(Checkout $checkout): TaxSettings
    {
        return TaxSettings::withoutGlobalScopes()
            ->where('store_id', $checkout->store_id)
            ->first() ?? new TaxSettings([
                'store_id' => $checkout->store_id,
                'mode' => 'manual',
                'provider' => 'none',
                'prices_include_tax' => false,
                'config_json' => [
                    'default_rate_bps' => 0,
                    'shipping_taxable' => false,
                ],
            ]);
    }
}


cmd: rg -n "one_per_customer|rules_json|onePerCustomer" app/Livewire/Admin/Discounts resources/views/livewire/admin/discounts tests/Feature/Admin/DiscountManagementTest.php


command: rg -n "one_per_customer|rules_json|onePerCustomer" app/Livewire/Admin/Discounts resources/views/livewire/admin/discounts tests/Feature/Admin/DiscountManagementTest.php

type: search

cmd: rg -n 'one_per_customer|rules_json|onePerCustomer' app/Livewire/Admin/Discounts resources/views/livewire/admin/discounts tests/Feature/Admin/DiscountManagementTest.php

query: one_per_customer|rules_json|onePerCustomer

path: Discounts

aggregated_output:
app/Livewire/Admin/Discounts/Form.php:47:    public bool $onePerCustomer = false;
app/Livewire/Admin/Discounts/Form.php:123:            'onePerCustomer' => ['boolean'],
app/Livewire/Admin/Discounts/Form.php:259:        $this->minimumPurchaseAmount = data_get($discount->rules_json, 'min_purchase_amount')
app/Livewire/Admin/Discounts/Form.php:260:            ? number_format(((int) data_get($discount->rules_json, 'min_purchase_amount')) / 100, 2, '.', '')
app/Livewire/Admin/Discounts/Form.php:262:        $this->specificProductIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
app/Livewire/Admin/Discounts/Form.php:265:        $this->specificCollectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
app/Livewire/Admin/Discounts/Form.php:269:        $this->onePerCustomer = (bool) data_get($discount->rules_json, 'one_per_customer', false);
app/Livewire/Admin/Discounts/Form.php:289:            'rules_json' => $this->rulesPayload(),
app/Livewire/Admin/Discounts/Form.php:303:            'one_per_customer' => $this->onePerCustomer,
tests/Feature/Admin/DiscountManagementTest.php:165:        ->set('onePerCustomer', true)
tests/Feature/Admin/DiscountManagementTest.php:179:        ->and(data_get($discount->rules_json, 'min_purchase_amount'))->toBe(2500)
tests/Feature/Admin/DiscountManagementTest.php:180:        ->and(data_get($discount->rules_json, 'one_per_customer'))->toBeTrue()
tests/Feature/Admin/DiscountManagementTest.php:181:        ->and(data_get($discount->rules_json, 'applicable_product_ids'))->toBe([$product->getKey()])
tests/Feature/Admin/DiscountManagementTest.php:182:        ->and(data_get($discount->rules_json, 'applicable_collection_ids'))->toBe([$collection->getKey()]);
resources/views/livewire/admin/discounts/form.blade.php:125:                    <flux:checkbox wire:model="onePerCustomer" label="Limit to one use per customer" />


output:
Chunk ID: 02142b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 481
Output:
app/Livewire/Admin/Discounts/Form.php:47:    public bool $onePerCustomer = false;
app/Livewire/Admin/Discounts/Form.php:123:            'onePerCustomer' => ['boolean'],
app/Livewire/Admin/Discounts/Form.php:259:        $this->minimumPurchaseAmount = data_get($discount->rules_json, 'min_purchase_amount')
app/Livewire/Admin/Discounts/Form.php:260:            ? number_format(((int) data_get($discount->rules_json, 'min_purchase_amount')) / 100, 2, '.', '')
app/Livewire/Admin/Discounts/Form.php:262:        $this->specificProductIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
app/Livewire/Admin/Discounts/Form.php:265:        $this->specificCollectionIds = collect(data_get($discount->rules_json, 'applicable_collection_ids', []))
app/Livewire/Admin/Discounts/Form.php:269:        $this->onePerCustomer = (bool) data_get($discount->rules_json, 'one_per_customer', false);
app/Livewire/Admin/Discounts/Form.php:289:            'rules_json' => $this->rulesPayload(),
app/Livewire/Admin/Discounts/Form.php:303:            'one_per_customer' => $this->onePerCustomer,
tests/Feature/Admin/DiscountManagementTest.php:165:        ->set('onePerCustomer', true)
tests/Feature/Admin/DiscountManagementTest.php:179:        ->and(data_get($discount->rules_json, 'min_purchase_amount'))->toBe(2500)
tests/Feature/Admin/DiscountManagementTest.php:180:        ->and(data_get($discount->rules_json, 'one_per_customer'))->toBeTrue()
tests/Feature/Admin/DiscountManagementTest.php:181:        ->and(data_get($discount->rules_json, 'applicable_product_ids'))->toBe([$product->getKey()])
tests/Feature/Admin/DiscountManagementTest.php:182:        ->and(data_get($discount->rules_json, 'applicable_collection_ids'))->toBe([$collection->getKey()]);
resources/views/livewire/admin/discounts/form.blade.php:125:                    <flux:checkbox wire:model="onePerCustomer" label="Limit to one use per customer" />


cmd: sed -n '1,220p' app/Services/CartService.php


command: sed -n '1,220p' app/Services/CartService.php

aggregated_output:
<?php

namespace App\Services;

use App\Enums\CartStatus;
use App\Enums\ProductStatus;
use App\Enums\VariantStatus;
use App\Exceptions\CartVersionMismatchException;
use App\Exceptions\InvalidCartOperationException;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Customer;
use App\Models\InventoryItem;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Support\Facades\DB;

class CartService
{
    public function __construct(
        private readonly InventoryService $inventory,
    ) {}

    public function create(Store $store, ?Customer $customer = null): Cart
    {
        return Cart::withoutGlobalScopes()->create([
            'store_id' => $store->getKey(),
            'customer_id' => $customer?->getKey(),
            'currency' => $store->default_currency,
            'cart_version' => 1,
            'status' => CartStatus::Active,
        ]);
    }

    public function getOrCreateForSession(Store $store, ?Customer $customer = null): Cart
    {
        $sessionCartId = session('cart_id');

        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first() ?? $this->create($store, $customer);

            if ($sessionCartId) {
                $guestCart = Cart::withoutGlobalScopes()
                    ->where('store_id', $store->getKey())
                    ->whereNull('customer_id')
                    ->where('status', CartStatus::Active)
                    ->find($sessionCartId);

                if ($guestCart instanceof Cart && $guestCart->isNot($customerCart)) {
                    $customerCart = $this->mergeOnLogin($guestCart, $customerCart);
                }

                session()->forget('cart_id');
            }

            return $customerCart->refresh();
        }

        if ($sessionCartId) {
            $cart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->whereNull('customer_id')
                ->where('status', CartStatus::Active)
                ->find($sessionCartId);

            if ($cart instanceof Cart) {
                return $cart;
            }
        }

        $cart = $this->create($store);
        session(['cart_id' => $cart->getKey()]);

        return $cart;
    }

    public function currentForSession(Store $store, ?Customer $customer = null): ?Cart
    {
        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first();

            if ($customerCart instanceof Cart) {
                return $customerCart;
            }
        }

        $sessionCartId = session('cart_id');

        if (! $sessionCartId) {
            return null;
        }

        return Cart::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('status', CartStatus::Active)
            ->where(function ($query) use ($customer): void {
                if ($customer instanceof Customer) {
                    $query
                        ->whereNull('customer_id')
                        ->orWhere('customer_id', $customer->getKey());

                    return;
                }

                $query->whereNull('customer_id');
            })
            ->find($sessionCartId);
    }

    public function addLine(Cart $cart, int $variantId, int $quantity, ?int $expectedVersion = null): CartLine
    {
        $this->assertPositiveQuantity($quantity);

        return DB::transaction(function () use ($cart, $variantId, $quantity, $expectedVersion): CartLine {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            $variant = $this->purchasableVariant($lockedCart, $variantId);
            $line = CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->where('variant_id', $variant->getKey())
                ->first();
            $newQuantity = ($line?->quantity ?? 0) + $quantity;

            $this->assertAvailable($variant->inventoryItem, $newQuantity);

            if (! $line instanceof CartLine) {
                $line = new CartLine([
                    'cart_id' => $lockedCart->getKey(),
                    'variant_id' => $variant->getKey(),
                    'unit_price_amount' => $variant->price_amount,
                ]);
            }

            $this->fillLineAmounts($line, $newQuantity, $line->unit_price_amount ?: $variant->price_amount);
            $line->save();
            $this->incrementVersion($lockedCart);

            return $line->refresh();
        });
    }

    public function updateLineQuantity(Cart $cart, int $lineId, int $quantity, ?int $expectedVersion = null): ?CartLine
    {
        if ($quantity <= 0) {
            $this->removeLine($cart, $lineId, $expectedVersion);

            return null;
        }

        return DB::transaction(function () use ($cart, $lineId, $quantity, $expectedVersion): CartLine {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            $line = CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->findOrFail($lineId);
            $variant = $this->purchasableVariant($lockedCart, $line->variant_id);

            $this->assertAvailable($variant->inventoryItem, $quantity);
            $this->fillLineAmounts($line, $quantity, $line->unit_price_amount);
            $line->save();
            $this->incrementVersion($lockedCart);

            return $line->refresh();
        });
    }

    public function removeLine(Cart $cart, int $lineId, ?int $expectedVersion = null): void
    {
        DB::transaction(function () use ($cart, $lineId, $expectedVersion): void {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->whereKey($lineId)
                ->delete();

            $this->incrementVersion($lockedCart);
        });
    }

    public function mergeOnLogin(Cart $guest, Cart $customer): Cart
    {
        return DB::transaction(function () use ($guest, $customer): Cart {
            $guestCart = $this->freshCart($guest);
            $customerCart = $this->freshCart($customer);

            CartLine::withoutGlobalScopes()
                ->where('cart_id', $guestCart->getKey())
                ->get()
                ->each(function (CartLine $guestLine) use ($customerCart): void {
                    $existingLine = CartLine::withoutGlobalScopes()
                        ->where('cart_id', $customerCart->getKey())
                        ->where('variant_id', $guestLine->variant_id)
                        ->first();

                    if ($existingLine instanceof CartLine) {
                        $quantity = max($existingLine->quantity, $guestLine->quantity);
                        $this->fillLineAmounts($existingLine, $quantity, $existingLine->unit_price_amount);
                        $existingLine->save();
                        $guestLine->delete();

                        return;
                    }


output:
Chunk ID: 6d9aee
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1969
Output:
<?php

namespace App\Services;

use App\Enums\CartStatus;
use App\Enums\ProductStatus;
use App\Enums\VariantStatus;
use App\Exceptions\CartVersionMismatchException;
use App\Exceptions\InvalidCartOperationException;
use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Customer;
use App\Models\InventoryItem;
use App\Models\ProductVariant;
use App\Models\Store;
use Illuminate\Support\Facades\DB;

class CartService
{
    public function __construct(
        private readonly InventoryService $inventory,
    ) {}

    public function create(Store $store, ?Customer $customer = null): Cart
    {
        return Cart::withoutGlobalScopes()->create([
            'store_id' => $store->getKey(),
            'customer_id' => $customer?->getKey(),
            'currency' => $store->default_currency,
            'cart_version' => 1,
            'status' => CartStatus::Active,
        ]);
    }

    public function getOrCreateForSession(Store $store, ?Customer $customer = null): Cart
    {
        $sessionCartId = session('cart_id');

        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first() ?? $this->create($store, $customer);

            if ($sessionCartId) {
                $guestCart = Cart::withoutGlobalScopes()
                    ->where('store_id', $store->getKey())
                    ->whereNull('customer_id')
                    ->where('status', CartStatus::Active)
                    ->find($sessionCartId);

                if ($guestCart instanceof Cart && $guestCart->isNot($customerCart)) {
                    $customerCart = $this->mergeOnLogin($guestCart, $customerCart);
                }

                session()->forget('cart_id');
            }

            return $customerCart->refresh();
        }

        if ($sessionCartId) {
            $cart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->whereNull('customer_id')
                ->where('status', CartStatus::Active)
                ->find($sessionCartId);

            if ($cart instanceof Cart) {
                return $cart;
            }
        }

        $cart = $this->create($store);
        session(['cart_id' => $cart->getKey()]);

        return $cart;
    }

    public function currentForSession(Store $store, ?Customer $customer = null): ?Cart
    {
        if ($customer instanceof Customer) {
            $customerCart = Cart::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->where('customer_id', $customer->getKey())
                ->where('status', CartStatus::Active)
                ->latest('id')
                ->first();

            if ($customerCart instanceof Cart) {
                return $customerCart;
            }
        }

        $sessionCartId = session('cart_id');

        if (! $sessionCartId) {
            return null;
        }

        return Cart::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('status', CartStatus::Active)
            ->where(function ($query) use ($customer): void {
                if ($customer instanceof Customer) {
                    $query
                        ->whereNull('customer_id')
                        ->orWhere('customer_id', $customer->getKey());

                    return;
                }

                $query->whereNull('customer_id');
            })
            ->find($sessionCartId);
    }

    public function addLine(Cart $cart, int $variantId, int $quantity, ?int $expectedVersion = null): CartLine
    {
        $this->assertPositiveQuantity($quantity);

        return DB::transaction(function () use ($cart, $variantId, $quantity, $expectedVersion): CartLine {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            $variant = $this->purchasableVariant($lockedCart, $variantId);
            $line = CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->where('variant_id', $variant->getKey())
                ->first();
            $newQuantity = ($line?->quantity ?? 0) + $quantity;

            $this->assertAvailable($variant->inventoryItem, $newQuantity);

            if (! $line instanceof CartLine) {
                $line = new CartLine([
                    'cart_id' => $lockedCart->getKey(),
                    'variant_id' => $variant->getKey(),
                    'unit_price_amount' => $variant->price_amount,
                ]);
            }

            $this->fillLineAmounts($line, $newQuantity, $line->unit_price_amount ?: $variant->price_amount);
            $line->save();
            $this->incrementVersion($lockedCart);

            return $line->refresh();
        });
    }

    public function updateLineQuantity(Cart $cart, int $lineId, int $quantity, ?int $expectedVersion = null): ?CartLine
    {
        if ($quantity <= 0) {
            $this->removeLine($cart, $lineId, $expectedVersion);

            return null;
        }

        return DB::transaction(function () use ($cart, $lineId, $quantity, $expectedVersion): CartLine {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            $line = CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->findOrFail($lineId);
            $variant = $this->purchasableVariant($lockedCart, $line->variant_id);

            $this->assertAvailable($variant->inventoryItem, $quantity);
            $this->fillLineAmounts($line, $quantity, $line->unit_price_amount);
            $line->save();
            $this->incrementVersion($lockedCart);

            return $line->refresh();
        });
    }

    public function removeLine(Cart $cart, int $lineId, ?int $expectedVersion = null): void
    {
        DB::transaction(function () use ($cart, $lineId, $expectedVersion): void {
            $lockedCart = $this->freshCart($cart);
            $this->assertExpectedVersion($lockedCart, $expectedVersion);
            $this->assertCartIsActive($lockedCart);

            CartLine::withoutGlobalScopes()
                ->where('cart_id', $lockedCart->getKey())
                ->whereKey($lineId)
                ->delete();

            $this->incrementVersion($lockedCart);
        });
    }

    public function mergeOnLogin(Cart $guest, Cart $customer): Cart
    {
        return DB::transaction(function () use ($guest, $customer): Cart {
            $guestCart = $this->freshCart($guest);
            $customerCart = $this->freshCart($customer);

            CartLine::withoutGlobalScopes()
                ->where('cart_id', $guestCart->getKey())
                ->get()
                ->each(function (CartLine $guestLine) use ($customerCart): void {
                    $existingLine = CartLine::withoutGlobalScopes()
                        ->where('cart_id', $customerCart->getKey())
                        ->where('variant_id', $guestLine->variant_id)
                        ->first();

                    if ($existingLine instanceof CartLine) {
                        $quantity = max($existingLine->quantity, $guestLine->quantity);
                        $this->fillLineAmounts($existingLine, $quantity, $existingLine->unit_price_amount);
                        $existingLine->save();
                        $guestLine->delete();

                        return;
                    }


cmd: sed -n '1,180p' database/factories/OrderLineFactory.php


command: sed -n '1,180p' database/factories/OrderLineFactory.php

aggregated_output:
<?php

namespace Database\Factories;

use App\Models\Order;
use App\Models\ProductVariant;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\OrderLine>
 */
class OrderLineFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $quantity = fake()->numberBetween(1, 3);
        $unitPrice = fake()->numberBetween(1000, 10000);

        return [
            'order_id' => Order::factory(),
            'product_id' => null,
            'variant_id' => ProductVariant::factory(),
            'title_snapshot' => fake()->words(3, true),
            'sku_snapshot' => 'SKU-'.fake()->unique()->numerify('####'),
            'quantity' => $quantity,
            'unit_price_amount' => $unitPrice,
            'total_amount' => $unitPrice * $quantity,
            'tax_lines_json' => [],
            'discount_allocations_json' => [],
        ];
    }
}


output:
Chunk ID: 09af04
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 262
Output:
<?php

namespace Database\Factories;

use App\Models\Order;
use App\Models\ProductVariant;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\OrderLine>
 */
class OrderLineFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $quantity = fake()->numberBetween(1, 3);
        $unitPrice = fake()->numberBetween(1000, 10000);

        return [
            'order_id' => Order::factory(),
            'product_id' => null,
            'variant_id' => ProductVariant::factory(),
            'title_snapshot' => fake()->words(3, true),
            'sku_snapshot' => 'SKU-'.fake()->unique()->numerify('####'),
            'quantity' => $quantity,
            'unit_price_amount' => $unitPrice,
            'total_amount' => $unitPrice * $quantity,
            'tax_lines_json' => [],
            'discount_allocations_json' => [],
        ];
    }
}


cmd: sed -n '1,120p' app/Exceptions/InvalidDiscountException.php


command: sed -n '1,120p' app/Exceptions/InvalidDiscountException.php

aggregated_output:
<?php

namespace App\Exceptions;

use RuntimeException;

class InvalidDiscountException extends RuntimeException
{
    public function __construct(
        public readonly string $reasonCode,
        string $message,
    ) {
        parent::__construct($message);
    }

    public static function because(string $reasonCode, string $message): self
    {
        return new self($reasonCode, $message);
    }
}


output:
Chunk ID: 5ee5ff
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 103
Output:
<?php

namespace App\Exceptions;

use RuntimeException;

class InvalidDiscountException extends RuntimeException
{
    public function __construct(
        public readonly string $reasonCode,
        string $message,
    ) {
        parent::__construct($message);
    }

    public static function because(string $reasonCode, string $message): self
    {
        return new self($reasonCode, $message);
    }
}


cmd: rg "discount_allocations|discount_code|usage_count" tests/Feature app -n


command: rg "discount_allocations|discount_code|usage_count" tests/Feature app -n

cmd: rg 'discount_allocations|discount_code|usage_count' tests/Feature app -n

query: discount_allocations|discount_code|usage_count

path: Feature

aggregated_output:
app/Services/DiscountService.php:76:        if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
app/Services/PricingEngine.php:41:            if ($checkout->discount_code) {
app/Services/PricingEngine.php:42:                $discount = $this->discounts->validate($checkout->discount_code, $checkout->store, $cart);
tests/Feature/Orders/OrderServiceTest.php:134:        $checkout->forceFill(['discount_code' => $discountCode])->save();
tests/Feature/Orders/OrderServiceTest.php:165:        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
tests/Feature/Orders/OrderServiceTest.php:171:        ->and($discount->refresh()->usage_count)->toBe(1);
tests/Feature/Admin/DiscountManagementTest.php:66:        'usage_count' => 3,
tests/Feature/Api/StorefrontCheckoutApiTest.php:120:        ->assertJsonPath('data.discount_code', 'SAVE10')
tests/Feature/Api/StorefrontCheckoutApiTest.php:126:        ->assertJsonPath('data.discount_code', null)
tests/Feature/Checkout/PricingServicesTest.php:135:        'discount_code' => 'save10',
tests/Feature/Checkout/PricingServicesTest.php:144:    $checkout->forceFill(['discount_code' => 'FREESHIP'])->save();
tests/Feature/Checkout/PricingServicesTest.php:190:    $checkout->forceFill(['discount_code' => 'SAVE10'])->save();
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:82:            'discount_code' => trim((string) $request->validated('code')) ?: null,
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:90:            $checkout->forceFill(['discount_code' => null])->save();
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:102:        abort_if($checkout->discount_code === null, 404);
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:104:        $checkout->forceFill(['discount_code' => null])->save();
tests/Feature/Storefront/CartCheckoutUiTest.php:104:    expect(session('cart_discount_code'))->toBe('SAVE10')
tests/Feature/Storefront/CartCheckoutUiTest.php:114:    expect($checkout->discount_code)->toBe('SAVE10')
tests/Feature/Storefront/CartCheckoutUiTest.php:178:        ->and($checkout->discount_code)->toBe('SAVE10')
app/Services/OrderService.php:265:                'discount_allocations_json' => $this->discountAllocations($checkout, $line),
app/Services/OrderService.php:287:        if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
app/Services/OrderService.php:292:            'code' => $checkout->discount_code,
app/Services/OrderService.php:345:        $code = trim((string) $checkout->discount_code);
app/Services/OrderService.php:354:            ->increment('usage_count');
app/Livewire/Storefront/Checkout/Show.php:163:            'discount_code' => trim($this->discountCode) !== '' ? trim($this->discountCode) : null,
app/Livewire/Storefront/Checkout/Show.php:170:            $checkout->forceFill(['discount_code' => null])->save();
app/Livewire/Storefront/Checkout/Show.php:241:            session()->forget(['cart_id', 'cart_discount_code']);
app/Livewire/Storefront/Checkout/Show.php:407:        $this->discountCode = (string) ($checkout->discount_code ?? '');
app/Livewire/Storefront/Checkout/Show.php:420:        $code = trim((string) session('cart_discount_code'));
app/Livewire/Storefront/Checkout/Show.php:422:        if ($code === '' || $checkout->discount_code !== null) {
app/Livewire/Storefront/Checkout/Show.php:426:        $checkout->forceFill(['discount_code' => $code])->save();
app/Livewire/Storefront/Checkout/Show.php:431:            $checkout->forceFill(['discount_code' => null])->save();
app/Livewire/Storefront/Checkout/Show.php:432:            session()->forget('cart_discount_code');
app/Models/Discount.php:30:        'usage_count',
app/Models/Discount.php:41:        'usage_count' => 0,
app/Models/Discount.php:66:            'usage_count' => 'integer',
app/Models/OrderLine.php:30:        'discount_allocations_json',
app/Models/OrderLine.php:75:            'discount_allocations_json' => 'array',
app/Livewire/Storefront/Cart/Show.php:38:        $this->appliedDiscountCode = trim((string) session('cart_discount_code')) ?: null;
app/Livewire/Storefront/Cart/Show.php:108:        session(['cart_discount_code' => $code]);
app/Livewire/Storefront/Cart/Show.php:116:        session()->forget('cart_discount_code');
app/Http/Resources/Storefront/V1/CheckoutResource.php:28:            'discount_code' => $this->discount_code,
app/Models/Checkout.php:29:        'discount_code',


output:
Chunk ID: 11e1e8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1134
Output:
app/Services/DiscountService.php:76:        if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
app/Services/PricingEngine.php:41:            if ($checkout->discount_code) {
app/Services/PricingEngine.php:42:                $discount = $this->discounts->validate($checkout->discount_code, $checkout->store, $cart);
tests/Feature/Orders/OrderServiceTest.php:134:        $checkout->forceFill(['discount_code' => $discountCode])->save();
tests/Feature/Orders/OrderServiceTest.php:165:        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
tests/Feature/Orders/OrderServiceTest.php:171:        ->and($discount->refresh()->usage_count)->toBe(1);
tests/Feature/Admin/DiscountManagementTest.php:66:        'usage_count' => 3,
tests/Feature/Api/StorefrontCheckoutApiTest.php:120:        ->assertJsonPath('data.discount_code', 'SAVE10')
tests/Feature/Api/StorefrontCheckoutApiTest.php:126:        ->assertJsonPath('data.discount_code', null)
tests/Feature/Checkout/PricingServicesTest.php:135:        'discount_code' => 'save10',
tests/Feature/Checkout/PricingServicesTest.php:144:    $checkout->forceFill(['discount_code' => 'FREESHIP'])->save();
tests/Feature/Checkout/PricingServicesTest.php:190:    $checkout->forceFill(['discount_code' => 'SAVE10'])->save();
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:82:            'discount_code' => trim((string) $request->validated('code')) ?: null,
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:90:            $checkout->forceFill(['discount_code' => null])->save();
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:102:        abort_if($checkout->discount_code === null, 404);
app/Http/Controllers/Api/Storefront/V1/CheckoutController.php:104:        $checkout->forceFill(['discount_code' => null])->save();
tests/Feature/Storefront/CartCheckoutUiTest.php:104:    expect(session('cart_discount_code'))->toBe('SAVE10')
tests/Feature/Storefront/CartCheckoutUiTest.php:114:    expect($checkout->discount_code)->toBe('SAVE10')
tests/Feature/Storefront/CartCheckoutUiTest.php:178:        ->and($checkout->discount_code)->toBe('SAVE10')
app/Services/OrderService.php:265:                'discount_allocations_json' => $this->discountAllocations($checkout, $line),
app/Services/OrderService.php:287:        if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
app/Services/OrderService.php:292:            'code' => $checkout->discount_code,
app/Services/OrderService.php:345:        $code = trim((string) $checkout->discount_code);
app/Services/OrderService.php:354:            ->increment('usage_count');
app/Livewire/Storefront/Checkout/Show.php:163:            'discount_code' => trim($this->discountCode) !== '' ? trim($this->discountCode) : null,
app/Livewire/Storefront/Checkout/Show.php:170:            $checkout->forceFill(['discount_code' => null])->save();
app/Livewire/Storefront/Checkout/Show.php:241:            session()->forget(['cart_id', 'cart_discount_code']);
app/Livewire/Storefront/Checkout/Show.php:407:        $this->discountCode = (string) ($checkout->discount_code ?? '');
app/Livewire/Storefront/Checkout/Show.php:420:        $code = trim((string) session('cart_discount_code'));
app/Livewire/Storefront/Checkout/Show.php:422:        if ($code === '' || $checkout->discount_code !== null) {
app/Livewire/Storefront/Checkout/Show.php:426:        $checkout->forceFill(['discount_code' => $code])->save();
app/Livewire/Storefront/Checkout/Show.php:431:            $checkout->forceFill(['discount_code' => null])->save();
app/Livewire/Storefront/Checkout/Show.php:432:            session()->forget('cart_discount_code');
app/Models/Discount.php:30:        'usage_count',
app/Models/Discount.php:41:        'usage_count' => 0,
app/Models/Discount.php:66:            'usage_count' => 'integer',
app/Models/OrderLine.php:30:        'discount_allocations_json',
app/Models/OrderLine.php:75:            'discount_allocations_json' => 'array',
app/Livewire/Storefront/Cart/Show.php:38:        $this->appliedDiscountCode = trim((string) session('cart_discount_code')) ?: null;
app/Livewire/Storefront/Cart/Show.php:108:        session(['cart_discount_code' => $code]);
app/Livewire/Storefront/Cart/Show.php:116:        session()->forget('cart_discount_code');
app/Http/Resources/Storefront/V1/CheckoutResource.php:28:            'discount_code' => $this->discount_code,
app/Models/Checkout.php:29:        'discount_code',


cmd: sed -n '100,190p' tests/Feature/Orders/OrderServiceTest.php


command: sed -n '100,190p' tests/Feature/Orders/OrderServiceTest.php

aggregated_output:
        'zone_id' => $zone->getKey(),
        'name' => 'Standard',
        'type' => 'flat',
        'config_json' => ['amount' => 499],
        'is_active' => true,
    ]);
}

/**
 * @return array{0: Checkout, 1: ProductVariant, 2: \App\Models\Cart}
 */
function orderCompletionCheckout(Store $store, string $paymentMethod = 'credit_card', ?string $discountCode = null): array
{
    $variant = orderCompletionVariant($store);
    $rate = orderCompletionShippingRate($store);
    $cart = app(CartService::class)->create($store);

    app(CartService::class)->addLine($cart, $variant->getKey(), 2);

    $checkout = app(CheckoutService::class)->createFromCart($cart);
    $checkout = app(CheckoutService::class)->setAddress($checkout, [
        'email' => 'buyer@example.test',
        'shipping_address' => [
            'first_name' => 'Test',
            'last_name' => 'Buyer',
            'address1' => 'Main Street 1',
            'city' => 'Berlin',
            'country' => 'DE',
            'postal_code' => '10115',
        ],
    ]);
    $checkout = app(CheckoutService::class)->setShippingMethod($checkout, $rate->getKey());

    if ($discountCode !== null) {
        $checkout->forceFill(['discount_code' => $discountCode])->save();
        app(PricingEngine::class)->calculate($checkout);
    }

    $checkout = app(CheckoutService::class)->selectPaymentMethod($checkout, $paymentMethod);

    return [$checkout, $variant, $cart];
}

test('checkout completion creates a paid order and commits inventory', function () {
    $store = orderCompletionStore();
    $discount = Discount::factory()
        ->fixed(500)
        ->create([
            'store_id' => $store->getKey(),
            'code' => 'SAVE500',
        ]);
    [$checkout, $variant, $cart] = orderCompletionCheckout($store, discountCode: 'SAVE500');

    Event::fake();

    $order = app(CheckoutService::class)->completeCheckout($checkout, [
        'card_number' => '4242 4242 4242 4242',
    ]);

    expect($order->order_number)->toBe('#1001')
        ->and($order->status)->toBe(OrderStatus::Paid)
        ->and($order->financial_status)->toBe(FinancialStatus::Paid)
        ->and($order->total_amount)->toBe(4999)
        ->and($order->lines)->toHaveCount(1)
        ->and($order->lines->first()->title_snapshot)->toContain($variant->product->title)
        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
        ->and($order->payments)->toHaveCount(1)
        ->and($order->payments->first()->status)->toBe(PaymentStatus::Captured)
        ->and($order->payments->first()->raw_json_encrypted['success'])->toBeTrue()
        ->and($cart->refresh()->status)->toBe(CartStatus::Converted)
        ->and($checkout->refresh()->status)->toBe(CheckoutStatus::Completed)
        ->and($discount->refresh()->usage_count)->toBe(1);

    $inventory = InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->firstOrFail();

    expect($inventory->quantity_on_hand)->toBe(8)
        ->and($inventory->quantity_reserved)->toBe(0);

    Event::assertDispatched(OrderCreated::class, fn (OrderCreated $event): bool => $event->order->is($order));
    Event::assertDispatched(OrderPaid::class, fn (OrderPaid $event): bool => $event->order->is($order));
});

test('checkout completion is idempotent for the same checkout', function () {
    $store = orderCompletionStore();
    [$checkout, $variant] = orderCompletionCheckout($store);

    $order = app(CheckoutService::class)->completeCheckout($checkout, [
        'card_number' => '4242 4242 4242 4242',
    ]);
    $sameOrder = app(CheckoutService::class)->completeCheckout($checkout->refresh(), [
        'card_number' => '4000 0000 0000 0002',


output:
Chunk ID: 2fd090
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 936
Output:
        'zone_id' => $zone->getKey(),
        'name' => 'Standard',
        'type' => 'flat',
        'config_json' => ['amount' => 499],
        'is_active' => true,
    ]);
}

/**
 * @return array{0: Checkout, 1: ProductVariant, 2: \App\Models\Cart}
 */
function orderCompletionCheckout(Store $store, string $paymentMethod = 'credit_card', ?string $discountCode = null): array
{
    $variant = orderCompletionVariant($store);
    $rate = orderCompletionShippingRate($store);
    $cart = app(CartService::class)->create($store);

    app(CartService::class)->addLine($cart, $variant->getKey(), 2);

    $checkout = app(CheckoutService::class)->createFromCart($cart);
    $checkout = app(CheckoutService::class)->setAddress($checkout, [
        'email' => 'buyer@example.test',
        'shipping_address' => [
            'first_name' => 'Test',
            'last_name' => 'Buyer',
            'address1' => 'Main Street 1',
            'city' => 'Berlin',
            'country' => 'DE',
            'postal_code' => '10115',
        ],
    ]);
    $checkout = app(CheckoutService::class)->setShippingMethod($checkout, $rate->getKey());

    if ($discountCode !== null) {
        $checkout->forceFill(['discount_code' => $discountCode])->save();
        app(PricingEngine::class)->calculate($checkout);
    }

    $checkout = app(CheckoutService::class)->selectPaymentMethod($checkout, $paymentMethod);

    return [$checkout, $variant, $cart];
}

test('checkout completion creates a paid order and commits inventory', function () {
    $store = orderCompletionStore();
    $discount = Discount::factory()
        ->fixed(500)
        ->create([
            'store_id' => $store->getKey(),
            'code' => 'SAVE500',
        ]);
    [$checkout, $variant, $cart] = orderCompletionCheckout($store, discountCode: 'SAVE500');

    Event::fake();

    $order = app(CheckoutService::class)->completeCheckout($checkout, [
        'card_number' => '4242 4242 4242 4242',
    ]);

    expect($order->order_number)->toBe('#1001')
        ->and($order->status)->toBe(OrderStatus::Paid)
        ->and($order->financial_status)->toBe(FinancialStatus::Paid)
        ->and($order->total_amount)->toBe(4999)
        ->and($order->lines)->toHaveCount(1)
        ->and($order->lines->first()->title_snapshot)->toContain($variant->product->title)
        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
        ->and($order->payments)->toHaveCount(1)
        ->and($order->payments->first()->status)->toBe(PaymentStatus::Captured)
        ->and($order->payments->first()->raw_json_encrypted['success'])->toBeTrue()
        ->and($cart->refresh()->status)->toBe(CartStatus::Converted)
        ->and($checkout->refresh()->status)->toBe(CheckoutStatus::Completed)
        ->and($discount->refresh()->usage_count)->toBe(1);

    $inventory = InventoryItem::withoutGlobalScopes()->where('variant_id', $variant->getKey())->firstOrFail();

    expect($inventory->quantity_on_hand)->toBe(8)
        ->and($inventory->quantity_reserved)->toBe(0);

    Event::assertDispatched(OrderCreated::class, fn (OrderCreated $event): bool => $event->order->is($order));
    Event::assertDispatched(OrderPaid::class, fn (OrderPaid $event): bool => $event->order->is($order));
});

test('checkout completion is idempotent for the same checkout', function () {
    $store = orderCompletionStore();
    [$checkout, $variant] = orderCompletionCheckout($store);

    $order = app(CheckoutService::class)->completeCheckout($checkout, [
        'card_number' => '4242 4242 4242 4242',
    ]);
    $sameOrder = app(CheckoutService::class)->completeCheckout($checkout->refresh(), [
        'card_number' => '4000 0000 0000 0002',


message: The service currently validates status, dates, usage count, minimum purchase, and applicability, but it never checks prior customer orders. I’m adding a usage-history check for `rules_json.one_per_customer` based on stored order-line discount allocations, and I’ll include `discount_id` in new explicit-code order allocations for more reliable future matching.

phase: commentary


type: message

role: assistant

text: The service currently validates status, dates, usage count, minimum purchase, and applicability, but it never checks prior customer orders. I’m adding a usage-history check for `rules_json.one_per_customer` based on stored order-line discount allocations, and I’ll include `discount_id` in new explicit-code order allocations for more reliable future matching.


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php
@@
 use App\Models\CartLine;
 use App\Models\Discount;
+use App\Models\OrderLine;
 use App\Models\ProductVariant;
 use App\Models\Store;
@@
         if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
             throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount usage limit has been reached.');
         }

+        if ($this->onePerCustomer($discount) && $this->customerHasUsedDiscount($discount, $cart)) {
+            throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount has already been used by this customer.');
+        }
+
         $lines = $this->cartLines($cart);
         $subtotal = $lines->sum('line_subtotal_amount');
         $minimum = (int) data_get($discount->rules_json, 'min_purchase_amount', data_get($discount->rules_json, 'minimum_purchase', 0));
@@
             ->values();
     }
+
+    private function onePerCustomer(Discount $discount): bool
+    {
+        return (bool) data_get($discount->rules_json, 'one_per_customer', false);
+    }
+
+    private function customerHasUsedDiscount(Discount $discount, Cart $cart): bool
+    {
+        if ($cart->customer_id === null) {
+            return false;
+        }
+
+        $discountCode = $discount->code ? mb_strtolower($discount->code) : null;
+
+        return OrderLine::query()
+            ->whereHas('order', function ($query) use ($discount, $cart): void {
+                $query
+                    ->withoutGlobalScopes()
+                    ->where('store_id', $discount->store_id)
+                    ->where('customer_id', $cart->customer_id)
+                    ->where('discount_amount', '>', 0);
+            })
+            ->get()
+            ->contains(function (OrderLine $line) use ($discount, $discountCode): bool {
+                return collect($line->discount_allocations_json ?? [])
+                    ->contains(function (mixed $allocation) use ($discount, $discountCode): bool {
+                        $allocationDiscountId = data_get($allocation, 'discount_id');
+
+                        if ($allocationDiscountId !== null && (int) $allocationDiscountId === $discount->getKey()) {
+                            return true;
+                        }
+
+                        if ($discountCode === null) {
+                            return false;
+                        }
+
+                        return mb_strtolower((string) data_get($allocation, 'code', '')) === $discountCode;
+                    });
+            });
+    }
 }
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/OrderService.php
@@
-    /**
-     * @return array<int, array{code: string, amount: int}>
-     */
+    /**
+     * @return array<int, array{discount_id: int|null, code: string, amount: int}>
+     */
     private function discountAllocations(Checkout $checkout, CartLine $line): array
     {
         if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
             return [];
         }

+        $code = trim($checkout->discount_code);
+        $discount = Discount::withoutGlobalScopes()
+            ->where('store_id', $checkout->store_id)
+            ->whereRaw('lower(code) = ?', [mb_strtolower($code)])
+            ->first();
+
         return [[
-            'code' => $checkout->discount_code,
+            'discount_id' => $discount?->getKey(),
+            'code' => $code,
             'amount' => $line->line_discount_amount,
         ]];
     }
*** End Patch


output:
apply_patch verification failed: Failed to find expected lines in /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:
            ->values();
    }
}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php
@@
 use App\Models\Cart;
 use App\Models\CartLine;
 use App\Models\Discount;
+use App\Models\OrderLine;
 use App\Models\ProductVariant;
 use App\Models\Store;
@@
         if ($discount->usage_limit !== null && $discount->usage_count >= $discount->usage_limit) {
             throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount usage limit has been reached.');
         }

+        if ($this->onePerCustomer($discount) && $this->customerHasUsedDiscount($discount, $cart)) {
+            throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount has already been used by this customer.');
+        }
+
         $lines = $this->cartLines($cart);
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php

type: update

unified_diff:
@@ -11,2 +11,3 @@
 use App\Models\Discount;
+use App\Models\OrderLine;
 use App\Models\ProductVariant;
@@ -79,2 +80,6 @@

+        if ($this->onePerCustomer($discount) && $this->customerHasUsedDiscount($discount, $cart)) {
+            throw InvalidDiscountException::because('discount_usage_limit_reached', 'Discount has already been used by this customer.');
+        }
+
         $lines = $this->cartLines($cart);


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php
@@
     private function qualifyingLines(Discount $discount, Collection $lines): Collection
     {
         $productIds = collect(data_get($discount->rules_json, 'applicable_product_ids', []))
@@
                 ->intersect($collectionIds)
                 ->isNotEmpty();
         });
     }
+
+    private function onePerCustomer(Discount $discount): bool
+    {
+        return (bool) data_get($discount->rules_json, 'one_per_customer', false);
+    }
+
+    private function customerHasUsedDiscount(Discount $discount, Cart $cart): bool
+    {
+        if ($cart->customer_id === null) {
+            return false;
+        }
+
+        $discountCode = $discount->code ? mb_strtolower($discount->code) : null;
+
+        return OrderLine::query()
+            ->whereHas('order', function ($query) use ($discount, $cart): void {
+                $query
+                    ->withoutGlobalScopes()
+                    ->where('store_id', $discount->store_id)
+                    ->where('customer_id', $cart->customer_id)
+                    ->where('discount_amount', '>', 0);
+            })
+            ->get()
+            ->contains(function (OrderLine $line) use ($discount, $discountCode): bool {
+                return collect($line->discount_allocations_json ?? [])
+                    ->contains(function (mixed $allocation) use ($discount, $discountCode): bool {
+                        $allocationDiscountId = data_get($allocation, 'discount_id');
+
+                        if ($allocationDiscountId !== null && (int) $allocationDiscountId === $discount->getKey()) {
+                            return true;
+                        }
+
+                        if ($discountCode === null) {
+                            return false;
+                        }
+
+                        return mb_strtolower((string) data_get($allocation, 'code', '')) === $discountCode;
+                    });
+            });
+    }
 }
*** End Patch


unified_diff:
@@ -207,2 +207,42 @@
     }
+
+    private function onePerCustomer(Discount $discount): bool
+    {
+        return (bool) data_get($discount->rules_json, 'one_per_customer', false);
+    }
+
+    private function customerHasUsedDiscount(Discount $discount, Cart $cart): bool
+    {
+        if ($cart->customer_id === null) {
+            return false;
+        }
+
+        $discountCode = $discount->code ? mb_strtolower($discount->code) : null;
+
+        return OrderLine::query()
+            ->whereHas('order', function ($query) use ($discount, $cart): void {
+                $query
+                    ->withoutGlobalScopes()
+                    ->where('store_id', $discount->store_id)
+                    ->where('customer_id', $cart->customer_id)
+                    ->where('discount_amount', '>', 0);
+            })
+            ->get()
+            ->contains(function (OrderLine $line) use ($discount, $discountCode): bool {
+                return collect($line->discount_allocations_json ?? [])
+                    ->contains(function (mixed $allocation) use ($discount, $discountCode): bool {
+                        $allocationDiscountId = data_get($allocation, 'discount_id');
+
+                        if ($allocationDiscountId !== null && (int) $allocationDiscountId === $discount->getKey()) {
+                            return true;
+                        }
+
+                        if ($discountCode === null) {
+                            return false;
+                        }
+
+                        return mb_strtolower((string) data_get($allocation, 'code', '')) === $discountCode;
+                    });
+            });
+    }
 }


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Services/DiscountService.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/OrderService.php
@@
-    /**
-     * @return array<int, array{code: string, amount: int}>
-     */
+    /**
+     * @return array<int, array{discount_id: int|null, code: string, amount: int}>
+     */
     private function discountAllocations(Checkout $checkout, CartLine $line): array
     {
         if ($checkout->discount_code === null || $line->line_discount_amount <= 0) {
             return [];
         }

+        $code = trim($checkout->discount_code);
+        $discount = Discount::withoutGlobalScopes()
+            ->where('store_id', $checkout->store_id)
+            ->whereRaw('lower(code) = ?', [mb_strtolower($code)])
+            ->first();
+
         return [[
-            'code' => $checkout->discount_code,
+            'discount_id' => $discount?->getKey(),
+            'code' => $code,
             'amount' => $line->line_discount_amount,
         ]];
     }
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Services/OrderService.php

unified_diff:
@@ -282,3 +282,3 @@
     /**
-     * @return array<int, array{code: string, amount: int}>
+     * @return array<int, array{discount_id: int|null, code: string, amount: int}>
      */
@@ -290,4 +290,11 @@

+        $code = trim($checkout->discount_code);
+        $discount = Discount::withoutGlobalScopes()
+            ->where('store_id', $checkout->store_id)
+            ->whereRaw('lower(code) = ?', [mb_strtolower($code)])
+            ->first();
+
         return [[
-            'code' => $checkout->discount_code,
+            'discount_id' => $discount?->getKey(),
+            'code' => $code,
             'amount' => $line->line_discount_amount,


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Services/OrderService.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Checkout/PricingServicesTest.php
@@
 use App\Enums\DiscountType;
 use App\Enums\DiscountValueType;
+use App\Exceptions\InvalidDiscountException;
 use App\Models\Checkout;
+use App\Models\Customer;
 use App\Models\Discount;
 use App\Models\InventoryItem;
+use App\Models\Order;
+use App\Models\OrderLine;
 use App\Models\Product;
 use App\Models\ProductVariant;
@@
 use App\Services\CartService;
+use App\Services\DiscountService;
 use App\Services\PricingEngine;
@@
 test('pricing engine stacks automatic discounts after explicit code discounts', function () {
@@
         ->and($line->line_total_amount)->toBe(4050);
 });

+test('discount validation enforces one use per customer from order history', function () {
+    $store = pricingStore();
+    $customer = Customer::factory()->create(['store_id' => $store->getKey()]);
+    $otherCustomer = Customer::factory()->create(['store_id' => $store->getKey()]);
+    $variant = pricingVariant($store);
+    $discount = Discount::factory()->create([
+        'store_id' => $store->getKey(),
+        'code' => 'ONCE',
+        'rules_json' => [
+            'customer_eligibility' => 'all',
+            'one_per_customer' => true,
+        ],
+    ]);
+    $order = Order::factory()
+        ->forCustomer($customer)
+        ->create([
+            'store_id' => $store->getKey(),
+            'discount_amount' => 500,
+        ]);
+    OrderLine::factory()->create([
+        'order_id' => $order->getKey(),
+        'discount_allocations_json' => [[
+            'discount_id' => $discount->getKey(),
+            'code' => 'ONCE',
+            'amount' => 500,
+        ]],
+    ]);
+
+    $usedCart = app(CartService::class)->create($store, $customer);
+    app(CartService::class)->addLine($usedCart, $variant->getKey(), 1);
+
+    try {
+        app(DiscountService::class)->validate('once', $store, $usedCart);
+
+        $this->fail('Expected one-per-customer discount validation to fail.');
+    } catch (InvalidDiscountException $exception) {
+        expect($exception->reasonCode)->toBe('discount_usage_limit_reached');
+    }
+
+    $otherCart = app(CartService::class)->create($store, $otherCustomer);
+    app(CartService::class)->addLine($otherCart, $variant->getKey(), 1);
+
+    expect(app(DiscountService::class)->validate('ONCE', $store, $otherCart)->is($discount))->toBeTrue();
+});
+
 test('shipping and tax calculators handle matching ranges and inclusive extraction', function () {
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/tests/Feature/Checkout/PricingServicesTest.php

unified_diff:
@@ -4,5 +4,9 @@
 use App\Enums\DiscountValueType;
+use App\Exceptions\InvalidDiscountException;
 use App\Models\Checkout;
+use App\Models\Customer;
 use App\Models\Discount;
 use App\Models\InventoryItem;
+use App\Models\Order;
+use App\Models\OrderLine;
 use App\Models\Product;
@@ -14,2 +18,3 @@
 use App\Services\CartService;
+use App\Services\DiscountService;
 use App\Services\PricingEngine;
@@ -200,2 +205,47 @@

+test('discount validation enforces one use per customer from order history', function () {
+    $store = pricingStore();
+    $customer = Customer::factory()->create(['store_id' => $store->getKey()]);
+    $otherCustomer = Customer::factory()->create(['store_id' => $store->getKey()]);
+    $variant = pricingVariant($store);
+    $discount = Discount::factory()->create([
+        'store_id' => $store->getKey(),
+        'code' => 'ONCE',
+        'rules_json' => [
+            'customer_eligibility' => 'all',
+            'one_per_customer' => true,
+        ],
+    ]);
+    $order = Order::factory()
+        ->forCustomer($customer)
+        ->create([
+            'store_id' => $store->getKey(),
+            'discount_amount' => 500,
+        ]);
+    OrderLine::factory()->create([
+        'order_id' => $order->getKey(),
+        'discount_allocations_json' => [[
+            'discount_id' => $discount->getKey(),
+            'code' => 'ONCE',
+            'amount' => 500,
+        ]],
+    ]);
+
+    $usedCart = app(CartService::class)->create($store, $customer);
+    app(CartService::class)->addLine($usedCart, $variant->getKey(), 1);
+
+    try {
+        app(DiscountService::class)->validate('once', $store, $usedCart);
+
+        $this->fail('Expected one-per-customer discount validation to fail.');
+    } catch (InvalidDiscountException $exception) {
+        expect($exception->reasonCode)->toBe('discount_usage_limit_reached');
+    }
+
+    $otherCart = app(CartService::class)->create($store, $otherCustomer);
+    app(CartService::class)->addLine($otherCart, $variant->getKey(), 1);
+
+    expect(app(DiscountService::class)->validate('ONCE', $store, $otherCart)->is($discount))->toBeTrue();
+});
+
 test('shipping and tax calculators handle matching ranges and inclusive extraction', function () {


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Checkout/PricingServicesTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Orders/OrderServiceTest.php
@@
-        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
+        ->and($order->lines->first()->discount_allocations_json)->toBe([[
+            'discount_id' => $discount->getKey(),
+            'code' => 'SAVE500',
+            'amount' => 500,
+        ]])
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/tests/Feature/Orders/OrderServiceTest.php

unified_diff:
@@ -164,3 +164,7 @@
         ->and($order->lines->first()->title_snapshot)->toContain($variant->product->title)
-        ->and($order->lines->first()->discount_allocations_json)->toBe([['code' => 'SAVE500', 'amount' => 500]])
+        ->and($order->lines->first()->discount_allocations_json)->toBe([[
+            'discount_id' => $discount->getKey(),
+            'code' => 'SAVE500',
+            'amount' => 500,
+        ]])
         ->and($order->payments)->toHaveCount(1)


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Orders/OrderServiceTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


message: The one-per-customer check is in place, and explicit order-line allocations now include `discount_id` alongside the code and amount. I’m formatting and running the pricing/order tests that cover validation and order allocation output.


text: The one-per-customer check is in place, and explicit order-line allocations now include `discount_id` alongside the code and amount. I’m formatting and running the pricing/order tests that cover validation and order allocation output.


output:
Chunk ID: c8a0a1
Wall time: 0.5431 seconds
Process exited with code 0
Original token count: 5
Output:
{"result":"pass"}


command:
/bin/zsh
-lc
vendor/bin/pint --dirty --format agent

type: unknown

cmd: vendor/bin/pint --dirty --format agent

result: pass


cmd: php artisan test --compact tests/Feature/Checkout/PricingServicesTest.php tests/Feature/Orders/OrderServiceTest.php


output:
Chunk ID: bfff47
Wall time: 0.8420 seconds
Process exited with code 0
Original token count: 19
Output:

  ...........

  Tests:    11 passed (63 assertions)
  Duration: 0.58s


command: php artisan test --compact tests/Feature/Checkout/PricingServicesTest.php tests/Feature/Orders/OrderServiceTest.php

aggregated_output:
  ...........

  Tests:    11 passed (63 assertions)
  Duration: 0.58s


message: The focused pricing/order tests are green: 11 tests, 63 assertions. I’m running the broader checkout and order suites next because this affects discount validation during checkout and order creation.


text: The focused pricing/order tests are green: 11 tests, 63 assertions. I’m running the broader checkout and order suites next because this affects discount validation during checkout and order creation.


cmd: php artisan test --compact tests/Feature/Checkout tests/Feature/Orders tests/Feature/Api/StorefrontCheckoutApiTest.php tests/Feature/Storefront/CartCheckoutUiTest.php


output:
Chunk ID: 16171e
Wall time: 4.2971 seconds
Process exited with code 0
Original token count: 24
Output:

  .................................

  Tests:    33 passed (196 assertions)
  Duration: 4.17s


command: php artisan test --compact tests/Feature/Checkout tests/Feature/Orders tests/Feature/Api/StorefrontCheckoutApiTest.php tests/Feature/Storefront/CartCheckoutUiTest.php


message: The broader checkout/order surface is green: 33 tests, 196 assertions. I’m running the full suite before updating the progress log and committing.


text: The broader checkout/order surface is green: 33 tests, 196 assertions. I’m running the full suite before updating the progress log and committing.


output:
Chunk ID: b5f314
Wall time: 30.0023 seconds
Process running with session ID 54128
Original token count: 42
Output:

  ............................................................................
  ............................................................................
  .....


output:
Chunk ID: 95e6f6
Wall time: 0.6108 seconds
Process exited with code 0
Original token count: 25
Output:
...................................

  Tests:    192 passed (966 assertions)
  Duration: 37.01s


command:
/bin/zsh
-lc
php artisan test --compact

cmd: php artisan test --compact


message: The full suite is green after the discount rule change: 192 tests, 966 assertions. I’m updating the progress log to remove the `one_per_customer` enforcement gap while keeping the separate automatic-discount allocation gap tracked.


text: The full suite is green after the discount rule change: 192 tests, 966 assertions. I’m updating the progress log to remove the `one_per_customer` enforcement gap while keeping the separate automatic-discount allocation gap tracked.


cmd: rg -n "DiscountService|one_per_customer|one per customer|discount_allocations|Discount `one_per_customer`|Business logic|Open Issues|Completion Summary|product media UI" specs/progress.md


command: rg -n "DiscountService|one_per_customer|one per customer|discount_allocations|Discount `one_per_customer`|Business logic|Open Issues|Completion Summary|product media UI" specs/progress.md

aggregated_output:
zsh:1: command not found: one_per_customer
33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService`, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
245:- 2026-05-04: `php artisan test --compact tests/Feature/Admin` passed after the product media UI changes: 41 tests, 246 assertions.
246:- 2026-05-04: `php artisan test --compact` passed after the product media UI changes: 191 tests, 964 assertions.
270:- Cart page discount codes are validated with `DiscountService`, saved in session, and applied to the checkout when the customer proceeds.
271:- Admin discount forms persist minimum purchase, product eligibility, and collection eligibility in the existing `discounts.rules_json` keys consumed by `DiscountService`; `one_per_customer` is captured for future redemption-history enforcement.
272:- `PricingEngine` applies an explicit checkout discount code first, then active automatic discounts returned by `DiscountService`.
302:## Open Issues
315:- Discount `one_per_customer` redemption enforcement still needs customer/order usage-history checks; the admin UI captures the rule but `DiscountService` does not enforce it yet.
316:- Order line `discount_allocations_json` still attributes explicit checkout codes only; automatic discount attribution needs to be recorded before final discount reporting is complete.
320:## Completion Summary


output:
Chunk ID: e33a9e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 745
Output:
zsh:1: command not found: one_per_customer
33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService`, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
245:- 2026-05-04: `php artisan test --compact tests/Feature/Admin` passed after the product media UI changes: 41 tests, 246 assertions.
246:- 2026-05-04: `php artisan test --compact` passed after the product media UI changes: 191 tests, 964 assertions.
270:- Cart page discount codes are validated with `DiscountService`, saved in session, and applied to the checkout when the customer proceeds.
271:- Admin discount forms persist minimum purchase, product eligibility, and collection eligibility in the existing `discounts.rules_json` keys consumed by `DiscountService`; `one_per_customer` is captured for future redemption-history enforcement.
272:- `PricingEngine` applies an explicit checkout discount code first, then active automatic discounts returned by `DiscountService`.
302:## Open Issues
315:- Discount `one_per_customer` redemption enforcement still needs customer/order usage-history checks; the admin UI captures the rule but `DiscountService` does not enforce it yet.
316:- Order line `discount_allocations_json` still attributes explicit checkout codes only; automatic discount attribution needs to be recorded before final discount reporting is complete.
320:## Completion Summary


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/specs/progress.md
@@
-| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService`, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `CartService`, `DiscountService`, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
+| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService`, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `CartService`, `DiscountService` including one-per-customer code discount redemption checks, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
@@
 - 2026-05-04: `npm run build` passed after the product media Blade changes.
 - 2026-05-04: `mcp__laravel_boost__.get_absolute_url(path: "/admin/products/1/edit")` resolved `http://shop.test/admin/products/1/edit`; Playwright MCP verified the product edit page renders the Media section and current console checks report no warnings/errors.
 - 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after product media browser verification to restore seeded fixtures.
+- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 query/JSON testing docs and Pest 4 dataset/testing docs before the discount redemption-history changes.
+- 2026-05-04: `vendor/bin/pint --dirty --format agent` passed after the discount service/order allocation changes.
+- 2026-05-04: `php artisan test --compact tests/Feature/Checkout/PricingServicesTest.php tests/Feature/Orders/OrderServiceTest.php` passed: 11 tests, 63 assertions.
+- 2026-05-04: `php artisan test --compact tests/Feature/Checkout tests/Feature/Orders tests/Feature/Api/StorefrontCheckoutApiTest.php tests/Feature/Storefront/CartCheckoutUiTest.php` passed after the discount redemption-history changes: 33 tests, 196 assertions.
+- 2026-05-04: `php artisan test --compact` passed after the discount redemption-history changes: 192 tests, 966 assertions.
@@
-- Admin discount forms persist minimum purchase, product eligibility, and collection eligibility in the existing `discounts.rules_json` keys consumed by `DiscountService`; `one_per_customer` is captured for future redemption-history enforcement.
+- Admin discount forms persist minimum purchase, product eligibility, collection eligibility, and `one_per_customer` in the existing `discounts.rules_json` keys consumed by `DiscountService`; code discounts now check customer order-line allocation history before allowing another use.
@@
-- Discount `one_per_customer` redemption enforcement still needs customer/order usage-history checks; the admin UI captures the rule but `DiscountService` does not enforce it yet.
-- Order line `discount_allocations_json` still attributes explicit checkout codes only; automatic discount attribution needs to be recorded before final discount reporting is complete.
+- Order line `discount_allocations_json` now records explicit checkout discount code and discount id; automatic discount attribution still needs to be recorded before final discount reporting is complete.
*** End Patch