From 3082903de5357d24673f11da20efc1a044722c07 Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Tue, 1 Sep 2026 12:39:51 +0530 Subject: [PATCH 1/7] Added spare packing migration file --- ..._01_092316_create_spare_packings_table.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 database/migrations/2026_09_01_092316_create_spare_packings_table.php diff --git a/database/migrations/2026_09_01_092316_create_spare_packings_table.php b/database/migrations/2026_09_01_092316_create_spare_packings_table.php new file mode 100644 index 0000000..26191d2 --- /dev/null +++ b/database/migrations/2026_09_01_092316_create_spare_packings_table.php @@ -0,0 +1,51 @@ + Date: Tue, 1 Sep 2026 12:40:27 +0530 Subject: [PATCH 2/7] Added spare packing model file --- app/Models/SparePacking.php | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 app/Models/SparePacking.php diff --git a/app/Models/SparePacking.php b/app/Models/SparePacking.php new file mode 100644 index 0000000..e708735 --- /dev/null +++ b/app/Models/SparePacking.php @@ -0,0 +1,44 @@ +belongsTo(Plant::class); + } + public function item(): BelongsTo + { + return $this->belongsTo(Item::class, 'item_id'); + } + + public function saleOrder(): BelongsTo + { + return $this->belongsTo(SaleOrderMaster::class, 'sale_order_master_id'); + } +} From eb25216153f7bcdbebf414e2d8cc7487644a759c Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Tue, 1 Sep 2026 12:41:26 +0530 Subject: [PATCH 3/7] Added spare packing resource pages --- .../Resources/SparePackingResource.php | 340 +++++++ .../Pages/CreateSparePacking.php | 845 ++++++++++++++++++ .../Pages/EditSparePacking.php | 22 + .../Pages/ListSparePackings.php | 19 + .../Pages/ViewSparePacking.php | 19 + 5 files changed, 1245 insertions(+) create mode 100644 app/Filament/Resources/SparePackingResource.php create mode 100644 app/Filament/Resources/SparePackingResource/Pages/CreateSparePacking.php create mode 100644 app/Filament/Resources/SparePackingResource/Pages/EditSparePacking.php create mode 100644 app/Filament/Resources/SparePackingResource/Pages/ListSparePackings.php create mode 100644 app/Filament/Resources/SparePackingResource/Pages/ViewSparePacking.php diff --git a/app/Filament/Resources/SparePackingResource.php b/app/Filament/Resources/SparePackingResource.php new file mode 100644 index 0000000..880e8e4 --- /dev/null +++ b/app/Filament/Resources/SparePackingResource.php @@ -0,0 +1,340 @@ +schema([ + Section::make('') + ->schema([ + Forms\Components\Select::make('plant_id') + ->label('Plant') + ->reactive() + ->relationship('plant', 'name') + ->options(function (callable $get) { + $userHas = Filament::auth()->user()->plant_id; + + return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray(); + }) + ->required(), + Forms\Components\Select::make('sale_order_master_id') + ->label('Sale Order Number') + ->reactive() + ->searchable() + ->options(function (callable $get) { + $plantId = $get('plant_id'); + if (empty($plantId)) { + return []; + } + return SaleOrderMaster::where('plant_id', $plantId)->distinct()->pluck('sale_order_number', 'sale_order_number'); + + }) + ->required() + ->afterStateUpdated(function ($state, callable $set) { + $set('spare_packing_number', null); + }), + Forms\Components\TextInput::make('spare_packing_number') + ->label('Scan Spare Packing No') + ->reactive() + ->required() + ->readonly() + ->extraAttributes([ + 'x-data' => '{ value: "" }', + 'x-model' => 'value', + 'x-on:keydown.enter.prevent' => '$wire.processPalletNo()', + ]) + ->suffixAction(fn ($get, $set) => Forms\Components\Actions\Action::make('addWirePackNo') + ->label('') + ->button() + ->icon('heroicon-o-plus') + ->color('primary') + ->extraAttributes([ + 'class' => 'p-1 w-7 h-7', + ]) + ->action(function ($get, $set, $livewire) { + + $plantId = $get('plant_id'); + + session(['pallet_clicked_time' => now()->toDateTimeString()]); + session(['pallet_created_by' => Filament::auth()->user()->name]); + + $year = now()->format('y'); + $month = now()->format('m'); + $prefix = "SP-{$year}{$month}"; + + $lastPallet = SparePacking::where('spare_packing_number', 'like', "{$prefix}%") + ->orderByDesc('spare_packing_number') + ->first(); + + if ($lastPallet) { + $lastNumber = (int) substr( + $lastPallet->spare_packing_number, + strlen($prefix) + ); + + $newNumber = $lastNumber + 1; + + $newNumber = $newNumber < 1000 + ? str_pad($newNumber, 3, '0', STR_PAD_LEFT) + : (string) $newNumber; + } else { + $newNumber = '001'; + } + + $newPalletNumber = "{$prefix}{$newNumber}"; + + $set('spare_packing_number', $newPalletNumber); + $set('plant_id', $plantId); + + // $livewire->redirectToQrPdf($newPalletNumber); + }) + ), + Forms\Components\TextInput::make('document_number') + ->label('Document No') + ->reactive() + ->readOnly(fn (callable $get) => ! $get('spare_packing_number')) + ->extraAttributes([ + 'x-on:keydown.enter.prevent' => '$wire.processDocNo()', + ]), + Forms\Components\TextInput::make('removeDoc_number') + ->label('Remove Document No') + ->reactive() + ->minLength(9) + ->readOnly(fn (callable $get) => ! $get('spare_packing_number') || $get('document_number')) + ->extraAttributes([ + 'x-data' => '{ value: "" }', + 'x-model' => 'value', + 'x-on:keydown.enter.prevent' => '$wire.processRemoveDocNo()', + ]), + Forms\Components\TextInput::make('Sno_quantity') + ->label('SNo. Quantity') + ->readOnly() + ->default('0'), + Forms\Components\Select::make('pending_pallet_list') + ->label('Pending Pallet List') + ->reactive() + ->afterStateUpdated(function ($state, callable $set) { + $set('spare_packing_number', $state); + $set('pallet_number_locked', false); + }) + ->options(function ($get) { + + $plantId = $get('plant_id'); + $saleOrder = $get('sale_order_master_id'); + + if (!$plantId || !$saleOrder) { + return []; + } + + $saleOrderIds = SaleOrderMaster::where('plant_id', $plantId) + ->where('sale_order_number', $saleOrder) + ->pluck('id'); + + return SparePacking::query() + ->where('plant_id', $plantId) + ->whereIn('sale_order_master_id', $saleOrderIds) + ->where(function ($query) { + $query->whereNull('spare_packing_status') + ->orWhere('spare_packing_status', ''); + }) + ->whereNotNull('spare_packing_number') + ->orderBy('spare_packing_number', 'asc') + ->pluck('spare_packing_number') + ->unique() + ->mapWithKeys(fn ($number) => [$number => $number]) + ->toArray(); + }), + Forms\Components\View::make('forms.components.save-spare-packing-button'), + Forms\Components\Hidden::make('created_by') + ->label('Created By'), + Forms\Components\Hidden::make('updated_by') + ->label('Updated By'), + ]) + ->columns(6), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('No.') + ->label('No.') + ->alignCenter() + ->getStateUsing(function ($record, $livewire, $column, $rowLoop) { + $paginator = $livewire->getTableRecords(); + $perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10; + $currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1; + + return ($currentPage - 1) * $perPage + $rowLoop->iteration; + }), + Tables\Columns\TextColumn::make('plant.name') + ->label('Plant') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('item.code') + ->label('Item') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('item.description') + ->label('Description') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('saleOrder.sale_order_number') + ->label('Sale Order No') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('saleOrder.supplier_name') + ->label('Supplier Name') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('spare_packing_number') + ->label('Spare Packing Number') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('document_number') + ->label('Document Number') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('quantity') + ->label('Quantity') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('spare_packing_status') + ->label('Spare Packing Status') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('created_at') + ->label('Created At') + ->dateTime() + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('created_by') + ->label('Created By') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('updated_at') + ->label('Updated At') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_by') + ->label('Updated By') + ->alignCenter() + ->searchable() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('scanned_at') + ->label('Scanned At') + ->dateTime() + ->sortable() + ->alignCenter() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('scanned_by') + ->label('Scanned By') + ->alignCenter() + ->searchable() + ->sortable() + ->alignCenter() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('deleted_at') + ->label('Deleted At') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + Tables\Filters\TrashedFilter::make(), + ]) + ->actions([ + Tables\Actions\ViewAction::make(), + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + Tables\Actions\ForceDeleteBulkAction::make(), + Tables\Actions\RestoreBulkAction::make(), + ]), + ]) + ->headerActions([ + ImportAction::make() + ->label('Import Spare Packing') + ->color('warning') + ->importer(SparePackingImporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view import spare packing'); + }), + ExportAction::make() + ->label('Export Spare Packing') + ->color('warning') + ->exporter(SparePackingExporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view export spare packing'); + }), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListSparePackings::route('/'), + 'create' => Pages\CreateSparePacking::route('/create'), + 'view' => Pages\ViewSparePacking::route('/{record}'), + 'edit' => Pages\EditSparePacking::route('/{record}/edit'), + ]; + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/SparePackingResource/Pages/CreateSparePacking.php b/app/Filament/Resources/SparePackingResource/Pages/CreateSparePacking.php new file mode 100644 index 0000000..545dbf3 --- /dev/null +++ b/app/Filament/Resources/SparePackingResource/Pages/CreateSparePacking.php @@ -0,0 +1,845 @@ + 'handleUpdateSnoQuantity', + ]; + + public function handleUpdateSnoQuantity($newValue) + { + $this->form->fill([ + 'Sno_quantity' => $newValue, + ]); + } + + public function processDocNo(){ + + $plantId = $this->form->getState()['plant_id']; + + $plantId = trim($plantId) ?? null; + + $docNo = trim($this->form->getState()['document_number'])?? null; + + $saleOrder = trim($this->form->getState()['sale_order_master_id'])?? null; + + $sparePackNo = trim($this->form->getState()['spare_packing_number'])?? null; + + $sparePackNo = trim($sparePackNo) ?? null; + + $user = Filament::auth()->user(); + + $operatorName = $user->name; + + if (empty($docNo) || $docNo == '') + { + Notification::make() + ->title("Document Number can't be empty") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + // $pattern = '/^([^|]+)\|([^|]+)\|([^|]+)\|(\d+(\.\d+)?)(kg)?$/i'; + + // $pattern = '/^([^|]+)\|([^|]+)\|([^|]+)\|(\d+(\.\d+)?)$/i'; + + $pattern = '/^([^|]+)\|([^|]+)\|(\d+(\.\d+)?)$/i'; + + if (!preg_match($pattern, $docNo, $matches)) + { + Notification::make() + ->title("Scan Valid Qr code ") + ->body("Expected Format : (MaterialCode|Document Number|Quantity)") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $materialCode = $matches[1]; + $docNo = $matches[2]; + $weight = $matches[3]; + + $icode = Item::where('code', $materialCode)->first(); + + if(!$icode) + { + Notification::make() + ->title("Unknown Item Code") + ->body("Item Code not found '$materialCode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $icodeAgaPlant = Item::where('code', $materialCode)->where('plant_id', $plantId)->first(); + + $plantCode = Plant::where('id', $plantId)->first(); + + $plantcode = $plantCode->code; + + $itemId = $icodeAgaPlant->id; + + if(!$icodeAgaPlant) + { + Notification::make() + ->title("Unknown Item Code") + ->body("Item Code not found '$materialCode' against Plant Code '$plantcode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $icodeAgaCPoPlant = SaleOrderMaster::where('item_id', $itemId)->where('plant_id', $plantId)->first(); + + if(!$icodeAgaCPoPlant) + { + Notification::make() + ->title("Unknown Item Code in Customer PO") + ->body("Item Code '$materialCode' not found in any active Customer PO against Plant Code '$plantcode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $processOrderAgaPlant = SparePacking::where('document_number', $docNo)->where('plant_id', $plantId)->first(); + + if($processOrderAgaPlant) + { + Notification::make() + ->title("Duplicate Document Number") + ->body("Duplicate document number found '$docNo' against Plant Code '$plantcode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $saleOrderRecord = SaleOrderMaster::where('sale_order_number', $saleOrder) + ->where('plant_id', $plantId) + ->where('item_id', $itemId) + ->first(); + + if(!$saleOrderRecord) + { + Notification::make() + ->title("Sale Order Not Found") + ->body("Sale Order '$saleOrder' for Item '$materialCode' not found against Plant '$plantcode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $alreadyScannedQty = SparePacking::where('sale_order_master_id', $saleOrderRecord->id) + ->where('plant_id', $plantId) + ->where('item_id', $itemId) + ->selectRaw('SUM(CAST(quantity AS NUMERIC)) as total_weight') + ->value('total_weight'); + + $totalQty = round((float)$alreadyScannedQty + (float)$weight, 3); + + + if($totalQty > round((float)$saleOrderRecord->quantity, 3)) + { + + Notification::make() + ->title("Scanned Quantity Exceeds Sale Order Quantity") + ->body("Scanned quantity '$weight' and already scanned quantity '$alreadyScannedQty' exceeds allowed quantity '{$saleOrderRecord->quantity}' for Sale Order '$saleOrderRecord->sale_order_number' and Item '$materialCode'") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'item_id' => $itemId, + 'spare_packing_number' => $sparePackNo, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + try + { + $existingPallet = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $sparePackNo) + ->first(); + + $count = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $sparePackNo) + ->count('spare_packing_number'); + + $createdAt = $existingPallet ? $existingPallet->created_at : $clickedAt ?? now(); + + $createdBy = $existingPallet ? $existingPallet->created_by : $clickedBy ?? $operatorName; + + $record = SparePacking::create([ + 'plant_id' => $plantId, + 'item_id' => $itemId, + 'spare_packing_number' => $sparePackNo, + 'document_number' => $docNo, + 'sale_order_master_id' => $saleOrderRecord->id, + 'quantity' => $weight, + 'created_by' => $createdBy, + 'scanned_by' => $operatorName, + 'created_at' => $createdAt, + 'scanned_at' => now(), + 'updated_by' => $operatorName, + ]); + + if ($record) + { + + $this->snoCount = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $sparePackNo) + ->count(); + + $this->dispatch('loadData', $sparePackNo, $plantId); + $this->form->fill([ + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'item_id' => $itemId, + 'spare_packing_number' => $sparePackNo, + 'document_number' => null, + // 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $this->snoCount, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + } + else + { + Notification::make() + ->title("Failed to insert scanned serial number '$docNo' into wire master table!") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $sparePackNo, $plantId); + $this->form->fill([ + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'item_id' => $itemId, + 'spare_packing_number' => $sparePackNo, + 'document_number' => null, + // 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'scanned_by' => $operatorName, + ]); + return; + } + } + catch (\Exception $e) + { + Notification::make() + ->title('Error: Serial not inserted.') + ->body("Something went wrong while inserting document number '{$docNo}' into pallet table!\nScan the new document number to proceed...") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $sparePackNo, $plantId); + $this->form->fill([ + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $sparePackNo, + 'document_number' => null, + // 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $this->dispatch('loadData', $sparePackNo, $plantId); + + } + + public function markAsComplete() + { + + $plantId = $this->form->getState()['plant_id']; + + $plantId = trim($plantId) ?? null; + + $pendingPallet = $this->form->getState()['pending_pallet_list']; + + $palletNumber = trim($this->form->getState()['spare_packing_number'])?? null; + + $palletNumber = trim($palletNumber) ?? null; + + $processOrder = trim($this->form->getState()['document_number'])?? null; + + $processOrder = trim($processOrder) ?? null; + + $user = Filament::auth()->user(); + + $operatorName = $user->name; + + $isCompleted = $this->data['is_completed'] ?? false; + + // $this->pendingPallet = $this->form->getState()['pending_pallet_list']; + + if (! ($this->data['is_completed'] ?? false)) { + Notification::make() + ->title('Completion required') + ->body('Please check the "Is Completed" checkbox to finish master packing.') + ->warning() + ->duration(3000) + ->send(); + + return; + } + + $palletExist = SparePacking::where('spare_packing_number', $palletNumber) + ->where('plant_id', $plantId) + ->first(); + + if (!$palletExist) + { + Notification::make() + ->title("Pallet number '$palletNumber' does not have process orders to save!
Add the valid process order into pallet number to proceed...") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $allCompleted = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->where('spare_packing_status', '=','Completed') + ->first(); + + if ($allCompleted) + { + Notification::make() + ->title("Spare Packing pallet number '$palletNumber' already completed the master packing!
Generate the new Spare Packing Pallet number or choose from pending pallet list!") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', '', $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'spare_packing_number' => null, + 'pending_pallet_list' => null,//$pendingPallet + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + // $count = PalletValidation::where('plant_id', $plantId) + // ->where('pallet_number', $palletNumber) + // ->count('pallet_number'); + + if (!$isCompleted) + { + $updated = SparePacking::where('spare_packing_number', $palletNumber) + ->where('plant_id', $plantId) + ->update([ + 'updated_at' => now(), + 'updated_by' => $operatorName, + ]); + + if ($updated > 0) + { + Notification::make() + ->title("Pallet number '$palletNumber' records saved successfully!") + ->success() + ->duration(800) + ->send(); + + $this->dispatch('loadData', '', $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'spare_packing_number' => null,//$palletNumber + 'pending_pallet_list' => null,//$pendingPallet + 'Sno_quantity' => 0,//$count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + } + else + { + $updated = SparePacking::where('spare_packing_number', $palletNumber) + ->where('plant_id', $plantId) + ->update([ + 'spare_packing_status' => 'Completed', + 'updated_at' => now(), + 'updated_by' => $operatorName, + ]); + + if ($updated > 0) + { + Notification::make() + ->title("Pallet number '$palletNumber' completed the master packing successfully!") + ->success() + ->duration(800) + ->send(); + + $this->dispatch('loadData', '', $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'spare_packing_number' => null,//$palletNumber + 'pending_pallet_list' => null,//$pendingPallet + 'Sno_quantity' => 0,//$count + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + } + } + + public function processPalletNo() + { + $plantId = $this->form->getState()['plant_id']; + + $plantId = trim($plantId) ?? null; + + $pendingPallet = $this->form->getState()['pending_pallet_list']; + + $palletNumber = trim($this->form->getState()['spare_packing_number']) ?? null; + + $saleOrder = trim($this->form->getState()['sale_order_master_id'])?? null; + + $palletNumber = trim($palletNumber) ?? null; + + $docNo = trim($this->form->getState()['document_number']) ?? null; + + $docNo = trim($docNo) ?? null; + + $user = Filament::auth()->user(); + + $operatorName = $user->name; + + //$this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'serial_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'pallet_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + + if (!$palletNumber) + { + Notification::make() + ->title('Pallet number is required.') + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', '', $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + if (strlen($palletNumber) < 10) + { + Notification::make() + ->title("Pallet number '$palletNumber' must be at least 10 digits.") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadLocator' ,'',$plantId); + $this->form->fill([ + 'serial_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $count = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->count('spare_packing_number'); + + $palletNotCompleted = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->where('spare_packing_status', '=','') + ->orWhere('spare_packing_status', '=',null) + ->first(); + + if (!$palletNotCompleted) + { + Notification::make() + ->title("Pallet number not found $palletNumber!") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $saleOrder, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + + $this->dispatch('loadData', $palletNumber, $plantId); + + } + + public function processRemoveDocNo() + { + $plantId = $this->form->getState()['plant_id']; + + $plantId = trim($plantId) ?? null; + + $pendingPallet = $this->form->getState()['pending_pallet_list']; + + $palletNumber = trim($this->form->getState()['spare_packing_number']) ?? null; + + $customerPo = trim($this->form->getState()['sale_order_master_id'])?? null; + + $palletNumber = trim($palletNumber) ?? null; + + $docNo = trim($this->form->getState()['removeDoc_number']) ?? null; + + $docNo = trim($docNo) ?? null; + + $user = Filament::auth()->user(); + + $operatorName = $user->name; + + if (!$palletNumber) + { + Notification::make() + ->title('Spare Pallet number is required to remove.') + ->danger() + ->duration(5000) + ->send(); + return; + } + + $pattern = '/^([^|]+)\|([^|]+)\|(\d+(\.\d+)?)$/i'; + + if (!preg_match($pattern, $docNo, $matches)) + { + Notification::make() + ->title("Scan Valid Qr code ") + ->body("Expected Format : (MaterialCode|Document Number|Quantity)") + ->danger() + ->duration(5000) + ->send(); + + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'Sno_quantity' => 0, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $materialCode = $matches[1]; + $docNo = $matches[2]; + $weight = $matches[3]; + + $count = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->count('spare_packing_number'); + + if (!$docNo) + { + Notification::make() + ->title('Document number is required to remove.') + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + + $processOrderexist = SparePacking::where('plant_id', $plantId) + ->where('document_number', $docNo) + ->first(); + if (!$processOrderexist) + { + Notification::make() + ->title('Document number not exists in pallet table.') + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + $palletExist = SparePacking::where('plant_id', $plantId) + ->where('document_number', $docNo) + ->where('spare_packing_number', '!=', '') + ->where('spare_packing_number', '!=', null) + ->first(); + + if ($palletExist && $palletExist->spare_packing_number != $palletNumber) + { + Notification::make() + ->title("Scanned document number exist in pallet number '$palletExist->spare_packing_number'.
Scan the valid exist document number to remove!") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + return; + } + + + $deleted = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->where('document_number', $docNo) + ->forceDelete(); + + if ($deleted) + { + Notification::make() + ->title("Scanned document number '$docNo' successfully removed from pallet table!
Scan the next exist document number to remove...") + ->success() + ->duration(600) + ->send(); + + $this->snoCount = SparePacking::where('plant_id', $plantId) + ->where('spare_packing_number', $palletNumber) + ->count(); + + $this->form->fill([ + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'removeDoc_number' => null, + 'pending_pallet_list' => $this->pendingPallet, + 'Sno_quantity' => $this->snoCount, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + + $this->dispatch('loadData', $palletNumber, $plantId); + } + else + { + Notification::make() + ->title("Failed to remove scanned document number '$docNo' from master pallet!") + ->danger() + ->duration(5000) + ->send(); + + $this->dispatch('loadData', $palletNumber, $plantId); + $this->form->fill([ + 'document_number' => null, + 'plant_id' => $plantId, + 'sale_order_master_id' => $customerPo, + 'spare_packing_number' => $palletNumber, + 'pending_pallet_list' => $pendingPallet, + 'Sno_quantity' => $count, + 'created_by' => $operatorName, + 'scanned_by' => $operatorName, + ]); + } + + //$this->dispatch('removeSno', $serialNumber, $palletNumber, $plantId); + } + + public function getFormActions(): array + { + return []; + } +} diff --git a/app/Filament/Resources/SparePackingResource/Pages/EditSparePacking.php b/app/Filament/Resources/SparePackingResource/Pages/EditSparePacking.php new file mode 100644 index 0000000..f002318 --- /dev/null +++ b/app/Filament/Resources/SparePackingResource/Pages/EditSparePacking.php @@ -0,0 +1,22 @@ + Date: Tue, 1 Sep 2026 12:42:07 +0530 Subject: [PATCH 4/7] Added spare packing importer and exporter --- app/Filament/Exports/SparePackingExporter.php | 68 +++++++++++++++++++ app/Filament/Imports/SparePackingImporter.php | 63 +++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 app/Filament/Exports/SparePackingExporter.php create mode 100644 app/Filament/Imports/SparePackingImporter.php diff --git a/app/Filament/Exports/SparePackingExporter.php b/app/Filament/Exports/SparePackingExporter.php new file mode 100644 index 0000000..f7bc4bd --- /dev/null +++ b/app/Filament/Exports/SparePackingExporter.php @@ -0,0 +1,68 @@ +label('NO') + ->state(function ($record) use (&$rowNumber) { + // Increment and return the row number + return ++$rowNumber; + }), + ExportColumn::make('plant.code') + ->label('PLANT CODE'), + ExportColumn::make('item.code') + ->label('ITEM CODE'), + ExportColumn::make('saleOrder.sale_order_number') + ->label('SALE ORDER NUMBER'), + ExportColumn::make('saleOrder.supplier_name') + ->label('SUPPLIER NAME'), + ExportColumn::make('spare_packing_number') + ->label('SPARE PACKING NUMBER'), + ExportColumn::make('document_number') + ->label('DOCUMENT NUMBER'), + ExportColumn::make('quantity') + ->label('QUANTITY'), + ExportColumn::make('spare_packing_status') + ->label('SPARE PACKING STATUS'), + ExportColumn::make('created_at') + ->label('CREATED AT'), + ExportColumn::make('updated_at') + ->label('UPDATED AT'), + ExportColumn::make('scanned_at') + ->label('SCANNED AT'), + ExportColumn::make('created_by') + ->label('CREATED BY'), + ExportColumn::make('updated_by') + ->label('UPDATED BY'), + ExportColumn::make('scanned_by') + ->label('SCANNED BY'), + ExportColumn::make('deleted_at') + ->label('DELETED AT') + ->enabledByDefault(false), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your spare packing export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.'; + + if ($failedRowsCount = $export->getFailedRowsCount()) { + $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.'; + } + + return $body; + } +} diff --git a/app/Filament/Imports/SparePackingImporter.php b/app/Filament/Imports/SparePackingImporter.php new file mode 100644 index 0000000..20e65d7 --- /dev/null +++ b/app/Filament/Imports/SparePackingImporter.php @@ -0,0 +1,63 @@ +requiredMapping() + ->relationship() + ->rules(['required']), + ImportColumn::make('item') + ->requiredMapping() + ->relationship() + ->rules(['required']), + ImportColumn::make('sale_order_master_id') + ->requiredMapping() + ->numeric() + ->rules(['required', 'integer']), + ImportColumn::make('spare_packing_number'), + ImportColumn::make('document_number'), + ImportColumn::make('batch_number'), + ImportColumn::make('quantity'), + ImportColumn::make('spare_packing_status'), + ImportColumn::make('scanned_at') + ->requiredMapping() + ->rules(['required', 'datetime']), + ImportColumn::make('created_by'), + ImportColumn::make('updated_by'), + ImportColumn::make('scanned_by'), + ]; + } + + public function resolveRecord(): ?SparePacking + { + // return SparePacking::firstOrNew([ + // // Update existing records, matching them by `$this->data['column_name']` + // 'email' => $this->data['email'], + // ]); + + return new SparePacking(); + } + + public static function getCompletedNotificationBody(Import $import): string + { + $body = 'Your spare packing import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.'; + + if ($failedRowsCount = $import->getFailedRowsCount()) { + $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.'; + } + + return $body; + } +} From f9b95a136a3f6f0b49ae3535fa2bb4d5411240fd Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Tue, 1 Sep 2026 12:43:03 +0530 Subject: [PATCH 5/7] Added spare pack data livewire pages --- app/Livewire/SparePackData.php | 55 +++++++++++++++++++ .../views/livewire/spare-pack-data.blade.php | 47 ++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 app/Livewire/SparePackData.php create mode 100644 resources/views/livewire/spare-pack-data.blade.php diff --git a/app/Livewire/SparePackData.php b/app/Livewire/SparePackData.php new file mode 100644 index 0000000..7b8c77e --- /dev/null +++ b/app/Livewire/SparePackData.php @@ -0,0 +1,55 @@ + 'loadSparePackingData', + ]; + + public function loadSparePackingData($sparePackNo, $plantId) + { + $this->plantId = $plantId; + $this->sparePackNo = $sparePackNo; + $this->records = []; + + $this->records = SparePacking::query() + ->where('plant_id', $this->plantId) + ->where('spare_packing_number', $this->sparePackNo) + ->orderBy('scanned_at') + ->get() + ->map(function ($record) { + return [ + 'created_at' => $record->created_at, + 'created_by' => $record->created_by ?? '', + 'spare_packing_number' => $record->spare_packing_number, + 'item_code' => $record->item?->code ?? '', + 'item_description' => $record->item?->description ?? '', + 'document_number' => $record->document_number, + 'quantity' => $record->quantity, + 'scanned_at' => $record->scanned_at, + 'scanned_by' => $record->scanned_by ?? '', + ]; + }) + ->toArray(); + + } + + public function render() + { + return view('livewire.spare-pack-data'); + } +} diff --git a/resources/views/livewire/spare-pack-data.blade.php b/resources/views/livewire/spare-pack-data.blade.php new file mode 100644 index 0000000..4eb66fe --- /dev/null +++ b/resources/views/livewire/spare-pack-data.blade.php @@ -0,0 +1,47 @@ +
+

+ SPARE PACKING DATA TABLE: +

+
+ + + + + + + + + + + + + + + + + @forelse ($records as $index => $record) + + + + + + + + + + + + + @empty + + + + @endforelse + +
NoCreated DatetimeCreated ByS PACKING NoItem CodeDescriptionDoc NoQuantityScanned DatetimeScanned By
{{ $index + 1 }}{{ $record['created_at'] ?? '-' }}{{ $record['created_by'] ?? '-' }}{{ $record['spare_packing_number'] ?? '-' }}{{ $record['item_code'] ?? '-' }}{{ $record['item_description'] ?? '-' }}{{ $record['document_number'] ?? '-' }}{{ $record['quantity'] ?? '-' }}{{ $record['scanned_at'] ?? '-' }}{{ $record['scanned_by'] ?? '-' }}
+ No spare packing records found. +
+
+ +
+ From f228cb44a280c903adb125452f61cd992d7a51ba Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Tue, 1 Sep 2026 12:43:57 +0530 Subject: [PATCH 6/7] Added create spare packing blade file --- .../create-spare-packing.blade.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 resources/views/filament/resources/spare-packing-resource/create-spare-packing.blade.php diff --git a/resources/views/filament/resources/spare-packing-resource/create-spare-packing.blade.php b/resources/views/filament/resources/spare-packing-resource/create-spare-packing.blade.php new file mode 100644 index 0000000..a12682a --- /dev/null +++ b/resources/views/filament/resources/spare-packing-resource/create-spare-packing.blade.php @@ -0,0 +1,32 @@ + + + + +
+ {{ $this->form }} +
+ +
+ +
+ +
+ + @foreach ($this->getFormActions() as $action) + {{ $action }} + @endforeach + +
+ + @push('scripts') + + @endpush +
From 778d2ef383c3318afe59dce9b5c99a88be3fbc20 Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Tue, 1 Sep 2026 12:44:58 +0530 Subject: [PATCH 7/7] Added is completed and save pallet button in spare packing page --- .../components/save-spare-packing-button.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 resources/views/forms/components/save-spare-packing-button.php diff --git a/resources/views/forms/components/save-spare-packing-button.php b/resources/views/forms/components/save-spare-packing-button.php new file mode 100644 index 0000000..0f04d0d --- /dev/null +++ b/resources/views/forms/components/save-spare-packing-button.php @@ -0,0 +1,19 @@ +
+
+ + +
+