diff --git a/app/Filament/Resources/WeightValidationResource.php b/app/Filament/Resources/WeightValidationResource.php index efcf10b..3a6f7ac 100644 --- a/app/Filament/Resources/WeightValidationResource.php +++ b/app/Filament/Resources/WeightValidationResource.php @@ -2,16 +2,32 @@ namespace App\Filament\Resources; +use App\Filament\Exports\WeightValidationExporter; +use App\Filament\Imports\WeightValidationImporter; use App\Filament\Resources\WeightValidationResource\Pages; use App\Filament\Resources\WeightValidationResource\RelationManagers; +use App\Models\Item; +use App\Models\Plant; use App\Models\WeightValidation; +use Filament\Actions\Action; +use Filament\Facades\Filament; use Filament\Forms; +use Filament\Forms\Components\FileUpload; +use Filament\Forms\Components\Select; use Filament\Forms\Form; +use Filament\Forms\Get; +use Filament\Notifications\Notification; use Filament\Resources\Resource; use Filament\Tables; +use Filament\Tables\Actions\ExportAction; +use Filament\Tables\Actions\ImportAction; use Filament\Tables\Table; +use Filament\Widgets\StatsOverviewWidget\Stat; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\Facades\Storage; +use Maatwebsite\Excel\Facades\Excel; +use Str; class WeightValidationResource extends Resource { @@ -63,26 +79,72 @@ class WeightValidationResource extends Resource { return $table ->columns([ - Tables\Columns\TextColumn::make('id') - ->label('ID') - ->numeric() - ->sortable(), - Tables\Columns\TextColumn::make('item.id') - ->numeric() - ->sortable(), + // Tables\Columns\TextColumn::make('id') + // ->label('ID') + // ->numeric() + // ->sortable(), + 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.name') - ->numeric() - ->sortable(), - Tables\Columns\TextColumn::make('created_at') - ->dateTime() + ->label('Plant') + ->alignCenter() ->sortable() - ->toggleable(isToggledHiddenByDefault: true), - Tables\Columns\TextColumn::make('updated_at') + ->searchable(), + Tables\Columns\TextColumn::make('item.code') + ->label('Item Code') + ->alignCenter() + ->searchable(), + Tables\Columns\TextColumn::make('obd_number') + ->label('OBD Number') + ->alignCenter() + ->searchable(), + Tables\Columns\TextColumn::make('line_number') + ->label('Line Number') + ->alignCenter(), + Tables\Columns\TextColumn::make('batch_number') + ->label('Batch Number') + ->alignCenter(), + Tables\Columns\TextColumn::make('heat_number') + ->label('Heat Number') + ->alignCenter(), + Tables\Columns\TextColumn::make('obd_weight') + ->label('Actual Weight') + ->alignCenter(), + Tables\Columns\TextColumn::make('vehicle_number') + ->label('Vehicle Number') + ->alignCenter() + ->searchable(), + Tables\Columns\TextColumn::make('bundle_number') + ->label('Bundle Number') + ->alignCenter(), + Tables\Columns\TextColumn::make('picked_weight') + ->label('Picked Weight') + ->alignCenter(), + Tables\Columns\TextColumn::make('scanned_by') + ->label('Scanned By') + ->alignCenter() + ->searchable(), + Tables\Columns\TextColumn::make('created_at') + ->label('Create At') ->dateTime() + ->alignCenter() + ->sortable(), + Tables\Columns\TextColumn::make('updated_at') + ->label('Updated At') + ->dateTime() + ->alignCenter() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('deleted_at') + ->label('Deleted At') ->dateTime() + ->alignCenter() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) @@ -99,6 +161,561 @@ class WeightValidationResource extends Resource Tables\Actions\ForceDeleteBulkAction::make(), Tables\Actions\RestoreBulkAction::make(), ]), + ]) + ->headerActions([ + Tables\Actions\Action::make('Import OBD Number') + ->label('Import OBD Weight Invoice') + ->form([ + Select::make('plant_id') + ->options(Plant::pluck('name', 'id')->toArray()) + ->label('Select Plant') + ->required() + ->default(function () { + return optional(WeightValidation::latest()->first())->plant_id; + }) + ->afterStateUpdated(function ($state, callable $set, callable $get) { + $set('invoice_obd_number', null); + }) + ->reactive(), + FileUpload::make('invoice_obd_number') + ->label('Import OBD Weight Invoice') + // ->required() + ->preserveFilenames() // <- this keeps the original filename + ->storeFiles(false) // prevent auto-storing, we will store manually + ->reactive() + ->required() + ->disk('local') //'local' refers to the local storage disk defined in config/filesystems.php, typically pointing to storage/app. + ->visible(fn (Get $get) => !empty($get('plant_id'))) + ->directory('uploads/temp'), + ]) + ->action(function (array $data) { + $uploadedFile = $data['invoice_obd_number']; + + $disk = Storage::disk('local'); + + $plantId = $data['plant_id']; + + // Get original filename + $originalName = $uploadedFile->getClientOriginalName(); // e.g. 3RA0018732.xlsx + + $originalNameOnly = pathinfo($originalName, PATHINFO_FILENAME); + + // Store manually using storeAs to keep original name + $path = $uploadedFile->storeAs('uploads/temp', $originalName, 'local'); // returns relative path + // uploads/temp/3RA0018735.xlsx + + $fullPath = Storage::disk('local')->path($path); + + // /home/iot-dev/projects/pds/storage/app/private/uploads/temp/3RA0018735.xlsx + + $totQuan = WeightValidation::where('obd_number',$originalNameOnly)->where('plant_id', $plantId)->count(); + $scanSQuan = WeightValidation::where('obd_number',$originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if($totQuan == $scanSQuan && $totQuan > 0) { + Notification::make() + ->title('Completed: OBD invoice') + ->body('OBD invoice already completed the scanning process for selected plant.') + ->danger() + ->send(); + + if ($disk->exists($path)) + { + $disk->delete($path); + } + return; + } + + // if($totQuan > 0) { + // Notification::make() + // ->title('OBD invoice already exist in database for selected plant.') + // ->danger() + // ->send(); + + // if ($disk->exists($path)) + // { + // $disk->delete($path); + // } + // return; + // } + + if ($fullPath && file_exists($fullPath)) { + $rows = Excel::toArray(null, $fullPath)[0]; + + if((count($rows) - 1) <= 0) { + Notification::make() + ->title('Records Not Found') + ->body("Import the valid 'OBD Invoice' file to proceed..!") + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $invalidMatCodes = []; + $invalidLines = []; + $duplicateLines = []; + $invalidBatch = []; + $invalidHeat = []; + $invalidWeight = []; + $materialCodes = []; + $lineNumbers = []; + $validRowsFound = false; + + foreach ($rows as $index => $row) + { + if ($index === 0) continue; // Skip header + + $materialCode = trim($row[0]); + $lineNumber = trim($row[1]); + $batchNumber = trim($row[2]); + $heatNumber = trim($row[3]); + $actualWeight = trim($row[4]); + + if (empty($materialCode)) { + continue; + } + + if (!empty($materialCode)) { + if(Str::length($materialCode) < 6 || !ctype_alnum($materialCode)) + { + $invalidMatCodes[] = $materialCode; + continue; + } + else + { + $materialCodes[] = $materialCode; + + $validData = true; + if(Str::length($lineNumber) < 1 || !is_numeric($lineNumber)) + { + $validData = false; + $invalidLines[] = $materialCode; + } + else if (in_array($lineNumber, $lineNumbers)) + { + $duplicateLines[] = $materialCode; + } + else + { + $lineNumbers[] = $lineNumber; + } + if(Str::length($batchNumber) < 8 || !is_numeric($batchNumber))//ctype_alnum + { + $validData = false; + $invalidBatch[] = $materialCode; + } + if(Str::length($heatNumber) < 4) + { + $validData = false; + $invalidHeat[] = $materialCode; + } + if(Str::length($actualWeight) < 1 || !is_numeric($actualWeight))//ctype_alnum + { + $validData = false; + $invalidWeight[] = $materialCode; + } + + if ($validData) + { + $validRowsFound = true; + } + } + } + else + { + continue; + } + } + + $uniqueInvalidCodes = array_unique($invalidMatCodes); + if (!empty($uniqueInvalidCodes)) { + Notification::make() + ->title('Invalid: Item Codes') + ->body('The following item codes should contain minimum 6 digit alpha numeric values:
' . implode(', ', $uniqueInvalidCodes)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueInvalidLines = array_unique($invalidLines); + if (!empty($uniqueInvalidLines)) { + Notification::make() + ->title('Invalid: Line Numbers') + ->body('Line number should contain minimum 1 digit numeric values!
Following item codes has invalid line number:
' . implode(', ', $uniqueInvalidLines)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueDupLines = array_unique($duplicateLines); + if (!empty($uniqueDupLines)) { + Notification::make() + ->title('Duplicate: Line Numbers') + ->body('The following item codes contains duplicate line numbers in invoice excel:
' . implode(', ', $uniqueDupLines)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueInvalidBatch = array_unique($invalidBatch); + if (!empty($uniqueInvalidBatch)) { + Notification::make() + ->title('Invalid: Batch Numbers') + ->body('Batch number should contain minimum 8 digit numeric values!
Following item codes has invalid batch number:
' . implode(', ', $uniqueInvalidBatch)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueInvalidHeat = array_unique($invalidHeat); + if (!empty($uniqueInvalidHeat)) { + Notification::make() + ->title('Invalid: Heat Numbers') + ->body('Heat number should contain minimum 4 characters!
Following item codes has invalid heat number:
' . implode(', ', $uniqueInvalidHeat)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueInvalidWeight = array_unique($invalidWeight); + if (!empty($uniqueInvalidWeight)) { + Notification::make() + ->title('Invalid: Actual Weights') + ->body('Actual weight should contain minimum 1 digit numeric value!
Following item codes has invalid actual weight:
' . implode(', ', $uniqueInvalidWeight)) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $uniqueCodes = array_unique($materialCodes); + + //itemNotFound. + $matchedItems = Item::whereIn('code', $uniqueCodes)->get(); + + // // Get all codes that exist in the database for the given plant_id + // $existingCodes = StickerMaster::where('plant_id', $plantId) + // ->whereHas('item', function ($query) use ($uniqueCodes) { + // $query->whereIn('code', $uniqueCodes); + // }) + // ->with('item') // Eager load for performance + // ->get() + // ->pluck('item.code') + // ->toArray(); + + $matchedCodes = $matchedItems->pluck('code')->toArray(); //item.code + $missingCodes = array_diff($uniqueCodes, $matchedCodes); + + if (!empty($missingCodes)) { + $missingCount = count($missingCodes); + + $message = $missingCount > 10 ? "'$missingCount' item codes are not found in database." : 'The following item codes are not found in database:
' . implode(', ', $missingCodes); + + Notification::make() + ->title('Unknown: Item Codes') + ->body($message) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + //plantNotFound + $matchedItems = Item::whereIn('code', $uniqueCodes)->where('plant_id', $plantId)->get(); + + $matchedCodes = $matchedItems->pluck('code')->toArray(); + $missingCodes = array_diff($uniqueCodes, $matchedCodes); + + if (!empty($missingCodes)) { + $missingCount = count($missingCodes); + + $message = $missingCount > 10 ? "'$missingCount' item codes are not found in database for choosed plant." : 'The following item codes are not found in database for choosed plant:
' . implode(', ', $missingCodes); + + Notification::make() + ->title('Unknown: Item Codes') + ->body($message) + ->danger() + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + if (!$validRowsFound) { + Notification::make() + ->title('Invalid OBD Invoice') + ->danger() // This makes the notification red to indicate an error + ->body('Uploaded excel sheet is empty or
contains no valid data.') + ->seconds(2) + ->send(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + return; + } + + $updateInv = WeightValidation::where('plant_id', $plantId)->where('obd_number', $originalNameOnly)->first()?->exists(); + + $availLines = WeightValidation::where('plant_id', $plantId)->where('obd_number', $originalNameOnly)->where(function($query) { + $query->whereNull('vehicle_number')->orWhere('vehicle_number', ''); + })->pluck('line_number')->toArray(); + + $updated = WeightValidation::where('plant_id', $plantId)->where('obd_number', $originalNameOnly)->where(function($query) { + $query->whereNull('vehicle_number')->orWhere('vehicle_number', ''); + })->count(); + + WeightValidation::where('plant_id', $plantId)->where('obd_number', $originalNameOnly)->where(function($query) { + $query->whereNull('vehicle_number')->orWhere('vehicle_number', ''); + }) + ->forceDelete(); + + $inserted = 0; + // $updated = 0; + foreach ($rows as $index => $row) + { + // Skip header + if ($index === 0) { continue; } + + $materialCode = trim($row[0]); + $lineNumber = trim($row[1]); + $batchNumber = trim($row[2]); + $heatNumber = trim($row[3]); + $actualWeight = trim($row[4]); + + if (empty($materialCode) || Str::length($materialCode) < 6) { + continue; + } + + if (!empty($materialCode)) { + $recordExist = WeightValidation::where('plant_id', $plantId)->where('obd_number', $originalNameOnly)->where('line_number', $lineNumber)->first(); + + $masItem = Item::where('plant_id', $plantId)->where('code', $materialCode)->first(); + if($recordExist) { + $skipUpd = $recordExist->vehicle_number ?? null; + if($skipUpd) + { + continue; + } + else + { + if ($masItem) { + $recordExist->update([ + 'item_id' => $masItem->id, + 'batch_number' => $batchNumber, + 'heat_number' => $heatNumber, + 'obd_weight' => $actualWeight, + 'updated_at' => now(), + ]); + $updated++; + } + } + } + else { + if ($masItem) { + WeightValidation::create([ + 'item_id' => $masItem->id, + 'plant_id' => $plantId, + 'obd_number' => $originalNameOnly, + 'line_number' => $lineNumber, + 'batch_number' => $batchNumber, + 'heat_number' => $heatNumber, + 'obd_weight' => $actualWeight, + ]); + if (in_array($lineNumber, $availLines)) + { + continue; + } + else + { + $inserted++; + } + } + } + } + } + + if($updateInv) { + if ($updated > 0) { + Notification::make() + ->title("Start the scanning process!") + ->body("'$updated' OBD lines were updated and
'$inserted' OBD lines were inserted for
imported OBD Invoice : '$originalNameOnly'.") + ->info() + // ->success() + ->seconds(1) + ->send(); + + // Update total quantity in the form + $totalQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->count(); + $scannedQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if ($totalQuantity === $scannedQuantity) + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshCompletedInvoice', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + else + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshInvoiceData', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + } + else if ($inserted > 0) { + Notification::make() + ->title("Start the scanning process!") + ->body("'$inserted' OBD lines were inserted for imported OBD Invoice : '$originalNameOnly'.") + ->info() + // ->success() + ->seconds(1) + ->send(); + + // Update total quantity in the form + $totalQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->count(); + $scannedQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if ($totalQuantity === $scannedQuantity) + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshCompletedInvoice', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + else + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshInvoiceData', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + } + else { + Notification::make() + ->title("Import Failed: OBD Invoice") + ->body("No exist records were updated for imported OBD Invoice : '$originalNameOnly'.") + ->danger() + ->seconds(2) + ->send(); + + $totalQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->count(); + $scannedQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshEmptyInvoice', invoiceNumber: $originalNameOnly, plantId: $plantId); + return; + } + } + else { + if ($inserted > 0) { + Notification::make() + ->title("Start the scanning process!") + ->body("'$inserted' OBD lines were inserted for imported OBD Invoice : '$originalNameOnly'.") + ->info() + // ->success() + ->seconds(1) + ->send(); + + // Update total quantity in the form + $totalQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->count(); + $scannedQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if ($totalQuantity === $scannedQuantity) + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshCompletedInvoice', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + else + { + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshInvoiceData', invoiceNumber: $originalNameOnly, plantId: $plantId); + } + } + else { + Notification::make() + ->title("Import Failed: OBD Invoice") + ->body("No new records were inserted for imported OBD Invoice : '$originalNameOnly'.") + ->danger() + ->seconds(2) + ->send(); + + $totalQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->count(); + $scannedQuantity = WeightValidation::where('obd_number', $originalNameOnly)->where('plant_id', $plantId)->whereNotNull('vehicle_number')->where('vehicle_number', '!=', '')->count(); + + if ($disk->exists($path)) { + $disk->delete($path); + } + // $this->dispatch('refreshEmptyInvoice', invoiceNumber: $originalNameOnly, plantId: $plantId); + return; + } + } + } + }) + ->visible(function() { + return Filament::auth()->user()->can('view import obd number validations'); + }), + ImportAction::make() + ->label('Import OBD Invoice') + ->color('warning') + ->importer(WeightValidationImporter::class) + ->visible(function() { + return Filament::auth()->user()->can('view import weight validation'); + }), + ExportAction::make() + ->label('Export OBD Invoices') + ->color('warning') + ->exporter(WeightValidationExporter::class) + ->visible(function() { + return Filament::auth()->user()->can('view export weight validation'); + }), ]); }