diff --git a/app/Filament/Exports/ProductionOrderExporter.php b/app/Filament/Exports/ProductionOrderExporter.php new file mode 100644 index 0000000..b210978 --- /dev/null +++ b/app/Filament/Exports/ProductionOrderExporter.php @@ -0,0 +1,65 @@ +label('NO') + ->state(function ($record) use (&$rowNumber) { + // Increment and return the row number + return ++$rowNumber; + }), + ExportColumn::make('plant_id') + ->label('PLANT CODE'), + ExportColumn::make('item_id') + ->label('ITEM CODE'), + ExportColumn::make('quantity') + ->label('QUANTITY'), + ExportColumn::make('start_date') + ->label('START DATETIME'), + ExportColumn::make('end_date') + ->label('END DATETIME'), + ExportColumn::make('production_order') + ->label('PRODUCTION ORDER'), + ExportColumn::make('from_serial_number') + ->label('FROM SERIAL NUMBER'), + ExportColumn::make('to_serial_number') + ->label('TO SERIAL NUMBER'), + ExportColumn::make('created_at') + ->label('CREATED AT'), + ExportColumn::make('created_by') + ->label('CREATED BY'), + ExportColumn::make('updated_at') + ->label('UPDATED AT'), + ExportColumn::make('updated_by') + ->label('UPDATED BY'), + ExportColumn::make('deleted_at') + ->label('DELETED AT') + ->enabledByDefault(false), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your production order 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/ProductionOrderImporter.php b/app/Filament/Imports/ProductionOrderImporter.php new file mode 100644 index 0000000..59d30fc --- /dev/null +++ b/app/Filament/Imports/ProductionOrderImporter.php @@ -0,0 +1,179 @@ +requiredMapping() + ->label('PLANT CODE') + ->exampleHeader('PLANT CODE') + ->example('1000') + ->relationship(resolveUsing: 'code') + ->rules(['required']), + ImportColumn::make('item') + ->requiredMapping() + ->label('ITEM CODE') + ->exampleHeader('ITEM CODE') + ->example('123456') + ->relationship(resolveUsing: 'code') + ->rules(['required']), + ImportColumn::make('quantity') + ->label('QUANTITY') + ->exampleHeader('QUANTITY') + ->example('3') + ->rules(['required']), + ImportColumn::make('start_date') + ->label('START DATETIME') + ->exampleHeader('START DATETIME') + ->example('2026-04-30 10:00:00') + ->rules(['required']), + ImportColumn::make('end_date') + ->label('END DATETIME') + ->exampleHeader('END DATETIME') + ->example('2026-05-30 10:00:00') + ->rules(['required']), + ImportColumn::make('production_order') + ->label('PRODUCTION ORDER') + ->exampleHeader('PRODUCTION ORDER') + ->example('260116180001') + ->rules(['required']), + ImportColumn::make('from_serial_number') + ->label('FROM SERIAL NUMBER') + ->exampleHeader('FROM SERIAL NUMBER') + ->example('1000211260400001') + ->rules(['required']), + ImportColumn::make('to_serial_number') + ->label('TO SERIAL NUMBER') + ->exampleHeader('TO SERIAL NUMBER') + ->example('1000211260400003') + ->rules(['required']), + ImportColumn::make('created_by') + ->label('CREATED BY') + ->exampleHeader('CREATED BY') + ->example('USER00001') + ->rules(['required']), + ImportColumn::make('updated_by') + ->label('UPDATED BY') + ->exampleHeader('UPDATED BY') + ->example('USER00001') + ->rules(['required']), + ]; + } + + public function resolveRecord(): ?ProductionOrder + { + $warnMsg = []; + $plant = null; + $plantCod = trim($this->data['plant']) ?? ''; + $plantId = null; + $item = null; + $itemCod = trim($this->data['item']) ?? ''; + $itemId = null; + $qnty = trim($this->data['quantity']) ?? ''; + $startAt = trim($this->data['start_date']) ?? ''; + $endAt = trim($this->data['end_date']) ?? ''; + $prodOrd = trim($this->data['production_order']) ?? ''; + $fromSerNo = trim($this->data['from_serial_number']) ?? ''; + $toSerNo = trim($this->data['to_serial_number']) ?? ''; + $createdBy = trim($this->data['created_by']) ?? ''; + $updatedBy = trim($this->data['updated_by']) ?? ''; + + if ($qnty) { + if ($plantCod == null || $plantCod == '') { + $warnMsg[] = "Plant code can't be empty!"; + } elseif (! is_numeric($plantCod)) { + $warnMsg[] = 'Plant code should contain only numeric values!'; + } elseif (Str::length($plantCod) < 4 || Str::length($plantCod) > 7) { + $warnMsg[] = 'Plant code must be between 4 and 7 digits only!'; + } elseif (! preg_match('/^[1-9]\d{6,}$/', $plantCod)) { + $warnMsg[] = 'Invalid plant code found!'; + } + + $dupProd = ProductionOrder::where('production_order', $prodOrd)->first(); // where('plant_id', $plantId)-> + if ($dupProd) { + $warnMsg[] = "Production Order '{$prodOrd}' already exists in database!"; + } + + if (! empty($warnMsg)) { + throw new RowImportFailedException(implode(', ', $warnMsg)); + } + + $now = Carbon::now(); + $plantId = $plantCod ?? null; + $quantity = $qnty ?? null; + $plantCode = Plant::find($plantId)->code; + + $year = $now->format('y'); // Year (last 2 digits) + $month = $now->format('m'); + + $monthText = strtoupper($now->format('M')); // APR + $monthNumber = ''; + + foreach (str_split($monthText) as $char) { + $curSeq = ord($char) - 64; + $monthNumber .= str_pad($curSeq, 2, '0', STR_PAD_LEFT); + } + + $prefix = $year.$monthNumber; + + $last = ProductionOrder::where('production_order', 'like', "{$prefix}%")->orderByDesc('production_order')->first(); // ProductionOrder::where('production_order', 'like', $prefix.'%')->orderBy('production_order', 'desc')->first(); + + if ($last) { + $lastSeq = substr($last->production_order, -4); + $nextSeq = str_pad(((int) $lastSeq + 1), 4, '0', STR_PAD_LEFT); + } else { + $nextSeq = '0001'; + } + + $productionOrder = $prefix.$nextSeq; + + $prefixSerial = $plantCode.$year.$month; + + $last = ProductionOrder::where('to_serial_number', 'like', "{$prefixSerial}%")->orderByDesc('to_serial_number')->first(); + + if ($last) { + $lastSeq = (int) substr($last->to_serial_number, -6); + $startSeq = $lastSeq + 1; + } else { + $startSeq = 1; + } + + $endSeq = $startSeq + $quantity - 1; + + $fromSerial = $prefixSerial.str_pad($startSeq, 6, '0', STR_PAD_LEFT); + $toSerial = $prefixSerial.str_pad($endSeq, 6, '0', STR_PAD_LEFT); + } + // return ProductionOrder::firstOrNew([ + // // Update existing records, matching them by `$this->data['column_name']` + // 'email' => $this->data['email'], + // ]); + + return new ProductionOrder; + } + + public static function getCompletedNotificationBody(Import $import): string + { + $body = 'Your production order 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; + } +}