diff --git a/app/Filament/Exports/ModelMasterExporter.php b/app/Filament/Exports/ModelMasterExporter.php new file mode 100644 index 0000000..4988b7d --- /dev/null +++ b/app/Filament/Exports/ModelMasterExporter.php @@ -0,0 +1,79 @@ +label('NO') + ->state(function ($record) use (&$rowNumber) { + // Increment and return the row number + return ++$rowNumber; + }), + ExportColumn::make('plant.code') + ->label('PLANT CODE'), + ExportColumn::make('machine.work_center') + ->label('WORK CENTER'), + ExportColumn::make('heading_name') + ->label('HEADING NAME'), + ExportColumn::make('heading_value') + ->label('HEADING VALUE'), + ExportColumn::make('type_name') + ->label('TYPE NAME'), + ExportColumn::make('type_value') + ->label('TYPE VALUE'), + ExportColumn::make('has_motor') + ->label('HAS MOTOR'), + ExportColumn::make('has_m_part') + ->label('HAS MOTOR PART'), + ExportColumn::make('has_m_count') + ->label('HAS MOTOR COUNT'), + ExportColumn::make('has_pump') + ->label('HAS PUMP'), + ExportColumn::make('has_p_part') + ->label('HAS PUMP PART'), + ExportColumn::make('has_p_count') + ->label('HAS PUMP COUNT'), + ExportColumn::make('has_name_plate') + ->label('HAS NAME PLATE'), + ExportColumn::make('has_np_part') + ->label('HAS NAME PLATE PART'), + ExportColumn::make('has_np_count') + ->label('HAS NAME PLATE COUNT'), + ExportColumn::make('created_at') + ->label('CREATED AT'), + ExportColumn::make('created_by') + ->label('CREATED BY'), + ExportColumn::make('updated_at') + ->label('UPDATED AT') + ->enabledByDefault(true), + ExportColumn::make('updated_by') + ->label('UPDATED BY') + ->enabledByDefault(true), + ExportColumn::make('deleted_at') + ->label('DELETED AT') + ->enabledByDefault(false), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your model master 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/ModelMasterImporter.php b/app/Filament/Imports/ModelMasterImporter.php new file mode 100644 index 0000000..4986680 --- /dev/null +++ b/app/Filament/Imports/ModelMasterImporter.php @@ -0,0 +1,249 @@ +label('PLANT CODE') + ->requiredMapping() + ->exampleHeader('PLANT CODE') + ->example('1000') + ->relationship(resolveUsing: 'code') + ->rules(['required']), + ImportColumn::make('machine') + ->label('WORK CENTER') + ->requiredMapping() + ->exampleHeader('WORK CENTER') + ->example('RMGLAS01') + ->relationship(resolveUsing: 'work_center') + ->rules(['required']), + ImportColumn::make('heading_name') + ->label('HEADING NAME') + ->exampleHeader('HEADING NAME') + ->example('ZMM_HEADING'), + ImportColumn::make('heading_value') + ->label('HEADING VALUE') + ->exampleHeader('HEADING VALUE') + ->example('PUMPS'), + ImportColumn::make('type_name') + ->label('TYPE NAME') + ->exampleHeader('TYPE NAME') + ->example(''), + ImportColumn::make('type_value') + ->label('TYPE VALUE') + ->exampleHeader('TYPE VALUE') + ->example(''), + ImportColumn::make('has_motor') + ->label('HAS MOTOR') + ->exampleHeader('HAS MOTOR') + ->example('1'), + ImportColumn::make('has_m_part') + ->label('HAS MOTOR PART') + ->exampleHeader('HAS MOTOR PART') + ->example('1'), + ImportColumn::make('has_m_count') + ->label('HAS MOTOR COUNT') + ->exampleHeader('HAS MOTOR COUNT') + ->example('1'), + ImportColumn::make('has_pump') + ->label('HAS PUMP') + ->exampleHeader('HAS PUMP') + ->example('1'), + ImportColumn::make('has_p_part') + ->label('HAS PUMP PART') + ->exampleHeader('HAS PUMP PART') + ->example('1'), + ImportColumn::make('has_p_count') + ->label('HAS PUMP COUNT') + ->exampleHeader('HAS PUMP COUNT') + ->example('1'), + ImportColumn::make('has_name_plate') + ->label('HAS NAME PLATE') + ->exampleHeader('HAS NAME PLATE') + ->example(''), + ImportColumn::make('has_np_part') + ->label('HAS NAME PLATE PART') + ->exampleHeader('HAS NAME PLATE PART') + ->example(''), + ImportColumn::make('has_np_count') + ->label('HAS NAME PLATE COUNT') + ->exampleHeader('HAS NAME PLATE COUNT') + ->example(''), + ]; + } + + public function resolveRecord(): ?ModelMaster + { + $warnMsg = []; + $plantCod = trim($this->data['plant']); + $plant = null; + $plantId = null; + $workCent = trim($this->data['machine']); + $machine = null; + $machineId = null; + $headingName = strtoupper(trim($this->data['heading_name'])); + $headingValue = strtoupper(trim($this->data['heading_value'])); + $typeName = strtoupper(trim($this->data['type_name'])); + $typeValue = (Str::length($typeName) <= 0) ? '' : strtoupper(trim($this->data['type_value'])); + $hasMotor = (trim($this->data['has_motor']) == '1') ? '1' : '0'; + $hasMPart = (trim($this->data['has_m_part']) == '1') ? '1' : '0'; + $hasMCount = ($hasMotor == '1') ? trim($this->data['has_m_count']) : '0'; + $hasPump = (trim($this->data['has_pump']) == '1') ? '1' : '0'; + $hasPPart = (trim($this->data['has_p_part']) == '1') ? '1' : '0'; + $hasPCount = ($hasPump == '1') ? trim($this->data['has_p_count']) : '0'; + $hasNamePlate = (trim($this->data['has_name_plate']) == '1') ? '1' : '0'; + $hasNpPart = (trim($this->data['has_np_part']) == '1') ? '1' : '0'; + $hasNpCount = ($hasNamePlate == '1') ? trim($this->data['has_np_count']) : '0'; + $createdBy = Filament::auth()->user()->name; + $updatedBy = Filament::auth()->user()->name; + + if ($plantCod == null || $plantCod == '' || ! $plantCod) { + $warnMsg[] = "Plant code can't be empty!"; + } elseif (! is_numeric($plantCod)) { + $warnMsg[] = "Plant code '{$plantCod}' should contain only numeric values!"; + } elseif (Str::length($plantCod) < 4 || Str::length($plantCod) > 7) { + $warnMsg[] = "Plant code '{$plantCod}' must be between 4 and 7 digits only!"; + } elseif (! preg_match('/^[1-9]\d{3,6}$/', $plantCod)) { + $warnMsg[] = "Invalid plant code '{$plantCod}' found!"; + } + + if ($workCent == null || $workCent == '' || ! $workCent) { + $warnMsg[] = "Work center can't be empty!"; + } elseif (Str::length($workCent) < 6) { + $warnMsg[] = "Work center '{$workCent}' should contain minimum 6 characters!"; + } elseif (! ctype_alnum($workCent)) { + $warnMsg[] = "Work center '{$workCent}' should contain only alpha-numeric values!"; + } elseif (! preg_match('/^[a-zA-Z0-9]{6,}$/', $workCent)) { + $warnMsg[] = "Invalid work center '{$workCent}' found!"; + } + + $columns = Schema::getColumnListing('class_characteristics'); + + if ($headingName == null || $headingName == '' || ! $headingName) { + $warnMsg[] = "Heading name can't be empty!"; + } elseif (Str::length($headingName) < 5) { + $warnMsg[] = "Heading name '{$headingName}' should contain minimum 5 characters!"; + } else { + if (! in_array($headingName, $columns, true)) { + $warnMsg[] = 'Unknown heading name found!'; + } + } + + if ($typeName != null && $typeName != '' && $typeName) { + if (Str::length($typeName) < 5) { + $warnMsg[] = "Type name '{$typeName}' should contain minimum 5 characters!"; + } else { + if (! in_array($typeName, $columns, true)) { + $warnMsg[] = 'Unknown type name found!'; + } + } + } + + if ($hasMotor != '1') { + $hasMPart = '0'; + $hasMCount = '0'; + } else { + $hasMPart = ($hasMPart != '1') ? '0' : '1'; + $hasMCount = ($hasMCount == '0' || empty($hasMCount) || ! is_numeric($hasMCount) || ! preg_match('/^([1-9]|[1-9][0-9])$/', $hasMCount)) ? '1' : $hasMCount; + } + + if ($hasPump != '1') { + $hasPPart = '0'; + $hasPCount = '0'; + } else { + $hasPPart = ($hasPPart != '1') ? '0' : '1'; + $hasPCount = ($hasPCount == '0' || empty($hasPCount) || ! is_numeric($hasPCount) || ! preg_match('/^([1-9]|[1-9][0-9])$/', $hasPCount)) ? '1' : $hasPCount; + } + + if ($hasNamePlate != '1') { + $hasNpPart = '0'; + $hasNpCount = '0'; + } else { + $hasNpPart = ($hasNpPart != '1') ? '0' : '1'; + $hasNpCount = ($hasNpCount == '0' || empty($hasNpCount) || ! is_numeric($hasNpCount) || ! preg_match('/^([1-9]|[1-9][0-9])$/', $hasNpCount)) ? '1' : $hasNpCount; + } + + $plant = Plant::where('code', $plantCod)->first(); + if (! $plant) { + $warnMsg[] = 'Plant code not found!'; + } else { + $plantId = $plant->id; + $machine = Machine::where('work_center', $workCent)->first(); + if (! $machine) { + $warnMsg[] = 'Work center not found!'; + } else { + $machine = Machine::where('work_center', $workCent)->where('plant_id', $plantId)->first(); + if (! $machine) { + $warnMsg[] = 'Work center not found for the plant!'; + } else { + $machineId = $machine->id; + + if (empty($warnMsg)) { + $recExist = ModelMaster::where('plant_id', $plantId)->where('machine_id', $machineId)->where('heading_name', $headingName)->where('heading_value', $headingValue)->where('type_name', $typeName)->where('type_value', $typeValue)->first()?->created_by; + + if ($recExist) { + $createdBy = $recExist; + } + } + } + } + } + + if (! empty($warnMsg)) { + throw new RowImportFailedException(implode(', ', $warnMsg)); + } + + return ModelMaster::updateOrCreate([ + 'plant_id' => $plantId, + 'machine_id' => $machineId, + 'heading_name' => $headingName, + 'heading_value' => $headingValue, + 'type_name' => $typeName, + 'type_value' => $typeValue, + ], + [ + 'has_motor' => $hasMotor, + 'has_m_part' => $hasMPart, + 'has_m_count' => $hasMCount, + 'has_pump' => $hasPump, + 'has_p_part' => $hasPPart, + 'has_p_count' => $hasPCount, + 'has_name_plate' => $hasNamePlate, + 'has_np_part' => $hasNpPart, + 'has_np_count' => $hasNpCount, + 'created_by' => $createdBy, + 'updated_by' => $updatedBy, + ] + ); + + // return new ModelMaster; + } + + public static function getCompletedNotificationBody(Import $import): string + { + $body = 'Your model master 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; + } +} diff --git a/app/Filament/Resources/ModelMasterResource.php b/app/Filament/Resources/ModelMasterResource.php new file mode 100644 index 0000000..480efe9 --- /dev/null +++ b/app/Filament/Resources/ModelMasterResource.php @@ -0,0 +1,394 @@ +schema([ + Forms\Components\Select::make('plant_id') + ->label('PLANT NAME') + ->relationship('plant', 'name') + ->reactive() + ->searchable() + ->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(); + }) + ->disabled(fn (Get $get) => ! empty($get('id'))) + ->default(function () { + $userHas = Filament::auth()->user()->plant_id; + + return ($userHas && strlen($userHas) > 0) ? $userHas : optional(ModelMaster::latest()->first())->plant_id; + }) + ->afterStateUpdated(function (callable $set, callable $get, ?string $state) { + $set('machine_id', null); + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\Select::make('machine_id') + ->label('WORK CENTER') + ->reactive() + ->searchable() + ->options(function (callable $get) { + $plantId = $get('plant_id'); + if (empty($plantId)) { + return []; + } + + return Machine::where('plant_id', $plantId)->orderBy('work_center')->pluck('work_center', 'id')->toArray(); + }) + ->disabled(fn (Get $get) => ! empty($get('id'))) + ->default(function (callable $get) { + $plantId = $get('plant_id'); + if (empty($plantId)) { + return null; + } + + return ModelMaster::where('plant_id', $plantId)->latest()->first()->machine_id ?? null; + }) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('heading_name') + ->label('HEADING NAME') + ->reactive() + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('heading_value') + ->label('HEADING VALUE') + ->reactive() + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('type_name') + ->label('TYPE NAME') + ->reactive() + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }), + Forms\Components\TextInput::make('type_value') + ->label('TYPE VALUE') + ->reactive() + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }), + Forms\Components\TextInput::make('has_motor') + ->label('HAS MOTOR') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_m_part') + ->label('HAS MOTOR PART') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_m_count') + ->label('HAS MOTOR COUNT') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(99) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_pump') + ->label('HAS PUMP') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_p_part') + ->label('HAS PUMP PART') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_p_count') + ->label('HAS PUMP COUNT') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(99) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_name_plate') + ->label('HAS NAME PLATE') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_np_part') + ->label('HAS NAME PLATE PART') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(1) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + Forms\Components\TextInput::make('has_np_count') + ->label('HAS NAME PLATE COUNT') + ->reactive() + ->minValue(0) + ->integer() + ->maxValue(99) + ->readOnly(fn (callable $get) => (! $get('plant_id') || ! $get('machine_id'))) + ->afterStateUpdated(function (callable $set) { + $set('updated_by', Filament::auth()->user()?->name); + }) + ->required(), + 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), + Forms\Components\TextInput::make('id') + ->hidden() + ->readOnly(), + ]); + } + + 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 NAME') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('machine.work_center') + ->label('WORK CENTER') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('heading_name') + ->label('HEADING NAME') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('heading_value') + ->label('HEADING VALUE') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('type_name') + ->label('TYPE NAME') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('type_value') + ->label('TYPE VALUE') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_motor') + ->label('HAS MOTOR') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_m_part') + ->label('HAS MOTOR PART') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_m_count') + ->label('HAS MOTOR COUNT') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_pump') + ->label('HAS PUMP') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_p_part') + ->label('HAS PUMP PART') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_p_count') + ->label('HAS PUMP COUNT') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_name_plate') + ->label('HAS NAME PLATE') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_np_part') + ->label('HAS NAME PLATE PART') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('has_np_count') + ->label('HAS NAME PLATE COUNT') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('created_at') + ->label('CREATED AT') + ->alignCenter() + ->dateTime() + ->sortable(), + Tables\Columns\TextColumn::make('created_by') + ->label('CREATED BY') + ->alignCenter(), + Tables\Columns\TextColumn::make('updated_at') + ->label('UPDATED AT') + ->alignCenter() + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: false), + Tables\Columns\TextColumn::make('updated_by') + ->label('UPDATED BY') + ->alignCenter() + ->toggleable(isToggledHiddenByDefault: false), + Tables\Columns\TextColumn::make('deleted_at') + ->label('DELETED AT') + ->alignCenter() + ->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 Model Masters') + ->color('warning') + ->importer(ModelMasterImporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view import model master'); + }), + ExportAction::make() + ->label('Export Model Masters') + ->color('warning') + ->exporter(ModelMasterExporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view export model master'); + }), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListModelMasters::route('/'), + 'create' => Pages\CreateModelMaster::route('/create'), + 'view' => Pages\ViewModelMaster::route('/{record}'), + 'edit' => Pages\EditModelMaster::route('/{record}/edit'), + ]; + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/ModelMasterResource/Pages/CreateModelMaster.php b/app/Filament/Resources/ModelMasterResource/Pages/CreateModelMaster.php new file mode 100644 index 0000000..bb125aa --- /dev/null +++ b/app/Filament/Resources/ModelMasterResource/Pages/CreateModelMaster.php @@ -0,0 +1,12 @@ +