Compare commits
137 Commits
ranjith-de
...
eae7e76ea4
| Author | SHA1 | Date | |
|---|---|---|---|
| eae7e76ea4 | |||
| 30e4415af9 | |||
| 40014570c9 | |||
| c5e45cd6dd | |||
| 2d40e51e78 | |||
| 98a3c5bc8e | |||
| e969e54f59 | |||
| 6bad120e70 | |||
| 02dea13222 | |||
| 07c642e3bd | |||
| 33e3b247b3 | |||
| 0bcc8d2113 | |||
| ff607695bc | |||
| 006857ec89 | |||
| 107e8cf505 | |||
| 49d5dfb664 | |||
| cf097255a0 | |||
| 27223612d3 | |||
| 22de1ba081 | |||
| d7c2fc3ff9 | |||
| 29a7a66ff4 | |||
| 5f87467dbf | |||
| c01df679a0 | |||
| 4b653f6bb0 | |||
| a877623567 | |||
| e8a6e88eac | |||
| ab49a0fc7c | |||
| e320e4486f | |||
| 4b5cdef55a | |||
| 82a022d3b4 | |||
| 7ec116ca0b | |||
| 048f069dd7 | |||
| b83e26c9d6 | |||
| 945d50c504 | |||
| ae0758a9c0 | |||
| 238d7bdfa7 | |||
| be1e7f36b2 | |||
| 165b7e6a77 | |||
| d0269a646c | |||
| 3eccb1c7d5 | |||
| 83888054e9 | |||
| 22e2fbd25d | |||
| d5d1243d5c | |||
| 2d2d3ac8c5 | |||
| 6e9e57682c | |||
| 3fa6ca578b | |||
| bc43d5ddf1 | |||
| 201c55121e | |||
| aa91bd80de | |||
| d4fc6f6f33 | |||
| dade6a3c0c | |||
| d8aea01b26 | |||
| a690faf6d7 | |||
| dd6cbd92a2 | |||
| b54a9af78b | |||
| 57f059aca8 | |||
| 5688e93862 | |||
| 8b556d3c9c | |||
| 091aaaa0d7 | |||
| 9dd5d04b3c | |||
| 4ac3eb4f2f | |||
| bcd493901c | |||
| af3259a842 | |||
| 6f41238b1e | |||
| 8779e66a18 | |||
| e0384780dc | |||
| afbbfe2aca | |||
| 4fb04c9fa8 | |||
| 656c58999c | |||
| 102bc62805 | |||
| ef1ad62749 | |||
| 1e0193e6c9 | |||
| 18c203552c | |||
| 61190edcce | |||
| 62774aec0f | |||
| 44e58d2d78 | |||
| 3ac4f88155 | |||
| 818f8a3e08 | |||
| 86798212d0 | |||
| 837bd58177 | |||
| d8b29ba000 | |||
| d1db3e0dad | |||
| f274de00e1 | |||
| f9c6405b61 | |||
| 7ec34a04fc | |||
| 9256e1ed12 | |||
| b334ba60fa | |||
| 8c81c140d1 | |||
| 5ff8acac74 | |||
| 2f2bab37d4 | |||
| a8d72923ae | |||
| e39ab23142 | |||
| 2373e1d427 | |||
| baa165e285 | |||
| 53e671d612 | |||
| 58be303cfe | |||
| a1c39c5f0e | |||
| 41e8c04b9d | |||
| 6a30fbc8f2 | |||
| 9e57461d3a | |||
| ab5a6b94c9 | |||
| 84b78ea0c1 | |||
| 5e46b080c5 | |||
| 17f337e8db | |||
| 2781d57e3f | |||
| 5628e8abd4 | |||
| e56733ce44 | |||
| b3f32f6813 | |||
| b2937ccfa8 | |||
| 026e4982b8 | |||
| 6478bc3722 | |||
| 1ab7ed2da9 | |||
| 9ecd04f0e3 | |||
| 53179ea538 | |||
| b4e24c581e | |||
| 4938fa62a7 | |||
| 01f6288c63 | |||
| c9c7c38088 | |||
| 90cfe9ef0d | |||
| aa806f2fe3 | |||
| 0eb6f76ca6 | |||
| 73366ccd70 | |||
| acbecedce9 | |||
| 614d89932b | |||
| c839c6fdbc | |||
| d9445a9d4b | |||
| 50476c8a2c | |||
| c57cfe71ca | |||
| 96c08c34c1 | |||
| 19cf7c8edd | |||
| 66086dd2d7 | |||
| 5da724c9a4 | |||
| 6b723c0929 | |||
| 8e20d0732d | |||
| 1d8fb1c9aa | |||
| 33bbce47ba | |||
| 860ff96134 |
@@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Exports;
|
|
||||||
|
|
||||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
|
||||||
use Maatwebsite\Excel\Concerns\FromArray;
|
|
||||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
|
||||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
|
||||||
|
|
||||||
class ProductionPlanExport implements FromArray, WithHeadings, WithMapping
|
|
||||||
{
|
|
||||||
|
|
||||||
protected array $data;
|
|
||||||
protected array $dates;
|
|
||||||
|
|
||||||
public function __construct(array $data, array $dates)
|
|
||||||
{
|
|
||||||
$this->data = $data;
|
|
||||||
$this->dates = $dates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function array(): array
|
|
||||||
{
|
|
||||||
return $this->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function headings(): array
|
|
||||||
{
|
|
||||||
$headings = [
|
|
||||||
'Plant Name',
|
|
||||||
'Line Name',
|
|
||||||
'Item Code',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add dynamic headings for each date: Target / Produced
|
|
||||||
foreach ($this->dates as $date) {
|
|
||||||
$headings[] = $date . ' - Target Plan';
|
|
||||||
$headings[] = $date . ' - Produced Quantity';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $headings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function map($row): array
|
|
||||||
{
|
|
||||||
$mapped = [
|
|
||||||
$row['plant_name'] ?? '',
|
|
||||||
$row['line_name'] ?? '',
|
|
||||||
$row['item_code'] ?? '',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add daily target and produced quantity for each date
|
|
||||||
foreach ($this->dates as $date) {
|
|
||||||
// $mapped[] = $row['daily_target_dynamic'] ?? 0;
|
|
||||||
$mapped[] = $row['daily_target_dynamic'][$date] ?? '-';
|
|
||||||
$mapped[] = $row['produced_quantity'][$date] ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Filament\Imports;
|
namespace App\Filament\Imports;
|
||||||
|
|
||||||
use App\Models\Block;
|
use App\Models\Block;
|
||||||
use App\Models\Item;
|
|
||||||
use App\Models\Line;
|
use App\Models\Line;
|
||||||
use App\Models\Plant;
|
use App\Models\Plant;
|
||||||
use App\Models\ProductionPlan;
|
use App\Models\ProductionPlan;
|
||||||
@@ -24,33 +23,11 @@ class ProductionPlanImporter extends Importer
|
|||||||
public static function getColumns(): array
|
public static function getColumns(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
// ImportColumn::make('created_at')
|
ImportColumn::make('created_at')
|
||||||
// ->requiredMapping()
|
|
||||||
// ->exampleHeader('Created DateTime')
|
|
||||||
// ->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
|
|
||||||
// ->label('Created DateTime')
|
|
||||||
// ->rules(['required']),
|
|
||||||
|
|
||||||
ImportColumn::make('plant')
|
|
||||||
->requiredMapping()
|
->requiredMapping()
|
||||||
->exampleHeader('Plant Code')
|
->exampleHeader('Created DateTime')
|
||||||
->example(['1000', '1000'])
|
->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
|
||||||
->label('Plant Code')
|
->label('Created DateTime')
|
||||||
->relationship(resolveUsing: 'code')
|
|
||||||
->rules(['required']),
|
|
||||||
ImportColumn::make('line')
|
|
||||||
->requiredMapping()
|
|
||||||
->exampleHeader('Line Name')
|
|
||||||
->example(['4 inch pump line', '4 inch pump line'])
|
|
||||||
->label('Line Name')
|
|
||||||
->relationship(resolveUsing: 'name')
|
|
||||||
->rules(['required']),
|
|
||||||
ImportColumn::make('item')
|
|
||||||
->requiredMapping()
|
|
||||||
->exampleHeader('Item Code')
|
|
||||||
->example(['123456', '210987'])
|
|
||||||
->label('Item Code')
|
|
||||||
->relationship(resolveUsing: 'code')
|
|
||||||
->rules(['required']),
|
->rules(['required']),
|
||||||
ImportColumn::make('plan_quantity')
|
ImportColumn::make('plan_quantity')
|
||||||
->requiredMapping()
|
->requiredMapping()
|
||||||
@@ -59,111 +36,175 @@ class ProductionPlanImporter extends Importer
|
|||||||
->label('Plan Quantity')
|
->label('Plan Quantity')
|
||||||
->numeric()
|
->numeric()
|
||||||
->rules(['required', 'integer']),
|
->rules(['required', 'integer']),
|
||||||
// ImportColumn::make('production_quantity')
|
ImportColumn::make('production_quantity')
|
||||||
// ->requiredMapping()
|
->requiredMapping()
|
||||||
// ->exampleHeader('Production Quantity')
|
->exampleHeader('Production Quantity')
|
||||||
// ->example(['0', '0'])
|
->example(['0', '0'])
|
||||||
// ->label('Production Quantity')
|
->label('Production Quantity')
|
||||||
// ->numeric()
|
->numeric()
|
||||||
// ->rules(['required', 'integer']),
|
->rules(['required', 'integer']),
|
||||||
|
ImportColumn::make('line')
|
||||||
// ImportColumn::make('block_reference')
|
->requiredMapping()
|
||||||
// ->requiredMapping() // Or optionalMapping() if not always present
|
->exampleHeader('Line Name')
|
||||||
// ->exampleHeader('Block Name')
|
->example(['4 inch pump line', '4 inch pump line'])
|
||||||
// ->example(['Block A', 'Block A'])
|
->label('Line Name')
|
||||||
// ->label('Block Name')
|
->relationship(resolveUsing:'name')
|
||||||
// ->rules(['required']), // Or remove if not required
|
->rules(['required']),
|
||||||
// ImportColumn::make('shift')
|
ImportColumn::make('block_reference')
|
||||||
// ->requiredMapping()
|
->requiredMapping() // Or optionalMapping() if not always present
|
||||||
// ->exampleHeader('Shift Name') // ID
|
->exampleHeader('Block Name')
|
||||||
// ->example(['Day', 'Night']) // '2', '7'
|
->example(['Block A', 'Block A'])
|
||||||
// ->label('Shift Name') // ID
|
->label('Block Name')
|
||||||
// ->relationship(resolveUsing: 'name')
|
->rules(['required']), // Or remove if not required
|
||||||
// ->rules(['required']),
|
ImportColumn::make('shift')
|
||||||
|
->requiredMapping()
|
||||||
// ImportColumn::make('updated_at')
|
->exampleHeader('Shift Name') //ID
|
||||||
// ->requiredMapping()
|
->example(['Day', 'Night']) //'2', '7'
|
||||||
// ->exampleHeader('Updated DateTime')
|
->label('Shift Name') // ID
|
||||||
// ->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
|
->relationship(resolveUsing: 'name')
|
||||||
// ->label('Updated DateTime')
|
->rules(['required']),
|
||||||
// ->rules(['required']),
|
ImportColumn::make('plant')
|
||||||
// ImportColumn::make('operator_id')
|
->requiredMapping()
|
||||||
// ->requiredMapping()
|
->exampleHeader('Plant Name')
|
||||||
// ->exampleHeader('Operator ID')
|
->example(['Ransar Industries-I', 'Ransar Industries-I'])
|
||||||
// ->example([Filament::auth()->user()->name, Filament::auth()->user()->name])
|
->label('Plant Name')
|
||||||
// ->label('Operator ID')
|
->relationship(resolveUsing:'name')
|
||||||
// ->rules(['required']),
|
->rules(['required']),
|
||||||
|
ImportColumn::make('updated_at')
|
||||||
|
->requiredMapping()
|
||||||
|
->exampleHeader('Updated DateTime')
|
||||||
|
->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
|
||||||
|
->label('Updated DateTime')
|
||||||
|
->rules(['required']),
|
||||||
|
ImportColumn::make('operator_id')
|
||||||
|
->requiredMapping()
|
||||||
|
->exampleHeader('Operator ID')
|
||||||
|
->example([Filament::auth()->user()->name, Filament::auth()->user()->name])
|
||||||
|
->label('Operator ID')
|
||||||
|
->rules(['required']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolveRecord(): ?ProductionPlan
|
public function resolveRecord(): ?ProductionPlan
|
||||||
{
|
{
|
||||||
$warnMsg = [];
|
$warnMsg = [];
|
||||||
$plantCod = $this->data['plant'];
|
$plant = Plant::where('name', $this->data['plant'])->first();
|
||||||
$itemCod = $this->data['item'];
|
|
||||||
$plant = null;
|
|
||||||
$line = null;
|
$line = null;
|
||||||
$block = null;
|
$block = null;
|
||||||
|
|
||||||
if (Str::length($plantCod) < 4 || ! is_numeric($plantCod) || ! preg_match('/^[1-9]\d{3,}$/', $plantCod)) {
|
|
||||||
$warnMsg[] = 'Invalid plant code found';
|
|
||||||
} else {
|
|
||||||
$plant = Plant::where('code', $plantCod)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$plant) {
|
if (!$plant) {
|
||||||
$warnMsg[] = 'Plant not found';
|
$warnMsg[] = "Plant not found";
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$line = Line::where('name', $this->data['line'])->where('plant_id', $plant->id)->first();
|
$line = Line::where('name', $this->data['line'])->where('plant_id', $plant->id)->first();
|
||||||
|
//block_reference
|
||||||
|
$block = Block::where('name', $this->data['block_reference'])->where('plant_id', $plant->id)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$line) {
|
if (!$line) {
|
||||||
$warnMsg[] = 'Line not found';
|
$warnMsg[] = "Line not found";
|
||||||
}
|
}
|
||||||
|
$shift = null;
|
||||||
if (Str::length($itemCod) < 6 || ! is_numeric($itemCod)) {
|
if (!$block) {
|
||||||
$warnMsg[] = 'Invalid item code found';
|
$warnMsg[] = "Block not found";
|
||||||
} else {
|
|
||||||
$item = Item::where('code', $itemCod)->first();
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (! $item) {
|
$shift = Shift::where('name', $this->data['shift'])->where('plant_id', $plant->id)->where('block_id', $block->id)->first();
|
||||||
$warnMsg[] = 'Item not found';
|
|
||||||
}
|
}
|
||||||
|
//$shift = Shift::where('id', $this->data['shift'])->where('plant_id', $plant->id)->first();
|
||||||
$plantId = $plant->id;
|
if (!$shift) {
|
||||||
|
$warnMsg[] = "Shift not found";
|
||||||
$itemAgaPlant = Item::where('plant_id', $plantId)->where('code', $itemCod)->first();
|
|
||||||
|
|
||||||
if(!$itemAgaPlant){
|
|
||||||
$warnMsg[] = 'Item not found against plant code';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = Filament::auth()->user();
|
|
||||||
|
|
||||||
$operatorName = $user->name;
|
|
||||||
|
|
||||||
if (Str::length($this->data['plan_quantity']) < 0 || !is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) {
|
if (Str::length($this->data['plan_quantity']) < 0 || !is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) {
|
||||||
$warnMsg[] = 'Invalid plan quantity found';
|
$warnMsg[] = "Invalid plan quantity found";
|
||||||
|
}
|
||||||
|
if (Str::length($this->data['production_quantity']) < 0 || !is_numeric($this->data['production_quantity']) || $this->data['production_quantity'] < 0) {
|
||||||
|
$warnMsg[] = "Invalid production quantity found";
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromDate = $this->data['created_at'];
|
||||||
|
$toDate = $this->data['updated_at'];
|
||||||
|
|
||||||
|
$formats = ['d-m-Y H:i', 'd-m-Y H:i:s']; //'07-05-2025 08:00' or '07-05-2025 08:00:00'
|
||||||
|
|
||||||
|
$fdateTime = null;
|
||||||
|
$tdateTime = null;
|
||||||
|
// Try parsing with multiple formats
|
||||||
|
foreach ($formats as $format) {
|
||||||
|
try {
|
||||||
|
$fdateTime = Carbon::createFromFormat($format, $fromDate);
|
||||||
|
break;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Optionally collect warning messages
|
||||||
|
// $warnMsg[] = "Date format mismatch with format: $format";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($formats as $format) {
|
||||||
|
try {
|
||||||
|
$tdateTime = Carbon::createFromFormat($format, $toDate);
|
||||||
|
break;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Optionally collect warning messages
|
||||||
|
// $warnMsg[] = "Date format mismatch with format: $format";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fDateOnly = '';
|
||||||
|
if (!isset($fdateTime)) {
|
||||||
|
// throw new \Exception('Invalid date time format');
|
||||||
|
$warnMsg[] = "Invalid 'Created DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$fDateOnly = $fdateTime->toDateString();
|
||||||
|
}
|
||||||
|
if (!isset($tdateTime)) {
|
||||||
|
$warnMsg[] = "Invalid 'Updated DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($fdateTime) && isset($tdateTime)) {
|
||||||
|
if ($fdateTime->greaterThan($tdateTime)) {
|
||||||
|
$warnMsg[] = "'Created DataTime' is greater than 'Updated DateTime'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!$fromDate) {
|
||||||
|
// $warnMsg[] = "Invalid 'Created DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
|
||||||
|
// }
|
||||||
|
// else if (!$toDate) {
|
||||||
|
// $warnMsg[] = "Invalid 'Updated DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
|
||||||
|
// }
|
||||||
|
|
||||||
|
$user = User::where('name', $this->data['operator_id'])->first();
|
||||||
|
if (!$user) {
|
||||||
|
$warnMsg[] = "Operator ID not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($warnMsg)) {
|
if (!empty($warnMsg)) {
|
||||||
throw new RowImportFailedException(implode(', ', $warnMsg));
|
throw new RowImportFailedException(implode(', ', $warnMsg));
|
||||||
} else {
|
}
|
||||||
|
else { //if (empty($warnMsg))
|
||||||
$productionPlan = ProductionPlan::where('plant_id', $plant->id)
|
$productionPlan = ProductionPlan::where('plant_id', $plant->id)
|
||||||
|
->where('shift_id', $shift->id)
|
||||||
->where('line_id', $line->id)
|
->where('line_id', $line->id)
|
||||||
->where('item_id', $itemAgaPlant->id)
|
->whereDate('created_at', $fDateOnly)
|
||||||
|
// ->where('plan_quantity', $productionQuantity->plan_quantity)
|
||||||
->latest()
|
->latest()
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($productionPlan) {
|
if ($productionPlan) {
|
||||||
|
// if($productionPlan->production_quantity)
|
||||||
|
// {
|
||||||
|
// throw new RowImportFailedException("{$productionPlan->created_at}, {$productionPlan->production_quantity}");
|
||||||
|
// }
|
||||||
|
// $warnMsg[] = "Production plan already exist on '{$fDateOnly}'!";
|
||||||
|
|
||||||
$productionPlan->update([
|
$productionPlan->update([
|
||||||
'plan_quantity' => $this->data['plan_quantity'],
|
'plan_quantity' => $this->data['plan_quantity'],
|
||||||
'operator_id' => $operatorName,
|
// 'production_quantity' => $productionPlan->production_quantity,
|
||||||
|
// 'created_at' => $productionPlan->created_at,//$fdateTime->format('Y-m-d H:i:s'),
|
||||||
|
// 'updated_at' => $tdateTime->format('Y-m-d H:i:s'),
|
||||||
|
'operator_id' => $this->data['operator_id'],
|
||||||
]);
|
]);
|
||||||
$productionPlan->save();
|
$productionPlan->save();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,15 +212,20 @@ class ProductionPlanImporter extends Importer
|
|||||||
ProductionPlan::updateOrCreate([
|
ProductionPlan::updateOrCreate([
|
||||||
'plant_id' => $plant->id,
|
'plant_id' => $plant->id,
|
||||||
'line_id' => $line->id,
|
'line_id' => $line->id,
|
||||||
'item_id' => $itemAgaPlant->id,
|
'shift_id' => $shift->id,
|
||||||
// 'shift_id' => $shift->id,
|
|
||||||
'plan_quantity' => $this->data['plan_quantity'],
|
'plan_quantity' => $this->data['plan_quantity'],
|
||||||
'created_at' =>now(),
|
'production_quantity' => $this->data['production_quantity'],
|
||||||
'updated_at' => now(),
|
'created_at' => $fdateTime->format('Y-m-d H:i:s'),//$this->data['created_at'],
|
||||||
'operator_id' => $operatorName,
|
'updated_at' => $tdateTime->format('Y-m-d H:i:s'),//$this->data['updated_at'],
|
||||||
|
'operator_id' => $this->data['operator_id'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
// return ProductionPlan::firstOrNew([
|
||||||
|
// // Update existing records, matching them by `$this->data['column_name']`
|
||||||
|
// 'email' => $this->data['email'],
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// return new ProductionPlan();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getCompletedNotificationBody(Import $import): string
|
public static function getCompletedNotificationBody(Import $import): string
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Filament\Pages;
|
|
||||||
|
|
||||||
use App\Models\CustomerPoMaster;
|
|
||||||
use App\Models\Plant;
|
|
||||||
use App\Models\ProductionPlan;
|
|
||||||
use App\Models\WireMasterPacking;
|
|
||||||
use Filament\Facades\Filament;
|
|
||||||
use Filament\Pages\Page;
|
|
||||||
use Filament\Forms\Concerns\InteractsWithForms;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Forms\Components\Section;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\DatePicker;
|
|
||||||
use Filament\Forms\Components\Grid;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Forms\Components\ViewField;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
use Filament\Forms\Components\Actions\Action;
|
|
||||||
use Filament\Forms\Components\Hidden;
|
|
||||||
|
|
||||||
class ProductionCalender extends Page
|
|
||||||
{
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-document-text';
|
|
||||||
|
|
||||||
protected static string $view = 'filament.pages.production-calender';
|
|
||||||
|
|
||||||
use InteractsWithForms;
|
|
||||||
|
|
||||||
protected $listeners = ['setWorkingDays'];
|
|
||||||
|
|
||||||
public $pId;
|
|
||||||
|
|
||||||
public array $filters = [];
|
|
||||||
|
|
||||||
public function setWorkingDays($days = null)
|
|
||||||
{
|
|
||||||
$this->form->fill([
|
|
||||||
'working_days' => $days ?? 0,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function form(Form $form): Form
|
|
||||||
{
|
|
||||||
return $form
|
|
||||||
->statePath('filters')
|
|
||||||
->schema([
|
|
||||||
Section::make('')
|
|
||||||
->schema([
|
|
||||||
Select::make('plant_id')
|
|
||||||
->label('Plant')
|
|
||||||
->reactive()
|
|
||||||
//->options(Plant::pluck('name', 'id'))
|
|
||||||
->options(function (callable $get) {
|
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
|
||||||
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
|
|
||||||
})
|
|
||||||
->columnSpan(['default' => 10, 'sm' => 7])
|
|
||||||
->required()
|
|
||||||
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
|
||||||
$set('working_days', null);
|
|
||||||
}),
|
|
||||||
TextInput::make('working_days')
|
|
||||||
->label('No. of Working Days')
|
|
||||||
->numeric()
|
|
||||||
->readOnly()
|
|
||||||
->columnSpan(['default' => 10, 'sm' => 2])
|
|
||||||
->required()
|
|
||||||
->minValue(0)
|
|
||||||
->maxValue(31)
|
|
||||||
->placeholder('Enter working days')
|
|
||||||
->id('working_days'),
|
|
||||||
|
|
||||||
Hidden::make('month')
|
|
||||||
->label('Month')
|
|
||||||
->id('month'),
|
|
||||||
|
|
||||||
Hidden::make('year')
|
|
||||||
->label('Year')
|
|
||||||
->id('year'),
|
|
||||||
|
|
||||||
Hidden::make('selected_dates')
|
|
||||||
->label('Selected Dates')
|
|
||||||
->id('selected_dates'),
|
|
||||||
|
|
||||||
ViewField::make('save')
|
|
||||||
->view('forms.save')
|
|
||||||
->columnSpan(['default' => 10, 'sm' => 1]),
|
|
||||||
|
|
||||||
ViewField::make('calendar')
|
|
||||||
->view('forms.calendar')
|
|
||||||
->columnspan(10),
|
|
||||||
])
|
|
||||||
->columns(10)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveWorkingDays(){
|
|
||||||
$plantId = $this->filters['plant_id'] ?? null;
|
|
||||||
$workingDays = $this->filters['working_days'] ?? null;
|
|
||||||
$month = $this->filters['month'] ?? null;
|
|
||||||
$year = $this->filters['year'] ?? null;
|
|
||||||
$dates = $this->filters['selected_dates'] ?? null;
|
|
||||||
|
|
||||||
if (!$plantId) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Unknown Plant')
|
|
||||||
->body("Please select a plant first!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!$workingDays) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Unknown Working Days')
|
|
||||||
->body("Working days can't be empty!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!$month) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Unknown Month')
|
|
||||||
->body("month can't be empty!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (!$year) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Unknown Year')
|
|
||||||
->body("Year can't be empty!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$updated = ProductionPlan::where('plant_id', $plantId)
|
|
||||||
->whereMonth('created_at', $month)
|
|
||||||
->whereYear('created_at', $year)
|
|
||||||
->update([
|
|
||||||
'working_days' => $workingDays,
|
|
||||||
'leave_dates' => $dates,
|
|
||||||
'updated_at' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($updated) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Success')
|
|
||||||
->body("Working days updated successfully!")
|
|
||||||
->success()
|
|
||||||
->send();
|
|
||||||
} else {
|
|
||||||
Notification::make()
|
|
||||||
->title('No Records Updated')
|
|
||||||
->body("No production plans found for this plant and month.")
|
|
||||||
->warning()
|
|
||||||
->send();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Filament\Pages;
|
|
||||||
|
|
||||||
use App\Models\Plant;
|
|
||||||
use Filament\Facades\Filament;
|
|
||||||
use Filament\Forms\Components\DatePicker;
|
|
||||||
use Filament\Pages\Page;
|
|
||||||
use Filament\Forms\Form;
|
|
||||||
use Filament\Forms\Components\Section;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Components\TextInput;
|
|
||||||
use Filament\Notifications\Notification;
|
|
||||||
|
|
||||||
class ProductionTarget extends Page
|
|
||||||
{
|
|
||||||
protected static ?string $navigationIcon = 'heroicon-o-document-text';
|
|
||||||
|
|
||||||
protected static string $view = 'filament.pages.production-target';
|
|
||||||
|
|
||||||
public array $filters = [];
|
|
||||||
|
|
||||||
|
|
||||||
public function form(Form $form): Form
|
|
||||||
{
|
|
||||||
return $form
|
|
||||||
->statePath('filters')
|
|
||||||
->schema([
|
|
||||||
Section::make('')
|
|
||||||
->schema([
|
|
||||||
Select::make('plant_id')
|
|
||||||
->label('Plant')
|
|
||||||
->reactive()
|
|
||||||
->options(function (callable $get) {
|
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
|
||||||
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
|
|
||||||
})
|
|
||||||
->required()
|
|
||||||
->afterStateUpdated(function ($state, callable $get, $set) {
|
|
||||||
$set('line_id', null);
|
|
||||||
$set('year', null);
|
|
||||||
$set('month', null);
|
|
||||||
$this->dispatch('loadData',$state, '', '', '');
|
|
||||||
}),
|
|
||||||
Select::make('line_id')
|
|
||||||
->label('Line')
|
|
||||||
->required()
|
|
||||||
->columnSpan(1)
|
|
||||||
->options(function (callable $get) {
|
|
||||||
if (!$get('plant_id')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return \App\Models\Line::where('plant_id', $get('plant_id'))
|
|
||||||
->pluck('name', 'id')
|
|
||||||
->toArray();
|
|
||||||
})
|
|
||||||
->reactive()
|
|
||||||
->afterStateUpdated(function ($state, callable $get, $set) {
|
|
||||||
$plantId = $get('plant_id');
|
|
||||||
$set('year', null);
|
|
||||||
$set('month', null);
|
|
||||||
$this->dispatch('loadData',$plantId, $state, '', '');
|
|
||||||
}),
|
|
||||||
Select::make('year')
|
|
||||||
->label('Year')
|
|
||||||
->reactive()
|
|
||||||
->options([
|
|
||||||
'2026' => '2026',
|
|
||||||
'2027' => '2027',
|
|
||||||
'2028' => '2028',
|
|
||||||
'2029' => '2029',
|
|
||||||
'2030' => '2030',
|
|
||||||
'2031' => '2031',
|
|
||||||
'2032' => '2032',
|
|
||||||
'2033' => '2033',
|
|
||||||
'2034' => '2034',
|
|
||||||
'2035' => '2035',
|
|
||||||
'2036' => '2036',
|
|
||||||
'2037' => '2037',
|
|
||||||
'2038' => '2038',
|
|
||||||
'2039' => '2039',
|
|
||||||
'2040' => '2040',
|
|
||||||
])
|
|
||||||
->required()
|
|
||||||
->afterStateUpdated(function ($state, callable $get, $set) {
|
|
||||||
$set('month', null);
|
|
||||||
$plantId = $get('plant_id');
|
|
||||||
$lineId = $get('line_id');
|
|
||||||
$this->dispatch('loadData',$plantId, $lineId, $state, '');
|
|
||||||
}),
|
|
||||||
|
|
||||||
Select::make('month')
|
|
||||||
->label('Month')
|
|
||||||
->reactive()
|
|
||||||
->options([
|
|
||||||
'01' => 'January',
|
|
||||||
'02' => 'February',
|
|
||||||
'03' => 'March',
|
|
||||||
'04' => 'April',
|
|
||||||
'05' => 'May',
|
|
||||||
'06' => 'June',
|
|
||||||
'07' => 'July',
|
|
||||||
'08' => 'August',
|
|
||||||
'09' => 'September',
|
|
||||||
'10' => 'October',
|
|
||||||
'11' => 'November',
|
|
||||||
'12' => 'December',
|
|
||||||
])
|
|
||||||
->required()
|
|
||||||
->afterStateUpdated(function ($state, callable $get) {
|
|
||||||
|
|
||||||
$plantId = $get('plant_id');
|
|
||||||
$lineId = $get('line_id');
|
|
||||||
// $month = $get('month');
|
|
||||||
$year = $get('year');
|
|
||||||
|
|
||||||
$month = (int) $get('month');
|
|
||||||
|
|
||||||
if (!$month) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->dispatch('loadData', $plantId, $lineId, $month, $year);
|
|
||||||
}),
|
|
||||||
|
|
||||||
])
|
|
||||||
->columns(4)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function export(){
|
|
||||||
|
|
||||||
$plantId = $this->filters['plant_id'] ?? null;
|
|
||||||
$lineId = $this->filters['line_id'] ?? null;
|
|
||||||
$year = $this->filters['year'] ?? null;
|
|
||||||
$month = $this->filters['month'] ?? null;
|
|
||||||
|
|
||||||
if (! $plantId) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Plant')
|
|
||||||
->body("please select plant to export data..!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (! $lineId) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Line')
|
|
||||||
->body("please select line to export data..!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (! $year) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Year')
|
|
||||||
->body("please select year to export data..!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (! $month) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Month')
|
|
||||||
->body("please select month to export data..!")
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dispatch('loadData1' ,$plantId, $lineId, $year, $month);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -82,9 +82,6 @@ class ItemResource extends Resource
|
|||||||
Forms\Components\TextInput::make('category')
|
Forms\Components\TextInput::make('category')
|
||||||
->label('Category')
|
->label('Category')
|
||||||
->placeholder('Scan the Category'),
|
->placeholder('Scan the Category'),
|
||||||
Forms\Components\TextInput::make('category')
|
|
||||||
->label('Category')
|
|
||||||
->placeholder('Scan the Category'),
|
|
||||||
Forms\Components\TextInput::make('code')
|
Forms\Components\TextInput::make('code')
|
||||||
->required()
|
->required()
|
||||||
->placeholder('Scan the valid code')
|
->placeholder('Scan the valid code')
|
||||||
|
|||||||
@@ -5,23 +5,23 @@ namespace App\Filament\Resources;
|
|||||||
use App\Filament\Exports\LineExporter;
|
use App\Filament\Exports\LineExporter;
|
||||||
use App\Filament\Imports\LineImporter;
|
use App\Filament\Imports\LineImporter;
|
||||||
use App\Filament\Resources\LineResource\Pages;
|
use App\Filament\Resources\LineResource\Pages;
|
||||||
use App\Models\Block;
|
use App\Filament\Resources\LineResource\RelationManagers;
|
||||||
use App\Models\Line;
|
use App\Models\Line;
|
||||||
use App\Models\Plant;
|
use App\Models\Plant;
|
||||||
use App\Models\WorkGroupMaster;
|
use App\Models\WorkGroupMaster;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Forms;
|
use Filament\Forms;
|
||||||
use Filament\Forms\Components\Section;
|
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Form;
|
||||||
use Filament\Forms\Get;
|
use Filament\Forms\Get;
|
||||||
use Filament\Forms\Set;
|
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Actions\ExportAction;
|
|
||||||
use Filament\Tables\Actions\ImportAction;
|
use Filament\Tables\Actions\ImportAction;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Set;
|
||||||
|
use Filament\Tables\Actions\ExportAction;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\Rules\Unique;
|
use Illuminate\Validation\Rules\Unique;
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@ class LineResource extends Resource
|
|||||||
->reactive()
|
->reactive()
|
||||||
->options(function (callable $get) {
|
->options(function (callable $get) {
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
$userHas = Filament::auth()->user()->plant_id;
|
||||||
|
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
|
||||||
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
|
|
||||||
})
|
})
|
||||||
->default(function () {
|
->default(function () {
|
||||||
return optional(Line::latest()->first())->plant_id;
|
return optional(Line::latest()->first())->plant_id;
|
||||||
@@ -61,9 +60,10 @@ class LineResource extends Resource
|
|||||||
// Ensure `linestop_id` is not cleared
|
// Ensure `linestop_id` is not cleared
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('lPlantError', 'Please select a plant first.');
|
$set('lPlantError', 'Please select a plant first.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('lPlantError', null);
|
$set('lPlantError', null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -72,39 +72,6 @@ class LineResource extends Resource
|
|||||||
])
|
])
|
||||||
->hint(fn ($get) => $get('lPlantError') ? $get('lPlantError') : null)
|
->hint(fn ($get) => $get('lPlantError') ? $get('lPlantError') : null)
|
||||||
->hintColor('danger'),
|
->hintColor('danger'),
|
||||||
Forms\Components\Select::make('block_id')
|
|
||||||
->label('Block')
|
|
||||||
->relationship('block', 'name')
|
|
||||||
->required()
|
|
||||||
// ->nullable(),
|
|
||||||
->reactive()
|
|
||||||
->options(function (callable $get) {
|
|
||||||
if (! $get('plant_id')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Block::where('plant_id', $get('plant_id'))
|
|
||||||
->pluck('name', 'id')
|
|
||||||
->toArray();
|
|
||||||
})
|
|
||||||
->default(function () {
|
|
||||||
return optional(Block::latest()->first())->plant_id;
|
|
||||||
})
|
|
||||||
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
|
||||||
$blockId = $get('block_id');
|
|
||||||
if (! $blockId) {
|
|
||||||
$set('lblockError', 'Please select a Block first.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$set('lblockError', null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
->extraAttributes(fn ($get) => [
|
|
||||||
'class' => $get('lblockError') ? 'border-red-500' : '',
|
|
||||||
])
|
|
||||||
->hint(fn ($get) => $get('lblockError') ? $get('lblockError') : null)
|
|
||||||
->hintColor('danger'),
|
|
||||||
Forms\Components\TextInput::make('name')
|
Forms\Components\TextInput::make('name')
|
||||||
->required()
|
->required()
|
||||||
->placeholder('Scan the valid name')
|
->placeholder('Scan the valid name')
|
||||||
@@ -132,9 +99,10 @@ class LineResource extends Resource
|
|||||||
// Ensure `linestop_id` is not cleared
|
// Ensure `linestop_id` is not cleared
|
||||||
if (!$lineNam) {
|
if (!$lineNam) {
|
||||||
$set('lNameError', 'Scan the valid name.');
|
$set('lNameError', 'Scan the valid name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// $exists = Line::where('name', $lineNam)
|
// $exists = Line::where('name', $lineNam)
|
||||||
// ->where('plant_id', $get('plant_id'))
|
// ->where('plant_id', $get('plant_id'))
|
||||||
// ->exists();
|
// ->exists();
|
||||||
@@ -188,9 +156,6 @@ class LineResource extends Resource
|
|||||||
'Base FG Line' => 'Base FG Line',
|
'Base FG Line' => 'Base FG Line',
|
||||||
'SFG Line' => 'SFG Line',
|
'SFG Line' => 'SFG Line',
|
||||||
'FG Line' => 'FG Line',
|
'FG Line' => 'FG Line',
|
||||||
'Process Base FG Line' => 'Process Base FG Line',
|
|
||||||
'Process SFG Line' => 'Process SFG Line',
|
|
||||||
'Process FG Line' => 'Process FG Line',
|
|
||||||
'Machining Cell' => 'Machining Cell',
|
'Machining Cell' => 'Machining Cell',
|
||||||
'Blanking Cell' => 'Blanking Cell',
|
'Blanking Cell' => 'Blanking Cell',
|
||||||
'Forming Cell' => 'Forming Cell',
|
'Forming Cell' => 'Forming Cell',
|
||||||
@@ -205,9 +170,10 @@ class LineResource extends Resource
|
|||||||
// Ensure `linestop_id` is not cleared
|
// Ensure `linestop_id` is not cleared
|
||||||
if (!$lineTyp) {
|
if (!$lineTyp) {
|
||||||
$set('lTypeError', 'Scan the valid type.');
|
$set('lTypeError', 'Scan the valid type.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('lTypeError', null);
|
$set('lTypeError', null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -238,7 +204,7 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
$partValidDispColumns = [
|
$partValidDispColumns = [
|
||||||
'work_group1_actual_id', 'work_group2_actual_id', 'work_group3_actual_id', 'work_group4_actual_id', 'work_group5_actual_id',
|
'work_group1_actual_id', 'work_group2_actual_id', 'work_group3_actual_id', 'work_group4_actual_id', 'work_group5_actual_id',
|
||||||
'work_group6_actual_id', 'work_group7_actual_id', 'work_group8_actual_id', 'work_group9_actual_id', 'work_group10_actual_id',
|
'work_group6_actual_id', 'work_group7_actual_id', 'work_group8_actual_id', 'work_group9_actual_id', 'work_group10_actual_id'
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($partValidDispColumns as $column) {
|
foreach ($partValidDispColumns as $column) {
|
||||||
@@ -289,7 +255,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group1_id_error', null);
|
$set('work_group1_id_error', null);
|
||||||
$set('work_group1_id', null);
|
$set('work_group1_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +263,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group1_id_error', 'Invalid plant name.');
|
$set('work_group1_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +272,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group1_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group1_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +297,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group1_actual_id', '');
|
$set('work_group1_actual_id', '');
|
||||||
$set('work_group1_id', null);
|
$set('work_group1_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group1_id_error', null);
|
$set('work_group1_id_error', null);
|
||||||
$set('work_group1_id', $workGroupRecord->id);
|
$set('work_group1_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -368,7 +332,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group2_id_error', null);
|
$set('work_group2_id_error', null);
|
||||||
$set('work_group2_id', null);
|
$set('work_group2_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +340,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group2_id_error', 'Invalid plant name.');
|
$set('work_group2_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +349,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group2_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group2_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,9 +369,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group2_actual_id', '');
|
$set('work_group2_actual_id', '');
|
||||||
$set('work_group2_id', null);
|
$set('work_group2_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group2_id_error', null);
|
$set('work_group2_id_error', null);
|
||||||
$set('work_group2_id', $workGroupRecord->id);
|
$set('work_group2_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -442,7 +404,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group3_id_error', null);
|
$set('work_group3_id_error', null);
|
||||||
$set('work_group3_id', null);
|
$set('work_group3_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +412,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group3_id_error', 'Invalid plant name.');
|
$set('work_group3_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +421,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group3_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group3_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,9 +441,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group3_actual_id', '');
|
$set('work_group3_actual_id', '');
|
||||||
$set('work_group3_id', null);
|
$set('work_group3_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group3_id_error', null);
|
$set('work_group3_id_error', null);
|
||||||
$set('work_group3_id', $workGroupRecord->id);
|
$set('work_group3_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -516,7 +476,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group4_id_error', null);
|
$set('work_group4_id_error', null);
|
||||||
$set('work_group4_id', null);
|
$set('work_group4_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +484,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group4_id_error', 'Invalid plant name.');
|
$set('work_group4_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,7 +493,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group4_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group4_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,9 +513,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group4_actual_id', '');
|
$set('work_group4_actual_id', '');
|
||||||
$set('work_group4_id', null);
|
$set('work_group4_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group4_id_error', null);
|
$set('work_group4_id_error', null);
|
||||||
$set('work_group4_id', $workGroupRecord->id);
|
$set('work_group4_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -590,7 +548,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group5_id_error', null);
|
$set('work_group5_id_error', null);
|
||||||
$set('work_group5_id', null);
|
$set('work_group5_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +556,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group5_id_error', 'Invalid plant name.');
|
$set('work_group5_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +565,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group5_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group5_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,9 +585,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group5_actual_id', '');
|
$set('work_group5_actual_id', '');
|
||||||
$set('work_group5_id', null);
|
$set('work_group5_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group5_id_error', null);
|
$set('work_group5_id_error', null);
|
||||||
$set('work_group5_id', $workGroupRecord->id);
|
$set('work_group5_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -664,7 +620,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group6_id_error', null);
|
$set('work_group6_id_error', null);
|
||||||
$set('work_group6_id', null);
|
$set('work_group6_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,7 +628,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group6_id_error', 'Invalid plant name.');
|
$set('work_group6_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +637,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group6_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group6_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,9 +657,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group6_actual_id', '');
|
$set('work_group6_actual_id', '');
|
||||||
$set('work_group6_id', null);
|
$set('work_group6_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group6_id_error', null);
|
$set('work_group6_id_error', null);
|
||||||
$set('work_group6_id', $workGroupRecord->id);
|
$set('work_group6_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -738,7 +692,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group7_id_error', null);
|
$set('work_group7_id_error', null);
|
||||||
$set('work_group7_id', null);
|
$set('work_group7_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,7 +700,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group7_id_error', 'Invalid plant name.');
|
$set('work_group7_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,7 +709,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group7_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group7_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,9 +729,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group7_actual_id', '');
|
$set('work_group7_actual_id', '');
|
||||||
$set('work_group7_id', null);
|
$set('work_group7_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group7_id_error', null);
|
$set('work_group7_id_error', null);
|
||||||
$set('work_group7_id', $workGroupRecord->id);
|
$set('work_group7_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -812,7 +764,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group8_id_error', null);
|
$set('work_group8_id_error', null);
|
||||||
$set('work_group8_id', null);
|
$set('work_group8_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,7 +772,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group8_id_error', 'Invalid plant name.');
|
$set('work_group8_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +781,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group8_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group8_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,9 +801,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group8_actual_id', '');
|
$set('work_group8_actual_id', '');
|
||||||
$set('work_group8_id', null);
|
$set('work_group8_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group8_id_error', null);
|
$set('work_group8_id_error', null);
|
||||||
$set('work_group8_id', $workGroupRecord->id);
|
$set('work_group8_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -886,7 +836,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group9_id_error', null);
|
$set('work_group9_id_error', null);
|
||||||
$set('work_group9_id', null);
|
$set('work_group9_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,7 +844,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group9_id_error', 'Invalid plant name.');
|
$set('work_group9_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,7 +853,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group9_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group9_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,9 +873,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group9_actual_id', '');
|
$set('work_group9_actual_id', '');
|
||||||
$set('work_group9_id', null);
|
$set('work_group9_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group9_id_error', null);
|
$set('work_group9_id_error', null);
|
||||||
$set('work_group9_id', $workGroupRecord->id);
|
$set('work_group9_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -960,7 +908,6 @@ class LineResource extends Resource
|
|||||||
if ($state == null || trim($state) == '') {
|
if ($state == null || trim($state) == '') {
|
||||||
$set('work_group10_id_error', null);
|
$set('work_group10_id_error', null);
|
||||||
$set('work_group10_id', null);
|
$set('work_group10_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +916,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('work_group10_id_error', 'Invalid plant name.');
|
$set('work_group10_id_error', 'Invalid plant name.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -979,7 +925,6 @@ class LineResource extends Resource
|
|||||||
|
|
||||||
if (!$workGroupRecord) {
|
if (!$workGroupRecord) {
|
||||||
$set('work_group10_id_error', 'Work group does not exist for this plant in master.');
|
$set('work_group10_id_error', 'Work group does not exist for this plant in master.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,9 +945,10 @@ class LineResource extends Resource
|
|||||||
->send();
|
->send();
|
||||||
$set('work_group10_actual_id', '');
|
$set('work_group10_actual_id', '');
|
||||||
$set('work_group10_id', null);
|
$set('work_group10_id', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('work_group10_id_error', null);
|
$set('work_group10_id_error', null);
|
||||||
$set('work_group10_id', $workGroupRecord->id);
|
$set('work_group10_id', $workGroupRecord->id);
|
||||||
}
|
}
|
||||||
@@ -1038,7 +984,6 @@ class LineResource extends Resource
|
|||||||
$paginator = $livewire->getTableRecords();
|
$paginator = $livewire->getTableRecords();
|
||||||
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
|
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
|
||||||
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
|
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
|
||||||
|
|
||||||
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
|
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
|
||||||
}),
|
}),
|
||||||
Tables\Columns\TextColumn::make('plant.name')
|
Tables\Columns\TextColumn::make('plant.name')
|
||||||
@@ -1046,11 +991,6 @@ class LineResource extends Resource
|
|||||||
->alignCenter()
|
->alignCenter()
|
||||||
->sortable()
|
->sortable()
|
||||||
->searchable(),
|
->searchable(),
|
||||||
Tables\Columns\TextColumn::make('block.name')
|
|
||||||
->label('Block')
|
|
||||||
->alignCenter()
|
|
||||||
->sortable()
|
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('name')
|
Tables\Columns\TextColumn::make('name')
|
||||||
->label('Line')
|
->label('Line')
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use AlperenErsoy\FilamentExport\Actions\FilamentExportBulkAction;
|
|||||||
use App\Filament\Exports\ProductionPlanExporter;
|
use App\Filament\Exports\ProductionPlanExporter;
|
||||||
use App\Filament\Imports\ProductionPlanImporter;
|
use App\Filament\Imports\ProductionPlanImporter;
|
||||||
use App\Filament\Resources\ProductionPlanResource\Pages;
|
use App\Filament\Resources\ProductionPlanResource\Pages;
|
||||||
|
use App\Filament\Resources\ProductionPlanResource\RelationManagers;
|
||||||
use App\Models\Block;
|
use App\Models\Block;
|
||||||
use App\Models\Item;
|
|
||||||
use App\Models\Line;
|
use App\Models\Line;
|
||||||
use App\Models\Plant;
|
use App\Models\Plant;
|
||||||
use App\Models\ProductionPlan;
|
use App\Models\ProductionPlan;
|
||||||
@@ -16,18 +16,19 @@ use Carbon\Carbon;
|
|||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Forms;
|
use Filament\Forms;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
use Filament\Forms\Components\Section;
|
|
||||||
use Filament\Forms\Components\Select;
|
|
||||||
use Filament\Forms\Form;
|
use Filament\Forms\Form;
|
||||||
use Filament\Forms\Get;
|
use Filament\Forms\Get;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Tables;
|
use Filament\Tables;
|
||||||
use Filament\Tables\Actions\ExportAction;
|
|
||||||
use Filament\Tables\Actions\ImportAction;
|
use Filament\Tables\Actions\ImportAction;
|
||||||
use Filament\Tables\Filters\Filter;
|
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||||
|
use Filament\Forms\Components\Section;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Tables\Actions\ExportAction;
|
||||||
|
use Filament\Tables\Filters\Filter;
|
||||||
|
use Illuminate\Support\Facades\Request;
|
||||||
|
|
||||||
class ProductionPlanResource extends Resource
|
class ProductionPlanResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -54,8 +55,7 @@ class ProductionPlanResource extends Resource
|
|||||||
->reactive()
|
->reactive()
|
||||||
->options(function (callable $get) {
|
->options(function (callable $get) {
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
$userHas = Filament::auth()->user()->plant_id;
|
||||||
|
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
|
||||||
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
|
|
||||||
})
|
})
|
||||||
->default(function () {
|
->default(function () {
|
||||||
return optional(ProductionPlan::latest()->first())->plant_id;
|
return optional(ProductionPlan::latest()->first())->plant_id;
|
||||||
@@ -67,9 +67,10 @@ class ProductionPlanResource extends Resource
|
|||||||
$set('block_name', null);
|
$set('block_name', null);
|
||||||
if (!$plantId) {
|
if (!$plantId) {
|
||||||
$set('ppPlantError', 'Please select a plant first.');
|
$set('ppPlantError', 'Please select a plant first.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('ppPlantError', null);
|
$set('ppPlantError', null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -78,6 +79,102 @@ class ProductionPlanResource extends Resource
|
|||||||
])
|
])
|
||||||
->hint(fn ($get) => $get('ppPlantError') ? $get('ppPlantError') : null)
|
->hint(fn ($get) => $get('ppPlantError') ? $get('ppPlantError') : null)
|
||||||
->hintColor('danger'),
|
->hintColor('danger'),
|
||||||
|
Forms\Components\Select::make('block_name')
|
||||||
|
->required()
|
||||||
|
// ->nullable()
|
||||||
|
->label('Block')
|
||||||
|
->options(function (callable $get) {
|
||||||
|
if (!$get('plant_id')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Block::where('plant_id', $get('plant_id'))
|
||||||
|
->pluck('name', 'id')
|
||||||
|
->toArray();
|
||||||
|
})
|
||||||
|
->reactive()
|
||||||
|
->default(function () {
|
||||||
|
$latestShiftId = optional(ProductionPlan::latest()->first())->shift_id;
|
||||||
|
return optional(Shift::where('id', $latestShiftId)->first())->block_id;
|
||||||
|
})
|
||||||
|
//->afterStateUpdated(fn ($set) => $set('shift_id', null))
|
||||||
|
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
||||||
|
if($get('id'))
|
||||||
|
{
|
||||||
|
$getShift = ProductionPlan::where('id', $get('id'))->first();
|
||||||
|
$getBlock = Shift::where('id', $getShift->shift_id)->first();
|
||||||
|
if($getBlock->block_id)
|
||||||
|
{
|
||||||
|
$set('block_name', $getBlock->block_id);
|
||||||
|
$set('ppBlockError', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockId = $get('block_name');
|
||||||
|
$set('shift_id', null);
|
||||||
|
|
||||||
|
if (!$blockId) {
|
||||||
|
$set('ppBlockError', 'Please select a block first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$set('ppBlockError', null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->extraAttributes(fn ($get) => [
|
||||||
|
'class' => $get('ppBlockError') ? 'border-red-500' : '',
|
||||||
|
])
|
||||||
|
->hint(fn ($get) => $get('ppBlockError') ? $get('ppBlockError') : null)
|
||||||
|
->hintColor('danger'),
|
||||||
|
Forms\Components\Select::make('shift_id')
|
||||||
|
->relationship('shift', 'name')
|
||||||
|
->required()
|
||||||
|
// ->nullable()
|
||||||
|
->autofocus(true)
|
||||||
|
->options(function (callable $get) {
|
||||||
|
if (!$get('plant_id') || !$get('block_name')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Shift::where('plant_id', $get('plant_id'))
|
||||||
|
->where('block_id', $get('block_name'))
|
||||||
|
->pluck('name', 'id')
|
||||||
|
->toArray();
|
||||||
|
})
|
||||||
|
->reactive()
|
||||||
|
->default(function () {
|
||||||
|
return optional(ProductionPlan::latest()->first())->shift_id;
|
||||||
|
})
|
||||||
|
// ->afterStateUpdated(fn ($set) => $set('line_id', null))
|
||||||
|
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
||||||
|
if($get('id'))
|
||||||
|
{
|
||||||
|
$getShift = ProductionPlan::where('id', $get('id'))->first();
|
||||||
|
if($getShift->shift_id)
|
||||||
|
{
|
||||||
|
$set('shift_id', $getShift->shift_id);
|
||||||
|
$set('ppShiftError', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$curShiftId = $get('shift_id');
|
||||||
|
$set('line_id', null);
|
||||||
|
|
||||||
|
if (!$curShiftId) {
|
||||||
|
$set('ppShiftError', 'Please select a shift first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$set('ppShiftError', null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->extraAttributes(fn ($get) => [
|
||||||
|
'class' => $get('ppShiftError') ? 'border-red-500' : '',
|
||||||
|
])
|
||||||
|
->hint(fn ($get) => $get('ppShiftError') ? $get('ppShiftError') : null)
|
||||||
|
->hintColor('danger'),
|
||||||
Forms\Components\Select::make('line_id')
|
Forms\Components\Select::make('line_id')
|
||||||
->relationship('line', 'name')
|
->relationship('line', 'name')
|
||||||
->required()
|
->required()
|
||||||
@@ -88,7 +185,7 @@ class ProductionPlanResource extends Resource
|
|||||||
// ->toArray() // Convert collection to array
|
// ->toArray() // Convert collection to array
|
||||||
// )
|
// )
|
||||||
->options(function (callable $get) {
|
->options(function (callable $get) {
|
||||||
if (! $get('plant_id')) {
|
if (!$get('plant_id') || !$get('block_name') || !$get('shift_id')) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,54 +197,207 @@ class ProductionPlanResource extends Resource
|
|||||||
// ->default(function () {
|
// ->default(function () {
|
||||||
// return optional(ProductionPlan::latest()->first())->line_id;
|
// return optional(ProductionPlan::latest()->first())->line_id;
|
||||||
// })
|
// })
|
||||||
// ->afterStateUpdated(function ($state, callable $set, callable $get) {
|
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
||||||
// if ($get('id')) {
|
if($get('id'))
|
||||||
// $getShift = ProductionPlan::where('id', $get('id'))->first();
|
{
|
||||||
// if ($getShift->line_id) {
|
$getShift = ProductionPlan::where('id', $get('id'))->first();
|
||||||
// $set('line_id', $getShift->line_id);
|
if($getShift->line_id)
|
||||||
// $set('ppLineError', null);
|
{
|
||||||
// }
|
$set('line_id', $getShift->line_id);
|
||||||
// } else {
|
$set('ppLineError', null);
|
||||||
// $currentDT = Carbon::now()->toDateTimeString();
|
}
|
||||||
// $set('created_at', $currentDT);
|
}
|
||||||
// $set('update_date', null);
|
else
|
||||||
// }
|
{
|
||||||
|
$currentDT = Carbon::now()->toDateTimeString();
|
||||||
|
$set('created_at', $currentDT);
|
||||||
|
$set('update_date', null);
|
||||||
|
}
|
||||||
|
|
||||||
// $lineId = $get('line_id');
|
$lineId = $get('line_id');
|
||||||
// // $set('plan_quantity', null);
|
// $set('plan_quantity', null);
|
||||||
|
|
||||||
// if (! $lineId) {
|
if (!$lineId) {
|
||||||
// $set('ppLineError', 'Please select a line first.');
|
$set('ppLineError', 'Please select a line first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$isUpdate = !empty($get('id'));
|
||||||
|
if (!$isUpdate)
|
||||||
|
{
|
||||||
|
$exists = ProductionPlan::where('plant_id', $get('plant_id'))
|
||||||
|
->where('shift_id', $get('shift_id'))
|
||||||
|
->where('line_id', $get('line_id'))
|
||||||
|
->whereDate('created_at', today())
|
||||||
|
->latest()
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($exists)
|
||||||
|
{
|
||||||
|
$set('line_id', null);
|
||||||
|
$set('ppLineError', 'Production plan already updated.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$existShifts = ProductionPlan::where('plant_id', $get('plant_id'))
|
||||||
|
->where('shift_id', $get('shift_id'))
|
||||||
|
->where('line_id', $get('line_id'))
|
||||||
|
->whereDate('created_at', Carbon::yesterday())
|
||||||
|
->latest()
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($existShifts) //if ($existShifts->count() > 0)
|
||||||
|
{
|
||||||
|
//$currentDate = date('Y-m-d');
|
||||||
|
$yesterday = date('Y-m-d', strtotime('-1 days'));
|
||||||
|
|
||||||
|
$shiftId = Shift::where('id', $get('shift_id'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
||||||
|
$hRs = (int) $hRs;
|
||||||
|
// $miNs = (int) $miNs;-*/
|
||||||
|
|
||||||
|
$totalMinutes = $hRs * 60 + $miNs;
|
||||||
|
|
||||||
|
$from_dt = $yesterday . ' ' . $shiftId->start_time;
|
||||||
|
|
||||||
|
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
||||||
|
|
||||||
|
$currentDateTime = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Check if current date time is within the range
|
||||||
|
if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
||||||
|
//echo "Choosed a valid shift...";
|
||||||
|
|
||||||
|
$set('line_id', null);
|
||||||
|
$set('ppLineError', 'Production plan already updated.');
|
||||||
|
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$currentDate = date('Y-m-d');
|
||||||
|
|
||||||
|
$shiftId = Shift::where('id', $get('shift_id'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
||||||
|
$hRs = (int) $hRs;
|
||||||
|
// $miNs = (int) $miNs;-*/
|
||||||
|
|
||||||
|
$totalMinutes = $hRs * 60 + $miNs;
|
||||||
|
|
||||||
|
$from_dt = $currentDate . ' ' . $shiftId->start_time;
|
||||||
|
|
||||||
|
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
||||||
|
|
||||||
|
$currentDateTime = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Check if current date time is within the range
|
||||||
|
if (!($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
|
||||||
|
//echo "Choosed a valid shift...";
|
||||||
|
|
||||||
|
$set('line_id', null);
|
||||||
|
$set('ppLineError', 'Choosed a invalid shift.');
|
||||||
|
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$set('ppLineError', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//$currentDate = date('Y-m-d');
|
||||||
|
$yesterday = date('Y-m-d', strtotime('-1 days'));
|
||||||
|
|
||||||
|
$shiftId = Shift::where('id', $get('shift_id'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
||||||
|
$hRs = (int) $hRs;
|
||||||
|
// $miNs = (int) $miNs;-*/
|
||||||
|
|
||||||
|
$totalMinutes = $hRs * 60 + $miNs;
|
||||||
|
|
||||||
|
$from_dt = $yesterday . ' ' . $shiftId->start_time;
|
||||||
|
|
||||||
|
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
||||||
|
|
||||||
|
$currentDateTime = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Check if current date time is within the range
|
||||||
|
if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
||||||
|
//echo "Choosed a valid shift...";
|
||||||
|
|
||||||
|
// here i'm updating created as yesterday
|
||||||
|
$set('created_at', $from_dt);
|
||||||
|
$set('update_date', '1');
|
||||||
|
|
||||||
|
$set('ppLineError', null);
|
||||||
|
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$currentDate = date('Y-m-d');
|
||||||
|
|
||||||
|
$shiftId = Shift::where('id', $get('shift_id'))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
||||||
|
$hRs = (int) $hRs;
|
||||||
|
// $miNs = (int) $miNs;-*/
|
||||||
|
|
||||||
|
$totalMinutes = $hRs * 60 + $miNs;
|
||||||
|
|
||||||
|
$from_dt = $currentDate . ' ' . $shiftId->start_time;
|
||||||
|
|
||||||
|
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
||||||
|
|
||||||
|
$currentDateTime = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Check if current date time is within the range
|
||||||
|
if (!($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
|
||||||
|
//echo "Choosed a valid shift...";
|
||||||
|
|
||||||
|
$set('line_id', null);
|
||||||
|
$set('ppLineError', 'Choosed a invalid shift.');
|
||||||
|
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$set('ppLineError', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// $isUpdate = ! empty($get('id'));
|
|
||||||
// if (! $isUpdate) {
|
|
||||||
// $exists = ProductionPlan::where('plant_id', $get('plant_id'))
|
// $exists = ProductionPlan::where('plant_id', $get('plant_id'))
|
||||||
// ->where('shift_id', $get('shift_id'))
|
// //->where('shift_id', $get('shift_id'))
|
||||||
// ->where('line_id', $get('line_id'))
|
// ->where('line_id', $get('line_id'))
|
||||||
// ->whereDate('created_at', today())
|
// ->whereDate('created_at', today())
|
||||||
// ->latest()
|
// ->latest() // Orders by created_at DESC
|
||||||
// ->exists();
|
// ->first();
|
||||||
|
|
||||||
// if ($exists) {
|
// if ($exists)
|
||||||
// $set('line_id', null);
|
// {
|
||||||
// $set('ppLineError', 'Production plan already updated.');
|
// $existingShifts = ProductionPlan::where('plant_id', $get('plant_id'))
|
||||||
|
// //->where('shift_id', $get('shift_id'))
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// $existShifts = ProductionPlan::where('plant_id', $get('plant_id'))
|
|
||||||
// ->where('shift_id', $get('shift_id'))
|
|
||||||
// ->where('line_id', $get('line_id'))
|
// ->where('line_id', $get('line_id'))
|
||||||
// ->whereDate('created_at', Carbon::yesterday())
|
// // ->whereDate('created_at', today())
|
||||||
// ->latest()
|
// ->whereDate('created_at', today())
|
||||||
// ->exists();
|
// ->get();
|
||||||
|
|
||||||
// if ($existShifts) { // if ($existShifts->count() > 0)
|
// foreach ($existingShifts as $shift) {
|
||||||
// // $currentDate = date('Y-m-d');
|
// $curShiftId = $shift->shift_id;
|
||||||
// $yesterday = date('Y-m-d', strtotime('-1 days'));
|
|
||||||
|
|
||||||
// $shiftId = Shift::where('id', $get('shift_id'))
|
// $currentDate = date('Y-m-d');
|
||||||
|
|
||||||
|
// $shiftId = \App\Models\Shift::where('id', $curShiftId)
|
||||||
// ->first();
|
// ->first();
|
||||||
|
|
||||||
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
||||||
@@ -156,7 +406,7 @@ class ProductionPlanResource extends Resource
|
|||||||
|
|
||||||
// $totalMinutes = $hRs * 60 + $miNs;
|
// $totalMinutes = $hRs * 60 + $miNs;
|
||||||
|
|
||||||
// $from_dt = $yesterday.' '.$shiftId->start_time;
|
// $from_dt = $currentDate . ' ' . $shiftId->start_time;
|
||||||
|
|
||||||
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
||||||
|
|
||||||
@@ -165,192 +415,30 @@ class ProductionPlanResource extends Resource
|
|||||||
// // Check if current date time is within the range
|
// // Check if current date time is within the range
|
||||||
// if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
// if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
||||||
// //echo "Choosed a valid shift...";
|
// //echo "Choosed a valid shift...";
|
||||||
|
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
||||||
|
|
||||||
// $set('line_id', null);
|
// $set('line_id', null);
|
||||||
// $set('ppLineError', 'Production plan already updated.');
|
// $set('ppLineError', 'Production plan already updated.');
|
||||||
|
|
||||||
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// $currentDate = date('Y-m-d');
|
|
||||||
|
|
||||||
// $shiftId = Shift::where('id', $get('shift_id'))
|
|
||||||
// ->first();
|
|
||||||
|
|
||||||
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
|
||||||
// $hRs = (int) $hRs;
|
|
||||||
// // $miNs = (int) $miNs;-*/
|
|
||||||
|
|
||||||
// $totalMinutes = $hRs * 60 + $miNs;
|
|
||||||
|
|
||||||
// $from_dt = $currentDate.' '.$shiftId->start_time;
|
|
||||||
|
|
||||||
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
|
|
||||||
|
|
||||||
// $currentDateTime = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// // Check if current date time is within the range
|
|
||||||
// if (! ($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
|
|
||||||
// // echo "Choosed a valid shift...";
|
|
||||||
|
|
||||||
// $set('line_id', null);
|
|
||||||
// $set('ppLineError', 'Choosed a invalid shift.');
|
|
||||||
|
|
||||||
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
// }
|
// // else {
|
||||||
|
// // $set('ppLineError', 'Choosed a invalid shift...');
|
||||||
// $set('ppLineError', null);
|
|
||||||
|
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// // $currentDate = date('Y-m-d');
|
|
||||||
// $yesterday = date('Y-m-d', strtotime('-1 days'));
|
|
||||||
|
|
||||||
// $shiftId = Shift::where('id', $get('shift_id'))
|
|
||||||
// ->first();
|
|
||||||
|
|
||||||
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
|
||||||
// $hRs = (int) $hRs;
|
|
||||||
// // $miNs = (int) $miNs;-*/
|
|
||||||
|
|
||||||
// $totalMinutes = $hRs * 60 + $miNs;
|
|
||||||
|
|
||||||
// $from_dt = $yesterday.' '.$shiftId->start_time;
|
|
||||||
|
|
||||||
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
|
|
||||||
|
|
||||||
// $currentDateTime = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// // Check if current date time is within the range
|
|
||||||
// if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
|
||||||
// // echo "Choosed a valid shift...";
|
|
||||||
|
|
||||||
// // here i'm updating created as yesterday
|
|
||||||
// $set('created_at', $from_dt);
|
|
||||||
// $set('update_date', '1');
|
|
||||||
|
|
||||||
// $set('ppLineError', null);
|
|
||||||
|
|
||||||
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
|
||||||
// return;
|
|
||||||
// } else {
|
|
||||||
// $currentDate = date('Y-m-d');
|
|
||||||
|
|
||||||
// $shiftId = Shift::where('id', $get('shift_id'))
|
|
||||||
// ->first();
|
|
||||||
|
|
||||||
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
|
||||||
// $hRs = (int) $hRs;
|
|
||||||
// // $miNs = (int) $miNs;-*/
|
|
||||||
|
|
||||||
// $totalMinutes = $hRs * 60 + $miNs;
|
|
||||||
|
|
||||||
// $from_dt = $currentDate.' '.$shiftId->start_time;
|
|
||||||
|
|
||||||
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
|
|
||||||
|
|
||||||
// $currentDateTime = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// // Check if current date time is within the range
|
|
||||||
// if (! ($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
|
|
||||||
// // echo "Choosed a valid shift...";
|
|
||||||
|
|
||||||
// $set('line_id', null);
|
|
||||||
// $set('ppLineError', 'Choosed a invalid shift.');
|
|
||||||
|
|
||||||
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $set('ppLineError', null);
|
|
||||||
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // $exists = ProductionPlan::where('plant_id', $get('plant_id'))
|
|
||||||
// // //->where('shift_id', $get('shift_id'))
|
|
||||||
// // ->where('line_id', $get('line_id'))
|
|
||||||
// // ->whereDate('created_at', today())
|
|
||||||
// // ->latest() // Orders by created_at DESC
|
|
||||||
// // ->first();
|
|
||||||
|
|
||||||
// // if ($exists)
|
|
||||||
// // {
|
|
||||||
// // $existingShifts = ProductionPlan::where('plant_id', $get('plant_id'))
|
|
||||||
// // //->where('shift_id', $get('shift_id'))
|
|
||||||
// // ->where('line_id', $get('line_id'))
|
|
||||||
// // // ->whereDate('created_at', today())
|
|
||||||
// // ->whereDate('created_at', today())
|
|
||||||
// // ->get();
|
|
||||||
|
|
||||||
// // foreach ($existingShifts as $shift) {
|
|
||||||
// // $curShiftId = $shift->shift_id;
|
|
||||||
|
|
||||||
// // $currentDate = date('Y-m-d');
|
|
||||||
|
|
||||||
// // $shiftId = \App\Models\Shift::where('id', $curShiftId)
|
|
||||||
// // ->first();
|
|
||||||
|
|
||||||
// // [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
|
|
||||||
// // $hRs = (int) $hRs;
|
|
||||||
// // // $miNs = (int) $miNs;-*/
|
|
||||||
|
|
||||||
// // $totalMinutes = $hRs * 60 + $miNs;
|
|
||||||
|
|
||||||
// // $from_dt = $currentDate . ' ' . $shiftId->start_time;
|
|
||||||
|
|
||||||
// // $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
|
|
||||||
|
|
||||||
// // $currentDateTime = date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// // // Check if current date time is within the range
|
|
||||||
// // if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
|
|
||||||
// // //echo "Choosed a valid shift...";
|
|
||||||
// // // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
|
|
||||||
|
|
||||||
// // $set('line_id', null);
|
|
||||||
// // $set('ppLineError', 'Production plan already updated.');
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
// // // else {
|
|
||||||
// // // $set('ppLineError', 'Choosed a invalid shift...');
|
|
||||||
// // // return;
|
|
||||||
// // // }
|
|
||||||
// // }
|
|
||||||
// // $set('ppLineError', null);
|
|
||||||
// // return;
|
// // return;
|
||||||
// // }
|
// // }
|
||||||
// }
|
// }
|
||||||
// }
|
|
||||||
// $set('ppLineError', null);
|
// $set('ppLineError', null);
|
||||||
|
// return;
|
||||||
// }
|
// }
|
||||||
// })
|
}
|
||||||
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
}
|
||||||
$set('item_id', null);
|
$set('ppLineError', null);
|
||||||
$set('plan_quantity', null);
|
}
|
||||||
})
|
})
|
||||||
->extraAttributes(fn ($get) => [
|
->extraAttributes(fn ($get) => [
|
||||||
'class' => $get('ppLineError') ? 'border-red-500' : '',
|
'class' => $get('ppLineError') ? 'border-red-500' : '',
|
||||||
])
|
])
|
||||||
->hint(fn ($get) => $get('ppLineError') ? $get('ppLineError') : null)
|
->hint(fn ($get) => $get('ppLineError') ? $get('ppLineError') : null)
|
||||||
->hintColor('danger'),
|
->hintColor('danger'),
|
||||||
Forms\Components\Select::make('item_id')
|
|
||||||
->label('Item')
|
|
||||||
->reactive()
|
|
||||||
->searchable()
|
|
||||||
->required()
|
|
||||||
->options(function (callable $get) {
|
|
||||||
if (! $get('plant_id')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Item::where('plant_id', $get('plant_id'))
|
|
||||||
->pluck('code', 'id')
|
|
||||||
->toArray();
|
|
||||||
}),
|
|
||||||
Forms\Components\TextInput::make('plan_quantity')
|
Forms\Components\TextInput::make('plan_quantity')
|
||||||
->required()
|
->required()
|
||||||
->integer()
|
->integer()
|
||||||
@@ -361,8 +449,10 @@ class ProductionPlanResource extends Resource
|
|||||||
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
||||||
$planQuan = $get('plan_quantity');
|
$planQuan = $get('plan_quantity');
|
||||||
|
|
||||||
if (! $get('update_date')) {
|
if(!$get('update_date') )
|
||||||
if (! $get('id')) {
|
{
|
||||||
|
if(!$get('id'))
|
||||||
|
{
|
||||||
$currentDT = Carbon::now()->toDateTimeString();
|
$currentDT = Carbon::now()->toDateTimeString();
|
||||||
$set('created_at', $currentDT);
|
$set('created_at', $currentDT);
|
||||||
}
|
}
|
||||||
@@ -370,9 +460,10 @@ class ProductionPlanResource extends Resource
|
|||||||
|
|
||||||
if (!$planQuan) {
|
if (!$planQuan) {
|
||||||
$set('ppPlanQuanError', 'Scan the valid plan quantity.');
|
$set('ppPlanQuanError', 'Scan the valid plan quantity.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$set('ppPlanQuanError', null);
|
$set('ppPlanQuanError', null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -381,19 +472,29 @@ class ProductionPlanResource extends Resource
|
|||||||
])
|
])
|
||||||
->hint(fn ($get) => $get('ppPlanQuanError') ? $get('ppPlanQuanError') : null)
|
->hint(fn ($get) => $get('ppPlanQuanError') ? $get('ppPlanQuanError') : null)
|
||||||
->hintColor('danger'),
|
->hintColor('danger'),
|
||||||
// Forms\Components\TextInput::make('production_quantity')
|
Forms\Components\TextInput::make('production_quantity')
|
||||||
// ->required()
|
->required()
|
||||||
// ->integer()
|
->integer()
|
||||||
// ->label('Production Quantity')
|
->label('Production Quantity')
|
||||||
// ->readOnly(fn (callable $get) => ! $get('id'))
|
->readOnly(fn (callable $get) => !$get('id'))
|
||||||
// ->default(0),
|
->default(0),
|
||||||
Forms\Components\TextInput::make('id')
|
Forms\Components\TextInput::make('id')
|
||||||
->hidden()
|
->hidden()
|
||||||
->readOnly(),
|
->readOnly(),
|
||||||
|
Forms\Components\TextInput::make('update_date')
|
||||||
|
->hidden()
|
||||||
|
->reactive()
|
||||||
|
->readOnly(),
|
||||||
|
Forms\Components\DateTimePicker::make('created_at')
|
||||||
|
->label('Created DateTime')
|
||||||
|
->hidden()
|
||||||
|
->reactive()
|
||||||
|
->required()
|
||||||
|
->readOnly(),
|
||||||
Forms\Components\Hidden::make('operator_id')
|
Forms\Components\Hidden::make('operator_id')
|
||||||
->default(Filament::auth()->user()->name),
|
->default(Filament::auth()->user()->name),
|
||||||
])
|
])
|
||||||
->columns(4),
|
->columns(2),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,55 +529,34 @@ class ProductionPlanResource extends Resource
|
|||||||
$paginator = $livewire->getTableRecords();
|
$paginator = $livewire->getTableRecords();
|
||||||
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
|
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
|
||||||
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
|
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
|
||||||
|
|
||||||
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
|
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
|
||||||
}),
|
}),
|
||||||
Tables\Columns\TextColumn::make('plant.name')
|
|
||||||
->label('Plant')
|
|
||||||
->alignCenter()
|
|
||||||
->sortable()
|
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('line.name')
|
|
||||||
->label('Plant')
|
|
||||||
->alignCenter()
|
|
||||||
->sortable()
|
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('item.code')
|
|
||||||
->label('Item')
|
|
||||||
->alignCenter()
|
|
||||||
->sortable()
|
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('plan_quantity')
|
Tables\Columns\TextColumn::make('plan_quantity')
|
||||||
->label('Plan Quantity')
|
->label('Plan Quantity')
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable()
|
->sortable(),
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('working_days')
|
|
||||||
->label('Working Days')
|
|
||||||
->alignCenter()
|
|
||||||
->numeric()
|
|
||||||
->sortable()
|
|
||||||
->searchable(),
|
|
||||||
Tables\Columns\TextColumn::make('production_quantity')
|
Tables\Columns\TextColumn::make('production_quantity')
|
||||||
->label('Production Quantity')
|
->label('Production Quantity')
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
->numeric()
|
->numeric()
|
||||||
->sortable()
|
->sortable(),
|
||||||
->searchable(),
|
Tables\Columns\TextColumn::make('line.name')
|
||||||
// Tables\Columns\TextColumn::make('line.name')
|
->label('Line')
|
||||||
// ->label('Line')
|
->alignCenter()
|
||||||
// ->alignCenter()
|
->sortable(),// ->searchable(),
|
||||||
// ->sortable(), // ->searchable(),
|
Tables\Columns\TextColumn::make('shift.block.name')
|
||||||
// Tables\Columns\TextColumn::make('shift.block.name')
|
->label('Block')
|
||||||
// ->label('Block')
|
->alignCenter()
|
||||||
// ->alignCenter()
|
->sortable(),
|
||||||
// ->sortable(),
|
Tables\Columns\TextColumn::make('shift.name')
|
||||||
// Tables\Columns\TextColumn::make('shift.name')
|
->label('Shift')
|
||||||
// ->label('Shift')
|
->alignCenter()
|
||||||
// ->alignCenter()
|
->sortable(),// ->searchable(),
|
||||||
// ->sortable(), // ->searchable(),
|
Tables\Columns\TextColumn::make('plant.name')
|
||||||
|
->label('Plant')
|
||||||
|
->alignCenter()
|
||||||
|
->sortable(),// ->searchable(),
|
||||||
Tables\Columns\TextColumn::make('created_at')
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
->label('Created At')
|
->label('Created At')
|
||||||
->dateTime()
|
->dateTime()
|
||||||
@@ -513,8 +593,7 @@ class ProductionPlanResource extends Resource
|
|||||||
// })
|
// })
|
||||||
->options(function (callable $get) {
|
->options(function (callable $get) {
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
$userHas = Filament::auth()->user()->plant_id;
|
||||||
|
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
|
||||||
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
|
|
||||||
})
|
})
|
||||||
->reactive()
|
->reactive()
|
||||||
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
->afterStateUpdated(function ($state, callable $set, callable $get) {
|
||||||
@@ -533,7 +612,6 @@ class ProductionPlanResource extends Resource
|
|||||||
if (!$plantId ) {
|
if (!$plantId ) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Line::where('plant_id', $plantId)
|
return Line::where('plant_id', $plantId)
|
||||||
->pluck('name', 'id');
|
->pluck('name', 'id');
|
||||||
})
|
})
|
||||||
@@ -549,7 +627,6 @@ class ProductionPlanResource extends Resource
|
|||||||
if (!$plantId ) {
|
if (!$plantId ) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Block::where('plant_id', $get('Plant'))->pluck('name', 'id');
|
return Block::where('plant_id', $get('Plant'))->pluck('name', 'id');
|
||||||
})
|
})
|
||||||
->reactive()
|
->reactive()
|
||||||
@@ -592,33 +669,25 @@ class ProductionPlanResource extends Resource
|
|||||||
return $query->whereRaw('1 = 0');
|
return $query->whereRaw('1 = 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($data['Plant'])) {// if ($plant = $data['Plant'] ?? null) {
|
if ($plant = $data['Plant'] ?? null) {
|
||||||
$query->where('plant_id', $data['Plant']);
|
$query->where('plant_id', $plant);
|
||||||
} else {
|
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
|
||||||
|
|
||||||
if ($userHas && strlen($userHas) > 0) {
|
|
||||||
return $query->whereRaw('1 = 0');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($data['Shift'])) {// if ($shift = $data['Shift'] ?? null) {
|
if ($shift = $data['Shift'] ?? null) {
|
||||||
$query->where('shift_id', $data['Shift']);
|
$query->where('shift_id', $shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($line = $data['Line'] ?? null) {
|
||||||
if (! empty($data['Line'])) {// if ($line = $data['Line'] ?? null) {
|
$query->where('line_id', $line);
|
||||||
$query->where('line_id', $data['Line']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($data['created_from'])) {// if ($from = $data['created_from'] ?? null) {
|
if ($from = $data['created_from'] ?? null) {
|
||||||
$query->where('created_at', '>=', $data['created_from']);
|
$query->where('created_at', '>=', $from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($data['created_to'])) {// if ($to = $data['created_to'] ?? null) {
|
if ($to = $data['created_to'] ?? null) {
|
||||||
$query->where('created_at', '<=', $data['created_to']);
|
$query->where('created_at', '<=', $to);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
})
|
})
|
||||||
->indicateUsing(function (array $data) {
|
->indicateUsing(function (array $data) {
|
||||||
@@ -626,12 +695,6 @@ class ProductionPlanResource extends Resource
|
|||||||
|
|
||||||
if (!empty($data['Plant'])) {
|
if (!empty($data['Plant'])) {
|
||||||
$indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name');
|
$indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name');
|
||||||
} else {
|
|
||||||
$userHas = Filament::auth()->user()->plant_id;
|
|
||||||
|
|
||||||
if ($userHas && strlen($userHas) > 0) {
|
|
||||||
return 'Plant: Choose plant to filter records.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($data['Shift'])) {
|
if (!empty($data['Shift'])) {
|
||||||
@@ -651,7 +714,7 @@ class ProductionPlanResource extends Resource
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $indicators;
|
return $indicators;
|
||||||
}),
|
})
|
||||||
])
|
])
|
||||||
->filtersFormMaxHeight('280px')
|
->filtersFormMaxHeight('280px')
|
||||||
->actions([
|
->actions([
|
||||||
@@ -663,7 +726,7 @@ class ProductionPlanResource extends Resource
|
|||||||
Tables\Actions\DeleteBulkAction::make(),
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
Tables\Actions\ForceDeleteBulkAction::make(),
|
Tables\Actions\ForceDeleteBulkAction::make(),
|
||||||
Tables\Actions\RestoreBulkAction::make(),
|
Tables\Actions\RestoreBulkAction::make(),
|
||||||
FilamentExportBulkAction::make('export'),
|
FilamentExportBulkAction::make('export')
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
->headerActions([
|
->headerActions([
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
|
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
|
||||||
|
|
||||||
use App\Filament\Resources\RfqTransporterBidResource;
|
use App\Filament\Resources\RfqTransporterBidResource;
|
||||||
use App\Models\RequestQuotation;
|
|
||||||
use App\Models\RfqTransporterBid;
|
use App\Models\RfqTransporterBid;
|
||||||
use App\Models\SpotRateTransportMaster;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\PushAlertNotification;
|
use App\Notifications\PushAlertNotification;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Resources\Pages\EditRecord;
|
use Filament\Resources\Pages\EditRecord;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class EditRfqTransporterBid extends EditRecord
|
class EditRfqTransporterBid extends EditRecord
|
||||||
{
|
{
|
||||||
@@ -20,147 +17,54 @@ class EditRfqTransporterBid extends EditRecord
|
|||||||
|
|
||||||
protected function afterSave(): void
|
protected function afterSave(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (! $this->record->wasChanged('total_freight_charge')) {
|
if (! $this->record->wasChanged('total_freight_charge')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dd($this->record);
|
||||||
|
|
||||||
$rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id)
|
$rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id)
|
||||||
->orderBy('total_freight_charge')
|
->orderBy('total_freight_charge')
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->search($this->record->id) + 1;
|
->search($this->record->id) + 1;
|
||||||
|
|
||||||
$requestQuotation = RequestQuotation::findOrFail(
|
$recipients = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->get();
|
||||||
$this->record->request_quotation_id
|
|
||||||
);
|
|
||||||
|
|
||||||
$spotRateId = $requestQuotation->spot_rate_transport_master_id;
|
|
||||||
|
|
||||||
$spotRate = SpotRateTransportMaster::findOrFail($spotRateId);
|
|
||||||
|
|
||||||
$userNames = $spotRate->user_name;
|
|
||||||
|
|
||||||
Log::info('User names from spot rate', [
|
|
||||||
'user_name_raw' => $spotRate->user_name,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!is_array($userNames)) {
|
|
||||||
Log::warning('user_name is not array, resetting', [
|
|
||||||
'user_name' => $userNames,
|
|
||||||
]);
|
|
||||||
$userNames = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$users = User::whereIn('name', $userNames)->get();
|
|
||||||
|
|
||||||
Log::info('Matched users', [
|
|
||||||
'count' => $users->count(),
|
|
||||||
'user_ids' => $users->pluck('id'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// $recipients = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->get();
|
|
||||||
|
|
||||||
// $recipients1 = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->whereHas('pushSubscriptions')->get();
|
|
||||||
|
|
||||||
$currentUser = Filament::auth()->user();
|
$currentUser = Filament::auth()->user();
|
||||||
|
|
||||||
// if ($currentUser && ! $recipients1->contains('id', $currentUser->id)) {
|
|
||||||
// $recipients1->push($currentUser);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if ($currentUser && ! $recipients->contains('id', $currentUser->id)) {
|
if ($currentUser && ! $recipients->contains('id', $currentUser->id)) {
|
||||||
// $recipients->push($currentUser);
|
$recipients->push($currentUser);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// $user1 = Filament::auth()->user();
|
// $user1 = Filament::auth()->user();
|
||||||
|
|
||||||
|
|
||||||
$rfqNumber = $this->record->requestQuotation->rfq_number;
|
$body = "{$currentUser->name} current rank is #{$rank}";
|
||||||
$body = "{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.";
|
|
||||||
|
|
||||||
// Notification::make()
|
|
||||||
// ->title('Rank Updated')
|
|
||||||
// ->body("{$currentUser->name} current rank is #{$rank}")
|
|
||||||
// ->success()
|
|
||||||
// ->sendToDatabase($recipients);
|
|
||||||
|
|
||||||
// \Log::info('Notification sent', [
|
|
||||||
// 'rank' => $rank,
|
|
||||||
// 'recipients' => $recipients->pluck('id'),
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title('Rank Updated')
|
->title('Rank Updated')
|
||||||
->body("{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.")
|
->body("{$currentUser->name} current rank is #{$rank}")
|
||||||
->success()
|
->success()
|
||||||
->sendToDatabase($users);
|
->sendToDatabase($recipients);
|
||||||
|
|
||||||
// foreach ($recipients1 as $user) {
|
\Log::info('Notification sent', [
|
||||||
// $user->notify(
|
'rank' => $rank,
|
||||||
// new PushAlertNotification(
|
'recipients' => $recipients->pluck('id'),
|
||||||
// 'Rank Updated',
|
|
||||||
// $body
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// foreach ($users as $user) {
|
|
||||||
|
|
||||||
// Log::info('Checking push subscription for user', [
|
|
||||||
// 'user_id' => $user->id,
|
|
||||||
// 'name' => $user->name,
|
|
||||||
// 'subscription_count' => $user->pushSubscriptions()->count(),
|
|
||||||
// ]);
|
|
||||||
// if ($user->pushSubscriptions()->exists()) {
|
|
||||||
|
|
||||||
// Log::info('Sending push notification', [
|
|
||||||
// 'user_id' => $user->id,
|
|
||||||
// ]);
|
|
||||||
// $user->notify(
|
|
||||||
// new PushAlertNotification(
|
|
||||||
// 'Rank Updated',
|
|
||||||
// $body
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// Log::warning('User has NO push subscription', [
|
|
||||||
// 'user_id' => $user->id,
|
|
||||||
// ]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($users as $user) {
|
|
||||||
|
|
||||||
$count = $user->pushSubscriptions()->count();
|
|
||||||
|
|
||||||
Log::info('Checking push subscription for user', [
|
|
||||||
'user_id' => $user->id,
|
|
||||||
'name' => $user->name,
|
|
||||||
'subscription_count' => $count,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($count == 0) {
|
foreach ($recipients as $user) {
|
||||||
Log::warning('User has NO push subscription', [
|
$user->notify(
|
||||||
'user_id' => $user->id,
|
new PushAlertNotification(
|
||||||
]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('Sending push notification', [
|
|
||||||
'user_id' => $user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ✅ THIS IS ALL YOU NEED
|
|
||||||
$user->notify(new PushAlertNotification(
|
|
||||||
'Rank Updated',
|
'Rank Updated',
|
||||||
$body
|
$body
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getHeaderActions(): array
|
protected function getHeaderActions(): array
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ use App\Filament\Resources\StickerValidationResource;
|
|||||||
use App\Models\Item;
|
use App\Models\Item;
|
||||||
use App\Models\ItemCharacteristic;
|
use App\Models\ItemCharacteristic;
|
||||||
use App\Models\ProductionQuantity;
|
use App\Models\ProductionQuantity;
|
||||||
use App\Models\StickerDetail;
|
|
||||||
use App\Models\StickerMappingMaster;
|
use App\Models\StickerMappingMaster;
|
||||||
use App\Models\StickerStructureDetail;
|
|
||||||
use App\Models\StickerValidation;
|
use App\Models\StickerValidation;
|
||||||
use App\Services\StickerPdfService;
|
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Facades\Filament;
|
use Filament\Facades\Filament;
|
||||||
use Filament\Notifications\Notification;
|
use Filament\Notifications\Notification;
|
||||||
@@ -95,7 +92,8 @@ class CreateStickerValidation extends CreateRecord
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
if (preg_match('/^([a-zA-Z0-9]{6,})\|([1-9][a-zA-Z0-9]{8,})\|?$/', $this->serNo, $matches)) {
|
||||||
$itemCode = $matches[1];
|
$itemCode = $matches[1];
|
||||||
$serialNumber = $matches[2];
|
$serialNumber = $matches[2];
|
||||||
|
|
||||||
@@ -197,6 +195,58 @@ class CreateStickerValidation extends CreateRecord
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$urls = [];
|
||||||
|
|
||||||
|
foreach ($stickers as $sticker) {
|
||||||
|
$urls[] = route('stickers1.pdf', [
|
||||||
|
'stickerId' => $sticker['sticker_id'],
|
||||||
|
'plant_id' => $this->plantId,
|
||||||
|
'item_characteristic_id' => $sticker['item_characteristic'],
|
||||||
|
'serial_number' => $serialNumber,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dispatch('open-stickers-sequence', urls: $urls);
|
||||||
|
|
||||||
|
// $pdfPath = storage_path('app/private/uploads/StickerTemplateOcr/multi.pdf');
|
||||||
|
|
||||||
|
// if (! file_exists($pdfPath)) {
|
||||||
|
// Notification::make()
|
||||||
|
// ->danger()
|
||||||
|
// ->title('Pdf Not Found')
|
||||||
|
// ->body("pdf file not exist.")
|
||||||
|
// ->send();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (! $printerName) {
|
||||||
|
// Notification::make()
|
||||||
|
// ->danger()
|
||||||
|
// ->title('Printer Not Found')
|
||||||
|
// ->body("No CUPS printer configured for IP: $iotsPrintIp")
|
||||||
|
// ->send();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// putenv('CUPS_SERVER=printer.iotsignin.com');
|
||||||
|
|
||||||
|
// $cmd = "lp -d " . escapeshellarg($printerName)
|
||||||
|
// . " -o fit-to-page "
|
||||||
|
// . escapeshellarg($pdfPath);
|
||||||
|
|
||||||
|
// exec($cmd, $out, $status);
|
||||||
|
|
||||||
|
// if ($status != 0) {
|
||||||
|
// Notification::make()
|
||||||
|
// ->danger()
|
||||||
|
// ->title('Print Failed')
|
||||||
|
// ->body('CUPS print command failed.')
|
||||||
|
// ->send();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
//dd($iotsPrintIp, $matchedSticker);
|
||||||
|
|
||||||
StickerValidation::create([
|
StickerValidation::create([
|
||||||
'plant_id' => $this->plantId,
|
'plant_id' => $this->plantId,
|
||||||
'machine_id' => $this->workCenter,
|
'machine_id' => $this->workCenter,
|
||||||
@@ -225,192 +275,24 @@ class CreateStickerValidation extends CreateRecord
|
|||||||
|
|
||||||
$this->dispatch('refreshEmptySticker', $plantId, $this->ref_number);
|
$this->dispatch('refreshEmptySticker', $plantId, $this->ref_number);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// foreach ($stickers as $sticker) {
|
}
|
||||||
|
|
||||||
// // $printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']);
|
private function getPrinterNameByIp(string $ip): ?string
|
||||||
|
|
||||||
// \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();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /** STEP 3: Stream PDF to CUPS (STDIN) */
|
|
||||||
// $process = proc_open(
|
|
||||||
// 'lp -d ' . escapeshellarg($printerName) . ' -o fit-to-page -',
|
|
||||||
// [
|
|
||||||
// ['pipe', 'r'], // STDIN
|
|
||||||
// ['pipe', 'w'], // STDOUT
|
|
||||||
// ['pipe', 'w'], // STDERR
|
|
||||||
// ],
|
|
||||||
// $pipes
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
|
||||||
// if (! is_resource($process)) {
|
|
||||||
// Notification::make()
|
|
||||||
// ->danger()
|
|
||||||
// ->title('Print Failed')
|
|
||||||
// ->body('Unable to start CUPS print process.')
|
|
||||||
// ->send();
|
|
||||||
// return;
|
|
||||||
// // continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $pdfContent = (new StickerPdfService())->generatePdf1(
|
|
||||||
// $structure->sticker_id,
|
|
||||||
// $dynamicElements,
|
|
||||||
// $itemCharacteristic,
|
|
||||||
// $serialNumber,
|
|
||||||
// $serNo
|
|
||||||
// );
|
|
||||||
|
|
||||||
// fwrite($pipes[0], $pdfContent);
|
|
||||||
// fclose($pipes[0]);
|
|
||||||
|
|
||||||
// $stderr = stream_get_contents($pipes[2]);
|
|
||||||
// fclose($pipes[1]);
|
|
||||||
// fclose($pipes[2]);
|
|
||||||
|
|
||||||
// $status = proc_close($process);
|
|
||||||
|
|
||||||
// if ($status != 0) {
|
|
||||||
// Notification::make()
|
|
||||||
// ->danger()
|
|
||||||
// ->title('Print Failed')
|
|
||||||
// ->body("CUPS error: {$stderr}")
|
|
||||||
// ->send();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($stickers as $sticker)
|
|
||||||
{
|
{
|
||||||
|
exec('lpstat -v', $output);
|
||||||
\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);
|
|
||||||
|
|
||||||
exec('lpstat -h cups:631 -v 2>&1', $output, $status);
|
|
||||||
|
|
||||||
if ($status != 0 || empty($output)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($output as $line) {
|
foreach ($output as $line) {
|
||||||
$parts = explode(':', $line, 2);
|
// Example:
|
||||||
if (count($parts) < 2) continue;
|
// device for TSC_WC_01: socket://192.168.1.50:9100
|
||||||
|
if (str_contains($line, $ip)) {
|
||||||
$printerName = trim(str_replace('device for', '', $parts[0]));
|
preg_match('/device for (.+?):/', $line, $matches);
|
||||||
$deviceUri = trim($parts[1]);
|
return $matches[1] ?? null;
|
||||||
|
|
||||||
if (str_contains($deviceUri, $ip)) {
|
|
||||||
return $printerName;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,482 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use App\Services\ChatbotService;
|
|
||||||
use App\Services\GeminiChatbotService;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ChatBot extends Component
|
|
||||||
{
|
|
||||||
// ── Panel state ───────────────────────────────────────────────────────────
|
|
||||||
public bool $isOpen = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 'select' → mode-picker screen shown first
|
|
||||||
* 'basic' → structured query UI (production or invoice report)
|
|
||||||
* 'advanced' → free-text natural-language UI (Gemini-powered)
|
|
||||||
*/
|
|
||||||
public string $mode = 'select';
|
|
||||||
|
|
||||||
// ── Basic mode — shared ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Which report the user picked inside basic mode.
|
|
||||||
* '' → report-type picker shown
|
|
||||||
* 'production' → production form
|
|
||||||
* 'invoice' → invoice type-lookup form
|
|
||||||
* 'invoice_status' → invoice scan-status form
|
|
||||||
*/
|
|
||||||
public string $reportType = '';
|
|
||||||
|
|
||||||
public array $plants = [];
|
|
||||||
|
|
||||||
// ── Basic mode — Production report ───────────────────────────────────────
|
|
||||||
public string $result = '';
|
|
||||||
public bool $hasResult = false;
|
|
||||||
|
|
||||||
public ?int $selectedPlantId = null;
|
|
||||||
public ?int $selectedLineId = null;
|
|
||||||
public string $dateFrom = '';
|
|
||||||
public string $dateTo = '';
|
|
||||||
public array $lines = [];
|
|
||||||
|
|
||||||
// ── Basic mode — Invoice report (type lookup) ─────────────────────────────
|
|
||||||
public ?int $invoicePlantId = null;
|
|
||||||
public string $invoiceItemCode = '';
|
|
||||||
public string $invoiceResult = '';
|
|
||||||
public bool $hasInvoiceResult = false;
|
|
||||||
|
|
||||||
// ── Basic mode — Invoice status (scan status) ─────────────────────────────
|
|
||||||
public string $invoiceNumber = '';
|
|
||||||
public string $invoiceStatusResult = ''; // kept for simple error strings
|
|
||||||
public bool $hasInvoiceStatusResult = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structured result from ChatbotService::getInvoiceData().
|
|
||||||
* Shape: type, message, invoice_number, total, scanned, not_scanned, unscanned_serials[]
|
|
||||||
*/
|
|
||||||
public array $invoiceStatusData = [];
|
|
||||||
|
|
||||||
/** Controls whether all unscanned serials are shown (vs the first 10). */
|
|
||||||
public bool $showAllUnscanned = false;
|
|
||||||
|
|
||||||
// ── Advanced mode ─────────────────────────────────────────────────────────
|
|
||||||
public string $advancedQuestion = '';
|
|
||||||
public string $advancedResult = '';
|
|
||||||
public bool $hasAdvancedResult = false;
|
|
||||||
public bool $isAdvancedLoading = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Conversation history shown in advanced mode.
|
|
||||||
* Each entry: ['role' => 'user'|'assistant', 'content' => '…']
|
|
||||||
*
|
|
||||||
* The full history is passed to GeminiChatbotService on every turn so
|
|
||||||
* Gemini can resolve follow-up messages (e.g. answering a clarification
|
|
||||||
* question) in context.
|
|
||||||
*/
|
|
||||||
public array $chatHistory = [];
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public function mount(): void
|
|
||||||
{
|
|
||||||
$this->plants = DB::table('plants')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name'])
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
$this->dateFrom = now()->startOfMonth()->format('Y-m-d');
|
|
||||||
$this->dateTo = now()->format('Y-m-d');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Mode switching ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public function setMode(string $mode): void
|
|
||||||
{
|
|
||||||
$this->mode = $mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setReportType(string $type): void
|
|
||||||
{
|
|
||||||
$this->reportType = $type;
|
|
||||||
|
|
||||||
// Clear previous results when switching report type
|
|
||||||
$this->result = '';
|
|
||||||
$this->hasResult = false;
|
|
||||||
$this->invoiceResult = '';
|
|
||||||
$this->hasInvoiceResult = false;
|
|
||||||
$this->invoiceStatusResult = '';
|
|
||||||
$this->hasInvoiceStatusResult = false;
|
|
||||||
$this->invoiceStatusData = [];
|
|
||||||
$this->showAllUnscanned = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Basic mode — Production helpers ──────────────────────────────────────
|
|
||||||
|
|
||||||
public function updatedSelectedPlantId(): void
|
|
||||||
{
|
|
||||||
$this->selectedLineId = null;
|
|
||||||
$this->lines = [];
|
|
||||||
$this->result = '';
|
|
||||||
$this->hasResult = false;
|
|
||||||
|
|
||||||
if ($this->selectedPlantId) {
|
|
||||||
$this->lines = DB::table('lines')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->where('plant_id', $this->selectedPlantId)
|
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name'])
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedSelectedLineId(): void
|
|
||||||
{
|
|
||||||
$this->result = '';
|
|
||||||
$this->hasResult = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fetchProduction(): void
|
|
||||||
{
|
|
||||||
if (! $this->selectedPlantId) {
|
|
||||||
$this->result = 'Please select a plant.';
|
|
||||||
$this->hasResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = DB::table('production_quantities')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->where('plant_id', $this->selectedPlantId)
|
|
||||||
->whereDate('created_at', '>=', $this->dateFrom)
|
|
||||||
->whereDate('created_at', '<=', $this->dateTo);
|
|
||||||
|
|
||||||
if ($this->selectedLineId) {
|
|
||||||
$query->where('line_id', $this->selectedLineId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$count = $query->count();
|
|
||||||
|
|
||||||
$plantName = collect($this->plants)
|
|
||||||
->firstWhere('id', $this->selectedPlantId)?->name ?? 'Unknown Plant';
|
|
||||||
|
|
||||||
$lineName = $this->selectedLineId
|
|
||||||
? (collect($this->lines)->firstWhere('id', $this->selectedLineId)?->name ?? 'Unknown Line')
|
|
||||||
: 'All Lines';
|
|
||||||
|
|
||||||
$from = \Carbon\Carbon::parse($this->dateFrom)->format('d M Y');
|
|
||||||
$to = \Carbon\Carbon::parse($this->dateTo)->format('d M Y');
|
|
||||||
|
|
||||||
$this->result = "Production count for {$plantName} / {$lineName} from {$from} to {$to}: {$count} records.";
|
|
||||||
$this->hasResult = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Basic mode — Invoice report (type lookup) ─────────────────────────────
|
|
||||||
|
|
||||||
public function updatedInvoicePlantId(): void
|
|
||||||
{
|
|
||||||
$this->invoiceResult = '';
|
|
||||||
$this->hasInvoiceResult = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedInvoiceItemCode(): void
|
|
||||||
{
|
|
||||||
$this->invoiceResult = '';
|
|
||||||
$this->hasInvoiceResult = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fetchInvoiceReport(): void
|
|
||||||
{
|
|
||||||
if (! $this->invoicePlantId) {
|
|
||||||
$this->invoiceResult = 'Please select a plant.';
|
|
||||||
$this->hasInvoiceResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$itemCode = trim($this->invoiceItemCode);
|
|
||||||
|
|
||||||
if ($itemCode === '') {
|
|
||||||
$this->invoiceResult = 'Please enter an item code.';
|
|
||||||
$this->hasInvoiceResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$plantName = collect($this->plants)
|
|
||||||
->firstWhere('id', $this->invoicePlantId)?->name ?? 'Unknown Plant';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rows = DB::select("
|
|
||||||
WITH plant_item AS (
|
|
||||||
SELECT ? AS user_plant,
|
|
||||||
? AS user_item_code
|
|
||||||
),
|
|
||||||
t1 AS (
|
|
||||||
SELECT
|
|
||||||
plants.id AS plant_id,
|
|
||||||
plants.name AS plant_name,
|
|
||||||
ARRAY_AGG(items.code) AS item_codes
|
|
||||||
FROM plants
|
|
||||||
LEFT JOIN items ON plants.id = items.plant_id
|
|
||||||
GROUP BY plants.id, plants.name
|
|
||||||
),
|
|
||||||
t2 AS (
|
|
||||||
SELECT
|
|
||||||
t1.plant_id,
|
|
||||||
t1.plant_name,
|
|
||||||
CASE
|
|
||||||
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END AS exists_flag
|
|
||||||
FROM t1
|
|
||||||
CROSS JOIN plant_item
|
|
||||||
WHERE t1.plant_name = plant_item.user_plant
|
|
||||||
),
|
|
||||||
t3 AS (
|
|
||||||
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
|
|
||||||
plant_item.user_item_code
|
|
||||||
FROM t2
|
|
||||||
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
|
|
||||||
),
|
|
||||||
t4 AS (
|
|
||||||
SELECT items.id AS item_id,
|
|
||||||
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
|
|
||||||
FROM t3
|
|
||||||
LEFT JOIN items
|
|
||||||
ON t3.plant_id = items.plant_id
|
|
||||||
AND t3.user_item_code = items.code
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
t4.item_id,
|
|
||||||
t4.plant_id,
|
|
||||||
t4.plant_name,
|
|
||||||
t4.exists_flag,
|
|
||||||
t4.user_item_code,
|
|
||||||
COALESCE(sticker_masters.material_type, 0) AS material_type,
|
|
||||||
CASE
|
|
||||||
WHEN sticker_masters.item_id IS NULL
|
|
||||||
THEN 'no match found'
|
|
||||||
WHEN COALESCE(sticker_masters.material_type, 0) = 0
|
|
||||||
THEN 'serial invoice'
|
|
||||||
ELSE 'material invoice'
|
|
||||||
END AS invoice_description
|
|
||||||
FROM t4
|
|
||||||
LEFT JOIN sticker_masters
|
|
||||||
ON sticker_masters.plant_id = t4.plant_id
|
|
||||||
AND sticker_masters.item_id = t4.item_id
|
|
||||||
", [$plantName, $itemCode]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('ChatBot: invoice report query failed', [
|
|
||||||
'plant' => $plantName,
|
|
||||||
'item_code' => $itemCode,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->invoiceResult = "Sorry, I couldn't fetch data. Please try again or contact support.";
|
|
||||||
$this->hasInvoiceResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($rows)) {
|
|
||||||
$this->invoiceResult = "No data found for plant \"{$plantName}\". Please verify the plant selection.";
|
|
||||||
$this->hasInvoiceResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = $rows[0];
|
|
||||||
|
|
||||||
if ((int) $row->exists_flag === 0) {
|
|
||||||
$this->invoiceResult = 'Provided item code does not exist in the item table.';
|
|
||||||
} else {
|
|
||||||
switch ($row->invoice_description) {
|
|
||||||
case 'no match found':
|
|
||||||
$this->invoiceResult = "Item not found in sticker master for the plant {$row->plant_name}.";
|
|
||||||
break;
|
|
||||||
case 'serial invoice':
|
|
||||||
$this->invoiceResult = 'It is a serial invoice item.';
|
|
||||||
break;
|
|
||||||
case 'material invoice':
|
|
||||||
$this->invoiceResult = 'It is a material invoice item.';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$this->invoiceResult = 'Unexpected result. Please contact support.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->hasInvoiceResult = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Basic mode — Invoice status (scan status) ─────────────────────────────
|
|
||||||
|
|
||||||
public function updatedInvoiceNumber(): void
|
|
||||||
{
|
|
||||||
$this->invoiceStatusResult = '';
|
|
||||||
$this->hasInvoiceStatusResult = false;
|
|
||||||
$this->invoiceStatusData = [];
|
|
||||||
$this->showAllUnscanned = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up how many serials within an invoice have been scanned / not scanned.
|
|
||||||
* Stores structured data in $invoiceStatusData so the blade can render
|
|
||||||
* a "show more" serial-number list without dumping 70+ serials in one blob.
|
|
||||||
*/
|
|
||||||
public function fetchInvoiceStatus(): void
|
|
||||||
{
|
|
||||||
$invoiceNumber = trim(preg_replace('/\s+/', '', $this->invoiceNumber));
|
|
||||||
|
|
||||||
if (empty($invoiceNumber)) {
|
|
||||||
$this->invoiceStatusResult = 'Please enter a valid invoice number.';
|
|
||||||
$this->invoiceStatusData = [];
|
|
||||||
$this->showAllUnscanned = false;
|
|
||||||
$this->hasInvoiceStatusResult = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/** @var \App\Services\ChatbotService $service */
|
|
||||||
$service = app(\App\Services\ChatbotService::class);
|
|
||||||
$data = $service->getInvoiceData($invoiceNumber);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
Log::error('ChatBot: invoice status fetch failed', [
|
|
||||||
'invoice' => $invoiceNumber,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
$data = [
|
|
||||||
'type' => 'error',
|
|
||||||
'message' => "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. "
|
|
||||||
. 'Please try again or contact support.',
|
|
||||||
'invoice_number' => $invoiceNumber,
|
|
||||||
'total' => 0,
|
|
||||||
'scanned' => 0,
|
|
||||||
'not_scanned' => 0,
|
|
||||||
'unscanned_serials' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->invoiceStatusData = $data;
|
|
||||||
$this->invoiceStatusResult = $data['message']; // fallback plain-text copy
|
|
||||||
$this->showAllUnscanned = false;
|
|
||||||
$this->hasInvoiceStatusResult = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the "show all / show less" state for unscanned serial numbers
|
|
||||||
* in the Basic → Invoice Status result card.
|
|
||||||
*/
|
|
||||||
public function toggleShowAllUnscanned(): void
|
|
||||||
{
|
|
||||||
$this->showAllUnscanned = ! $this->showAllUnscanned;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Advanced mode (Gemini-powered) ────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a free-text user message in advanced mode.
|
|
||||||
*
|
|
||||||
* Steps:
|
|
||||||
* 1. Appends the user message to chatHistory immediately (UI feedback).
|
|
||||||
* 2. Calls GeminiChatbotService with the full prior history for context.
|
|
||||||
* 3. Gemini classifies the intent, extracts params, and either:
|
|
||||||
* a) runs the appropriate DB query and returns the result, or
|
|
||||||
* b) returns a clarification question if intent is ambiguous.
|
|
||||||
* 4. Appends the assistant reply to chatHistory.
|
|
||||||
*/
|
|
||||||
public function askAdvanced(): void
|
|
||||||
{
|
|
||||||
$question = trim($this->advancedQuestion);
|
|
||||||
|
|
||||||
if (empty($question)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the user's message in the chat bubble immediately
|
|
||||||
$this->chatHistory[] = [
|
|
||||||
'role' => 'user',
|
|
||||||
'content' => $question,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->advancedQuestion = '';
|
|
||||||
$this->isAdvancedLoading = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
/** @var GeminiChatbotService $gemini */
|
|
||||||
$gemini = app(GeminiChatbotService::class);
|
|
||||||
|
|
||||||
// Pass history *without* the turn we just appended — the new user
|
|
||||||
// message is passed separately so Gemini sees it as the latest turn.
|
|
||||||
$priorHistory = array_slice($this->chatHistory, 0, -1);
|
|
||||||
$answer = $gemini->processMessage($priorHistory, $question);
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
Log::error('ChatBot: advanced ask failed', [
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString(),
|
|
||||||
]);
|
|
||||||
$answer = 'Sorry, something went wrong. Please try again.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->chatHistory[] = [
|
|
||||||
'role' => 'assistant',
|
|
||||||
'content' => $answer,
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->isAdvancedLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearAdvancedChat(): void
|
|
||||||
{
|
|
||||||
$this->chatHistory = [];
|
|
||||||
$this->advancedQuestion = '';
|
|
||||||
$this->isAdvancedLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Panel controls ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public function toggleChat(): void
|
|
||||||
{
|
|
||||||
$this->isOpen = ! $this->isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetForm(): void
|
|
||||||
{
|
|
||||||
// Basic mode — shared
|
|
||||||
$this->reportType = '';
|
|
||||||
|
|
||||||
// Basic mode — production
|
|
||||||
$this->selectedPlantId = null;
|
|
||||||
$this->selectedLineId = null;
|
|
||||||
$this->lines = [];
|
|
||||||
$this->result = '';
|
|
||||||
$this->hasResult = false;
|
|
||||||
$this->dateFrom = now()->startOfMonth()->format('Y-m-d');
|
|
||||||
$this->dateTo = now()->format('Y-m-d');
|
|
||||||
|
|
||||||
// Basic mode — invoice type lookup
|
|
||||||
$this->invoicePlantId = null;
|
|
||||||
$this->invoiceItemCode = '';
|
|
||||||
$this->invoiceResult = '';
|
|
||||||
$this->hasInvoiceResult = false;
|
|
||||||
|
|
||||||
// Basic mode — invoice scan status
|
|
||||||
$this->invoiceNumber = '';
|
|
||||||
$this->invoiceStatusResult = '';
|
|
||||||
$this->hasInvoiceStatusResult = false;
|
|
||||||
$this->invoiceStatusData = [];
|
|
||||||
$this->showAllUnscanned = false;
|
|
||||||
|
|
||||||
// Advanced mode
|
|
||||||
$this->clearAdvancedChat();
|
|
||||||
|
|
||||||
// Go back to mode selector
|
|
||||||
$this->mode = 'select';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.chat-bot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use App\Exports\ProductionPlanExport;
|
|
||||||
use App\Models\ProductionPlan;
|
|
||||||
use App\Models\ProductionQuantity;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use DB;
|
|
||||||
use Maatwebsite\Excel\Facades\Excel;
|
|
||||||
|
|
||||||
class ProductionTargetPlan extends Component
|
|
||||||
{
|
|
||||||
|
|
||||||
public $plantId, $lineId, $month, $year;
|
|
||||||
|
|
||||||
public $records = [];
|
|
||||||
|
|
||||||
public $dates = [];
|
|
||||||
|
|
||||||
public $leaveDates = [];
|
|
||||||
|
|
||||||
public $productionPlanDates = '';
|
|
||||||
|
|
||||||
|
|
||||||
protected $listeners = [
|
|
||||||
'loadData' => 'loadProductionData',
|
|
||||||
'loadData1' => 'exportProductionData',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function getMonthDates($month, $year)
|
|
||||||
{
|
|
||||||
$start = Carbon::createFromDate($year, $month, 1);
|
|
||||||
$days = $start->daysInMonth;
|
|
||||||
|
|
||||||
$dates = [];
|
|
||||||
|
|
||||||
for ($i = 1; $i <= $days; $i++) {
|
|
||||||
$dates[] = Carbon::createFromDate($year, $month, $i)
|
|
||||||
->format('Y-m-d');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadProductionData($plantId, $lineId, $month, $year){
|
|
||||||
|
|
||||||
if (!$plantId || !$lineId || !$month || !$year) {
|
|
||||||
$this->records = [];
|
|
||||||
$this->dates = [];
|
|
||||||
$this->leaveDates = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->dates = $this->getMonthDates($month, $year);
|
|
||||||
|
|
||||||
$data = ProductionPlan::query()
|
|
||||||
->join('items', 'items.id', '=', 'production_plans.item_id')
|
|
||||||
->join('lines', 'lines.id', '=', 'production_plans.line_id')
|
|
||||||
->join('plants', 'plants.id', '=', 'production_plans.plant_id')
|
|
||||||
->where('production_plans.plant_id', $plantId)
|
|
||||||
->where('production_plans.line_id', $lineId)
|
|
||||||
->whereMonth('production_plans.created_at', $month)
|
|
||||||
->whereYear('production_plans.created_at', $year)
|
|
||||||
->select(
|
|
||||||
'production_plans.created_at',
|
|
||||||
'production_plans.operator_id',
|
|
||||||
'plants.name as plant',
|
|
||||||
'items.code as item_code',
|
|
||||||
'items.description as item_description',
|
|
||||||
'lines.name as line_name',
|
|
||||||
'production_plans.leave_dates'
|
|
||||||
)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($data && $data->leave_dates) {
|
|
||||||
$this->leaveDates = array_map('trim', explode(',', $data->leave_dates));
|
|
||||||
}
|
|
||||||
|
|
||||||
$producedData = ProductionQuantity::selectRaw("
|
|
||||||
plant_id,
|
|
||||||
line_id,
|
|
||||||
item_id,
|
|
||||||
DATE(created_at) as prod_date,
|
|
||||||
COUNT(*) as total_qty
|
|
||||||
")
|
|
||||||
->where('plant_id', $plantId)
|
|
||||||
->where('line_id', $lineId)
|
|
||||||
->whereMonth('created_at', $month)
|
|
||||||
->whereYear('created_at', $year)
|
|
||||||
->groupBy('plant_id', 'line_id', 'item_id', DB::raw('DATE(created_at)'))
|
|
||||||
->get()
|
|
||||||
->groupBy(function ($row) {
|
|
||||||
return $row->plant_id . '_' . $row->line_id . '_' . $row->item_id;
|
|
||||||
})
|
|
||||||
->map(function ($group) {
|
|
||||||
return $group->keyBy('prod_date');
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->records = ProductionPlan::query()
|
|
||||||
->join('items', 'items.id', '=', 'production_plans.item_id')
|
|
||||||
->join('lines', 'lines.id', '=', 'production_plans.line_id')
|
|
||||||
->join('plants', 'plants.id', '=', 'production_plans.plant_id')
|
|
||||||
->where('production_plans.plant_id', $plantId)
|
|
||||||
->where('production_plans.line_id', $lineId)
|
|
||||||
->whereMonth('production_plans.created_at', $month)
|
|
||||||
->whereYear('production_plans.created_at', $year)
|
|
||||||
->select(
|
|
||||||
'production_plans.item_id',
|
|
||||||
'production_plans.plant_id',
|
|
||||||
'production_plans.line_id',
|
|
||||||
'production_plans.plan_quantity',
|
|
||||||
'production_plans.working_days',
|
|
||||||
'items.code as item_code',
|
|
||||||
'items.description as item_description',
|
|
||||||
'lines.name as line_name',
|
|
||||||
'plants.name as plant_name'
|
|
||||||
)
|
|
||||||
->get()
|
|
||||||
// ->map(function ($row) use ($producedData) {
|
|
||||||
// $row = $row->toArray();
|
|
||||||
|
|
||||||
// $row['daily_target'] = ($row['working_days'] > 0)
|
|
||||||
// ? round($row['plan_quantity'] / $row['working_days'], 2)
|
|
||||||
// : 0;
|
|
||||||
|
|
||||||
// // $key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
|
|
||||||
|
|
||||||
// // foreach ($this->dates as $date) {
|
|
||||||
// // $found = $producedData[$key][$date] ?? null;
|
|
||||||
// // $row['produced_quantity'][$date] = $found->total_qty ?? 0;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// $remainingDays = $row['working_days'];
|
|
||||||
// $pendingQty = 0;
|
|
||||||
|
|
||||||
// $row['daily_target_dynamic'] = [];
|
|
||||||
// $row['produced_quantity'] = [];
|
|
||||||
|
|
||||||
// $key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
|
|
||||||
|
|
||||||
// foreach ($this->dates as $date) {
|
|
||||||
|
|
||||||
// $found = $producedData[$key][$date] ?? null;
|
|
||||||
// $producedQty = $found->total_qty ?? 0;
|
|
||||||
|
|
||||||
// // today's adjusted target
|
|
||||||
// $todayTarget = $baseDailyTarget;
|
|
||||||
|
|
||||||
// if ($remainingDays > 1 && $pendingQty > 0) {
|
|
||||||
// $todayTarget += $pendingQty / $remainingDays;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $row['daily_target_dynamic'][$date] = round($todayTarget, 2);
|
|
||||||
// $row['produced_quantity'][$date] = $producedQty;
|
|
||||||
|
|
||||||
// // calculate today's shortfall
|
|
||||||
// $pendingQty += ($todayTarget - $producedQty);
|
|
||||||
|
|
||||||
// if ($pendingQty < 0) {
|
|
||||||
// $pendingQty = 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $remainingDays--;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return $row;
|
|
||||||
// })
|
|
||||||
->map(function ($row) use ($producedData) {
|
|
||||||
|
|
||||||
$row = $row->toArray();
|
|
||||||
|
|
||||||
$remainingQty = $row['plan_quantity'];
|
|
||||||
$remainingDays = $row['working_days'];
|
|
||||||
|
|
||||||
$row['daily_target_dynamic'] = [];
|
|
||||||
$row['produced_quantity'] = [];
|
|
||||||
|
|
||||||
$key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
|
|
||||||
|
|
||||||
foreach ($this->dates as $date) {
|
|
||||||
|
|
||||||
// Skip leave dates
|
|
||||||
if (in_array($date, $this->leaveDates)) {
|
|
||||||
$row['daily_target_dynamic'][$date] = '-';
|
|
||||||
$row['produced_quantity'][$date] = '-';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$todayTarget = $remainingDays > 0
|
|
||||||
? round($remainingQty / $remainingDays, 2)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
//$todayTarget = $remainingDays > 0
|
|
||||||
// ? $remainingQty / $remainingDays
|
|
||||||
// : 0;
|
|
||||||
|
|
||||||
$producedQty = isset($producedData[$key][$date])
|
|
||||||
? $producedData[$key][$date]->total_qty
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
$row['daily_target_dynamic'][$date] = $todayTarget;
|
|
||||||
$row['produced_quantity'][$date] = $producedQty;
|
|
||||||
|
|
||||||
// Carry forward pending
|
|
||||||
$remainingQty -= $producedQty;
|
|
||||||
if ($remainingQty < 0) {
|
|
||||||
$remainingQty = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$remainingDays--;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
|
||||||
})
|
|
||||||
->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exportProductionData()
|
|
||||||
{
|
|
||||||
return Excel::download(
|
|
||||||
new ProductionPlanExport($this->records, $this->dates),
|
|
||||||
'production_plan_data.xlsx'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
// return view('livewire.production-target-plan');
|
|
||||||
return view('livewire.production-target-plan', [
|
|
||||||
'records' => $this->records,
|
|
||||||
'dates' => $this->dates,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,13 +12,11 @@ class Item extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'plant_id',
|
'plant_id',
|
||||||
'line_id',
|
|
||||||
'category',
|
'category',
|
||||||
'code',
|
'code',
|
||||||
'description',
|
'description',
|
||||||
'hourly_quantity',
|
'hourly_quantity',
|
||||||
'uom',
|
'uom',
|
||||||
'line_capacity',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function plant(): BelongsTo
|
public function plant(): BelongsTo
|
||||||
@@ -26,11 +24,6 @@ class Item extends Model
|
|||||||
return $this->belongsTo(Plant::class);
|
return $this->belongsTo(Plant::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function line(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Line::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function stickerMasters()
|
public function stickerMasters()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StickerMaster::class, 'item_id', 'id');
|
return $this->hasMany(StickerMaster::class, 'item_id', 'id');
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Line extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
"plant_id",
|
"plant_id",
|
||||||
"block_id",
|
|
||||||
"name",
|
"name",
|
||||||
"type",
|
"type",
|
||||||
"group_work_center",
|
"group_work_center",
|
||||||
@@ -35,11 +34,6 @@ class Line extends Model
|
|||||||
return $this->belongsTo(Plant::class);
|
return $this->belongsTo(Plant::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function block(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Block::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testingPanelReadings()
|
public function testingPanelReadings()
|
||||||
{
|
{
|
||||||
return $this->hasMany(TestingPanelReading::class);
|
return $this->hasMany(TestingPanelReading::class);
|
||||||
|
|||||||
@@ -12,15 +12,12 @@ class ProductionPlan extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
"plant_id",
|
"plant_id",
|
||||||
"item_id",
|
|
||||||
"shift_id",
|
"shift_id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"line_id",
|
"line_id",
|
||||||
"plan_quantity",
|
"plan_quantity",
|
||||||
"production_quantity",
|
"production_quantity",
|
||||||
"operator_id",
|
"operator_id",
|
||||||
"working_days",
|
|
||||||
"leave_dates",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function plant(): BelongsTo
|
public function plant(): BelongsTo
|
||||||
@@ -37,9 +34,4 @@ class ProductionPlan extends Model
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Line::class);
|
return $this->belongsTo(Line::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function item(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Item::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ class ProductionQuantity extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
"plant_id",
|
"plant_id",
|
||||||
"machine_id",
|
|
||||||
"shift_id",
|
"shift_id",
|
||||||
"line_id",
|
"line_id",
|
||||||
"item_id",
|
"item_id",
|
||||||
@@ -51,11 +50,6 @@ class ProductionQuantity extends Model
|
|||||||
return $this->belongsTo(Item::class);
|
return $this->belongsTo(Item::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function machine(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Machine::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($productionQuantity) {
|
static::created(function ($productionQuantity) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use Illuminate\Notifications\Notifiable;
|
|||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use NotificationChannels\WebPush\HasPushSubscriptions;
|
use NotificationChannels\WebPush\HasPushSubscriptions;
|
||||||
use NotificationChannels\WebPush\PushSubscription;
|
|
||||||
|
|
||||||
class User extends Authenticatable implements FilamentUser
|
class User extends Authenticatable implements FilamentUser
|
||||||
{
|
{
|
||||||
@@ -65,9 +64,4 @@ class User extends Authenticatable implements FilamentUser
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Plant::class);
|
return $this->belongsTo(Plant::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pushSubscriptions()
|
|
||||||
{
|
|
||||||
return $this->morphMany(PushSubscription::class, 'subscribable');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ use App\Filament\Auth\CustomLogin as AuthCustomLogin;
|
|||||||
use App\Filament\Pages\CustomLogin;
|
use App\Filament\Pages\CustomLogin;
|
||||||
use Filament\View\PanelsRenderHook;
|
use Filament\View\PanelsRenderHook;
|
||||||
use Filament\Support\Facades\FilamentView;
|
use Filament\Support\Facades\FilamentView;
|
||||||
use Illuminate\Support\Facades\Blade;
|
|
||||||
|
|
||||||
|
|
||||||
class AdminPanelProvider extends PanelProvider
|
class AdminPanelProvider extends PanelProvider
|
||||||
@@ -161,10 +160,5 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
FilamentView::registerRenderHook(
|
|
||||||
PanelsRenderHook::BODY_END,
|
|
||||||
fn (): string => Blade::render("@livewire('chat-bot')"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,403 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChatbotService (Advanced Mode)
|
|
||||||
* ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
* Parses STRUCTURED user inputs via regex — no LLM involved.
|
|
||||||
*
|
|
||||||
* HOW TO ADD A NEW COMMAND:
|
|
||||||
* 1. Add an entry to $handlers with a regex pattern and a handler method name.
|
|
||||||
* 2. Write the private handler method (receives the captured group as $value).
|
|
||||||
* 3. Add a usage example to unknownCommand() so users know about it.
|
|
||||||
*
|
|
||||||
* Pattern convention: /keyword\s*=\s*(.+)/i
|
|
||||||
* - The first capture group is the raw value after "=".
|
|
||||||
* - The handler receives it already trimmed.
|
|
||||||
*/
|
|
||||||
class ChatbotService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Registry of structured-command handlers.
|
|
||||||
*
|
|
||||||
* Each entry:
|
|
||||||
* 'pattern' — PCRE regex; capture group 1 is the extracted value.
|
|
||||||
* 'handler' — name of the private method that processes the command.
|
|
||||||
*
|
|
||||||
* For two-value commands (e.g. invoice report), group 1 and group 2 are
|
|
||||||
* both passed to the handler; single-value handlers simply ignore $value2.
|
|
||||||
*/
|
|
||||||
private array $handlers = [
|
|
||||||
[
|
|
||||||
'pattern' => '/inv(?:oice)?(?:\s*(?:number|num|no\.?))?\s*(?:=|is| |equal\s+to)\s*([^\s,\.]+)/i',
|
|
||||||
'handler' => 'handleInvoice',
|
|
||||||
],
|
|
||||||
|
|
||||||
// ── Invoice report: item type lookup ──────────────────────────────────
|
|
||||||
// Accepts patterns like:
|
|
||||||
// item = 674071 plant = Vahinie Unit 2
|
|
||||||
// item code = 674071 plant = Vahinie Unit 2
|
|
||||||
// check item 674071 for plant Vahinie Unit 2
|
|
||||||
[
|
|
||||||
'pattern' => '/item(?:\s*code)?\s*(?:=|is|:)?\s*([^\s,]+)\s+(?:for\s+)?plant\s*(?:=|is|:)?\s*(.+)/i',
|
|
||||||
'handler' => 'handleInvoiceReport',
|
|
||||||
],
|
|
||||||
|
|
||||||
// ── Add more commands here ────────────────────────────────────────────
|
|
||||||
// Example:
|
|
||||||
// [
|
|
||||||
// 'pattern' => '/^\s*serial\s*=\s*(.+)/i',
|
|
||||||
// 'handler' => 'handleSerial',
|
|
||||||
// ],
|
|
||||||
];
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Public entry point
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch the user's input to the matching handler.
|
|
||||||
*/
|
|
||||||
public function ask(string $input): string
|
|
||||||
{
|
|
||||||
$input = trim($input);
|
|
||||||
|
|
||||||
foreach ($this->handlers as $entry) {
|
|
||||||
if (preg_match($entry['pattern'], $input, $matches)) {
|
|
||||||
$value = trim($matches[1] ?? '');
|
|
||||||
$value2 = trim($matches[2] ?? '');
|
|
||||||
return $this->{$entry['handler']}($value, $value2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->unknownCommand($input);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Handler: invoice = <invoice_number>
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up scan status for an invoice number in invoice_validations.
|
|
||||||
* Returns a plain-English string (used by the Advanced / free-text path).
|
|
||||||
* Structured callers should use getInvoiceData() directly.
|
|
||||||
*/
|
|
||||||
private function handleInvoice(string $invoiceNumber, string $_unused = ''): string
|
|
||||||
{
|
|
||||||
$data = $this->getInvoiceData($invoiceNumber);
|
|
||||||
|
|
||||||
// For the plain-text path (advanced mode / ChatbotService::ask()),
|
|
||||||
// reassemble a human-readable sentence from the structured data.
|
|
||||||
if (in_array($data['type'], ['invalid', 'error', 'not_found'], true)) {
|
|
||||||
return $data['message'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data['type'] === 'all_scanned') {
|
|
||||||
$n = $data['total'];
|
|
||||||
$itemWord = $n === 1 ? 'serial number' : 'serial numbers';
|
|
||||||
return "For invoice number {$data['invoice_number']}, all {$n} {$itemWord} "
|
|
||||||
. ($n === 1 ? 'has' : 'have') . ' been scanned. ✅';
|
|
||||||
}
|
|
||||||
|
|
||||||
// partial or none_scanned
|
|
||||||
$total = $data['total'];
|
|
||||||
$scanned = $data['scanned'];
|
|
||||||
$notScan = $data['not_scanned'];
|
|
||||||
$inv = $data['invoice_number'];
|
|
||||||
$itemWord = $total === 1 ? 'serial number' : 'serial numbers';
|
|
||||||
|
|
||||||
if ($scanned === 0) {
|
|
||||||
$msg = "For invoice number {$inv}, there "
|
|
||||||
. ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} "
|
|
||||||
. 'and none have been scanned.';
|
|
||||||
} else {
|
|
||||||
$msg = "For invoice number {$inv}, there "
|
|
||||||
. ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} in total. "
|
|
||||||
. "Out of which {$scanned} "
|
|
||||||
. ($scanned === 1 ? 'has' : 'have') . ' been scanned and '
|
|
||||||
. "{$notScan} "
|
|
||||||
. ($notScan === 1 ? 'has' : 'have') . ' not been scanned.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! empty($data['unscanned_serials'])) {
|
|
||||||
$msg .= ' Unscanned serial numbers are: '
|
|
||||||
. implode(', ', $data['unscanned_serials']) . '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Public structured accessor — used by ChatBot (Basic mode)
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns structured scan-status data for an invoice number.
|
|
||||||
*
|
|
||||||
* Return shape:
|
|
||||||
* [
|
|
||||||
* 'type' => 'all_scanned' | 'partial' | 'none_scanned'
|
|
||||||
* | 'not_found' | 'error' | 'invalid',
|
|
||||||
* 'message' => string, // one-line human summary (no serial list)
|
|
||||||
* 'invoice_number' => string,
|
|
||||||
* 'total' => int,
|
|
||||||
* 'scanned' => int,
|
|
||||||
* 'not_scanned' => int,
|
|
||||||
* 'unscanned_serials' => string[], // full list — may be large
|
|
||||||
* ]
|
|
||||||
*/
|
|
||||||
public function getInvoiceData(string $invoiceNumber): array
|
|
||||||
{
|
|
||||||
$invoiceNumber = preg_replace('/\s+/', '', $invoiceNumber);
|
|
||||||
|
|
||||||
if (empty($invoiceNumber)) {
|
|
||||||
return [
|
|
||||||
'type' => 'invalid',
|
|
||||||
'message' => 'Please provide a valid invoice number. Example: invoice = 3RA0013333',
|
|
||||||
'invoice_number' => '',
|
|
||||||
'total' => 0,
|
|
||||||
'scanned' => 0,
|
|
||||||
'not_scanned' => 0,
|
|
||||||
'unscanned_serials' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rows = DB::select("
|
|
||||||
SELECT
|
|
||||||
COALESCE(scanned_status, 'not scanned') AS status,
|
|
||||||
COUNT(*) AS total_count,
|
|
||||||
STRING_AGG(
|
|
||||||
CASE
|
|
||||||
WHEN scanned_status IS NULL THEN serial_number::text
|
|
||||||
END,
|
|
||||||
', '
|
|
||||||
) AS serial_numbers_not_scanned
|
|
||||||
FROM invoice_validations
|
|
||||||
WHERE invoice_number = ?
|
|
||||||
GROUP BY scanned_status
|
|
||||||
", [$invoiceNumber]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('ChatbotService: invoice query failed', [
|
|
||||||
'invoice' => $invoiceNumber,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'type' => 'error',
|
|
||||||
'message' => "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. "
|
|
||||||
. 'Please try again or contact support if this keeps happening.',
|
|
||||||
'invoice_number' => $invoiceNumber,
|
|
||||||
'total' => 0,
|
|
||||||
'scanned' => 0,
|
|
||||||
'not_scanned' => 0,
|
|
||||||
'unscanned_serials' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($rows)) {
|
|
||||||
return [
|
|
||||||
'type' => 'not_found',
|
|
||||||
'message' => "No records found for invoice number {$invoiceNumber}. "
|
|
||||||
. 'Please double-check the invoice number and try again.',
|
|
||||||
'invoice_number' => $invoiceNumber,
|
|
||||||
'total' => 0,
|
|
||||||
'scanned' => 0,
|
|
||||||
'not_scanned' => 0,
|
|
||||||
'unscanned_serials' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Aggregate rows ────────────────────────────────────────────────────
|
|
||||||
$totalScanned = 0;
|
|
||||||
$totalNotScanned = 0;
|
|
||||||
$unscannedSerials = [];
|
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
if ($row->status === 'not scanned') {
|
|
||||||
$totalNotScanned = (int) $row->total_count;
|
|
||||||
if (! empty($row->serial_numbers_not_scanned)) {
|
|
||||||
$unscannedSerials = array_values(
|
|
||||||
array_filter(
|
|
||||||
array_map('trim', explode(',', $row->serial_numbers_not_scanned))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$totalScanned += (int) $row->total_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$grandTotal = $totalScanned + $totalNotScanned;
|
|
||||||
|
|
||||||
// ── All scanned ───────────────────────────────────────────────────────
|
|
||||||
if ($totalNotScanned === 0) {
|
|
||||||
$n = $grandTotal;
|
|
||||||
$itemWord = $n === 1 ? 'serial number' : 'serial numbers';
|
|
||||||
return [
|
|
||||||
'type' => 'all_scanned',
|
|
||||||
'message' => "All {$n} {$itemWord} scanned for invoice {$invoiceNumber}. ✅",
|
|
||||||
'invoice_number' => $invoiceNumber,
|
|
||||||
'total' => $grandTotal,
|
|
||||||
'scanned' => $totalScanned,
|
|
||||||
'not_scanned' => 0,
|
|
||||||
'unscanned_serials' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── None / partial scanned ────────────────────────────────────────────
|
|
||||||
$type = $totalScanned === 0 ? 'none_scanned' : 'partial';
|
|
||||||
$itemWord = $grandTotal === 1 ? 'serial number' : 'serial numbers';
|
|
||||||
|
|
||||||
if ($totalScanned === 0) {
|
|
||||||
$summary = "Invoice {$invoiceNumber} — {$grandTotal} {$itemWord}, none scanned yet.";
|
|
||||||
} else {
|
|
||||||
$summary = "Invoice {$invoiceNumber} — {$grandTotal} {$itemWord} total: "
|
|
||||||
. "{$totalScanned} scanned, {$totalNotScanned} not scanned.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'type' => $type,
|
|
||||||
'message' => $summary,
|
|
||||||
'invoice_number' => $invoiceNumber,
|
|
||||||
'total' => $grandTotal,
|
|
||||||
'scanned' => $totalScanned,
|
|
||||||
'not_scanned' => $totalNotScanned,
|
|
||||||
'unscanned_serials' => $unscannedSerials,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Handler: item = <item_code> plant = <plant_name>
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether an item is a serial invoice or material invoice
|
|
||||||
* for a given plant, using the sticker_masters table.
|
|
||||||
*
|
|
||||||
* @param string $itemCode Extracted item code (capture group 1)
|
|
||||||
* @param string $plantName Extracted plant name (capture group 2)
|
|
||||||
*/
|
|
||||||
private function handleInvoiceReport(string $itemCode, string $plantName): string
|
|
||||||
{
|
|
||||||
$itemCode = trim($itemCode);
|
|
||||||
$plantName = trim($plantName);
|
|
||||||
|
|
||||||
if (empty($itemCode)) {
|
|
||||||
return 'Please provide an item code. Example: item = 674071 plant = Vahinie Unit 2';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($plantName)) {
|
|
||||||
return 'Please provide a plant name. Example: item = 674071 plant = Vahinie Unit 2';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$rows = DB::select("
|
|
||||||
WITH plant_item AS (
|
|
||||||
SELECT ? AS user_plant,
|
|
||||||
? AS user_item_code
|
|
||||||
),
|
|
||||||
t1 AS (
|
|
||||||
SELECT
|
|
||||||
plants.id AS plant_id,
|
|
||||||
plants.name AS plant_name,
|
|
||||||
ARRAY_AGG(items.code) AS item_codes
|
|
||||||
FROM plants
|
|
||||||
LEFT JOIN items ON plants.id = items.plant_id
|
|
||||||
GROUP BY plants.id, plants.name
|
|
||||||
),
|
|
||||||
t2 AS (
|
|
||||||
SELECT
|
|
||||||
t1.plant_id,
|
|
||||||
t1.plant_name,
|
|
||||||
CASE
|
|
||||||
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END AS exists_flag
|
|
||||||
FROM t1
|
|
||||||
CROSS JOIN plant_item
|
|
||||||
WHERE t1.plant_name = plant_item.user_plant
|
|
||||||
),
|
|
||||||
t3 AS (
|
|
||||||
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
|
|
||||||
plant_item.user_item_code
|
|
||||||
FROM t2
|
|
||||||
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
|
|
||||||
),
|
|
||||||
t4 AS (
|
|
||||||
SELECT items.id AS item_id,
|
|
||||||
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
|
|
||||||
FROM t3
|
|
||||||
LEFT JOIN items
|
|
||||||
ON t3.plant_id = items.plant_id
|
|
||||||
AND t3.user_item_code = items.code
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
t4.item_id,
|
|
||||||
t4.plant_id,
|
|
||||||
t4.plant_name,
|
|
||||||
t4.exists_flag,
|
|
||||||
t4.user_item_code,
|
|
||||||
COALESCE(sticker_masters.material_type, 0) AS material_type,
|
|
||||||
CASE
|
|
||||||
WHEN sticker_masters.item_id IS NULL
|
|
||||||
THEN 'no match found'
|
|
||||||
WHEN COALESCE(sticker_masters.material_type, 0) = 0
|
|
||||||
THEN 'serial invoice'
|
|
||||||
ELSE 'material invoice'
|
|
||||||
END AS invoice_description
|
|
||||||
FROM t4
|
|
||||||
LEFT JOIN sticker_masters
|
|
||||||
ON sticker_masters.plant_id = t4.plant_id
|
|
||||||
AND sticker_masters.item_id = t4.item_id
|
|
||||||
", [$plantName, $itemCode]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('ChatbotService: invoice report query failed', [
|
|
||||||
'plant' => $plantName,
|
|
||||||
'item_code' => $itemCode,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return "Sorry, I couldn't fetch data for item {$itemCode} in plant {$plantName}. "
|
|
||||||
. 'Please try again or contact support.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($rows)) {
|
|
||||||
return "No data found for plant \"{$plantName}\". Please check the plant name and try again.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = $rows[0];
|
|
||||||
|
|
||||||
if ((int) $row->exists_flag === 0) {
|
|
||||||
return 'Provided item code does not exist in the item table.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return match ($row->invoice_description) {
|
|
||||||
'no match found' => "Item not found in sticker master for the plant {$row->plant_name}.",
|
|
||||||
'serial invoice' => 'It is a serial invoice item.',
|
|
||||||
'material invoice' => 'It is a material invoice item.',
|
|
||||||
default => 'Unexpected result. Please contact support.',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Fallback for unrecognised input
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private function unknownCommand(string $input): string
|
|
||||||
{
|
|
||||||
return "I didn't recognise that command. Please include a supported keyword with a value after '='.\n\n"
|
|
||||||
. "• Invoice scan status:\n"
|
|
||||||
. " invoice = 3RA0013333\n"
|
|
||||||
. " what is the status of invoice = 3RA0013333\n\n"
|
|
||||||
. "• Invoice type lookup:\n"
|
|
||||||
. " item = 674071 plant = Vahinie Unit 2\n"
|
|
||||||
. " item code = 674071 plant = Vahinie Unit 2\n\n"
|
|
||||||
. 'Any sentence containing keyword = value will work.';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,819 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GeminiChatbotService
|
|
||||||
* ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
* Powers the "Advanced" chatbot mode with plain-English understanding.
|
|
||||||
*
|
|
||||||
* FLOW:
|
|
||||||
* 1. Takes the full chat history + new user message.
|
|
||||||
* 2. Sends them to Gemini with a structured system prompt.
|
|
||||||
* 3. Gemini returns JSON: { task, params, missing, clarification }.
|
|
||||||
* 4. If task is a known, complete query → runs the matching DB handler.
|
|
||||||
* 5. If task is "unknown" → sends the message to Gemini again as a free-form
|
|
||||||
* conversational assistant so it can answer or ask the user what they need.
|
|
||||||
* 6. If required params are missing → Gemini's clarification question is returned.
|
|
||||||
*
|
|
||||||
* SUPPORTED TASKS:
|
|
||||||
* - invoice_status → scan status of an invoice number
|
|
||||||
* - invoice_report → serial/material invoice type for an item + plant
|
|
||||||
* - production_report → production count for a plant / line / date range
|
|
||||||
* - unknown → handled via a free-form Gemini conversation turn
|
|
||||||
*
|
|
||||||
* CONFIGURATION (.env):
|
|
||||||
* GEMINI_API_KEY=your_key_here
|
|
||||||
* GEMINI_MODEL=gemini-2.5-flash-preview ← set whichever model your key supports
|
|
||||||
*
|
|
||||||
* HOW TO ADD A NEW TASK:
|
|
||||||
* 1. Describe it in buildSystemPrompt() under TASKS.
|
|
||||||
* 2. Add a private handle*() method below.
|
|
||||||
* 3. Add a match arm in processMessage().
|
|
||||||
*/
|
|
||||||
class GeminiChatbotService
|
|
||||||
{
|
|
||||||
private string $apiKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full REST endpoint — model name is part of the URL path, e.g.:
|
|
||||||
* https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview:generateContent
|
|
||||||
*
|
|
||||||
* Built in __construct() from config('services.gemini.model').
|
|
||||||
* Change GEMINI_MODEL in .env to switch models without editing code.
|
|
||||||
*/
|
|
||||||
private string $apiUrl;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->apiKey = config('services.gemini.api_key', '');
|
|
||||||
|
|
||||||
$model = config('services.gemini.model', 'gemini-3-flash-preview');
|
|
||||||
|
|
||||||
$this->apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Public entry point
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a user message in context of the prior conversation.
|
|
||||||
*
|
|
||||||
* @param array $chatHistory Previous turns: [['role'=>'user'|'assistant','content'=>'…'], …]
|
|
||||||
* @param string $userInput The new message just typed.
|
|
||||||
* @return string Plain-text reply to show in the chat bubble.
|
|
||||||
*/
|
|
||||||
public function processMessage(array $chatHistory, string $userInput): string
|
|
||||||
{
|
|
||||||
if (empty($this->apiKey)) {
|
|
||||||
return '⚠️ AI features are not configured. Please set GEMINI_API_KEY in your .env file.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 1: Classify the intent ───────────────────────────────────────
|
|
||||||
try {
|
|
||||||
$classification = $this->classifyWithGemini($chatHistory, $userInput);
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
// Surface the specific error directly in the chat bubble
|
|
||||||
return $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
$task = $classification['task'] ?? 'unknown';
|
|
||||||
$params = $classification['params'] ?? [];
|
|
||||||
$missing = $classification['missing'] ?? [];
|
|
||||||
$clarification = $classification['clarification'] ?? null;
|
|
||||||
|
|
||||||
// ── Step 2: Missing required params → ask user for them ───────────────
|
|
||||||
if (! empty($missing) && ! empty($clarification)) {
|
|
||||||
return $clarification;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 3: Dispatch to the appropriate handler ───────────────────────
|
|
||||||
return match ($task) {
|
|
||||||
'invoice_status' => $this->handleInvoiceStatus($params),
|
|
||||||
'invoice_report' => $this->handleInvoiceReport($params),
|
|
||||||
'production_report' => $this->handleProductionReport($params),
|
|
||||||
|
|
||||||
// ── Unknown intent: hand off to Gemini as a free-form assistant ──
|
|
||||||
'unknown' => $this->handleUnknown($chatHistory, $userInput, $clarification),
|
|
||||||
|
|
||||||
default => $this->handleUnknown($chatHistory, $userInput, null),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Gemini API calls
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Phase 1 — Classify the user's intent and extract structured params.
|
|
||||||
*
|
|
||||||
* @return array|null Parsed JSON array, or null on failure.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @throws \RuntimeException with a user-facing message describing exactly what failed.
|
|
||||||
*/
|
|
||||||
private function classifyWithGemini(array $history, string $userInput): array
|
|
||||||
{
|
|
||||||
$requestBody = [
|
|
||||||
'system_instruction' => [
|
|
||||||
'parts' => [['text' => $this->buildSystemPrompt()]],
|
|
||||||
],
|
|
||||||
'contents' => $this->buildGeminiContents($history, $userInput),
|
|
||||||
'generationConfig' => [
|
|
||||||
'temperature' => 0.1,
|
|
||||||
'responseMimeType' => 'application/json',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// ── HTTP call ─────────────────────────────────────────────────────────
|
|
||||||
try {
|
|
||||||
$response = Http::withHeaders(['Content-Type' => 'application/json'])
|
|
||||||
->timeout(15)
|
|
||||||
->post($this->apiUrl . '?key=' . $this->apiKey, $requestBody);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('GeminiChatbotService: HTTP exception', ['error' => $e->getMessage()]);
|
|
||||||
throw new \RuntimeException(
|
|
||||||
'⚠️ Could not reach the Gemini API. Check your network or firewall. ('
|
|
||||||
. $e->getMessage() . ')'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── HTTP-level error ──────────────────────────────────────────────────
|
|
||||||
if (! $response->successful()) {
|
|
||||||
$status = $response->status();
|
|
||||||
$body = $response->body();
|
|
||||||
|
|
||||||
Log::error('GeminiChatbotService: API HTTP error', [
|
|
||||||
'status' => $status,
|
|
||||||
'body' => $body,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Parse Google's error message when available
|
|
||||||
$googleMsg = $response->json('error.message') ?? $body;
|
|
||||||
|
|
||||||
throw new \RuntimeException(
|
|
||||||
"⚠️ Gemini API returned HTTP {$status}: {$googleMsg}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Extract text from response ────────────────────────────────────────
|
|
||||||
$text = $response->json('candidates.0.content.parts.0.text');
|
|
||||||
|
|
||||||
if (empty($text)) {
|
|
||||||
// Check for prompt-blocking
|
|
||||||
$blockReason = $response->json('promptFeedback.blockReason');
|
|
||||||
$finishReason = $response->json('candidates.0.finishReason');
|
|
||||||
|
|
||||||
Log::error('GeminiChatbotService: empty response text', [
|
|
||||||
'blockReason' => $blockReason,
|
|
||||||
'finishReason' => $finishReason,
|
|
||||||
'full' => $response->json(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$hint = $blockReason
|
|
||||||
? "prompt was blocked (reason: {$blockReason})"
|
|
||||||
: ($finishReason ? "finish reason: {$finishReason}" : 'no text returned');
|
|
||||||
|
|
||||||
throw new \RuntimeException("⚠️ Gemini returned no content — {$hint}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── JSON decode ───────────────────────────────────────────────────────
|
|
||||||
$clean = preg_replace('/^```json\s*/i', '', trim($text));
|
|
||||||
$clean = preg_replace('/\s*```$/i', '', $clean);
|
|
||||||
$parsed = json_decode($clean, true);
|
|
||||||
|
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
||||||
Log::error('GeminiChatbotService: JSON decode failed', ['raw' => $text]);
|
|
||||||
throw new \RuntimeException(
|
|
||||||
'⚠️ Gemini returned a non-JSON response: ' . mb_substr($text, 0, 200)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Phase 2 (unknown task only) — Ask Gemini to respond conversationally.
|
|
||||||
*
|
|
||||||
* Sends the full chat history + new user message to Gemini as a friendly
|
|
||||||
* factory-operations assistant (no JSON constraint). Gemini can ask for
|
|
||||||
* clarification, answer general questions, or guide the user to one of the
|
|
||||||
* supported tasks.
|
|
||||||
*
|
|
||||||
* @param string|null $hintFromClassification Optional clarification from the
|
|
||||||
* classification step — prepended as assistant context if present.
|
|
||||||
* @return string Plain-text reply from Gemini.
|
|
||||||
*/
|
|
||||||
private function callGeminiConversational(
|
|
||||||
array $history,
|
|
||||||
string $userInput,
|
|
||||||
?string $hintFromClassification = null
|
|
||||||
): ?string {
|
|
||||||
// If the classifier already produced a good clarification question, use it
|
|
||||||
// directly and skip the second API call to save latency + quota.
|
|
||||||
if (! empty($hintFromClassification)) {
|
|
||||||
return $hintFromClassification;
|
|
||||||
}
|
|
||||||
|
|
||||||
$requestBody = [
|
|
||||||
'system_instruction' => [
|
|
||||||
'parts' => [['text' => $this->buildConversationalSystemPrompt()]],
|
|
||||||
],
|
|
||||||
'contents' => $this->buildGeminiContents($history, $userInput),
|
|
||||||
'generationConfig' => [
|
|
||||||
'temperature' => 0.7, // more natural conversational tone
|
|
||||||
'maxOutputTokens' => 400,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = Http::withHeaders(['Content-Type' => 'application/json'])
|
|
||||||
->timeout(20)
|
|
||||||
->post($this->apiUrl . '?key=' . $this->apiKey, $requestBody);
|
|
||||||
|
|
||||||
if (! $response->successful()) {
|
|
||||||
Log::error('GeminiChatbotService: API error (conversational)', [
|
|
||||||
'status' => $response->status(),
|
|
||||||
'body' => $response->body(),
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response->json('candidates.0.content.parts.0.text');
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('GeminiChatbotService: exception (conversational)', ['error' => $e->getMessage()]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Prompt builders
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System prompt for Phase 1 (classification) — forces JSON output.
|
|
||||||
*/
|
|
||||||
private function buildSystemPrompt(): string
|
|
||||||
{
|
|
||||||
$today = now()->format('Y-m-d');
|
|
||||||
$startOfMonth = now()->startOfMonth()->format('Y-m-d');
|
|
||||||
|
|
||||||
return <<<PROMPT
|
|
||||||
You are a factory operations assistant that classifies plain-English user queries into structured tasks.
|
|
||||||
|
|
||||||
TASKS:
|
|
||||||
1. "invoice_status"
|
|
||||||
The user wants to check how many serial numbers in an invoice have been scanned / not scanned.
|
|
||||||
Required params: invoice_number
|
|
||||||
Examples:
|
|
||||||
- "check invoice 3RA0013333"
|
|
||||||
- "is invoice 3RA0013333 fully scanned?"
|
|
||||||
- "what's the scan status of 3RA0013333"
|
|
||||||
- "show me unscanned serials for invoice ABC123"
|
|
||||||
|
|
||||||
2. "invoice_report"
|
|
||||||
The user wants to know whether an item is a serial invoice or material invoice for a given plant.
|
|
||||||
Required params: item_code, plant_name
|
|
||||||
Examples:
|
|
||||||
- "check item 674071 for plant Vahinie Unit 2"
|
|
||||||
- "is item 500100 a serial or material invoice in Chennai plant?"
|
|
||||||
- "what type is item code 200300 at Vahinie?"
|
|
||||||
|
|
||||||
3. "production_report"
|
|
||||||
The user wants the production count for a plant and optional line over a date range.
|
|
||||||
Required params: plant_name
|
|
||||||
Optional params: line_name, date_from, date_to
|
|
||||||
Default dates: date_from = {$startOfMonth}, date_to = {$today}
|
|
||||||
Interpret relative dates: "this month", "last week", "yesterday", "today", etc.
|
|
||||||
Examples:
|
|
||||||
- "show production for Chennai plant this month"
|
|
||||||
- "how many units were produced in line 1 of Vahinie Unit 2 last week?"
|
|
||||||
- "production report for all plants from 2024-01-01 to 2024-01-31"
|
|
||||||
|
|
||||||
4. "unknown"
|
|
||||||
The query cannot be clearly matched to any of the above tasks.
|
|
||||||
Use this when the user is asking something general, greeting, asking what the bot can do,
|
|
||||||
asking a follow-up question that doesn't map to a task, or if you need more context.
|
|
||||||
In this case, set "clarification" to a friendly, helpful response — it may be a question,
|
|
||||||
a helpful explanation, or guidance toward the supported tasks.
|
|
||||||
|
|
||||||
CONVERSATION CONTEXT:
|
|
||||||
Consider the full conversation history. If the previous assistant message asked a clarifying question
|
|
||||||
(e.g. "Do you mean Invoice Status or Invoice Report?") and the user is now answering that question,
|
|
||||||
classify accordingly based on the combined context.
|
|
||||||
|
|
||||||
OUTPUT FORMAT (return ONLY this JSON, no markdown fences, no extra text):
|
|
||||||
{
|
|
||||||
"task": "invoice_status | invoice_report | production_report | unknown",
|
|
||||||
"params": {
|
|
||||||
"invoice_number": "...",
|
|
||||||
"item_code": "...",
|
|
||||||
"plant_name": "...",
|
|
||||||
"line_name": "...",
|
|
||||||
"date_from": "YYYY-MM-DD",
|
|
||||||
"date_to": "YYYY-MM-DD"
|
|
||||||
},
|
|
||||||
"missing": ["list of required params that were not found in the user input"],
|
|
||||||
"clarification": "Friendly response for unknown tasks or missing-param prompts. Set to null when task is clear and params are complete."
|
|
||||||
}
|
|
||||||
|
|
||||||
RULES:
|
|
||||||
- Only include params relevant to the detected task.
|
|
||||||
- If a required param is missing, add it to "missing" and set a helpful "clarification" asking only for that missing value.
|
|
||||||
- If task is "unknown", always set "missing" to [] and put your full helpful response in "clarification".
|
|
||||||
- When task is clear and all required params are present, set "missing" to [] and "clarification" to null.
|
|
||||||
PROMPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System prompt for Phase 2 (conversational fallback) — plain-text output.
|
|
||||||
*
|
|
||||||
* Used only when the classifier returns "unknown" AND produced no clarification.
|
|
||||||
*/
|
|
||||||
private function buildConversationalSystemPrompt(): string
|
|
||||||
{
|
|
||||||
return <<<PROMPT
|
|
||||||
You are a helpful factory operations assistant integrated into an internal management panel.
|
|
||||||
|
|
||||||
You can perform these tasks when the user gives you enough information:
|
|
||||||
• Invoice Status — check how many serial numbers in an invoice have been scanned and list any unscanned ones. Requires: invoice number.
|
|
||||||
• Invoice Report — find out whether an item is a serial invoice or material invoice for a given plant. Requires: item code and plant name.
|
|
||||||
• Production Report — get the production count for a plant, optionally filtered by line and date range. Requires: plant name.
|
|
||||||
|
|
||||||
When the user's request does not match any of the above:
|
|
||||||
- Answer general questions helpfully and concisely.
|
|
||||||
- If you need more information to perform a task, ask for only the missing detail.
|
|
||||||
- Guide the user toward one of the supported tasks when relevant.
|
|
||||||
- Keep replies short and conversational (2-4 sentences max).
|
|
||||||
- Do NOT mention JSON, APIs, or technical internals.
|
|
||||||
PROMPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert Livewire chat history + new user message into Gemini's contents array.
|
|
||||||
* Gemini uses "model" for the assistant role (not "assistant").
|
|
||||||
*/
|
|
||||||
private function buildGeminiContents(array $history, string $userInput): array
|
|
||||||
{
|
|
||||||
$contents = [];
|
|
||||||
|
|
||||||
// Include the last 8 turns at most to stay within token limits
|
|
||||||
foreach (array_slice($history, -8) as $msg) {
|
|
||||||
$contents[] = [
|
|
||||||
'role' => $msg['role'] === 'user' ? 'user' : 'model',
|
|
||||||
'parts' => [['text' => $msg['content']]],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents[] = [
|
|
||||||
'role' => 'user',
|
|
||||||
'parts' => [['text' => $userInput]],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Task handlers
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice Status — delegate to ChatbotService (regex-based, same as basic mode).
|
|
||||||
*/
|
|
||||||
private function handleInvoiceStatus(array $params): string
|
|
||||||
{
|
|
||||||
$invoiceNumber = trim(preg_replace('/\s+/', '', $params['invoice_number'] ?? ''));
|
|
||||||
|
|
||||||
if (empty($invoiceNumber)) {
|
|
||||||
return 'I need the invoice number to check the scan status. What is the invoice number?';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var ChatbotService $svc */
|
|
||||||
$svc = app(ChatbotService::class);
|
|
||||||
|
|
||||||
return $svc->ask("invoice = {$invoiceNumber}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice Report — resolves plant name with fuzzy matching, then runs the
|
|
||||||
* same CTE query as ChatBot::fetchInvoiceReport() directly.
|
|
||||||
*
|
|
||||||
* We bypass ChatbotService::ask() here so that resolvePlant()'s multi-strategy
|
|
||||||
* fuzzy logic is applied rather than the simpler LIKE inside ChatbotService.
|
|
||||||
*/
|
|
||||||
private function handleInvoiceReport(array $params): string
|
|
||||||
{
|
|
||||||
$itemCode = trim($params['item_code'] ?? '');
|
|
||||||
$plantName = trim($params['plant_name'] ?? '');
|
|
||||||
|
|
||||||
if (empty($itemCode)) {
|
|
||||||
return 'I need the item code to look up the invoice type. What is the item code?';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($plantName)) {
|
|
||||||
return 'I need the plant name to look up the invoice type. Which plant are you asking about?';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Fuzzy-resolve the plant name ──────────────────────────────────────
|
|
||||||
$plant = $this->resolvePlant($plantName);
|
|
||||||
|
|
||||||
if ($plant === null) {
|
|
||||||
return "I couldn't find a plant matching \"{$plantName}\". "
|
|
||||||
. 'Please check the plant name and try again.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Run the same CTE as ChatBot::fetchInvoiceReport() ─────────────────
|
|
||||||
try {
|
|
||||||
$rows = DB::select("
|
|
||||||
WITH plant_item AS (
|
|
||||||
SELECT ? AS user_plant,
|
|
||||||
? AS user_item_code
|
|
||||||
),
|
|
||||||
t1 AS (
|
|
||||||
SELECT
|
|
||||||
plants.id AS plant_id,
|
|
||||||
plants.name AS plant_name,
|
|
||||||
ARRAY_AGG(items.code) AS item_codes
|
|
||||||
FROM plants
|
|
||||||
LEFT JOIN items ON plants.id = items.plant_id
|
|
||||||
GROUP BY plants.id, plants.name
|
|
||||||
),
|
|
||||||
t2 AS (
|
|
||||||
SELECT
|
|
||||||
t1.plant_id,
|
|
||||||
t1.plant_name,
|
|
||||||
CASE
|
|
||||||
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
|
|
||||||
ELSE 0
|
|
||||||
END AS exists_flag
|
|
||||||
FROM t1
|
|
||||||
CROSS JOIN plant_item
|
|
||||||
WHERE t1.plant_name = plant_item.user_plant
|
|
||||||
),
|
|
||||||
t3 AS (
|
|
||||||
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
|
|
||||||
plant_item.user_item_code
|
|
||||||
FROM t2
|
|
||||||
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
|
|
||||||
),
|
|
||||||
t4 AS (
|
|
||||||
SELECT items.id AS item_id,
|
|
||||||
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
|
|
||||||
FROM t3
|
|
||||||
LEFT JOIN items
|
|
||||||
ON t3.plant_id = items.plant_id
|
|
||||||
AND t3.user_item_code = items.code
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
t4.item_id,
|
|
||||||
t4.plant_id,
|
|
||||||
t4.plant_name,
|
|
||||||
t4.exists_flag,
|
|
||||||
t4.user_item_code,
|
|
||||||
COALESCE(sticker_masters.material_type, 0) AS material_type,
|
|
||||||
CASE
|
|
||||||
WHEN sticker_masters.item_id IS NULL
|
|
||||||
THEN 'no match found'
|
|
||||||
WHEN COALESCE(sticker_masters.material_type, 0) = 0
|
|
||||||
THEN 'serial invoice'
|
|
||||||
ELSE 'material invoice'
|
|
||||||
END AS invoice_description
|
|
||||||
FROM t4
|
|
||||||
LEFT JOIN sticker_masters
|
|
||||||
ON sticker_masters.plant_id = t4.plant_id
|
|
||||||
AND sticker_masters.item_id = t4.item_id
|
|
||||||
", [$plant->name, $itemCode]);
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('GeminiChatbotService: invoice report query failed', [
|
|
||||||
'plant' => $plant->name,
|
|
||||||
'item_code' => $itemCode,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
return "Sorry, I couldn't fetch data. Please try again or contact support.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($rows)) {
|
|
||||||
return "No data found for plant \"{$plant->name}\". Please verify the plant name.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = $rows[0];
|
|
||||||
|
|
||||||
if ((int) $row->exists_flag === 0) {
|
|
||||||
return 'The provided item code does not exist in the item table.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return match ($row->invoice_description) {
|
|
||||||
'serial invoice' => 'It is a serial invoice item.',
|
|
||||||
'material invoice' => 'It is a material invoice item.',
|
|
||||||
'no match found' => "Item not found in sticker master for plant {$plant->name}.",
|
|
||||||
default => 'Unexpected result. Please contact support.',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Production Report — resolves plant/line names to IDs and runs the count query.
|
|
||||||
* Mirrors ChatBot::fetchProduction() but works with plain names instead of IDs,
|
|
||||||
* using resolvePlant() for robust fuzzy matching.
|
|
||||||
*/
|
|
||||||
private function handleProductionReport(array $params): string
|
|
||||||
{
|
|
||||||
$plantName = trim($params['plant_name'] ?? '');
|
|
||||||
$lineName = trim($params['line_name'] ?? '');
|
|
||||||
$dateFrom = $params['date_from'] ?? now()->startOfMonth()->format('Y-m-d');
|
|
||||||
$dateTo = $params['date_to'] ?? now()->format('Y-m-d');
|
|
||||||
|
|
||||||
if (empty($plantName)) {
|
|
||||||
return 'I need a plant name to fetch the production report. Which plant are you asking about?';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Fuzzy-resolve the plant name ──────────────────────────────────────
|
|
||||||
$plant = $this->resolvePlant($plantName);
|
|
||||||
|
|
||||||
if ($plant === null) {
|
|
||||||
return "I couldn't find a plant matching \"{$plantName}\". "
|
|
||||||
. 'Please check the plant name and try again.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Base query ────────────────────────────────────────────────────────
|
|
||||||
$query = DB::table('production_quantities')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->where('plant_id', $plant->id)
|
|
||||||
->whereDate('created_at', '>=', $dateFrom)
|
|
||||||
->whereDate('created_at', '<=', $dateTo);
|
|
||||||
|
|
||||||
$lineLabel = 'All Lines';
|
|
||||||
|
|
||||||
// ── Optionally filter by line (fuzzy LIKE match) ──────────────────────
|
|
||||||
if (! empty($lineName)) {
|
|
||||||
$line = $this->resolveLine($lineName, $plant->id);
|
|
||||||
|
|
||||||
if ($line === null) {
|
|
||||||
return "I couldn't find a line matching \"{$lineName}\" "
|
|
||||||
. "in plant \"{$plant->name}\". Please check the line name.";
|
|
||||||
}
|
|
||||||
|
|
||||||
$query->where('line_id', $line->id);
|
|
||||||
$lineLabel = $line->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$count = $query->count();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('GeminiChatbotService: production query failed', [
|
|
||||||
'plant' => $plant->name,
|
|
||||||
'line' => $lineLabel,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return "Sorry, I couldn't fetch production data for {$plant->name}. "
|
|
||||||
. 'Please try again or contact support.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$from = \Carbon\Carbon::parse($dateFrom)->format('d M Y');
|
|
||||||
$to = \Carbon\Carbon::parse($dateTo)->format('d M Y');
|
|
||||||
|
|
||||||
return "📊 Production count for {$plant->name} / {$lineLabel} "
|
|
||||||
. "from {$from} to {$to}: {$count} records.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
// Fuzzy name resolvers
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a user-supplied plant name to the best matching DB row.
|
|
||||||
*
|
|
||||||
* Strategy cascade (stops at first hit):
|
|
||||||
* 1. Exact case-insensitive match → "ransar industries-i" == "Ransar Industries-I"
|
|
||||||
* 2. Normalised LIKE match → strips hyphens/spaces, swaps I↔1
|
|
||||||
* 3. Every significant word present (LIKE) → "ransar unit 2" matches "Ransar Industries Unit 2"
|
|
||||||
* 4. Best token-overlap score → picks the DB row sharing the most words
|
|
||||||
*
|
|
||||||
* @return object|null stdClass with {id, name} or null if no match.
|
|
||||||
*/
|
|
||||||
private function resolvePlant(string $userInput): ?object
|
|
||||||
{
|
|
||||||
$allPlants = DB::table('plants')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->get(['id', 'name']);
|
|
||||||
|
|
||||||
$norm = $this->normaliseForMatching($userInput);
|
|
||||||
|
|
||||||
// ── Strategy 1: exact normalised match ────────────────────────────────
|
|
||||||
foreach ($allPlants as $plant) {
|
|
||||||
if ($this->normaliseForMatching($plant->name) === $norm) {
|
|
||||||
return $plant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Strategy 2: normalised LIKE (user input contained in plant name or vice-versa) ──
|
|
||||||
foreach ($allPlants as $plant) {
|
|
||||||
$dbNorm = $this->normaliseForMatching($plant->name);
|
|
||||||
if (str_contains($dbNorm, $norm) || str_contains($norm, $dbNorm)) {
|
|
||||||
return $plant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Strategy 3: all significant user words appear in the plant name ───
|
|
||||||
$userTokens = $this->significantTokens($norm);
|
|
||||||
|
|
||||||
if (count($userTokens) >= 1) {
|
|
||||||
foreach ($allPlants as $plant) {
|
|
||||||
$dbNorm = $this->normaliseForMatching($plant->name);
|
|
||||||
$allFound = true;
|
|
||||||
foreach ($userTokens as $token) {
|
|
||||||
if (! str_contains($dbNorm, $token)) {
|
|
||||||
$allFound = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($allFound) {
|
|
||||||
return $plant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Strategy 4: best token-overlap score ─────────────────────────────
|
|
||||||
$bestPlant = null;
|
|
||||||
$bestScore = 0;
|
|
||||||
|
|
||||||
foreach ($allPlants as $plant) {
|
|
||||||
$dbTokens = $this->significantTokens($this->normaliseForMatching($plant->name));
|
|
||||||
$shared = count(array_intersect($userTokens, $dbTokens));
|
|
||||||
|
|
||||||
// Require at least half the user tokens to match to avoid false positives
|
|
||||||
$threshold = max(1, (int) ceil(count($userTokens) / 2));
|
|
||||||
|
|
||||||
if ($shared >= $threshold && $shared > $bestScore) {
|
|
||||||
$bestScore = $shared;
|
|
||||||
$bestPlant = $plant;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $bestPlant;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a user-supplied line name within a specific plant.
|
|
||||||
* Uses the same normalisation + token strategies as resolvePlant().
|
|
||||||
*
|
|
||||||
* @return object|null stdClass with {id, name} or null if no match.
|
|
||||||
*/
|
|
||||||
private function resolveLine(string $userInput, int $plantId): ?object
|
|
||||||
{
|
|
||||||
$allLines = DB::table('lines')
|
|
||||||
->whereNull('deleted_at')
|
|
||||||
->where('plant_id', $plantId)
|
|
||||||
->get(['id', 'name']);
|
|
||||||
|
|
||||||
$norm = $this->normaliseForMatching($userInput);
|
|
||||||
|
|
||||||
// Strategy 1: exact normalised
|
|
||||||
foreach ($allLines as $line) {
|
|
||||||
if ($this->normaliseForMatching($line->name) === $norm) {
|
|
||||||
return $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy 2: normalised LIKE
|
|
||||||
foreach ($allLines as $line) {
|
|
||||||
$dbNorm = $this->normaliseForMatching($line->name);
|
|
||||||
if (str_contains($dbNorm, $norm) || str_contains($norm, $dbNorm)) {
|
|
||||||
return $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy 3: all user tokens found in line name
|
|
||||||
$userTokens = $this->significantTokens($norm);
|
|
||||||
|
|
||||||
foreach ($allLines as $line) {
|
|
||||||
$dbNorm = $this->normaliseForMatching($line->name);
|
|
||||||
$allFound = true;
|
|
||||||
foreach ($userTokens as $token) {
|
|
||||||
if (! str_contains($dbNorm, $token)) {
|
|
||||||
$allFound = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($allFound) {
|
|
||||||
return $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy 4: best token-overlap
|
|
||||||
$bestLine = null;
|
|
||||||
$bestScore = 0;
|
|
||||||
|
|
||||||
foreach ($allLines as $line) {
|
|
||||||
$dbTokens = $this->significantTokens($this->normaliseForMatching($line->name));
|
|
||||||
$shared = count(array_intersect($userTokens, $dbTokens));
|
|
||||||
$threshold = max(1, (int) ceil(count($userTokens) / 2));
|
|
||||||
|
|
||||||
if ($shared >= $threshold && $shared > $bestScore) {
|
|
||||||
$bestScore = $shared;
|
|
||||||
$bestLine = $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $bestLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalise a plant/line name for fuzzy comparison:
|
|
||||||
* - lowercase
|
|
||||||
* - replace Roman numeral suffixes I/II/III/IV → 1/2/3/4 (and vice-versa digits → numerals as a canonical form)
|
|
||||||
* - collapse hyphens, underscores, extra spaces into a single space
|
|
||||||
* - strip leading/trailing whitespace
|
|
||||||
*
|
|
||||||
* Both the user input AND the DB value are passed through this before comparing,
|
|
||||||
* so the comparison is always apples-to-apples.
|
|
||||||
*/
|
|
||||||
private function normaliseForMatching(string $value): string
|
|
||||||
{
|
|
||||||
$v = strtolower($value);
|
|
||||||
|
|
||||||
// 1. Punctuation/separators → space
|
|
||||||
$v = str_replace(['-', '_', '.', ','], ' ', $v);
|
|
||||||
|
|
||||||
// 2. Split any letter→digit or digit→letter boundary with a space.
|
|
||||||
// e.g. "industries1" → "industries 1", "unit2" → "unit 2", "2unit" → "2 unit"
|
|
||||||
// This must happen BEFORE Roman numeral conversion so isolated digits are
|
|
||||||
// already separated from words.
|
|
||||||
$v = preg_replace('/([a-z])(\d)/', '$1 $2', $v);
|
|
||||||
$v = preg_replace('/(\d)([a-z])/', '$1 $2', $v);
|
|
||||||
|
|
||||||
// 3. Convert standalone Roman numerals to digits.
|
|
||||||
// Applied AFTER splitting so "industries" is never touched —
|
|
||||||
// the \b boundary ensures only whole tokens are matched.
|
|
||||||
// Order matters: longer patterns first (iii before ii before i).
|
|
||||||
$romanMap = [
|
|
||||||
'/\bviii\b/' => '8',
|
|
||||||
'/\bvii\b/' => '7',
|
|
||||||
'/\bvi\b/' => '6',
|
|
||||||
'/\biv\b/' => '4',
|
|
||||||
'/\biii\b/' => '3',
|
|
||||||
'/\bii\b/' => '2',
|
|
||||||
'/\bv\b/' => '5',
|
|
||||||
'/\bi\b/' => '1', // last — single i only after all others consumed
|
|
||||||
];
|
|
||||||
foreach ($romanMap as $pattern => $digit) {
|
|
||||||
$v = preg_replace($pattern, $digit, $v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Collapse multiple spaces
|
|
||||||
$v = preg_replace('/\s+/', ' ', $v);
|
|
||||||
|
|
||||||
return trim($v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a normalised string into significant tokens (drops noise words).
|
|
||||||
*
|
|
||||||
* @return array<string>
|
|
||||||
*/
|
|
||||||
private function significantTokens(string $normalised): array
|
|
||||||
{
|
|
||||||
$stopWords = ['and', 'the', 'of', 'for', 'at', 'in', 'a'];
|
|
||||||
$tokens = explode(' ', $normalised);
|
|
||||||
|
|
||||||
return array_values(array_filter($tokens, function (string $t) use ($stopWords) {
|
|
||||||
return strlen($t) >= 2 && ! in_array($t, $stopWords, true);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown task — let Gemini respond conversationally.
|
|
||||||
*
|
|
||||||
* If the classification step already produced a useful clarification string
|
|
||||||
* (e.g. "Could you clarify — are you asking about scan status or invoice type?"),
|
|
||||||
* we return that directly without a second API call.
|
|
||||||
* Otherwise we hit Gemini again with a conversational system prompt.
|
|
||||||
*/
|
|
||||||
private function handleUnknown(
|
|
||||||
array $chatHistory,
|
|
||||||
string $userInput,
|
|
||||||
?string $clarificationFromClassifier
|
|
||||||
): string {
|
|
||||||
$reply = $this->callGeminiConversational(
|
|
||||||
$chatHistory,
|
|
||||||
$userInput,
|
|
||||||
$clarificationFromClassifier
|
|
||||||
);
|
|
||||||
|
|
||||||
return $reply
|
|
||||||
?? "I'm not sure I understood that. I can help you with:\n\n"
|
|
||||||
. "• Invoice Status — scan progress of an invoice\n"
|
|
||||||
. "• Invoice Report — serial vs material type for an item\n"
|
|
||||||
. "• Production Report — unit count for a plant / line\n\n"
|
|
||||||
. "What would you like to check?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Models\Item;
|
|
||||||
use App\Models\ItemCharacteristic;
|
use App\Models\ItemCharacteristic;
|
||||||
use App\Models\ProductionQuantity;
|
|
||||||
use App\Models\StickerDetail;
|
use App\Models\StickerDetail;
|
||||||
use App\Models\StickerMappingMaster;
|
|
||||||
use App\Models\StickerStructureDetail;
|
use App\Models\StickerStructureDetail;
|
||||||
use App\Models\StickerValidation;
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use TCPDF;
|
use TCPDF;
|
||||||
|
|
||||||
@@ -452,10 +447,11 @@ class StickerPdfService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return $pdf->Output('sticker.pdf', 'S');
|
// return $pdf->Output('sticker.pdf', 'S');
|
||||||
$pdfContent = $pdf->Output('', 'S'); // 'S' returns string
|
$pdfContent = $pdf->Output('sticker.pdf', 'S');
|
||||||
|
|
||||||
// Encode as base64
|
return (new Response($pdfContent, 200))
|
||||||
return base64_encode($pdfContent);
|
->header('Content-Type', 'application/pdf')
|
||||||
|
->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,11 +788,10 @@ class StickerPdfService
|
|||||||
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generatePdf1(string $stickerId, Collection $dynamicElements, ?ItemCharacteristic $itemCharacteristic, ?string $serialNumber, $serNo)
|
public function generatePdf1(string $stickerId, Collection $dynamicElements, ?ItemCharacteristic $itemCharacteristic, ?string $serialNumber)
|
||||||
{
|
{
|
||||||
|
|
||||||
$dynamicValueMap = [];
|
$dynamicValueMap = [];
|
||||||
$itemCode = $itemCharacteristic?->item?->code ?? '';
|
|
||||||
|
|
||||||
foreach ($dynamicElements as $element) {
|
foreach ($dynamicElements as $element) {
|
||||||
|
|
||||||
@@ -832,29 +827,42 @@ class StickerPdfService
|
|||||||
|
|
||||||
$pdf = new TCPDF('P', 'mm', [$width, $height], true, 'UTF-8', false);
|
$pdf = new TCPDF('P', 'mm', [$width, $height], true, 'UTF-8', false);
|
||||||
|
|
||||||
|
// $pdf->SetMargins(
|
||||||
|
// (float) $structure->sticker_lmargin,
|
||||||
|
// (float) $structure->sticker_tmargin,
|
||||||
|
// (float) $structure->sticker_rmargin,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// //$pdf->SetAutoPageBreak(false, (float) $structure->sticker_bmargin);
|
||||||
|
// $pdf->SetAutoPageBreak(false, (float) $structure->sticker_bmargin);
|
||||||
|
|
||||||
$pdf->setPrintHeader(false);
|
$pdf->setPrintHeader(false);
|
||||||
$pdf->setPrintFooter(false);
|
$pdf->setPrintFooter(false);
|
||||||
|
// $pdf->setCellPaddings(0, 0, 0, 0);
|
||||||
|
// $pdf->setCellMargins(5, 5, 5, 5);
|
||||||
|
|
||||||
|
// Set margins
|
||||||
|
// $pdf->SetMargins(5, 5, 5); // left, top, right
|
||||||
|
|
||||||
$pdf->SetMargins(
|
$pdf->SetMargins(
|
||||||
(float) $structure->sticker_lmargin,
|
(float) $structure->sticker_lmargin,
|
||||||
(float) $structure->sticker_tmargin,
|
(float) $structure->sticker_tmargin,
|
||||||
(float) $structure->sticker_rmargin,
|
(float) $structure->sticker_rmargin,
|
||||||
(float) $structure->sticker_bmargin,
|
|
||||||
);
|
);
|
||||||
$pdf->SetAutoPageBreak(false, 0);
|
$pdf->SetAutoPageBreak(false, 0);
|
||||||
|
|
||||||
$pdf->AddPage();
|
$pdf->AddPage();
|
||||||
|
|
||||||
// if (!empty($serialNumber)) {
|
if (!empty($serialNumber)) {
|
||||||
// $pdf->SetFont('helvetica', 'B', 10);
|
$pdf->SetFont('helvetica', 'B', 10);
|
||||||
// $pdf->SetTextColor(0, 0, 0);
|
$pdf->SetTextColor(0, 0, 0);
|
||||||
|
|
||||||
// // HARD-CODED POSITION (mm)
|
// HARD-CODED POSITION (mm)
|
||||||
// $x = 40; // change as needed
|
$x = 40; // change as needed
|
||||||
// $y = 60; // change as needed
|
$y = 60; // change as needed
|
||||||
|
|
||||||
// $pdf->Text($x, $y, (string) $serialNumber);
|
$pdf->Text($x, $y, (string) $serialNumber);
|
||||||
// }
|
}
|
||||||
|
|
||||||
$pdf->SetFont('helvetica', 'B', 10);
|
$pdf->SetFont('helvetica', 'B', 10);
|
||||||
|
|
||||||
@@ -892,21 +900,6 @@ class StickerPdfService
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'QR':
|
case 'QR':
|
||||||
if (
|
|
||||||
($row->element_type) == 'Dynamic'
|
|
||||||
) {
|
|
||||||
$qrContent = $serNo ?? '';
|
|
||||||
$pdf->write2DBarcode(
|
|
||||||
$qrContent,
|
|
||||||
'QRCODE,H',
|
|
||||||
(float) ($row->qr_x_value ?? 0),
|
|
||||||
(float) ($row->qr_y_value ?? 0),
|
|
||||||
(float) ($row->qr_size ?? 10),
|
|
||||||
(float) ($row->qr_size ?? 10)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$pdf->write2DBarcode(
|
$pdf->write2DBarcode(
|
||||||
$row->qr_value ?? '',
|
$row->qr_value ?? '',
|
||||||
'QRCODE,H',
|
'QRCODE,H',
|
||||||
@@ -916,8 +909,6 @@ class StickerPdfService
|
|||||||
(float) ($row->qr_size ?? 10)
|
(float) ($row->qr_size ?? 10)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case 'Image':
|
case 'Image':
|
||||||
|
|
||||||
@@ -1018,122 +1009,21 @@ class StickerPdfService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// $pdfContent = $pdf->Output('', 'S'); // 'S' returns string
|
// return $pdf->Output('sticker.pdf', 'S');
|
||||||
|
$pdfContent = $pdf->Output('sticker1.pdf', 'S');
|
||||||
|
$filename = "sticker_{$stickerId}_" . time() . ".pdf";
|
||||||
|
|
||||||
// // Encode as base64
|
// return (new Response($pdfContent, 200))
|
||||||
// return base64_encode($pdfContent);
|
// ->header('Content-Type', 'application/pdf');
|
||||||
|
|
||||||
|
|
||||||
// $pdfContent = $pdf->Output('', 'S');
|
|
||||||
|
|
||||||
// return response($pdfContent)
|
|
||||||
// ->header('Content-Type', 'application/pdf')
|
|
||||||
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
||||||
$pdfContent = $pdf->Output('', 'S'); // 'S' returns the PDF as a string
|
return response($pdfContent, 200)
|
||||||
|
|
||||||
// Return the PDF as a response
|
|
||||||
try {
|
|
||||||
$pdfContent = $pdf->Output('', 'S'); // 'S' returns the PDF as a string
|
|
||||||
return response($pdfContent)
|
|
||||||
->header('Content-Type', 'application/pdf')
|
->header('Content-Type', 'application/pdf')
|
||||||
->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
->header('Content-Disposition', 'inline; filename="'.$filename.'"')
|
||||||
} catch (\Exception $e) {
|
->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
Log::error('PDF generation failed: '.$e->getMessage());
|
->header('Pragma', 'no-cache')
|
||||||
abort(500, 'Failed to generate PDF');
|
->header('Expires', '0');
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function generatePdfBySerial($item, $serNo, $plantId, $refNumber)
|
|
||||||
{
|
|
||||||
$recFound = ProductionQuantity::where('plant_id', $plantId)
|
|
||||||
->where('production_order', $refNumber)
|
|
||||||
->where('serial_number', $serNo)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$recFound) {
|
|
||||||
abort(404, 'Serial not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$duplicate = StickerValidation::where('plant_id', $plantId)
|
|
||||||
->where('production_order', $refNumber)
|
|
||||||
->where('serial_number', $serNo)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
$itemC = Item::where('code', $item)
|
|
||||||
->where('plant_id', $plantId)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$itemC) {
|
|
||||||
abort(404, 'Item not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$item = ItemCharacteristic::where('item_id', $itemC->id)
|
|
||||||
->where('plant_id', $plantId)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$item) {
|
|
||||||
abort(404, 'Item characteristic not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$mapping = StickerMappingMaster::where('plant_id', $plantId)
|
|
||||||
->where('item_characteristic_id', $item->id)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$mapping) {
|
|
||||||
abort(404, 'Sticker mapping not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$structure = StickerStructureDetail::findOrFail($mapping->sticker_structure1_id);
|
|
||||||
|
|
||||||
$dynamicElements = StickerDetail::where(
|
|
||||||
'sticker_structure_detail_id',
|
|
||||||
$structure->id
|
|
||||||
)->where('element_type', 'Dynamic')->get();
|
|
||||||
|
|
||||||
return $this->generatePdf1(
|
|
||||||
$structure->sticker_id,
|
|
||||||
$dynamicElements,
|
|
||||||
$item,
|
|
||||||
$serNo,
|
|
||||||
$serNo
|
|
||||||
);
|
|
||||||
|
|
||||||
// return response($pdf)
|
|
||||||
// ->header('Content-Type', 'application/pdf')
|
|
||||||
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
|
|
||||||
}
|
|
||||||
|
|
||||||
// public function printStickersToUSB(array $stickers, int $plantId, ?string $serialNumber)
|
|
||||||
// {
|
|
||||||
// $printerPort = 'USB001';
|
|
||||||
|
|
||||||
// foreach ($stickers as $sticker) {
|
|
||||||
|
|
||||||
// $dynamicElements = StickerDetail::where('sticker_structure_detail_id', $sticker['sticker_id'])
|
|
||||||
// ->get();
|
|
||||||
|
|
||||||
// $itemCharacteristic = ItemCharacteristic::find(
|
|
||||||
// $sticker['item_characteristic']
|
|
||||||
// );
|
|
||||||
|
|
||||||
// $pdfContent = $this->generatePdf1(
|
|
||||||
// $sticker['sticker_id'],
|
|
||||||
// $dynamicElements,
|
|
||||||
// $itemCharacteristic,
|
|
||||||
// $serialNumber
|
|
||||||
// );
|
|
||||||
|
|
||||||
// $handle = fopen("{$printerPort}:", "wb");
|
|
||||||
|
|
||||||
// if (! $handle) {
|
|
||||||
// throw new \Exception("Cannot open printer port {$printerPort}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fwrite($handle, $pdfContent);
|
|
||||||
// fclose($handle);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// private function hexToRgb($hex)
|
// private function hexToRgb($hex)
|
||||||
// {
|
// {
|
||||||
@@ -1144,7 +1034,6 @@ class StickerPdfService
|
|||||||
// hexdec(substr($hex, 4, 2)),
|
// hexdec(substr($hex, 4, 2)),
|
||||||
// ];
|
// ];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private function hexToRgb($hex)
|
private function hexToRgb($hex)
|
||||||
{
|
{
|
||||||
if (! is_string($hex) || ($hex = trim($hex)) === '') {
|
if (! is_string($hex) || ($hex = trim($hex)) === '') {
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ return [
|
|||||||
'token' => env('POSTMARK_TOKEN'),
|
'token' => env('POSTMARK_TOKEN'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'gemini' => [
|
|
||||||
'api_key' => env('GEMINI_API_KEY'),
|
|
||||||
'model' => env('GEMINI_MODEL', 'gemini-3-flash-preview'),
|
|
||||||
],
|
|
||||||
|
|
||||||
'ses' => [
|
'ses' => [
|
||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql = <<<'SQL'
|
|
||||||
ALTER TABLE production_plans
|
|
||||||
ADD COLUMN item_id BIGINT DEFAULT NULL,
|
|
||||||
ADD CONSTRAINT production_plans_item_id_fkey
|
|
||||||
FOREIGN KEY (item_id) REFERENCES items(id);
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('production_plans', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql = <<<'SQL'
|
|
||||||
ALTER TABLE lines
|
|
||||||
ADD COLUMN block_id BIGINT DEFAULT NULL,
|
|
||||||
ADD CONSTRAINT lines_block_id_fkey
|
|
||||||
FOREIGN KEY (block_id) REFERENCES blocks(id);
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('lines', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql1 = <<<'SQL'
|
|
||||||
ALTER TABLE items
|
|
||||||
ADD COLUMN line_id BIGINT DEFAULT NULL,
|
|
||||||
ADD CONSTRAINT items_line_id_fkey
|
|
||||||
FOREIGN KEY (line_id) REFERENCES lines(id);
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql1);
|
|
||||||
|
|
||||||
$sql2 = <<<'SQL'
|
|
||||||
ALTER TABLE items
|
|
||||||
ADD COLUMN line_capacity TEXT DEFAULT NULL
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('items', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql = <<<'SQL'
|
|
||||||
ALTER TABLE production_quantities
|
|
||||||
ADD COLUMN machine_id BIGINT DEFAULT NULL,
|
|
||||||
ADD CONSTRAINT production_quantities_machine_id_fkey
|
|
||||||
FOREIGN KEY (machine_id) REFERENCES machines(id);
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('production_quantities', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql1 = <<<'SQL'
|
|
||||||
ALTER TABLE production_plans
|
|
||||||
ADD COLUMN working_days TEXT DEFAULT NULL
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('production_plans', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql1 = <<<'SQL'
|
|
||||||
ALTER TABLE production_plans
|
|
||||||
ADD COLUMN leave_dates TEXT DEFAULT NULL
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
DB::statement($sql1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Schema::table('production_plans', function (Blueprint $table) {
|
|
||||||
// //
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(subscription)
|
body: JSON.stringify(subscription)
|
||||||
});
|
});
|
||||||
|
|
||||||
alert("Push notifications enabled ✅");
|
alert("Push notifications enabled ✅");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<x-filament-panels::page>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
|
|
||||||
{{-- Render the Select form fields --}}
|
|
||||||
<div class="space-y-4">
|
|
||||||
{{ $this->form }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</x-filament-panels::page>
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</div> --}}
|
</div> --}}
|
||||||
<div class="flex gap-6 -mt-6">
|
<div class="flex gap-6 -mt-6">
|
||||||
<!-- Scan QR Code -->
|
<!-- Scan QR Code -->
|
||||||
<div class="w-full">
|
<div class="w-1/2">
|
||||||
<label for="qr-scan-input" class="block text-sm font-medium text-gray-700 mb-2">
|
<label for="qr-scan-input" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
SCAN QR CODE
|
SCAN QR CODE
|
||||||
</label>
|
</label>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Last Scanned QR -->
|
<!-- Last Scanned QR -->
|
||||||
{{-- <div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<label for="recent-qr-input" class="block text-sm font-medium text-gray-700 mb-2">
|
<label for="recent-qr-input" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
LAST SCANNED QR
|
LAST SCANNED QR
|
||||||
</label>
|
</label>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
readonly
|
readonly
|
||||||
wire:model="recent_qr"
|
wire:model="recent_qr"
|
||||||
/>
|
/>
|
||||||
</div> --}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
<x-filament-panels::page>
|
|
||||||
<div class="space-y-4">
|
|
||||||
|
|
||||||
{{-- Render the Select form fields --}}
|
|
||||||
<div class="space-y-4">
|
|
||||||
{{ $this->form }}
|
|
||||||
</div>
|
|
||||||
<x-filament::button
|
|
||||||
wire:click="export"
|
|
||||||
color="primary"
|
|
||||||
class="mt-4"
|
|
||||||
>
|
|
||||||
Export
|
|
||||||
</x-filament::button>
|
|
||||||
<div class="bg-white shadow rounded-xl p-4 mt-6">
|
|
||||||
<livewire:production-target-plan />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-filament-panels::page>
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<!-- <select id="yearSelect">
|
|
||||||
<option value="">Select Year</option>
|
|
||||||
</select> -->
|
|
||||||
|
|
||||||
|
|
||||||
<div id="calendar" wire:ignore></div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <input type="text" name="working_days" placeholder="Working Days"> -->
|
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.css" rel="stylesheet">
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
|
|
||||||
let selectedDates = [];
|
|
||||||
let calendarEl = document.getElementById('calendar');
|
|
||||||
|
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
||||||
initialView: 'dayGridMonth',
|
|
||||||
height: 600,
|
|
||||||
showNonCurrentDates: true,
|
|
||||||
|
|
||||||
datesSet: function(info) {
|
|
||||||
// Clear previous month selections
|
|
||||||
selectedDates = [];
|
|
||||||
|
|
||||||
// Remove background events
|
|
||||||
calendar.removeAllEvents();
|
|
||||||
|
|
||||||
// Recalculate working days for new month
|
|
||||||
updateWorkingDays(info.view.currentStart);
|
|
||||||
},
|
|
||||||
|
|
||||||
dateClick: function(info) {
|
|
||||||
|
|
||||||
let viewMonth = calendar.view.currentStart.getMonth();
|
|
||||||
let clickedMonth = info.date.getMonth();
|
|
||||||
|
|
||||||
if (viewMonth != clickedMonth) return;
|
|
||||||
|
|
||||||
let dateStr = info.dateStr;
|
|
||||||
|
|
||||||
if (selectedDates.includes(dateStr)) {
|
|
||||||
selectedDates = selectedDates.filter(d => d !== dateStr);
|
|
||||||
|
|
||||||
calendar.getEvents().forEach(event => {
|
|
||||||
if (event.startStr == dateStr) event.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
selectedDates.push(dateStr);
|
|
||||||
|
|
||||||
calendar.addEvent({
|
|
||||||
start: dateStr,
|
|
||||||
display: 'background',
|
|
||||||
color: '#f03f17'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateWorkingDays(info.date);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
||||||
// initialView: 'dayGridMonth',
|
|
||||||
// height: 600,
|
|
||||||
// showNonCurrentDates: true,
|
|
||||||
|
|
||||||
// dateClick: function(info) {
|
|
||||||
|
|
||||||
// let viewMonth = calendar.view.currentStart.getMonth();
|
|
||||||
// let clickedMonth = info.date.getMonth();
|
|
||||||
|
|
||||||
// // let month = info.date.getMonth() + 1; // JS month: 0-11 → 1-12
|
|
||||||
// // let year = info.date.getFullYear();
|
|
||||||
|
|
||||||
// if (viewMonth != clickedMonth) {
|
|
||||||
// return; // Ignore next/prev month dates
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let dateStr = info.dateStr;
|
|
||||||
|
|
||||||
// if (selectedDates.includes(dateStr)) {
|
|
||||||
// selectedDates = selectedDates.filter(d => d !== dateStr);
|
|
||||||
|
|
||||||
// calendar.getEvents().forEach(event => {
|
|
||||||
// if (event.startStr == dateStr) {
|
|
||||||
// event.remove();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// selectedDates.push(dateStr);
|
|
||||||
|
|
||||||
// calendar.addEvent({
|
|
||||||
// start: dateStr,
|
|
||||||
// display: 'background',
|
|
||||||
// color: '#f03f17'
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// updateWorkingDays(info.date);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// yearSelect.addEventListener('change', function () {
|
|
||||||
// let year = this.value;
|
|
||||||
// if (!year) return;
|
|
||||||
|
|
||||||
// let currentDate = calendar.getDate();
|
|
||||||
// let newDate = new Date(year, currentDate.getMonth(), 1);
|
|
||||||
|
|
||||||
// calendar.gotoDate(newDate);
|
|
||||||
// });
|
|
||||||
|
|
||||||
function updateWorkingDays(date) {
|
|
||||||
let totalDays = new Date(
|
|
||||||
date.getFullYear(),
|
|
||||||
date.getMonth()+1,
|
|
||||||
0
|
|
||||||
).getDate();
|
|
||||||
|
|
||||||
let workingDays = totalDays - selectedDates.length;
|
|
||||||
// document.querySelector('input[name="working_days"]').value = workingDays;
|
|
||||||
|
|
||||||
const input = document.querySelector('#working_days');
|
|
||||||
|
|
||||||
input.value = workingDays;
|
|
||||||
|
|
||||||
input.dispatchEvent(new Event('input'));
|
|
||||||
|
|
||||||
const monthInput = document.querySelector('#month');
|
|
||||||
monthInput.value = date.getMonth() + 1; // 1–12 month number
|
|
||||||
monthInput.dispatchEvent(new Event('input'));
|
|
||||||
|
|
||||||
const yearInput = document.querySelector('#year');
|
|
||||||
yearInput.value = date.getFullYear();
|
|
||||||
yearInput.dispatchEvent(new Event('input'));
|
|
||||||
|
|
||||||
const selectedDatesInput = document.querySelector('#selected_dates');
|
|
||||||
selectedDatesInput.value = selectedDates.join(',');
|
|
||||||
selectedDatesInput.dispatchEvent(new Event('input'));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
calendar.render();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="inline-flex items-center px-3 py-1 bg-primary-600 text-white rounded hover:bg-primary-700"
|
|
||||||
wire:click="saveWorkingDays"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,734 +0,0 @@
|
|||||||
<div style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:9999;display:flex;flex-direction:column;align-items:flex-end;gap:0.75rem;">
|
|
||||||
<style>
|
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
|
||||||
@keyframes fadeIn { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
|
|
||||||
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
|
|
||||||
|
|
||||||
.cb-mode-card {
|
|
||||||
background: #111827;
|
|
||||||
border: 1px solid #374151;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color .2s, background .2s;
|
|
||||||
text-align: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.cb-mode-card:hover { border-color: #f59e0b; background: #1a2436; }
|
|
||||||
|
|
||||||
.cb-report-pill {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
padding: 0.4rem 0.875rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
border: 1px solid #374151;
|
|
||||||
background: #111827;
|
|
||||||
color: #9ca3af;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color .15s, background .15s, color .15s;
|
|
||||||
}
|
|
||||||
.cb-report-pill:hover { border-color: #f59e0b; color: #f9fafb; background: #1a2436; }
|
|
||||||
.cb-report-pill.active-prod { border-color: #f59e0b; background: #f59e0b22; color: #f59e0b; }
|
|
||||||
.cb-report-pill.active-inv { border-color: #10b981; background: #10b98122; color: #10b981; }
|
|
||||||
.cb-report-pill.active-inv-stat { border-color: #3b82f6; background: #3b82f622; color: #3b82f6; }
|
|
||||||
|
|
||||||
.cb-chat-bubble-user {
|
|
||||||
align-self: flex-end;
|
|
||||||
background: #f59e0b;
|
|
||||||
color: #1f2937;
|
|
||||||
border-radius: 1rem 1rem 0.25rem 1rem;
|
|
||||||
padding: 0.625rem 0.875rem;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
max-width: 85%;
|
|
||||||
line-height: 1.5;
|
|
||||||
animation: fadeIn .2s ease;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.cb-chat-bubble-assistant {
|
|
||||||
align-self: flex-start;
|
|
||||||
background: #1f2937;
|
|
||||||
border: 1px solid #374151;
|
|
||||||
color: #f9fafb;
|
|
||||||
border-radius: 1rem 1rem 1rem 0.25rem;
|
|
||||||
padding: 0.625rem 0.875rem;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
max-width: 90%;
|
|
||||||
line-height: 1.6;
|
|
||||||
animation: fadeIn .2s ease;
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
.cb-typing-dot {
|
|
||||||
display:inline-block; width:6px; height:6px; border-radius:50%;
|
|
||||||
background:#f59e0b; animation: pulse 1.2s ease infinite;
|
|
||||||
}
|
|
||||||
.cb-typing-dot:nth-child(2) { animation-delay:.2s; }
|
|
||||||
.cb-typing-dot:nth-child(3) { animation-delay:.4s; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{{-- ── Chat Panel ──────────────────────────────────────────────────────── --}}
|
|
||||||
@if($isOpen)
|
|
||||||
<div style="width:380px;background:#1f2937;border-radius:1rem;box-shadow:0 25px 50px -12px rgba(0,0,0,.5);border:1px solid #374151;display:flex;flex-direction:column;overflow:hidden;">
|
|
||||||
|
|
||||||
{{-- ── Header ── --}}
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:0.75rem 1rem;background:#f59e0b;flex-shrink:0;">
|
|
||||||
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#fff;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z" />
|
|
||||||
</svg>
|
|
||||||
<span style="font-weight:600;font-size:0.875rem;color:#fff;">Report Assistant ⒶⓇ</span>
|
|
||||||
|
|
||||||
{{-- Mode badge --}}
|
|
||||||
@if($mode !== 'select')
|
|
||||||
<span style="background:rgba(0,0,0,.2);color:#fff;font-size:0.65rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:.05em;text-transform:uppercase;">
|
|
||||||
{{ $mode }}
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
{{-- Back to mode select (only when in a mode) --}}
|
|
||||||
@if($mode !== 'select')
|
|
||||||
<button wire:click="setMode('select')" title="Switch Mode"
|
|
||||||
style="background:rgba(0,0,0,.2);border:none;cursor:pointer;padding:0.25rem 0.5rem;border-radius:0.375rem;color:#fff;font-size:0.7rem;font-weight:600;display:flex;align-items:center;gap:0.25rem;">
|
|
||||||
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
|
||||||
</svg>
|
|
||||||
Switch
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- Reset --}}
|
|
||||||
<button wire:click="resetForm" title="Reset"
|
|
||||||
style="background:transparent;border:none;cursor:pointer;padding:0.25rem;border-radius:0.25rem;color:#fff;display:flex;align-items:center;">
|
|
||||||
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Close --}}
|
|
||||||
<button wire:click="toggleChat" title="Close"
|
|
||||||
style="background:transparent;border:none;cursor:pointer;padding:0.25rem;border-radius:0.25rem;color:#fff;display:flex;align-items:center;">
|
|
||||||
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── MODE SELECT SCREEN ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($mode === 'select')
|
|
||||||
<div style="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;">
|
|
||||||
|
|
||||||
<div style="text-align:center;">
|
|
||||||
<p style="color:#9ca3af;font-size:0.8125rem;margin:0 0 0.25rem;">How would you like to query?</p>
|
|
||||||
<p style="color:#6b7280;font-size:0.7rem;margin:0;">Choose a mode to get started</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:flex;gap:0.75rem;">
|
|
||||||
|
|
||||||
{{-- Basic card --}}
|
|
||||||
<button wire:click="setMode('basic')" class="cb-mode-card" style="border:none;width:50%;">
|
|
||||||
<div style="display:flex;justify-content:center;margin-bottom:0.625rem;">
|
|
||||||
<div style="background:#f59e0b22;border-radius:0.625rem;padding:0.625rem;">
|
|
||||||
<svg style="width:1.5rem;height:1.5rem;color:#f59e0b;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="color:#f9fafb;font-size:0.875rem;font-weight:700;margin:0 0 0.25rem;">Basic</p>
|
|
||||||
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.4;">Input the required information to generate reports</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Advanced card --}}
|
|
||||||
<button wire:click="setMode('advanced')" class="cb-mode-card" style="border:none;width:50%;">
|
|
||||||
<div style="display:flex;justify-content:center;margin-bottom:0.625rem;">
|
|
||||||
<div style="background:#8b5cf622;border-radius:0.625rem;padding:0.625rem;">
|
|
||||||
<svg style="width:1.5rem;height:1.5rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p style="color:#f9fafb;font-size:0.875rem;font-weight:700;margin:0 0 0.25rem;">Advanced</p>
|
|
||||||
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.4;">Ask anything in plain English — powered by Gemini AI</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Hint --}}
|
|
||||||
<div style="background:#111827;border-radius:0.5rem;padding:0.75rem;border:1px solid #374151;">
|
|
||||||
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.5;">
|
|
||||||
💡 <strong style="color:#9ca3af;">Tip:</strong> Use <em>Advanced</em> to just describe what you need in plain English — Gemini will figure out the rest
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── BASIC MODE ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($mode === 'basic')
|
|
||||||
<div style="padding:1rem;display:flex;flex-direction:column;gap:0.875rem;max-height:480px;overflow-y:auto;">
|
|
||||||
|
|
||||||
{{-- ── Report Type Selector (dropdown) ──────────────────────────── --}}
|
|
||||||
<div style="position:relative;">
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.5rem;">Report Type</label>
|
|
||||||
{{-- Custom wrapper gives us the chevron icon and accent border on selection --}}
|
|
||||||
<div style="position:relative;">
|
|
||||||
<select
|
|
||||||
wire:change="setReportType($event.target.value)"
|
|
||||||
style="
|
|
||||||
width:100%;
|
|
||||||
appearance:none;
|
|
||||||
-webkit-appearance:none;
|
|
||||||
background:#111827;
|
|
||||||
border:1px solid {{ $reportType !== '' ? '#f59e0b' : '#374151' }};
|
|
||||||
border-radius:0.5rem;
|
|
||||||
color:{{ $reportType !== '' ? '#f9fafb' : '#6b7280' }};
|
|
||||||
padding:0.55rem 2.25rem 0.55rem 0.875rem;
|
|
||||||
font-size:0.8125rem;
|
|
||||||
font-weight:{{ $reportType !== '' ? '600' : '400' }};
|
|
||||||
outline:none;
|
|
||||||
cursor:pointer;
|
|
||||||
transition:border-color .15s, color .15s;
|
|
||||||
">
|
|
||||||
<option value="" {{ $reportType === '' ? 'selected' : '' }}>— Select Report Type —</option>
|
|
||||||
<option value="production" {{ $reportType === 'production' ? 'selected' : '' }}>📊 Production Report</option>
|
|
||||||
<option value="invoice" {{ $reportType === 'invoice' ? 'selected' : '' }}>📄 Invoice Report</option>
|
|
||||||
<option value="invoice_status" {{ $reportType === 'invoice_status' ? 'selected' : '' }}>🔍 Invoice Status</option>
|
|
||||||
</select>
|
|
||||||
{{-- Chevron icon --}}
|
|
||||||
<div style="pointer-events:none;position:absolute;right:0.75rem;top:50%;transform:translateY(-50%);">
|
|
||||||
<svg style="width:0.875rem;height:0.875rem;color:{{ $reportType !== '' ? '#f59e0b' : '#6b7280' }};" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- ── Placeholder when no report type selected ─────────────────── --}}
|
|
||||||
@if($reportType === '')
|
|
||||||
<div style="background:#111827;border:1px dashed #374151;border-radius:0.625rem;padding:1.25rem;text-align:center;">
|
|
||||||
<svg style="width:2rem;height:2rem;color:#4b5563;margin:0 auto 0.5rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
|
|
||||||
</svg>
|
|
||||||
<p style="color:#6b7280;font-size:0.8rem;margin:0;line-height:1.5;">Select a report type from the dropdown to get started</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── PRODUCTION REPORT FORM ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($reportType === 'production')
|
|
||||||
|
|
||||||
{{-- Plant --}}
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Plant</label>
|
|
||||||
<select wire:model.live="selectedPlantId"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;">
|
|
||||||
<option value="">— Select Plant —</option>
|
|
||||||
@foreach($plants as $plant)
|
|
||||||
<option value="{{ $plant->id }}">{{ $plant->name }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Line --}}
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">
|
|
||||||
Line <span style="color:#6b7280;font-weight:400;">(optional — leave blank for all lines)</span>
|
|
||||||
</label>
|
|
||||||
<select wire:model.live="selectedLineId"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;"
|
|
||||||
@if(!$selectedPlantId) disabled @endif>
|
|
||||||
<option value="">— All Lines —</option>
|
|
||||||
@foreach($lines as $line)
|
|
||||||
<option value="{{ $line->id }}">{{ $line->name }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
@if(!$selectedPlantId)
|
|
||||||
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Select a plant first</p>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Date Range --}}
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">From</label>
|
|
||||||
<input type="date" wire:model="dateFrom"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">To</label>
|
|
||||||
<input type="date" wire:model="dateTo"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Fetch Button --}}
|
|
||||||
<button wire:click="fetchProduction"
|
|
||||||
wire:loading.attr="disabled"
|
|
||||||
wire:target="fetchProduction"
|
|
||||||
style="width:100%;background:#f59e0b;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
|
|
||||||
<span wire:loading.remove wire:target="fetchProduction" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
|
|
||||||
</svg>
|
|
||||||
Get Production Count
|
|
||||||
</span>
|
|
||||||
<span wire:loading wire:target="fetchProduction" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
|
||||||
</svg>
|
|
||||||
Fetching…
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Production Result --}}
|
|
||||||
@if($hasResult)
|
|
||||||
<div style="background:#111827;border:1px solid #f59e0b;border-radius:0.5rem;padding:0.875rem;display:flex;gap:0.75rem;align-items:flex-start;animation:fadeIn .25s ease;">
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
|
|
||||||
</svg>
|
|
||||||
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.5;margin:0;">{{ $result }}</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@endif {{-- end production --}}
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── INVOICE REPORT FORM (type lookup) ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($reportType === 'invoice')
|
|
||||||
|
|
||||||
{{-- Plant --}}
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Plant</label>
|
|
||||||
<select wire:model.live="invoicePlantId"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;">
|
|
||||||
<option value="">— Select Plant —</option>
|
|
||||||
@foreach($plants as $plant)
|
|
||||||
<option value="{{ $plant->id }}">{{ $plant->name }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Item Code --}}
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Item Code</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
wire:model="invoiceItemCode"
|
|
||||||
wire:keydown.enter="fetchInvoiceReport"
|
|
||||||
placeholder="e.g. 674071"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;font-family:monospace;"
|
|
||||||
/>
|
|
||||||
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Press Enter or click the button below</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Fetch Button --}}
|
|
||||||
<button wire:click="fetchInvoiceReport"
|
|
||||||
wire:loading.attr="disabled"
|
|
||||||
wire:target="fetchInvoiceReport"
|
|
||||||
style="width:100%;background:#10b981;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
|
|
||||||
<span wire:loading.remove wire:target="fetchInvoiceReport" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
|
|
||||||
</svg>
|
|
||||||
Check Invoice Type
|
|
||||||
</span>
|
|
||||||
<span wire:loading wire:target="fetchInvoiceReport" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
|
||||||
</svg>
|
|
||||||
Fetching…
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Invoice Report Result --}}
|
|
||||||
@if($hasInvoiceResult)
|
|
||||||
<div style="background:#111827;border:1px solid #10b981;border-radius:0.5rem;padding:0.875rem;display:flex;gap:0.75rem;align-items:flex-start;animation:fadeIn .25s ease;">
|
|
||||||
@if(str_contains($invoiceResult, 'serial invoice'))
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
|
|
||||||
</svg>
|
|
||||||
@elseif(str_contains($invoiceResult, 'material invoice'))
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
|
||||||
</svg>
|
|
||||||
@elseif(str_contains($invoiceResult, 'does not exist') || str_contains($invoiceResult, 'not found'))
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
|
|
||||||
</svg>
|
|
||||||
@else
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
|
||||||
</svg>
|
|
||||||
@endif
|
|
||||||
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.5;margin:0;">{{ $invoiceResult }}</p>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@endif {{-- end invoice report --}}
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── INVOICE STATUS FORM (scan status) ── NEW ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($reportType === 'invoice_status')
|
|
||||||
|
|
||||||
{{-- Description banner --}}
|
|
||||||
<div style="background:#1e3a5f;border:1px solid #3b82f633;border-radius:0.5rem;padding:0.625rem 0.75rem;display:flex;gap:0.5rem;align-items:flex-start;">
|
|
||||||
<svg style="width:1rem;height:1rem;color:#60a5fa;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
|
||||||
</svg>
|
|
||||||
<p style="font-size:0.7rem;color:#93c5fd;line-height:1.5;margin:0;">
|
|
||||||
Enter an invoice number to see how many serial numbers have been scanned, how many are pending, and the list of unscanned serials.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Invoice Number input --}}
|
|
||||||
<div>
|
|
||||||
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Invoice Number</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
wire:model="invoiceNumber"
|
|
||||||
wire:keydown.enter="fetchInvoiceStatus"
|
|
||||||
placeholder="e.g. 3RA0013333"
|
|
||||||
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;font-family:monospace;letter-spacing:0.03em;"
|
|
||||||
/>
|
|
||||||
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Press Enter or click the button below</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Fetch Button --}}
|
|
||||||
<button wire:click="fetchInvoiceStatus"
|
|
||||||
wire:loading.attr="disabled"
|
|
||||||
wire:target="fetchInvoiceStatus"
|
|
||||||
style="width:100%;background:#3b82f6;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
|
|
||||||
<span wire:loading.remove wire:target="fetchInvoiceStatus" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
|
|
||||||
</svg>
|
|
||||||
Check Scan Status
|
|
||||||
</span>
|
|
||||||
<span wire:loading wire:target="fetchInvoiceStatus" style="display:flex;align-items:center;gap:0.5rem;">
|
|
||||||
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
|
||||||
</svg>
|
|
||||||
Fetching…
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Invoice Status Result --}}
|
|
||||||
@if($hasInvoiceStatusResult)
|
|
||||||
|
|
||||||
@php
|
|
||||||
$sd = $invoiceStatusData;
|
|
||||||
$sdType = $sd['type'] ?? 'error';
|
|
||||||
$sdTotal = $sd['total'] ?? 0;
|
|
||||||
$sdScanned = $sd['scanned'] ?? 0;
|
|
||||||
$sdNot = $sd['not_scanned'] ?? 0;
|
|
||||||
$sdSerials = $sd['unscanned_serials'] ?? [];
|
|
||||||
$sdCount = count($sdSerials);
|
|
||||||
$sdInv = $sd['invoice_number'] ?? '';
|
|
||||||
$isGood = $sdType === 'all_scanned';
|
|
||||||
$isWarn = in_array($sdType, ['error', 'not_found', 'invalid']);
|
|
||||||
$borderCol = $isGood ? '#10b981' : ($isWarn ? '#f59e0b' : '#3b82f6');
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div style="background:#111827;border:1px solid {{ $borderCol }};border-radius:0.5rem;padding:0.875rem;animation:fadeIn .25s ease;">
|
|
||||||
|
|
||||||
{{-- ── Icon + summary row ── --}}
|
|
||||||
<div style="display:flex;gap:0.75rem;align-items:flex-start;">
|
|
||||||
@if($isGood)
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
||||||
</svg>
|
|
||||||
@elseif($isWarn)
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
|
|
||||||
</svg>
|
|
||||||
@else
|
|
||||||
<svg style="width:1.25rem;height:1.25rem;color:#3b82f6;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
|
|
||||||
</svg>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div style="flex:1;min-width:0;">
|
|
||||||
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.6;margin:0;">
|
|
||||||
{{ $sd['message'] ?? $invoiceStatusResult }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{-- ── Count pills (only for real invoice data) ── --}}
|
|
||||||
@if($sdTotal > 0)
|
|
||||||
<div style="display:flex;gap:0.375rem;flex-wrap:wrap;margin-top:0.625rem;">
|
|
||||||
<span style="background:#1e3a2f;border:1px solid #10b98155;color:#6ee7b7;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
|
|
||||||
Total: {{ $sdTotal }}
|
|
||||||
</span>
|
|
||||||
<span style="background:#1a3350;border:1px solid #3b82f655;color:#93c5fd;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
|
|
||||||
✔ Scanned: {{ $sdScanned }}
|
|
||||||
</span>
|
|
||||||
@if($sdNot > 0)
|
|
||||||
<span style="background:#3b1a1a;border:1px solid #ef444455;color:#fca5a5;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
|
|
||||||
✘ Not scanned: {{ $sdNot }}
|
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- ── Unscanned serial numbers section ── --}}
|
|
||||||
@if($sdCount > 0)
|
|
||||||
<div style="margin-top:0.875rem;border-top:1px solid #374151;padding-top:0.75rem;">
|
|
||||||
|
|
||||||
<p style="font-size:0.72rem;font-weight:600;color:#9ca3af;margin:0 0 0.5rem;text-transform:uppercase;letter-spacing:.05em;">
|
|
||||||
Unscanned serial numbers
|
|
||||||
<span style="background:#3b1a1a;color:#fca5a5;border-radius:9999px;padding:0.1rem 0.45rem;font-size:0.68rem;margin-left:0.25rem;">
|
|
||||||
{{ $sdCount }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{{-- Serial chips — first 10, or all when expanded --}}
|
|
||||||
@php
|
|
||||||
$visibleSerials = $showAllUnscanned
|
|
||||||
? $sdSerials
|
|
||||||
: array_slice($sdSerials, 0, 10);
|
|
||||||
$hiddenCount = $sdCount - 10;
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.35rem;
|
|
||||||
@if($showAllUnscanned) max-height:200px;overflow-y:auto;padding-right:2px; @endif">
|
|
||||||
@foreach($visibleSerials as $serial)
|
|
||||||
<span style="
|
|
||||||
display:inline-block;
|
|
||||||
background:#1e293b;
|
|
||||||
border:1px solid #475569;
|
|
||||||
color:#e2e8f0;
|
|
||||||
font-family:monospace;
|
|
||||||
font-size:0.72rem;
|
|
||||||
padding:0.2rem 0.5rem;
|
|
||||||
border-radius:0.3rem;
|
|
||||||
white-space:nowrap;
|
|
||||||
">{{ $serial }}</span>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Show more / Show less button --}}
|
|
||||||
@if($sdCount > 10)
|
|
||||||
<button
|
|
||||||
wire:click="toggleShowAllUnscanned"
|
|
||||||
style="
|
|
||||||
margin-top:0.625rem;
|
|
||||||
background:transparent;
|
|
||||||
border:1px solid #3b82f655;
|
|
||||||
border-radius:0.375rem;
|
|
||||||
color:#60a5fa;
|
|
||||||
font-size:0.75rem;
|
|
||||||
font-weight:600;
|
|
||||||
padding:0.3rem 0.75rem;
|
|
||||||
cursor:pointer;
|
|
||||||
display:flex;
|
|
||||||
align-items:center;
|
|
||||||
gap:0.35rem;
|
|
||||||
transition:background .15s, border-color .15s;
|
|
||||||
"
|
|
||||||
onmouseover="this.style.background='#1e3a5f';this.style.borderColor='#3b82f6';"
|
|
||||||
onmouseout="this.style.background='transparent';this.style.borderColor='#3b82f655';">
|
|
||||||
@if($showAllUnscanned)
|
|
||||||
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
|
|
||||||
</svg>
|
|
||||||
Show less
|
|
||||||
@else
|
|
||||||
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
|
||||||
</svg>
|
|
||||||
Show {{ $hiddenCount }} more…
|
|
||||||
@endif
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@endif {{-- end invoice_status --}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
{{-- ── ADVANCED MODE ── --}}
|
|
||||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
|
||||||
@if($mode === 'advanced')
|
|
||||||
<div style="display:flex;flex-direction:column;height:420px;">
|
|
||||||
|
|
||||||
{{-- ── Conversation area ── --}}
|
|
||||||
<div id="cb-scroll-area"
|
|
||||||
style="flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:0.75rem;scroll-behavior:smooth;"
|
|
||||||
x-data
|
|
||||||
x-init="
|
|
||||||
const el = document.getElementById('cb-scroll-area');
|
|
||||||
const observer = new MutationObserver(() => el.scrollTop = el.scrollHeight);
|
|
||||||
observer.observe(el, { childList:true, subtree:true });
|
|
||||||
">
|
|
||||||
|
|
||||||
@if(empty($chatHistory))
|
|
||||||
{{-- ── Empty state ── --}}
|
|
||||||
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.75rem;padding:1rem 0;">
|
|
||||||
<div style="background:#8b5cf622;border-radius:50%;padding:1rem;">
|
|
||||||
<svg style="width:2rem;height:2rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div style="text-align:center;">
|
|
||||||
<p style="color:#f9fafb;font-size:0.875rem;font-weight:600;margin:0 0 0.25rem;">AI-Powered Assistant</p>
|
|
||||||
<p style="color:#6b7280;font-size:0.75rem;margin:0;line-height:1.5;">Ask anything in plain English — tap an example below to get started</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Example prompt cards --}}
|
|
||||||
<div style="display:flex;flex-direction:column;gap:0.375rem;width:100%;">
|
|
||||||
|
|
||||||
{{-- Card 1 — Invoice scan status --}}
|
|
||||||
<button
|
|
||||||
wire:click="$set('advancedQuestion', 'Is invoice 3RA0013333 fully scanned?')"
|
|
||||||
style="background:#111827;border:1px solid #8b5cf6;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
|
|
||||||
onmouseover="this.style.background='#1a1245'"
|
|
||||||
onmouseout="this.style.background='#111827'">
|
|
||||||
<span style="color:#8b5cf6;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📋 Invoice Scan Status</span>
|
|
||||||
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Is invoice 3RA0013333 fully scanned?"</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Card 2 — Invoice type lookup --}}
|
|
||||||
<button
|
|
||||||
wire:click="$set('advancedQuestion', 'Is item 674071 a serial or material invoice for Chennai plant?')"
|
|
||||||
style="background:#111827;border:1px solid #10b981;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
|
|
||||||
onmouseover="this.style.background='#0d2e24'"
|
|
||||||
onmouseout="this.style.background='#111827'">
|
|
||||||
<span style="color:#10b981;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📄 Invoice Type Lookup</span>
|
|
||||||
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Is item 674071 serial or material for Chennai plant?"</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Card 3 — Production report --}}
|
|
||||||
<button
|
|
||||||
wire:click="$set('advancedQuestion', 'Show production count for Vahinie Unit 2 this month')"
|
|
||||||
style="background:#111827;border:1px solid #f59e0b;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
|
|
||||||
onmouseover="this.style.background='#2a1e05'"
|
|
||||||
onmouseout="this.style.background='#111827'">
|
|
||||||
<span style="color:#f59e0b;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📊 Production Report</span>
|
|
||||||
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Show production count for Vahinie Unit 2 this month"</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- ── Chat bubbles ── --}}
|
|
||||||
@foreach($chatHistory as $message)
|
|
||||||
@if($message['role'] === 'user')
|
|
||||||
<div style="display:flex;justify-content:flex-end;">
|
|
||||||
<div class="cb-chat-bubble-user">{{ $message['content'] }}</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div style="display:flex;justify-content:flex-start;gap:0.5rem;align-items:flex-start;">
|
|
||||||
<div style="background:#8b5cf622;border-radius:50%;padding:0.3rem;flex-shrink:0;margin-top:0.1rem;">
|
|
||||||
<svg style="width:0.875rem;height:0.875rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="cb-chat-bubble-assistant">{{ $message['content'] }}</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
{{-- ── Typing indicator ── --}}
|
|
||||||
@if($isAdvancedLoading)
|
|
||||||
<div style="display:flex;justify-content:flex-start;gap:0.5rem;align-items:flex-start;">
|
|
||||||
<div style="background:#8b5cf622;border-radius:50%;padding:0.3rem;flex-shrink:0;margin-top:0.1rem;">
|
|
||||||
<svg style="width:0.875rem;height:0.875rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="cb-chat-bubble-assistant" style="display:flex;align-items:center;gap:0.375rem;padding:0.625rem 0.875rem;">
|
|
||||||
<span class="cb-typing-dot"></span>
|
|
||||||
<span class="cb-typing-dot"></span>
|
|
||||||
<span class="cb-typing-dot"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- ── Input bar ── --}}
|
|
||||||
<div style="border-top:1px solid #374151;padding:0.75rem;background:#1a2233;flex-shrink:0;">
|
|
||||||
<div style="display:flex;gap:0.5rem;align-items:flex-end;">
|
|
||||||
<textarea
|
|
||||||
wire:model="advancedQuestion"
|
|
||||||
wire:keydown.enter.prevent="askAdvanced"
|
|
||||||
placeholder="Ask anything, e.g. 'Is invoice 3RA0013333 scanned?'"
|
|
||||||
rows="1"
|
|
||||||
style="flex:1;background:#111827;border:1px solid #374151;border-radius:0.625rem;color:#f9fafb;padding:0.625rem 0.75rem;font-size:0.8125rem;outline:none;resize:none;line-height:1.5;font-family:monospace;max-height:80px;overflow-y:auto;"
|
|
||||||
oninput="this.style.height='auto';this.style.height=Math.min(this.scrollHeight,80)+'px'"
|
|
||||||
@if($isAdvancedLoading) disabled @endif
|
|
||||||
></textarea>
|
|
||||||
<button wire:click="askAdvanced"
|
|
||||||
wire:loading.attr="disabled"
|
|
||||||
wire:target="askAdvanced"
|
|
||||||
@if($isAdvancedLoading) disabled @endif
|
|
||||||
style="background:#8b5cf6;border:none;border-radius:0.625rem;padding:0.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;opacity:{{ $isAdvancedLoading ? '.5' : '1' }};">
|
|
||||||
<span wire:loading.remove wire:target="askAdvanced">
|
|
||||||
<svg style="width:1rem;height:1rem;color:#fff;" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span wire:loading wire:target="askAdvanced">
|
|
||||||
<svg style="width:1rem;height:1rem;color:#fff;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p style="color:#4b5563;font-size:0.65rem;margin:0.375rem 0 0;text-align:center;">
|
|
||||||
Press Enter to send · Ask anything in plain English
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- ── FAB Toggle Button ───────────────────────────────────────────────── --}}
|
|
||||||
<button wire:click="toggleChat" title="Production Assistant"
|
|
||||||
style="width:56px;height:56px;background-color:#f59e0b;color:#fff;border-radius:9999px;box-shadow:0 10px 15px -3px rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;border:none;cursor:pointer;">
|
|
||||||
@if($isOpen)
|
|
||||||
<svg style="width:1.5rem;height:1.5rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
@else
|
|
||||||
<svg style="width:1.6rem;height:1.6rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
|
||||||
d="M12 3C7.03 3 3 6.58 3 11c0 2.1.85 4 2.24 5.43L4.5 21l4.86-1.62A9.54 9.54 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" />
|
|
||||||
<circle cx="9" cy="11" r="0.85" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="12" cy="11" r="0.85" fill="currentColor" stroke="none" />
|
|
||||||
<circle cx="15" cy="11" r="0.85" fill="currentColor" stroke="none" />
|
|
||||||
</svg>
|
|
||||||
@endif
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<div class="p-4">
|
|
||||||
<h2 class="text-lg font-bold mb-4 text-gray-700 uppercase tracking-wider">
|
|
||||||
PRODUCTION PLAN TABLE:
|
|
||||||
</h2>
|
|
||||||
<div class="overflow-x-auto rounded-lg shadow">
|
|
||||||
<table class="w-full divide-y divide-gray-200 text-sm text-center">
|
|
||||||
{{-- <thead class="bg-gray-100 text-s font-semibold uppercase text-gray-700">
|
|
||||||
<tr>
|
|
||||||
<th class="border px-4 py-2">No</th>
|
|
||||||
<th class="border px-4 py-2">Created Datetime</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap">Created By</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap">Plant</th>
|
|
||||||
<th class="border px-4 py-2">Line</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap">Item Code</th>
|
|
||||||
<th class="border px-4 py-2">Production Plan Dates</th>
|
|
||||||
</tr>
|
|
||||||
</thead> --}}
|
|
||||||
<thead class="bg-gray-100 text-s font-semibold uppercase text-gray-700">
|
|
||||||
<tr>
|
|
||||||
<th class="border px-4 py-2" rowspan="3">No</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Plant</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Line</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Item Code</th>
|
|
||||||
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap" colspan="{{ count($dates) * 2 }}" class="text-center">
|
|
||||||
Production Plan Dates
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
@foreach($dates as $date)
|
|
||||||
<th colspan="2" class="text-center">
|
|
||||||
{{ $date }}
|
|
||||||
</th>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
@foreach($dates as $date)
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap">Target Plan</th>
|
|
||||||
<th class="border px-4 py-2 whitespace-nowrap">Produced Quantity</th>
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody class="divide-y divide-gray-100">
|
|
||||||
@forelse ($records as $index => $record)
|
|
||||||
<tr class="hover:bg-gray-50">
|
|
||||||
<td class="border px-4 py-2">{{ $index + 1 }}</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['plant_name'] }}</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['line_name'] }}</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['item_code'] }}</td>
|
|
||||||
|
|
||||||
{{-- @foreach($dates as $date)
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['target_plan'][$date] ?? '-' }}</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['production_plan'][$date] ?? '-' }}</td>
|
|
||||||
@endforeach --}}
|
|
||||||
|
|
||||||
@foreach($dates as $date)
|
|
||||||
@if(in_array($date, $leaveDates))
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">-</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">-</td>
|
|
||||||
@else
|
|
||||||
{{-- <td class="border px-4 py-2 whitespace-nowrap">{{ $record['daily_target'] ?? '-' }}</td> --}}
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">
|
|
||||||
{{ $record['daily_target_dynamic'][$date] ?? '-' }}
|
|
||||||
</td>
|
|
||||||
<td class="border px-4 py-2 whitespace-nowrap">
|
|
||||||
{{ $record['produced_quantity'][$date] ?? '-' }}
|
|
||||||
</td>
|
|
||||||
{{-- <td class="border px-4 py-2 whitespace-nowrap">{{ $record['produced_quantity'] ?? '-' }}</td> --}}
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<tr>
|
|
||||||
<td colspan="9" class="px-4 py-4 text-center text-gray-500">
|
|
||||||
No production plan data found.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -25,15 +25,12 @@ use App\Http\Controllers\ObdController;
|
|||||||
use App\Http\Controllers\PalletController;
|
use App\Http\Controllers\PalletController;
|
||||||
use App\Http\Controllers\PdfController;
|
use App\Http\Controllers\PdfController;
|
||||||
use App\Http\Controllers\PlantController;
|
use App\Http\Controllers\PlantController;
|
||||||
use App\Http\Controllers\PrintController;
|
|
||||||
use App\Http\Controllers\ProductionStickerReprintController;
|
use App\Http\Controllers\ProductionStickerReprintController;
|
||||||
use App\Http\Controllers\SapFileController;
|
use App\Http\Controllers\SapFileController;
|
||||||
use App\Http\Controllers\StickerMasterController;
|
use App\Http\Controllers\StickerMasterController;
|
||||||
// use App\Http\Controllers\TelegramController;
|
// use App\Http\Controllers\TelegramController;
|
||||||
use App\Http\Controllers\TestingPanelController;
|
use App\Http\Controllers\TestingPanelController;
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
use App\Models\WebPushSubscription;
|
|
||||||
use Filament\Facades\Filament;
|
|
||||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -188,46 +185,3 @@ Route::post('file/store', [SapFileController::class, 'store'])->name('file.store
|
|||||||
// Route::post('send-telegram', [TelegramController::class, 'sendMessage']);
|
// Route::post('send-telegram', [TelegramController::class, 'sendMessage']);
|
||||||
|
|
||||||
// Route::post('invoice-exit', [InvoiceValidationController::class, 'handle']);
|
// Route::post('invoice-exit', [InvoiceValidationController::class, 'handle']);
|
||||||
|
|
||||||
|
|
||||||
Route::post('/print-pdf', [PrintController::class, 'print']);
|
|
||||||
|
|
||||||
|
|
||||||
Route::post('/push/subscribe', function (Request $request) {
|
|
||||||
|
|
||||||
$user = Filament::auth()->user();
|
|
||||||
abort_if(!$user, 401);
|
|
||||||
|
|
||||||
$request->validate([
|
|
||||||
'endpoint' => 'required|string',
|
|
||||||
'keys.p256dh' => 'required|string',
|
|
||||||
'keys.auth' => 'required|string',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// WebPushSubscription::updateOrCreate(
|
|
||||||
// ['endpoint' => $request->endpoint],
|
|
||||||
// [
|
|
||||||
// 'subscribable_type' => get_class($user),
|
|
||||||
// 'subscribable_id' => $user->id,
|
|
||||||
// 'public_key' => $request->keys['p256dh'],
|
|
||||||
// 'auth_token' => $request->keys['auth'],
|
|
||||||
// 'content_encoding' => $request->contentEncoding ?? 'aesgcm',
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
|
|
||||||
WebPushSubscription::updateOrCreate(
|
|
||||||
[
|
|
||||||
'endpoint' => $request->endpoint,
|
|
||||||
'subscribable_type' => get_class($user),
|
|
||||||
'subscribable_id' => $user->id,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'public_key' => $request->keys['p256dh'],
|
|
||||||
'auth_token' => $request->keys['auth'],
|
|
||||||
'content_encoding' => $request->contentEncoding ?? 'aesgcm',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return response()->json(['success' => true]);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\PdfController;
|
use App\Http\Controllers\PdfController;
|
||||||
use App\Http\Controllers\PrintController;
|
|
||||||
use App\Mail\test;
|
use App\Mail\test;
|
||||||
use App\Models\EquipmentMaster;
|
use App\Models\EquipmentMaster;
|
||||||
use App\Models\InvoiceValidation;
|
use App\Models\InvoiceValidation;
|
||||||
@@ -14,14 +13,11 @@ use Illuminate\Support\Facades\Route;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use thiagoalessio\TesseractOCR\TesseractOCR;
|
use thiagoalessio\TesseractOCR\TesseractOCR;
|
||||||
use App\Http\Livewire\CustomLogin;
|
use App\Http\Livewire\CustomLogin;
|
||||||
use App\Services\StickerPdfService;
|
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return redirect('/admin');
|
return redirect('/admin');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/sticker/pdf/{item}/{serial}/{plantId}/{po}', [PrintController::class, 'generate']);
|
|
||||||
|
|
||||||
// Route::get('/admin', function () {
|
// Route::get('/admin', function () {
|
||||||
// return redirect('/admin/welcome');
|
// return redirect('/admin/welcome');
|
||||||
// });
|
// });
|
||||||
@@ -63,9 +59,10 @@ use App\Services\StickerPdfService;
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
WebPushSubscription::updateOrCreate(
|
WebPushSubscription::updateOrCreate(
|
||||||
|
// ✅ UNIQUE PER DEVICE
|
||||||
['endpoint' => $request->endpoint],
|
['endpoint' => $request->endpoint],
|
||||||
[
|
[
|
||||||
'subscribable_type' => get_class($user),
|
'subscribable_type' => get_class($user), // 🔥 important
|
||||||
'subscribable_id' => $user->id,
|
'subscribable_id' => $user->id,
|
||||||
'public_key' => $request->keys['p256dh'],
|
'public_key' => $request->keys['p256dh'],
|
||||||
'auth_token' => $request->keys['auth'],
|
'auth_token' => $request->keys['auth'],
|
||||||
@@ -73,20 +70,6 @@ use App\Services\StickerPdfService;
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// WebPushSubscription::updateOrCreate(
|
|
||||||
// [
|
|
||||||
// 'endpoint' => $request->endpoint,
|
|
||||||
// 'subscribable_type' => get_class($user),
|
|
||||||
// 'subscribable_id' => $user->id,
|
|
||||||
// ],
|
|
||||||
// [
|
|
||||||
// 'public_key' => $request->keys['p256dh'],
|
|
||||||
// 'auth_token' => $request->keys['auth'],
|
|
||||||
// 'content_encoding' => $request->contentEncoding ?? 'aesgcm',
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
|
||||||
return response()->json(['success' => true]);
|
return response()->json(['success' => true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user