diff --git a/app/Filament/Resources/Manage/Orders/OrderResource.php b/app/Filament/Resources/Manage/Orders/OrderResource.php new file mode 100644 index 0000000..830614e --- /dev/null +++ b/app/Filament/Resources/Manage/Orders/OrderResource.php @@ -0,0 +1,261 @@ +replace('.', '', (string) ($get('egg_quantity') ?? 0)); + $unitPrice = (int) str()->replace('.', '', (string) ($get('unit_price') ?? 0)); + $discount = (int) str()->replace('.', '', (string) ($get('discount') ?? 0)); + + $subtotal = $quantity * $unitPrice; + $totalAmount = max(0, $subtotal - $discount); + + $set('total_amount', number_format($totalAmount, 0, ',', '.')); + }; + + return $schema->components([ + Hidden::make('unit_id') + ->required(), + + Grid::make(2) + ->schema([ + Select::make('egg_price_id') + ->label('Harga Telur') + ->options(EggPrice::query()->with('unit')->get()->mapWithKeys(function ($item) { + return [$item->id => $item->name.' ('.($item->unit?->alias ?? '-').')']; + })) + ->searchable() + ->preload() + ->native(false) + ->live() + ->afterStateHydrated(function ($state, callable $set, callable $get, $record): void { + if ($record && ! $state) { + $eggPrice = EggPrice::where('unit_id', $record->unit_id) + ->where('price', $record->unit_price) + ->first(); + + if ($eggPrice) { + $set('egg_price_id', $eggPrice->id); + } + } + }) + ->afterStateUpdated(function ($state, callable $set, callable $get) use ($recalculateTotal): void { + if (! $state) { + $set('unit_price', 0); + $set('unit_id', null); + $recalculateTotal($get, $set); + + return; + } + + $price = EggPrice::with('unit')->find($state); + + $set('unit_price', number_format($price?->price ?? 0, 0, ',', '.')); + $set('unit_id', $price?->unit_id); + + $recalculateTotal($get, $set); + }) + ->dehydrated(false) + ->required(), + + Select::make('customer_id') + ->label('Pelanggan') + ->relationship('customer', 'name') + ->searchable() + ->preload() + ->native(false), + + DatePicker::make('order_date') + ->label('Tanggal Order') + ->default(now()) + ->required() + ->native(false) + ->displayFormat('l, d F Y'), + + TextInput::make('egg_quantity') + ->label('Jumlah Telur') + ->placeholder('0') + ->autocomplete(false) + ->required() + ->live() + ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { + $recalculateTotal($get, $set); + }) + ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0) + ->dehydrateStateUsing(fn ($state) => (int) str()->replace('.', '', (string) ($state ?? 0))), + ]), + + Grid::make(3) + ->schema([ + TextInput::make('unit_price') + ->label('Harga per Satuan') + ->placeholder('0') + ->readOnly() + ->prefix('Rp') + ->required() + ->formatStateUsing( + fn ($state) => number_format((int) ($state ?? 0), 0, ',', '.') + ) + ->dehydrateStateUsing(fn ($state) => (int) str()->replace('.', '', (string) ($state ?? 0))) + ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { + $recalculateTotal($get, $set); + }), + + TextInput::make('discount') + ->label('Diskon') + ->placeholder('0') + ->default(0) + ->prefix('Rp') + ->live() + ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { + $recalculateTotal($get, $set); + }) + ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0) + ->dehydrateStateUsing(fn ($state) => (int) str()->replace('.', '', (string) ($state ?? 0))), + + TextInput::make('total_amount') + ->label('Total') + ->placeholder('0') + ->readOnly() + ->prefix('Rp') + ->required() + ->formatStateUsing( + fn ($state) => number_format((int) ($state ?? 0), 0, ',', '.') + ) + ->dehydrateStateUsing(fn ($state) => (int) str()->replace('.', '', (string) ($state ?? 0))), + ]), + + Textarea::make('notes') + ->label('Catatan') + ->placeholder('...') + ->autocomplete(false) + ->columnSpanFull(), + ]) + ->columns(1); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('order_date') + ->label('Tanggal Order') + ->date('l, d F Y') + ->sortable(), + + TextColumn::make('customer.name') + ->label('Pelanggan') + ->searchable() + ->sortable(), + + TextColumn::make('egg_quantity') + ->label('Jumlah') + ->suffix(fn ($record) => ' '.$record->unit?->alias) + ->numeric() + ->sortable(), + + TextColumn::make('unit_price') + ->label('Harga per Satuan') + ->money('IDR', decimalPlaces: 0) + ->sortable(), + + TextColumn::make('discount') + ->label('Diskon') + ->money('IDR', decimalPlaces: 0) + ->sortable() + ->default(0), + + TextColumn::make('total_amount') + ->label('Total') + ->money('IDR', decimalPlaces: 0) + ->sortable(), + + TextColumn::make('created_at') + ->label('Dibuat') + ->dateTime('d/m/Y H:i') + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + TrashedFilter::make() + ->native(false), + ]) + ->recordActions([ + EditAction::make() + ->label('Ubah'), + + DeleteAction::make(), + + ForceDeleteAction::make(), + + RestoreAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + ...DefaultBulkActions::make('Order'), + ]), + ]) + ->emptyStateIcon(Heroicon::ShoppingCart) + ->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.') + ->defaultSort('created_at', 'desc') + ->deferFilters(false); + } + + public static function getPages(): array + { + return [ + 'index' => ManageOrders::route('/'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/Manage/Orders/Pages/ManageOrders.php b/app/Filament/Resources/Manage/Orders/Pages/ManageOrders.php new file mode 100644 index 0000000..fd08a4a --- /dev/null +++ b/app/Filament/Resources/Manage/Orders/Pages/ManageOrders.php @@ -0,0 +1,22 @@ +label('Tambah'), + ]; + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php new file mode 100644 index 0000000..f746358 --- /dev/null +++ b/app/Models/Order.php @@ -0,0 +1,33 @@ + 'date', + 'egg_quantity' => 'integer', + 'unit_price' => 'integer', + 'total_amount' => 'integer', + 'discount' => 'integer', + ]; + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } + + public function unit(): BelongsTo + { + return $this->belongsTo(Unit::class); + } +} diff --git a/database/migrations/2026_02_18_000004_create_orders_table.php b/database/migrations/2026_02_18_000004_create_orders_table.php new file mode 100644 index 0000000..9f5545c --- /dev/null +++ b/database/migrations/2026_02_18_000004_create_orders_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('customer_id')->nullable()->constrained()->cascadeOnDelete(); + $table->foreignId('unit_id')->constrained()->cascadeOnDelete(); + $table->unsignedInteger('egg_quantity'); + $table->unsignedInteger('unit_price'); + $table->date('order_date'); + $table->string('status', 20)->default('pending'); + $table->unsignedInteger('total_amount'); + $table->text('notes')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('orders'); + } +}; diff --git a/database/migrations/2026_02_19_020225_add_discount_to_orders_table.php b/database/migrations/2026_02_19_020225_add_discount_to_orders_table.php new file mode 100644 index 0000000..9dc010c --- /dev/null +++ b/database/migrations/2026_02_19_020225_add_discount_to_orders_table.php @@ -0,0 +1,28 @@ +unsignedInteger('discount')->default(0)->after('total_amount'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('orders', function (Blueprint $table) { + $table->dropColumn('discount'); + }); + } +};