'/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 = // ───────────────────────────────────────────────────────────────────────── /** * Looks up scan status for an invoice number in invoice_validations. */ private function handleInvoice(string $invoiceNumber, string $_unused = ''): string { // Strip any whitespace within the invoice number itself $invoiceNumber = preg_replace('/\s+/', '', $invoiceNumber); if (empty($invoiceNumber)) { return 'Please provide a valid invoice number. Example: invoice = 3RA0013333'; } 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 "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. " . 'Please try again or contact support if this keeps happening.'; } if (empty($rows)) { return "No records found for invoice number {$invoiceNumber}. " . 'Please double-check the invoice number and try again.'; } return $this->formatInvoiceResponse($invoiceNumber, $rows); } /** * Turn the raw DB rows into a plain-English summary. */ private function formatInvoiceResponse(string $invoiceNumber, array $rows): string { $totalScanned = 0; $totalNotScanned = 0; $unscannedList = null; foreach ($rows as $row) { if ($row->status === 'not scanned') { $totalNotScanned = (int) $row->total_count; $unscannedList = $row->serial_numbers_not_scanned; } else { $totalScanned += (int) $row->total_count; } } $grandTotal = $totalScanned + $totalNotScanned; $itemWord = $grandTotal === 1 ? 'serial number' : 'serial numbers'; // ── All scanned ─────────────────────────────────────────────────────── if ($totalNotScanned === 0) { return "For invoice number {$invoiceNumber}, all {$grandTotal} {$itemWord} " . ($grandTotal === 1 ? 'has' : 'have') . ' been scanned. ✅'; } // ── None scanned ────────────────────────────────────────────────────── if ($totalScanned === 0) { $msg = "For invoice number {$invoiceNumber}, there " . ($grandTotal === 1 ? 'is' : 'are') . " {$grandTotal} {$itemWord} " . 'and none have been scanned.'; if ($unscannedList) { $msg .= " Unscanned serial numbers are: {$unscannedList}."; } return $msg; } // ── Mixed ───────────────────────────────────────────────────────────── $msg = "For invoice number {$invoiceNumber}, there " . ($grandTotal === 1 ? 'is' : 'are') . " {$grandTotal} {$itemWord} in total. " . "Out of which {$totalScanned} " . ($totalScanned === 1 ? 'has' : 'have') . ' been scanned and ' . "{$totalNotScanned} " . ($totalNotScanned === 1 ? 'has' : 'have') . ' not been scanned.'; if ($unscannedList) { $msg .= " Unscanned serial numbers are: {$unscannedList}."; } return $msg; } // ───────────────────────────────────────────────────────────────────────── // Handler: item = plant = // ───────────────────────────────────────────────────────────────────────── /** * 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.'; } }