diff --git a/app/Filament/Admin/Resources/CustomerReportResource.php b/app/Filament/Admin/Resources/CustomerReportResource.php index a86a331..c155484 100644 --- a/app/Filament/Admin/Resources/CustomerReportResource.php +++ b/app/Filament/Admin/Resources/CustomerReportResource.php @@ -124,7 +124,7 @@ public static function table(Table $table): Table public static function canAccess(): bool { - return auth()->user()->is_admin; + return auth()->user()->is_admin ?? false; } public static function getRelations(): array diff --git a/app/Filament/Admin/Resources/OrderResource.php b/app/Filament/Admin/Resources/OrderResource.php index aa9055b..854342c 100644 --- a/app/Filament/Admin/Resources/OrderResource.php +++ b/app/Filament/Admin/Resources/OrderResource.php @@ -6,7 +6,6 @@ use App\Enums\OrderAttributes; use App\Enums\OrderStatus; use App\Enums\OrderType; -use App\Models\Contact; use App\Models\Customer; use App\Models\Order; use App\Models\OrderProduct; @@ -62,18 +61,8 @@ public static function form(Form $form): Form ->required() ->label('Customer') ->options(Customer::all()->pluck('company_name', 'id')) -// ->reactive() ->searchable(), - // Select::make('contact_id') - // ->label('Contact') - // ->options(fn ($get): array => Contact::where('customer_id', $get('customer_id') ?? null) - // ->get() - // ->pluck('full_name', 'id') - // ->toArray()) - // ->searchable(), - // ]), - TextInput::make('customer_po') ->required() ->label('Customer PO'), diff --git a/app/Filament/Admin/Resources/PaymentResource.php b/app/Filament/Admin/Resources/PaymentResource.php index 777cda3..9730538 100644 --- a/app/Filament/Admin/Resources/PaymentResource.php +++ b/app/Filament/Admin/Resources/PaymentResource.php @@ -11,7 +11,6 @@ use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Resources\Resource; -use Filament\Tables; use Filament\Tables\Actions\ViewAction; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; @@ -72,16 +71,8 @@ public static function table(Table $table): Table ->label('Balance') ->money(), ]) - ->filters([ - // - ]) ->actions([ ViewAction::make(), - ]) - ->bulkActions([ - // Tables\Actions\BulkActionGroup::make([ - // Tables\Actions\DeleteBulkAction::make(), - // ]), ]); } diff --git a/app/Filament/Admin/Resources/QuoteResource.php b/app/Filament/Admin/Resources/QuoteResource.php index 9764112..2fdc17f 100644 --- a/app/Filament/Admin/Resources/QuoteResource.php +++ b/app/Filament/Admin/Resources/QuoteResource.php @@ -4,16 +4,17 @@ use App\Enums\IconEnum; use App\Models\Customer; -use App\Models\Order; use App\Models\Quote; -use Filament\Forms\Components\RichEditor; +use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Section; use Filament\Forms\Components\Select; -use Filament\Forms\Components\Split; +use Filament\Forms\Components\Textarea; +use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; +use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater; class QuoteResource extends Resource { @@ -30,28 +31,87 @@ public static function form(Form $form): Form return $form ->schema([ Section::make([ - Split::make([ - Select::make('customer_id') - ->required() - ->label('Customer') - ->options(Customer::all()->pluck('company_name', 'id')) - ->reactive() - ->searchable(), + Select::make('customer_id') + ->required() + ->label('Customer') + ->options(Customer::all()->pluck('company_name', 'id')) + ->reactive() + ->searchable() + ->columnSpan(1), - Select::make('order_id') - ->label('Order') - ->options(fn ($get): array => Order::where('customer_id', $get('customer_id') ?? null) - ->get() - ->pluck('customer_po', 'id') - ->toArray()) - ->searchable(), + DatePicker::make('date') + ->default(today()) + ->required(), - ])->columnSpan(2), - RichEditor::make('body') + TextArea::make('notes') ->columnSpan(2), - // ->rows(8), - ]), - ])->columns(3); + ]) + ->columns(2), + + TableRepeater::make('embroideryEntries') + ->relationship('embroideryEntries') + ->schema([ + TextInput::make('logo') + ->label('Logo name'), + TextInput::make('placement'), + TextInput::make('quantity') + ->prefix('#'), + TextInput::make('width') + ->suffix('"'), + TextInput::make('height') + ->suffix('"'), + TextInput::make('stitch_count'), + TextInput::make('digitizing_cost') + ->prefix('$'), + TextInput::make('run_charge') + ->prefix('$'), + ]) + ->addActionLabel('Add Embroidery Entry') + ->defaultItems(0), + + TableRepeater::make('screenPrintEntries') + ->relationship('screenPrintEntries') + ->schema([ + TextInput::make('logo') + ->label('Logo name'), + TextInput::make('quantity') + ->prefix('#'), + TextInput::make('width') + ->suffix('"'), + TextInput::make('height') + ->suffix('"'), + TextInput::make('color_amount'), + TextInput::make('color_match') + ->prefix('$'), + TextInput::make('flash') + ->prefix('$'), + TextInput::make('fleece') + ->prefix('$'), + TextInput::make('poly_ink') + ->prefix('$'), + TextInput::make('other_charges') + ->prefix('$'), + ]) + ->addActionLabel('Add Screen Print Entry') + ->defaultItems(0), + + TableRepeater::make('heatTransferEntries') + ->relationship('heatTransferEntries') + ->schema([ + TextInput::make('logo') + ->label('Logo name'), + TextInput::make('quantity') + ->prefix('#'), + TextInput::make('width') + ->suffix('"'), + TextInput::make('height') + ->suffix('"'), + TextInput::make('price') + ->prefix('$'), + ]) + ->addActionLabel('Add Heat Transfer Entry') + ->defaultItems(0), + ])->columns(1); } public static function table(Table $table): Table diff --git a/app/Models/EmbroideryEntry.php b/app/Models/EmbroideryEntry.php new file mode 100644 index 0000000..e2f14ea --- /dev/null +++ b/app/Models/EmbroideryEntry.php @@ -0,0 +1,26 @@ +belongsTo(Quote::class); + } +} diff --git a/app/Models/HeatTransferEntry.php b/app/Models/HeatTransferEntry.php new file mode 100644 index 0000000..1087cdd --- /dev/null +++ b/app/Models/HeatTransferEntry.php @@ -0,0 +1,23 @@ +belongsTo(Quote::class); + } +} diff --git a/app/Models/Quote.php b/app/Models/Quote.php index e86a67f..0c4074a 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -5,18 +5,35 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class Quote extends Model { use HasFactory; protected $fillable = [ - 'body', - 'order_id', + 'customer_id', + 'date', + 'notes', ]; public function order(): BelongsTo { return $this->belongsTo(Order::class); } + + public function embroideryEntries(): HasMany + { + return $this->hasMany(EmbroideryEntry::class); + } + + public function screenPrintEntries(): HasMany + { + return $this->hasMany(ScreenPrintEntry::class); + } + + public function heatTransferEntries(): HasMany + { + return $this->hasMany(HeatTransferEntry::class); + } } diff --git a/app/Models/ScreenPrintEntry.php b/app/Models/ScreenPrintEntry.php new file mode 100644 index 0000000..c732a56 --- /dev/null +++ b/app/Models/ScreenPrintEntry.php @@ -0,0 +1,32 @@ +belongsTo(Quote::class); + } +} diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 195ee36..0392e51 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -8,11 +8,10 @@ class PaymentService { public function distributePayments() { - $payments = Payment::where('unapplied_amount', '>', 0)->get(); foreach ($payments as $payment) { - $payment->applyToInvoices(); // Apply remaining amounts to the new invoice + $payment->applyToInvoices(); } } } diff --git a/database/migrations/015_create_quotes_table.php b/database/migrations/015_create_quotes_table.php index ea4193a..d4d59fa 100644 --- a/database/migrations/015_create_quotes_table.php +++ b/database/migrations/015_create_quotes_table.php @@ -14,9 +14,9 @@ public function up(): void Schema::create('quotes', function (Blueprint $table) { $table->id(); - $table->foreignId('order_id')->nullable()->constrained(); - - $table->longText('body')->nullable(); + $table->foreignId('customer_id')->constrained(); + $table->date('date'); + $table->longText('notes')->nullable(); $table->timestamps(); }); diff --git a/database/migrations/020_create_embroidery_entries_table.php b/database/migrations/020_create_embroidery_entries_table.php new file mode 100644 index 0000000..3f39c24 --- /dev/null +++ b/database/migrations/020_create_embroidery_entries_table.php @@ -0,0 +1,40 @@ +id(); + + $table->foreignId('quote_id')->constrained(); + + $table->integer('quantity')->nullable(); + $table->string('logo')->nullable(); + $table->decimal('width', 6, 2)->nullable(); + $table->decimal('height', 6, 2)->nullable(); + $table->string('placement')->nullable(); + $table->string('stitch_count')->nullable(); + $table->string('digitizing_cost')->nullable(); + $table->string('run_charge')->nullable(); + $table->text('notes')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('embroidery_entries'); + } +}; diff --git a/database/migrations/021_create_screen_print_entries_table.php b/database/migrations/021_create_screen_print_entries_table.php new file mode 100644 index 0000000..4de50a0 --- /dev/null +++ b/database/migrations/021_create_screen_print_entries_table.php @@ -0,0 +1,45 @@ +id(); + + $table->foreignId('quote_id')->constrained(); + + $table->integer('quantity')->nullable(); + $table->string('logo')->nullable(); + $table->decimal('width', 6, 2)->nullable(); + $table->decimal('height', 6, 2)->nullable(); + $table->integer('color_amount')->nullable(); + $table->integer('setup_amount')->nullable(); + $table->decimal('run_charge', 8, 2)->nullable(); + $table->decimal('color_change', 8, 2)->default(false); + $table->decimal('color_match', 8, 2)->default(false); + $table->decimal('flash', 8, 2)->default(false); + $table->decimal('fleece', 8, 2)->default(false); + $table->decimal('poly_ink', 8, 2)->default(false); + $table->decimal('other_charges', 8, 2)->default(false); + $table->text('notes')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('screen_print_entries'); + } +}; diff --git a/database/migrations/022_create_heat_transfer_entries_table.php b/database/migrations/022_create_heat_transfer_entries_table.php new file mode 100644 index 0000000..d8dd7d6 --- /dev/null +++ b/database/migrations/022_create_heat_transfer_entries_table.php @@ -0,0 +1,36 @@ +id(); + + $table->foreignId('quote_id')->constrained(); + + $table->integer('quantity')->nullable(); + $table->string('logo')->nullable(); + $table->decimal('width', 6, 2)->nullable(); + $table->decimal('height', 6, 2)->nullable(); + $table->decimal('price', 8, 2)->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('heat_transfer_entries'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 8bd5966..3c3f748 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -26,7 +26,7 @@ public function run(): void ServiceTypeSeeder::class, ProductServiceSeeder::class, ServiceFileSeeder::class, - QuoteSeeder::class, + // QuoteSeeder::class, InvoiceSeeder::class, InvoiceReportSeeder::class, ]); diff --git a/public/invoice-inv400038.pdf b/public/invoice-inv400038.pdf new file mode 100644 index 0000000..e3db113 Binary files /dev/null and b/public/invoice-inv400038.pdf differ diff --git a/resources/css/app.css b/resources/css/app.css index 4443db6..4318477 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,3 +1,3 @@ /*@tailwind base;*/ /*@tailwind components;*/ -/*@tailwind utilities;*/ +/*@tailwind utilities;*/ \ No newline at end of file diff --git a/resources/views/vendor/filament-table-repeater/table-repeater.blade.php b/resources/views/vendor/filament-table-repeater/table-repeater.blade.php index 5e1f198..125147e 100644 --- a/resources/views/vendor/filament-table-repeater/table-repeater.blade.php +++ b/resources/views/vendor/filament-table-repeater/table-repeater.blade.php @@ -37,7 +37,7 @@ {{ $attributes ->merge($getExtraAttributes(), escape: false) - ->class(['bg-white border border-gray-150 rounded-xl relative dark:bg-gray-900 dark:border-gray-700']) + ->class(['bg-white border border-gray-150 rounded-xl relative dark:bg-gray-900 dark:border-gray-800']) }} > @@ -74,14 +74,14 @@ @endif -
+
@foreach($columnLabels as $columnLabel) @if($columnLabel['display']) -
actingAs(User::factory(['is_admin' => true])->create()); - livewire(ListQuotes::class)->assertSuccessful(); + + livewire(ListCustomerReports::class)->assertSuccessful(); }); it('cannot render the list page if user isn\'t an admin', function () { $this->actingAs(User::factory()->create()); - livewire(ListQuotes::class)->assertForbidden(); + livewire(ListCustomerReports::class)->assertForbidden(); }); diff --git a/tests/Unit/CustomerTest.php b/tests/Unit/CustomerTest.php index c5d4375..914987b 100644 --- a/tests/Unit/CustomerTest.php +++ b/tests/Unit/CustomerTest.php @@ -1,5 +1,6 @@ actingAs(User::factory()->create()); livewire(ListCustomers::class)->assertSuccessful(); + // livewire(ListCustomers::class) + // ->call('create') + // ->assertSuccessful(); + // livewire(EditCustomer::class)->assertSuccessful(); }); diff --git a/tests/Unit/QuoteTest.php b/tests/Unit/QuoteTest.php index 570766b..474d958 100644 --- a/tests/Unit/QuoteTest.php +++ b/tests/Unit/QuoteTest.php @@ -1,6 +1,8 @@ actingAs(User::factory()->create()); livewire(ListQuotes::class)->assertForbidden(); }); + +it('can create a quote using the form', function () { + $this->actingAs(User::factory(['is_admin' => true])->create()); + + $customer = Customer::factory()->create(); + + $formData = [ + 'customer_id' => $customer->id, + 'date' => today(), + 'notes' => 'Some note', + ]; + + $this->livewire(CreateQuote::class) + ->fillForm($formData) + ->call('create') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('quotes', $formData); +}); + +it('can add an embroidery entry to the quote using the form', function () { + $this->actingAs(User::factory(['is_admin' => true])->create()); + + $customer = Customer::factory()->create(); + + $formData = [ + 'customer_id' => $customer->id, + 'date' => today(), + 'notes' => 'Some note', + + 'embroideryEntries' => [ + [ + 'logo' => 'logo name', + 'placement' => 'Right sleeve', + 'quantity' => 5, + 'width' => 1.5, + 'height' => 2.5, + 'stitch_count' => '3k - 4k', + 'digitizing_cost' => 10.5, + 'run_charge' => 12, + ], + ], + ]; + + $this->livewire(CreateQuote::class) + ->fillForm($formData) + ->call('create') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('embroidery_entries', $formData['embroideryEntries'][0]); +}); + +it('can add a screen printing entry to the quote using the form', function () { + $this->actingAs(User::factory(['is_admin' => true])->create()); + + $customer = Customer::factory()->create(); + + $formData = [ + 'customer_id' => $customer->id, + 'date' => today(), + 'notes' => 'Some note', + + 'screenPrintEntries' => [ + [ + 'logo' => 'logo name', + 'quantity' => 5, + 'width' => 1.5, + 'height' => 2.5, + 'color_amount' => 2, + 'color_match' => 5.10, + 'flash' => 5.20, + 'fleece' => 5.30, + 'poly_ink' => 5.40, + 'other_charges' => 5.50, + ], + ], + ]; + + $this->livewire(CreateQuote::class) + ->fillForm($formData) + ->call('create') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('screen_print_entries', $formData['screenPrintEntries'][0]); +}); + +it('can add a heat transfer entry to the quote using the form', function () { + $this->actingAs(User::factory(['is_admin' => true])->create()); + + $customer = Customer::factory()->create(); + + $formData = [ + 'customer_id' => $customer->id, + 'date' => today(), + 'notes' => 'Some note', + + 'heatTransferEntries' => [ + [ + 'logo' => 'logo name', + 'quantity' => 5, + 'width' => 1.5, + 'height' => 2.5, + 'price' => 2, + ], + ], + ]; + + $this->livewire(CreateQuote::class) + ->fillForm($formData) + ->call('create') + ->assertHasNoErrors(); + + $this->assertDatabaseHas('heat_transfer_entries', $formData['heatTransferEntries'][0]); +});