diff --git a/resources/boost/guidelines/blueprint.blade.php b/resources/boost/guidelines/blueprint.blade.php
new file mode 100644
index 000000000..e79cc0a43
--- /dev/null
+++ b/resources/boost/guidelines/blueprint.blade.php
@@ -0,0 +1,864 @@
+# Sharp Blueprint
+
+A Sharp Blueprint is a detailed implementation plan that helps AI agents write correct Sharp code. It bridges the gap between requirements and implementation by providing a structured specification for Sharp's unique patterns.
+
+## Core Philosophy
+
+Sharp separates the data structure from its visual representation. A blueprint should clearly map domain models to Sharp components:
+- **Entity Lists**: The primary data table view
+- **Forms**: Data entry and validation
+- **Show Pages**: Detailed read-only views
+- **Commands**: Business logic triggers (instance, entity, wizard)
+- **State Handlers**: Lifecycle management
+- **Filters**: Data filtering for entity lists
+
+## Blueprint Structure
+
+When asking an AI to create a blueprint, it should produce:
+1. **Overview**: Brief description of the system and key decisions
+2. **User Flows**: Step-by-step navigation through features
+3. **Commands**: Required Artisan commands for scaffolding
+4. **Models**: Detailed attributes, relationships, casts, and methods
+5. **Sharp Resources**: Specific configurations for Lists, Forms, Shows, Commands, and State Handlers
+
+---
+
+# SaaS Invoicing System - Sharp Implementation Plan
+
+## Overview
+
+A single-tenant admin panel for managing customers, products, invoices with line items, and tracking payments. All authenticated users can access all data.
+
+**Key decisions:**
+- Single-tenant (all authenticated users see all data)
+- Manual invoice sending (mark as sent, no email automation)
+- Partial payments supported (multiple payments until balance = 0)
+- In-app Sharp notifications only
+
+---
+
+## User Flows
+
+### Flow 1: Creating an Invoice
+1. User navigates to Invoices → Create
+2. User selects a Customer (autocomplete field with search)
+3. User sets invoice date and due date
+4. User adds line items via List field:
+ - Select Product (autocomplete, shows name + price)
+ - Enter quantity
+ - Line total auto-calculates (quantity × unit_price)
+5. Tax rate is entered (percentage)
+6. Subtotal, tax amount, and total auto-calculate
+7. Invoice saves as Draft status with auto-generated invoice number
+
+### Flow 2: Sending an Invoice
+1. User views a Draft invoice in Show Page
+2. User clicks "Mark as Sent" (Instance Command)
+3. Confirmation modal appears
+4. On confirm: status → Sent, sent_at → now()
+5. Success notification shown
+
+### Flow 3: Recording a Payment
+1. User views a Sent invoice (or Overdue)
+2. User clicks "Record Payment" (Instance Command)
+3. Modal form appears with:
+ - Amount (defaults to balance due, validates ≤ balance)
+ - Payment method (select)
+ - Reference (optional)
+ - Payment date
+4. On submit: Payment record created
+5. If total payments ≥ invoice total: status → Paid, paid_at → now()
+6. Success notification shown
+
+### Flow 4: Viewing Invoice Status
+1. User navigates to Invoices list
+2. Table shows status badges (Draft/Sent/Paid/Overdue/Cancelled)
+3. User can filter by status, customer, date range
+4. User can search by invoice number or customer name
+
+---
+
+## Commands
+
+Run these in order:
+
+```bash
+# 1. Create models with migrations and factories
+php artisan make:model Customer -mf --no-interaction
+php artisan make:model Product -mf --no-interaction
+php artisan make:model Invoice -mf --no-interaction
+php artisan make:model InvoiceItem -mf --no-interaction
+php artisan make:model Payment -mf --no-interaction
+
+# 2. Create enums
+php artisan make:enum InvoiceStatus
+php artisan make:enum PaymentMethod
+
+# 3. Create Sharp resources
+php artisan sharp:make:entity-list InvoiceList --model=Invoice
+php artisan sharp:make:form InvoiceForm --model=Invoice
+php artisan sharp:make:show InvoiceShow --model=Invoice
+php artisan sharp:make:entity-list CustomerList --model=Customer
+php artisan sharp:make:form CustomerForm --model=Customer
+php artisan sharp:make:show CustomerShow --model=Customer
+php artisan sharp:make:entity-list ProductList --model=Product
+php artisan sharp:make:form ProductForm --model=Product
+
+# 4. Create state handler
+php artisan sharp:make:state-handler InvoiceStateHandler
+
+# 5. Create commands
+php artisan sharp:make:instance-command SendInvoiceCommand
+php artisan sharp:make:instance-command RecordPaymentCommand
+
+# 6. Create filters
+php artisan sharp:make:entity-list-filter InvoiceStatusFilter
+php artisan sharp:make:entity-list-filter CustomerFilter
+php artisan sharp:make:entity-list-filter InvoiceDateRangeFilter
+```
+
+---
+
+## Models
+
+### Enum: InvoiceStatus
+
+```
+Enum: InvoiceStatus
+ Location: App\Enums\InvoiceStatus
+ Type: string backed enum
+ Cases:
+ - Draft = 'draft'
+ - Sent = 'sent'
+ - Paid = 'paid'
+ - Overdue = 'overdue'
+ - Cancelled = 'cancelled'
+ Methods:
+ - label(): string (Draft, Sent, Paid, Overdue, Cancelled)
+ - color(): string (gray, blue, green, red, orange)
+```
+
+### Enum: PaymentMethod
+
+```
+Enum: PaymentMethod
+ Location: App\Enums\PaymentMethod
+ Type: string backed enum
+ Cases:
+ - Cash = 'cash'
+ - Check = 'check'
+ - BankTransfer = 'bank_transfer'
+ - CreditCard = 'credit_card'
+ - Other = 'other'
+ Methods:
+ - label(): string
+```
+
+### Model: Customer
+
+```
+Model: Customer
+ Table: customers
+ Attributes:
+ - id: bigint, primary
+ - name: string, required
+ - email: string, required
+ - phone: string, nullable
+ - address_line_1: string, nullable
+ - address_line_2: string, nullable
+ - city: string, nullable
+ - state: string, nullable
+ - postal_code: string, nullable
+ - country: string, nullable
+ - notes: text, nullable
+ - created_at: timestamp
+ - updated_at: timestamp
+ - deleted_at: timestamp, nullable
+ Relationships:
+ - hasMany: Invoice via customer_id
+ Traits:
+ - SoftDeletes
+```
+
+### Model: Product
+
+```
+Model: Product
+ Table: products
+ Attributes:
+ - id: bigint, primary
+ - name: string, required
+ - sku: string, nullable, unique
+ - description: text, nullable
+ - unit_price: integer, required (stored in cents)
+ - is_active: boolean, default:true
+ - created_at: timestamp
+ - updated_at: timestamp
+ - deleted_at: timestamp, nullable
+ Relationships:
+ - hasMany: InvoiceItem via product_id
+ Traits:
+ - SoftDeletes
+ Casts:
+ - unit_price: integer
+ - is_active: boolean
+```
+
+### Model: Invoice
+
+```
+Model: Invoice
+ Table: invoices
+ Attributes:
+ - id: bigint, primary
+ - customer_id: bigint, foreign(customers.id), required
+ - invoice_number: string, required, unique
+ - status: string, default:'draft' (uses InvoiceStatus enum)
+ - invoice_date: date, required
+ - due_date: date, required
+ - subtotal: integer, default:0 (cents)
+ - tax_rate: decimal(5,2), default:0
+ - tax_amount: integer, default:0 (cents)
+ - total: integer, default:0 (cents)
+ - amount_paid: integer, default:0 (cents)
+ - notes: text, nullable
+ - sent_at: timestamp, nullable
+ - paid_at: timestamp, nullable
+ - created_at: timestamp
+ - updated_at: timestamp
+ - deleted_at: timestamp, nullable
+ Relationships:
+ - belongsTo: Customer via customer_id
+ - hasMany: InvoiceItem via invoice_id
+ - hasMany: Payment via invoice_id
+ Traits:
+ - SoftDeletes
+ Casts:
+ - status: InvoiceStatus::class
+ - invoice_date: date
+ - due_date: date
+ - tax_rate: decimal:2
+ - sent_at: datetime
+ - paid_at: datetime
+ Accessors:
+ - balance_due: int (total - amount_paid)
+ Methods:
+ - generateInvoiceNumber(): string (format: INV-YYYYMM-XXXX)
+ - recalculateTotals(): void (sum line items, apply tax)
+ - markAsSent(): void
+ - markAsPaid(): void
+ - recordPayment(int $amount, PaymentMethod $method, ?string $reference, Carbon $date): Payment
+```
+
+### Model: InvoiceItem
+
+```
+Model: InvoiceItem
+ Table: invoice_items
+ Attributes:
+ - id: bigint, primary
+ - invoice_id: bigint, foreign(invoices.id), required, onDelete:cascade
+ - product_id: bigint, foreign(products.id), nullable
+ - description: string, required
+ - quantity: integer, required, default:1
+ - unit_price: integer, required (cents)
+ - total: integer, required (cents, quantity × unit_price)
+ - sort_order: integer, default:0
+ - created_at: timestamp
+ - updated_at: timestamp
+ Relationships:
+ - belongsTo: Invoice via invoice_id
+ - belongsTo: Product via product_id (nullable)
+ Casts:
+ - quantity: integer
+ - unit_price: integer
+ - total: integer
+ - sort_order: integer
+```
+
+### Model: Payment
+
+```
+Model: Payment
+ Table: payments
+ Attributes:
+ - id: bigint, primary
+ - invoice_id: bigint, foreign(invoices.id), required, onDelete:cascade
+ - amount: integer, required (cents)
+ - method: string, required (uses PaymentMethod enum)
+ - reference: string, nullable
+ - payment_date: date, required
+ - notes: text, nullable
+ - created_at: timestamp
+ - updated_at: timestamp
+ Relationships:
+ - belongsTo: Invoice via invoice_id
+ Casts:
+ - method: PaymentMethod::class
+ - amount: integer
+ - payment_date: date
+```
+
+---
+
+## Sharp Resources
+
+### CustomerList
+
+@verbatim
+```
+Resource: CustomerList
+ Location: App\Sharp\Customers\CustomerList
+ Extends: Code16\Sharp\EntityList\SharpEntityList
+ Docs: https://sharp.code16.fr/docs/guide/building-entity-list
+
+ Method: buildList(EntityListFieldsContainer $fields): void
+ Fields:
+ - EntityListField::make('name')
+ ->setLabel('Name')
+ ->setSortable()
+
+ - EntityListField::make('email')
+ ->setLabel('Email')
+ ->setSortable()
+
+ - EntityListField::make('phone')
+ ->setLabel('Phone')
+ ->hideOnSmallScreens()
+
+ - EntityListField::make('city')
+ ->setLabel('City')
+ ->hideOnSmallScreens()
+
+ - EntityListField::make('invoices_count')
+ ->setLabel('Invoices')
+ ->setSortable()
+
+ Method: buildListConfig(): void
+ Config:
+ - configureSearchable()
+ - configureDefaultSort('name', 'asc')
+ - configureReorderable(false)
+ - configurePaginated()
+
+ Method: getListData(EntityListQueryParams $params): array
+ - Query: Customer::query()->withCount('invoices')
+ - Search: name, email, phone
+ - Transform: id, name, email, phone, city, invoices_count
+```
+@endverbatim
+
+### CustomerForm
+
+@verbatim
+```
+Resource: CustomerForm
+ Location: App\Sharp\Customers\CustomerForm
+ Extends: Code16\Sharp\Form\SharpForm
+ Docs: https://sharp.code16.fr/docs/guide/building-form
+
+ Method: buildFormFields(FieldsContainer $formFields): void
+ Fields:
+ - SharpFormTextField::make('name')
+ ->setLabel('Name')
+ ->setMaxLength(255)
+
+ - SharpFormTextField::make('email')
+ ->setLabel('Email')
+ ->setMaxLength(255)
+
+ - SharpFormTextField::make('phone')
+ ->setLabel('Phone')
+ ->setMaxLength(50)
+
+ - SharpFormTextField::make('address_line_1')
+ ->setLabel('Address Line 1')
+ ->setMaxLength(255)
+
+ - SharpFormTextField::make('address_line_2')
+ ->setLabel('Address Line 2')
+ ->setMaxLength(255)
+
+ - SharpFormTextField::make('city')
+ ->setLabel('City')
+ ->setMaxLength(100)
+
+ - SharpFormTextField::make('state')
+ ->setLabel('State')
+ ->setMaxLength(100)
+
+ - SharpFormTextField::make('postal_code')
+ ->setLabel('Postal Code')
+ ->setMaxLength(20)
+
+ - SharpFormTextField::make('country')
+ ->setLabel('Country')
+ ->setMaxLength(100)
+
+ - SharpFormTextareaField::make('notes')
+ ->setLabel('Notes')
+ ->setRowCount(4)
+
+ Method: buildFormLayout(FormLayout $formLayout): void
+ Layout:
+ - Column 8:
+ - Fieldset "Contact Information":
+ - name (full width)
+ - Row: email, phone
+ - Fieldset "Address":
+ - address_line_1 (full width)
+ - address_line_2 (full width)
+ - Row: city, state, postal_code
+ - country (full width)
+ - Column 4:
+ - Fieldset "Additional Information":
+ - notes (full width)
+
+ Method: create(): array
+ - Return: empty customer data array
+
+ Method: update(mixed $id, array $data): bool
+ - Find/create Customer model
+ - Save attributes
+ - Return true
+
+ Method: find(mixed $id): array
+ - Find Customer by id
+ - Transform to array
+```
+@endverbatim
+
+### InvoiceList
+
+@verbatim
+```
+Resource: InvoiceList
+ Location: App\Sharp\Invoices\InvoiceList
+ Extends: Code16\Sharp\EntityList\SharpEntityList
+ Docs: https://sharp.code16.fr/docs/guide/building-entity-list
+
+ Method: buildList(EntityListFieldsContainer $fields): void
+ Fields:
+ - EntityListField::make('invoice_number')
+ ->setLabel('Number')
+ ->setSortable()
+
+ - EntityListField::make('customer:name')
+ ->setLabel('Customer')
+ ->setSortable()
+
+ - EntityListField::make('invoice_date')
+ ->setLabel('Date')
+ ->setSortable()
+
+ - EntityListField::make('due_date')
+ ->setLabel('Due Date')
+ ->setSortable()
+ ->hideOnSmallScreens()
+
+ - EntityListField::make('total')
+ ->setLabel('Total')
+ ->setSortable()
+
+ - EntityListStateField::make()
+ ->setLabel('Status')
+
+ Method: buildListConfig(): void
+ Config:
+ - configureSearchable()
+ - configureDefaultSort('invoice_date', 'desc')
+ - configureEntityState('status', InvoiceStateHandler::class)
+ - configurePaginated()
+
+ Filters:
+ - InvoiceStatusFilter
+ - CustomerFilter
+ - InvoiceDateRangeFilter
+
+ Method: getListData(EntityListQueryParams $params): array
+ - Query: Invoice::with('customer')
+ - Search: invoice_number, customer.name
+ - Filters: status, customer_id, date range
+ - Transform: id, invoice_number, customer:name, invoice_date, due_date, total (formatted), status
+```
+@endverbatim
+
+### InvoiceForm
+
+@verbatim
+```
+Resource: InvoiceForm
+ Location: App\Sharp\Invoices\InvoiceForm
+ Extends: Code16\Sharp\Form\SharpForm
+ Docs: https://sharp.code16.fr/docs/guide/building-form
+
+ Method: buildFormFields(FieldsContainer $formFields): void
+ Fields:
+ - SharpFormAutocompleteField::make('customer_id', 'remote')
+ ->setLabel('Customer')
+ ->setRemoteEndpoint('/sharp/api/autocomplete/customers')
+ ->setResultItemInlineTemplate('{{ $name }} - {{ $email }}')
+ ->setListItemInlineTemplate('{{ $name }}')
+
+ - SharpFormTextField::make('invoice_number')
+ ->setLabel('Invoice Number')
+ ->setReadOnly()
+ ->setMaxLength(50)
+
+ - SharpFormDateField::make('invoice_date')
+ ->setLabel('Invoice Date')
+
+ - SharpFormDateField::make('due_date')
+ ->setLabel('Due Date')
+
+ - SharpFormListField::make('items')
+ ->setLabel('Line Items')
+ ->setAddable()
+ ->setRemovable()
+ ->setSortable()
+ ->setOrderAttribute('sort_order')
+ ->addItemField(
+ SharpFormAutocompleteField::make('product_id', 'remote')
+ ->setLabel('Product')
+ ->setRemoteEndpoint('/sharp/api/autocomplete/products')
+ ->setResultItemInlineTemplate('{{ $name }} - {{ $unit_price }}')
+ )
+ ->addItemField(
+ SharpFormTextField::make('description')
+ ->setLabel('Description')
+ ->setMaxLength(255)
+ )
+ ->addItemField(
+ SharpFormTextField::make('quantity')
+ ->setLabel('Quantity')
+ )
+ ->addItemField(
+ SharpFormTextField::make('unit_price')
+ ->setLabel('Unit Price')
+ )
+ ->addItemField(
+ SharpFormTextField::make('total')
+ ->setLabel('Total')
+ ->setReadOnly()
+ )
+
+ - SharpFormTextField::make('tax_rate')
+ ->setLabel('Tax Rate (%)')
+ ->setInputTypeNumber()
+ ->setStep(0.01)
+
+ - SharpFormTextField::make('subtotal')
+ ->setLabel('Subtotal')
+ ->setReadOnly()
+
+ - SharpFormTextField::make('tax_amount')
+ ->setLabel('Tax Amount')
+ ->setReadOnly()
+
+ - SharpFormTextField::make('total')
+ ->setLabel('Total')
+ ->setReadOnly()
+
+ - SharpFormTextareaField::make('notes')
+ ->setLabel('Notes')
+ ->setRowCount(4)
+
+ Method: buildFormLayout(FormLayout $formLayout): void
+ Layout:
+ - Column 8:
+ - Fieldset "General":
+ - customer_id (full width)
+ - invoice_number (full width)
+ - Row: invoice_date, due_date
+ - Fieldset "Line Items":
+ - items (full width)
+ - Fieldset "Notes":
+ - notes (full width)
+ - Column 4:
+ - Fieldset "Totals":
+ - tax_rate (full width)
+ - subtotal (full width)
+ - tax_amount (full width)
+ - total (full width)
+
+ Method: create(): array
+ - Generate invoice number
+ - Set default dates
+ - Return empty invoice data
+
+ Method: update(mixed $id, array $data): bool
+ - Find/create Invoice model
+ - Save attributes and relationships
+ - Recalculate totals
+ - Return true
+
+ Method: find(mixed $id): array
+ - Find Invoice with items
+ - Transform to array with calculated totals
+```
+@endverbatim
+
+### InvoiceShow
+
+@verbatim
+```
+Resource: InvoiceShow
+ Location: App\Sharp\Invoices\InvoiceShow
+ Extends: Code16\Sharp\Show\SharpShow
+ Docs: https://sharp.code16.fr/docs/guide/building-show-page
+
+ Method: buildShowFields(FieldsContainer $showFields): void
+ Fields:
+ - SharpShowTextField::make('invoice_number')
+ ->setLabel('Invoice Number')
+
+ - SharpShowTextField::make('status')
+ ->setLabel('Status')
+
+ - SharpShowTextField::make('customer')
+ ->setLabel('Customer')
+
+ - SharpShowTextField::make('invoice_date')
+ ->setLabel('Invoice Date')
+
+ - SharpShowTextField::make('due_date')
+ ->setLabel('Due Date')
+
+ - SharpShowListField::make('items')
+ ->setLabel('Line Items')
+ ->addItemField(SharpShowTextField::make('description')->setLabel('Description'))
+ ->addItemField(SharpShowTextField::make('quantity')->setLabel('Quantity'))
+ ->addItemField(SharpShowTextField::make('unit_price')->setLabel('Unit Price'))
+ ->addItemField(SharpShowTextField::make('total')->setLabel('Total'))
+
+ - SharpShowTextField::make('subtotal')
+ ->setLabel('Subtotal')
+
+ - SharpShowTextField::make('tax_rate')
+ ->setLabel('Tax Rate')
+
+ - SharpShowTextField::make('tax_amount')
+ ->setLabel('Tax Amount')
+
+ - SharpShowTextField::make('total')
+ ->setLabel('Total')
+
+ - SharpShowTextField::make('amount_paid')
+ ->setLabel('Amount Paid')
+
+ - SharpShowTextField::make('balance_due')
+ ->setLabel('Balance Due')
+
+ - SharpShowEntityListField::make('payments', PaymentEntity::class)
+ ->setLabel('Payments')
+ ->hideFilterWithValue('invoice', fn($instanceId) => $instanceId)
+
+ - SharpShowTextField::make('notes')
+ ->setLabel('Notes')
+
+ Method: buildShowLayout(ShowLayout $showLayout): void
+ Layout:
+ - Section "Invoice Details":
+ - Column 8:
+ - invoice_number, status, customer
+ - invoice_date, due_date
+ - Column 4: (empty for spacing)
+ - Section "Line Items":
+ - Column 12:
+ - items (full width)
+ - Section "Totals":
+ - Column 8: (empty)
+ - Column 4:
+ - subtotal, tax_rate, tax_amount, total
+ - amount_paid, balance_due
+ - Section "Payments":
+ - Column 12:
+ - payments (full width)
+ - Section "Additional Information":
+ - Column 12:
+ - notes (full width)
+
+ Method: find(mixed $id): array
+ - Find Invoice with customer, items, payments
+ - Transform to array with formatted values
+
+ Commands:
+ - SendInvoiceCommand (instance)
+ - RecordPaymentCommand (instance)
+```
+@endverbatim
+
+### InvoiceStateHandler
+
+@verbatim
+
+namespace App\Sharp\Invoices;
+
+use App\Enums\InvoiceStatus;
+use Code16\Sharp\EntityList\Commands\EntityState;
+
+class InvoiceStateHandler extends EntityState
+{
+ protected function buildStates(): void
+ {
+ $this
+ ->addState(InvoiceStatus::Draft->value, InvoiceStatus::Draft->label(), InvoiceStatus::Draft->color())
+ ->addState(InvoiceStatus::Sent->value, InvoiceStatus::Sent->label(), InvoiceStatus::Sent->color())
+ ->addState(InvoiceStatus::Paid->value, InvoiceStatus::Paid->label(), InvoiceStatus::Paid->color())
+ ->addState(InvoiceStatus::Overdue->value, InvoiceStatus::Overdue->label(), InvoiceStatus::Overdue->color())
+ ->addState(InvoiceStatus::Cancelled->value, InvoiceStatus::Cancelled->label(), InvoiceStatus::Cancelled->color());
+ }
+
+ protected function updateState(mixed $instanceId, string $stateId): array
+ {
+ $invoice = \App\Models\Invoice::findOrFail($instanceId);
+
+ $invoice->update([
+ 'status' => InvoiceStatus::from($stateId),
+ ]);
+
+ if ($stateId === InvoiceStatus::Sent->value && !$invoice->sent_at) {
+ $invoice->update(['sent_at' => now()]);
+ }
+
+ if ($stateId === InvoiceStatus::Paid->value && !$invoice->paid_at) {
+ $invoice->update(['paid_at' => now()]);
+ }
+
+ return $this->reload();
+ }
+}
+
+@endverbatim
+
+### SendInvoiceCommand
+
+@verbatim
+
+namespace App\Sharp\Invoices\Commands;
+
+use App\Enums\InvoiceStatus;
+use App\Models\Invoice;
+use Code16\Sharp\EntityList\Commands\InstanceCommand;
+
+class SendInvoiceCommand extends InstanceCommand
+{
+ public function label(): string
+ {
+ return 'Mark as Sent';
+ }
+
+ public function execute(mixed $instanceId, array $data = []): array
+ {
+ $invoice = Invoice::findOrFail($instanceId);
+
+ if ($invoice->status !== InvoiceStatus::Draft) {
+ return $this->error('Only draft invoices can be sent.');
+ }
+
+ $invoice->markAsSent();
+
+ return $this->reload();
+ }
+
+ public function authorizeFor(mixed $instanceId): bool
+ {
+ $invoice = Invoice::find($instanceId);
+
+ return $invoice && $invoice->status === InvoiceStatus::Draft;
+ }
+}
+
+@endverbatim
+
+### RecordPaymentCommand
+
+@verbatim
+
+namespace App\Sharp\Invoices\Commands;
+
+use App\Enums\PaymentMethod;
+use App\Models\Invoice;
+use Code16\Sharp\EntityList\Commands\InstanceCommand;
+use Code16\Sharp\Form\Fields\SharpFormDateField;
+use Code16\Sharp\Form\Fields\SharpFormSelectField;
+use Code16\Sharp\Form\Fields\SharpFormTextareaField;
+use Code16\Sharp\Form\Fields\SharpFormTextField;
+use Code16\Sharp\Utils\Fields\FieldsContainer;
+
+class RecordPaymentCommand extends InstanceCommand
+{
+ public function label(): string
+ {
+ return 'Record Payment';
+ }
+
+ public function buildFormFields(FieldsContainer $formFields): void
+ {
+ $formFields
+ ->addField(
+ SharpFormTextField::make('amount')
+ ->setLabel('Amount')
+ )
+ ->addField(
+ SharpFormSelectField::make('method', collect(PaymentMethod::cases())->map(fn($case) => [
+ 'id' => $case->value,
+ 'label' => $case->label(),
+ ])->all())
+ ->setLabel('Payment Method')
+ )
+ ->addField(
+ SharpFormTextField::make('reference')
+ ->setLabel('Reference')
+ ->setMaxLength(255)
+ )
+ ->addField(
+ SharpFormDateField::make('payment_date')
+ ->setLabel('Payment Date')
+ )
+ ->addField(
+ SharpFormTextareaField::make('notes')
+ ->setLabel('Notes')
+ ->setRowCount(3)
+ );
+ }
+
+ public function initialData(mixed $instanceId): array
+ {
+ $invoice = Invoice::findOrFail($instanceId);
+
+ return [
+ 'amount' => $invoice->balance_due / 100,
+ 'payment_date' => now()->format('Y-m-d'),
+ ];
+ }
+
+ public function execute(mixed $instanceId, array $data = []): array
+ {
+ $invoice = Invoice::findOrFail($instanceId);
+
+ $amountInCents = (int) ($data['amount'] * 100);
+
+ if ($amountInCents > $invoice->balance_due) {
+ return $this->error('Payment amount cannot exceed balance due.');
+ }
+
+ $invoice->recordPayment(
+ $amountInCents,
+ PaymentMethod::from($data['method']),
+ $data['reference'] ?? null,
+ \Carbon\Carbon::parse($data['payment_date'])
+ );
+
+ return $this->reload();
+ }
+
+ public function authorizeFor(mixed $instanceId): bool
+ {
+ $invoice = Invoice::find($instanceId);
+
+ return $invoice && $invoice->balance_due > 0;
+ }
+}
+
+@endverbatim