diff --git a/app/Filament/Resources/StickerValidationResource.php b/app/Filament/Resources/StickerValidationResource.php new file mode 100644 index 0000000..273e6a2 --- /dev/null +++ b/app/Filament/Resources/StickerValidationResource.php @@ -0,0 +1,165 @@ +schema([ + Section::make('') + ->schema([ + Forms\Components\Select::make('plant_id') + ->label('Plant') + ->reactive() + ->relationship('plant', 'name') + ->required(), + Forms\Components\Select::make('machine_id') + ->label('Work Center') + ->required() + ->reactive() + ->options(function (callable $get) { + $plantId = $get('plant_id'); + if (empty($plantId)) { + return []; + } + return Machine::where('plant_id', $plantId)->pluck('work_center', 'id'); + }) + ->searchable(), + Forms\Components\TextInput::make('production_order') + ->label('Production Order') + ->reactive() + ->extraAttributes([ + 'id' => 'production_order_input', + 'x-data' => '{ value: "" }', + 'x-model' => 'value', + 'wire:keydown.enter.prevent' => 'processProOrder(value)', + ]), + Forms\Components\TextInput::make('serial_number') + ->label('Serial Number') + ->reactive() + ->extraAttributes([ + 'id' => 'serial_number_input', + 'x-data' => '{ value: "" }', + 'x-model' => 'value', + 'wire:keydown.enter.prevent' => 'processSno(value)', + ]), + Forms\Components\Hidden::make('created_by') + ->label('Created By') + ->default(Filament::auth()->user()?->name), + Forms\Components\Hidden::make('updated_by') + ->label('Updated By') + ->default(Filament::auth()->user()?->name), + ]) + ->columns(4), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('No.') + ->label('No.') + ->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.code') + ->label('Plant') + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('machine.work_center') + ->label('Work Center') + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('production_order') + ->label('Production Order') + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('serial_number') + ->label('Serial Number') + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('status') + ->label('Status') + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('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(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListStickerValidations::route('/'), + 'create' => Pages\CreateStickerValidation::route('/create'), + 'view' => Pages\ViewStickerValidation::route('/{record}'), + 'edit' => Pages\EditStickerValidation::route('/{record}/edit'), + ]; + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/StickerValidationResource/Pages/CreateStickerValidation.php b/app/Filament/Resources/StickerValidationResource/Pages/CreateStickerValidation.php new file mode 100644 index 0000000..146738a --- /dev/null +++ b/app/Filament/Resources/StickerValidationResource/Pages/CreateStickerValidation.php @@ -0,0 +1,336 @@ +getCancelFormAction(), + ]; + } + + protected function getRedirectUrl(): string + { + return $this->getResource()::getUrl('create'); + } + + + public function processProOrder($value) + { + $plantId = $this->form->getState()['plant_id']; + + $this->plantId = $plantId; + + $this->ref_number = $value; + + $this->dispatch('refreshEmptySticker', $plantId, $value); + + $this->dispatch('focus-serial-number'); + } + + public function processSno($serNo) + { + $plantId = $this->form->getState()['plant_id']; + + $this->plantId = $plantId; + + $workCenter = $this->form->getState()['machine_id']; + + $this->workCenter = $workCenter; + + $this->ref_number = $this->form->getState()['production_order']; + + $this->serNo = $serNo; + + $user = Filament::auth()->user(); + + $operatorName = $user->name; + + if (! preg_match('/^([a-zA-Z0-9]{6,})\|([1-9][a-zA-Z0-9]{8,})\|?$/', $this->serNo, $matches)) { + Notification::make() + ->danger() + ->title('Invalid Serial QR Format') + ->body('Scan valid Serial QR code proceed!
Sample formats are:
123456|1234567890123| OR 123456|1234567890123') + ->seconds(3) + ->send(); + + $this->dispatch('playWarnSound'); + + $this->form->fill([ + 'plant_id' => $this->plantId, + 'machine_id' => $this->workCenter, + 'production_order' => $this->ref_number, + 'serial_number' => null, + ]); + + $this->dispatch('focus-serial-number'); + + return; + } + else { + $itemCode = $matches[1]; + $serialNumber = $matches[2]; + + $recFound = ProductionQuantity::where('plant_id', $this->plantId) + ->where('production_order', $this->ref_number) + ->where('serial_number', $serialNumber) + ->first(); + + if(!$recFound){ + Notification::make() + ->danger() + ->title('Unknown Serial Number') + ->body("Scanned serial number '$serialNumber' not found for the given plant and production order") + ->seconds(3) + ->send(); + $this->form->fill([ + 'plant_id' => $this->plantId, + 'machine_id' => $this->workCenter, + 'production_order' => $this->ref_number, + 'serial_number' => null, + ]); + return; + } + + $duplicate = StickerValidation::where('plant_id', $this->plantId) + ->where('production_order', $this->ref_number) + ->where('serial_number', $serialNumber) + ->first(); + + if ($duplicate) { + Notification::make() + ->danger() + ->title('Duplicate Serial Number') + ->body("Serial number $serialNumber already exists for this plant and production order!") + ->seconds(3) + ->send(); + + $this->form->fill([ + 'plant_id' => $this->plantId, + 'machine_id' => $this->workCenter, + 'production_order' => $this->ref_number, + 'serial_number' => null, + ]); + return; + } + + $itemC = Item::where('code', $itemCode) + ->where('plant_id',$this->plantId) + ->first(); + + $itemId = $itemC->id; + + $item = ItemCharacteristic::where('item_id', $itemId) + ->where('plant_id',$this->plantId) + ->first(); + + $itemI = $item->id; + + $mapping = StickerMappingMaster::where('plant_id', $this->plantId) + ->where('item_characteristic_id', $itemI) + ->first(); + + if (!$mapping) { + Notification::make() + ->danger() + ->title('Sticker Mapping Not Found') + ->body("No sticker mapping found for this item and plant.") + ->send(); + return; + } + + $stickers = []; + + for ($i = 1; $i <= 8; $i++) { + $machineColumn = "sticker{$i}_machine_id"; + $ipColumn = "sticker{$i}_print_ip"; + $stickerColumn = "sticker_structure{$i}_id"; + $itemColumn = "item_characteristic_id"; + + if ( + !empty($mapping->$machineColumn) && + !empty($mapping->$stickerColumn) + ) { + $stickers[] = [ + 'machine_id' => $mapping->$machineColumn, + 'sticker_id' => $mapping->$stickerColumn, + 'item_characteristic' => $mapping->$itemColumn, + 'print_ip' => $mapping->$ipColumn, + ]; + } + } + + if (empty($stickers)) { + Notification::make() + ->danger() + ->title('No Sticker Configuration Found') + ->body('No sticker and machine mappings configured for this item and plant.') + ->send(); + return; + } + + StickerValidation::create([ + 'plant_id' => $this->plantId, + 'machine_id' => $this->workCenter, + 'production_order' => $this->ref_number ?? null, + 'serial_number' => $serialNumber, + 'status' => 'Printed', + // 'sticker_id' => $matchedSticker, + 'created_by' => $operatorName, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + Notification::make() + ->success() + ->title('Sticker Recorded') + ->body("Item: $itemCode, Serial: $serialNumber recorded successfully!") + ->seconds(3) + ->send(); + + $this->form->fill([ + 'plant_id' => $this->plantId, + 'machine_id' => $this->workCenter, + 'production_order' => $this->ref_number, + 'serial_number' => null, + ]); + + $this->dispatch('refreshEmptySticker', $plantId, $this->ref_number); + + foreach ($stickers as $sticker) + { + + \Log::info("Looking up printer for IP: " . $sticker['print_ip']); + + $printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']); + + \Log::info("Found printer: " . ($printerName ?? 'NULL')); + + if (! $printerName) { + Notification::make() + ->danger() + ->title('Printer Not Found') + ->body("No CUPS printer configured for IP: {$sticker['print_ip']}") + ->send(); + return; + } + + $structure = StickerStructureDetail::findOrFail($sticker['sticker_id']); + + $itemCharacteristic = ItemCharacteristic::where('plant_id', $this->plantId) + ->where('id', $sticker['item_characteristic']) + ->firstOrFail(); + + $dynamicElements = StickerDetail::where( + 'sticker_structure_detail_id', + $structure->id + )->where('element_type', 'Dynamic')->get(); + + $pdfContent = (new StickerPdfService())->generatePdf1( + $structure->sticker_id, + $dynamicElements, + $itemCharacteristic, + $serialNumber, + $serNo + ); + + $tempPdfPath = storage_path('app/temp_sticker_' . uniqid() . '.pdf'); + file_put_contents($tempPdfPath, $pdfContent); + + exec( + "lp -d " . escapeshellarg($printerName) . " " . escapeshellarg($tempPdfPath), + $output, + $status + ); + + \Log::info("LP Output:", $output); + \Log::info("LP Status: " . $status); + + if ($status != 0) { + Notification::make() + ->danger() + ->title('Print Failed') + ->body("CUPS error while printing.") + ->send(); + + if (file_exists($tempPdfPath)) { + unlink($tempPdfPath); + } + + return; + } + + if (file_exists($tempPdfPath)) { + unlink($tempPdfPath); + } + } + + Notification::make() + ->success() + ->title('Sticker Printed') + ->body("Sticker for Serial Number: $serialNumber printed successfully!") + ->seconds(3) + ->send(); + + // [$itemCode, $serialNumber] = explode('|', $serNo); + + // $this->dispatch('open-sticker-pdf', [ + // 'url' => url("/sticker/pdf/{$itemCode}/{$serialNumber}/$this->plantId/$this->ref_number") + // ]); + + } + } + + protected function getCupsPrinterNameByIp(string $ip): ?string + { + + exec('lpstat -v 2>&1', $output, $status); //locak + + // exec('lpstat -h cups:631 -v 2>&1', $output, $status); + + if ($status != 0 || empty($output)) { + return null; + } + + foreach ($output as $line){ + $parts = explode(':', $line, 2); + if (count($parts) < 2) continue; + + $printerName = trim(str_replace('device for', '', $parts[0])); + $deviceUri = trim($parts[1]); + + if (str_contains($deviceUri, $ip)) { + return $printerName; + } + } + return null; + } +} diff --git a/app/Filament/Resources/StickerValidationResource/Pages/EditStickerValidation.php b/app/Filament/Resources/StickerValidationResource/Pages/EditStickerValidation.php new file mode 100644 index 0000000..a783f95 --- /dev/null +++ b/app/Filament/Resources/StickerValidationResource/Pages/EditStickerValidation.php @@ -0,0 +1,22 @@ +