143 Commits

Author SHA1 Message Date
dhanabalan
2dd3b35eb5 added profile logo in visitor
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 15:56:46 +05:30
dhanabalan
4129a7a251 added default avatar ui image in visitor
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 15:31:10 +05:30
dhanabalan
d6aea93d00 removed default image url in visitor
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 15:28:23 +05:30
dhanabalan
fb91bea7c4 changed logic in visito entry
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-26 15:27:25 +05:30
dhanabalan
f970d6eb0f Changed logic in create visitor entry
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 15:14:14 +05:30
dhanabalan
b567f3ee02 Changed image path of visitor photos
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 14:33:06 +05:30
dhanabalan
e7fa446fc3 changed logic in create visitor
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-26 14:21:09 +05:30
dhanabalan
f399808249 Added photo column in table section
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-25 17:20:32 +05:30
dhanabalan
575d3675cf Added logic to show captured image
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-25 15:55:45 +05:30
dhanabalan
7435928fcf changed logic in livewire
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-25 15:48:42 +05:30
dhanabalan
d6acded332 Added capture photo logic in livewire
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-25 15:46:47 +05:30
dhanabalan
915a766aa0 Added visitor entry resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:45:42 +05:30
dhanabalan
e123dd65b5 Added visitor entry model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:44:35 +05:30
dhanabalan
f420f05179 Added visitor entry migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:44:01 +05:30
dhanabalan
84f165beea Added employee master importer and exporter pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-25 15:41:18 +05:30
dhanabalan
8d397f11e1 Added employee master resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:40:48 +05:30
dhanabalan
19feaba66a Added employee master model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:39:47 +05:30
dhanabalan
912b793e8d Added employee master migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-05-25 15:39:23 +05:30
dhanabalan
ccd922b5bb modified logic in chat bot
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-20 12:20:22 +05:30
dhanabalan
203d09712b Added chat bot pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-05-20 11:21:57 +05:30
dhanabalan
d9f531fbc6 Chnaged logic for showing qr code based on content
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-04-27 16:25:32 +05:30
dhanabalan
142094fd00 Added PrintController and updated route for sticker PDF generation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-04-27 14:09:17 +05:30
dhanabalan
ab86517fc1 Changed logic in sticker pdf service
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-04-27 14:08:17 +05:30
dhanabalan
f66839131e Changed printer logic in sticker validation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-04-27 11:06:38 +05:30
dhanabalan
7244028d02 Added category in item resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-03-21 14:38:27 +05:30
dhanabalan
8104da98d4 added production quantity in production plan resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m32s
Laravel Pint / pint (pull_request) Failing after 2m41s
2026-02-21 14:23:22 +05:30
dhanabalan
1d51067355 Removed production quantity in prodcution plan resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 22s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 28s
Laravel Larastan / larastan (pull_request) Failing after 2m36s
Laravel Pint / pint (pull_request) Failing after 3m1s
2026-02-21 10:00:47 +05:30
dhanabalan
1fd2538048 Chnaged logic in production quantity page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 16:25:14 +05:30
dhanabalan
d8fdf02417 Changed logic in export of production plan
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 15:14:46 +05:30
dhanabalan
1031a972de Changed logic for showing data in dashboard in production quantity
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 14:17:22 +05:30
dhanabalan
f5173d9861 Changed logic in production quantity page for last scanned qr
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 13:00:55 +05:30
dhanabalan
9602be624d Changed logic in production quantity page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 12:29:02 +05:30
dhanabalan
85c7a3e286 Added line_id and line_capacity in item model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:50:24 +05:30
dhanabalan
6071c8b898 Added machine id column in production quantity model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:48:33 +05:30
dhanabalan
9bf5337383 Added block_id column in line model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:47:01 +05:30
dhanabalan
621cf13565 Added columns in production plan model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:44:53 +05:30
dhanabalan
6c334359b2 Added Block in line resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:41:34 +05:30
dhanabalan
fd444a7749 Changed logic in production plan resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:29:25 +05:30
dhanabalan
f7a421681e Changed logic against item_id in prouction plan importer
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:28:12 +05:30
dhanabalan
8bd64c80d4 Added production plan export in production target page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:26:20 +05:30
dhanabalan
c4177887d6 Changed logic in production quantity page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:25:15 +05:30
dhanabalan
09772d1984 Added production target screen pages and livewire pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:16:08 +05:30
dhanabalan
3376d35eaa Added Production Calendar screen page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:14:03 +05:30
dhanabalan
8feb2fc612 Added leave dates column to production plan table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:09:38 +05:30
dhanabalan
04d472805f Added working days column in production plan table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:08:27 +05:30
dhanabalan
f9d2e14210 Added machine_id foreign key in production quantities table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:06:40 +05:30
dhanabalan
13bef51af5 Added line_id and line_capacity column in items table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:05:13 +05:30
dhanabalan
1711ce5646 Added block id foreign key in lines table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 11:03:11 +05:30
dhanabalan
b7da185912 Added Item id foreign key in production plans table
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-02-16 10:59:47 +05:30
dhanabalan
4a796a670a Refactor WebPushSubscription logic to streamline subscription creation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-31 18:05:10 +05:30
dhanabalan
4577f67d0a Fix CSRF token handling and update fetch URL for push subscription
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
2026-01-31 18:04:26 +05:30
dhanabalan
be2151a072 Fix fetch URL in push notification subscription to include leading slash
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-31 18:01:42 +05:30
dhanabalan
a4251ae532 Add print PDF route and update WebPushSubscription logic for user subscriptions
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-31 18:00:36 +05:30
dhanabalan
93d55765ae Remove unused CSRF token in push notification registration script
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-31 17:59:00 +05:30
dhanabalan
17d54cc52e Refactor push notification logic in afterSave method to streamline subscription checks and improve logging
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-30 12:21:28 +05:30
dhanabalan
61467d88cd Enhance push notification logic in afterSave method to handle missing subscriptions and log warnings
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-30 12:13:01 +05:30
dhanabalan
af0b17e674 Fix push subscriptions retrieval in afterSave method
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-30 12:07:59 +05:30
dhanabalan
dd7111a8d9 Refactor push notification logic in afterSave method
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-30 12:05:49 +05:30
dhanabalan
464ee6c3c7 Added web subscription logic in web.php
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Has been cancelled
Laravel Larastan / larastan (pull_request) Has been cancelled
Laravel Pint / pint (pull_request) Has been cancelled
2026-01-30 11:53:23 +05:30
dhanabalan
984d686182 added logs in edit transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Laravel Larastan / larastan (pull_request) Failing after 3m47s
Laravel Pint / pint (pull_request) Failing after 3m59s
2026-01-28 13:13:43 +05:30
dhanabalan
fd87748a38 changed message in edit transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 4m47s
Laravel Pint / pint (pull_request) Failing after 4m53s
2026-01-27 14:24:02 +05:30
dhanabalan
f9aa6cd1ba chnaged logic in rfq transporter
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Larastan / larastan (pull_request) Failing after 5m19s
Laravel Pint / pint (pull_request) Failing after 6m39s
2026-01-27 14:17:58 +05:30
dhanabalan
d743b2df26 chnaged logic in rfq transporter
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 17s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Laravel Larastan / larastan (pull_request) Failing after 6m12s
Laravel Pint / pint (pull_request) Failing after 6m58s
2026-01-27 14:15:50 +05:30
dhanabalan
814281a6bf changed logic in edit rfq transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Larastan / larastan (pull_request) Failing after 5m14s
Laravel Pint / pint (pull_request) Failing after 5m12s
2026-01-27 14:14:41 +05:30
dhanabalan
dc445b17f5 changed logic in edit transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Pint / pint (pull_request) Failing after 3m47s
Laravel Larastan / larastan (pull_request) Failing after 4m43s
2026-01-27 14:10:20 +05:30
dhanabalan
cd553651f3 removed dd in edit transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Pint / pint (pull_request) Failing after 6m4s
Laravel Larastan / larastan (pull_request) Failing after 8m28s
2026-01-27 12:41:06 +05:30
dhanabalan
ac20e96358 Added dd in edit transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 20s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Pint / pint (pull_request) Failing after 5m21s
Laravel Larastan / larastan (pull_request) Failing after 5m34s
2026-01-27 12:32:42 +05:30
dhanabalan
0fb9c91b28 changed logic in edit transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Larastan / larastan (pull_request) Failing after 6m8s
Laravel Pint / pint (pull_request) Failing after 6m19s
2026-01-27 12:12:08 +05:30
dhanabalan
21d602d86a removed dd in edit transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Laravel Pint / pint (pull_request) Failing after 5m14s
Laravel Larastan / larastan (pull_request) Failing after 5m54s
2026-01-27 12:04:03 +05:30
dhanabalan
06628072dc removed _id in model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 21s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 7m0s
Laravel Pint / pint (pull_request) Failing after 7m34s
2026-01-27 12:02:35 +05:30
dhanabalan
cdf9f60ffd changed logic in edit transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Pint / pint (pull_request) Failing after 6m3s
Laravel Larastan / larastan (pull_request) Failing after 6m40s
2026-01-27 11:59:33 +05:30
dhanabalan
b419a538dc changed logic in user model
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 14s
Laravel Larastan / larastan (pull_request) Failing after 6m20s
Laravel Pint / pint (pull_request) Failing after 6m39s
2026-01-27 11:54:02 +05:30
dhanabalan
fea15e0d1b added dd in edit transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 7m54s
Laravel Pint / pint (pull_request) Failing after 7m53s
2026-01-27 11:51:50 +05:30
dhanabalan
f5a1e453d5 changed logic in edit transporter bid page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 6m24s
Laravel Pint / pint (pull_request) Failing after 6m29s
2026-01-27 11:44:05 +05:30
dhanabalan
fd6149ccbe Added PushSubscription method in the user model
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 20s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Pint / pint (pull_request) Failing after 7m31s
Laravel Larastan / larastan (pull_request) Failing after 8m57s
2026-01-27 11:42:40 +05:30
dhanabalan
085a4f72fa chnaged logic in edit transporter bid page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 11m18s
Laravel Pint / pint (pull_request) Failing after 11m30s
2026-01-27 11:41:44 +05:30
dhanabalan
6b41a27d31 changed registerPush method logic in push.js
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Laravel Pint / pint (pull_request) Failing after 8m22s
Laravel Larastan / larastan (pull_request) Failing after 12m1s
2026-01-27 11:24:22 +05:30
dhanabalan
648b676453 change dlogin in web.php and added web push subscription model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 12m59s
Laravel Pint / pint (pull_request) Failing after 13m9s
2026-01-27 11:01:28 +05:30
dhanabalan
6f4d81025b changed logic in edit transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 4m44s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 4m56s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 9m23s
Laravel Larastan / larastan (pull_request) Failing after 7m9s
Laravel Pint / pint (pull_request) Failing after 21m44s
2026-01-27 10:30:48 +05:30
dhanabalan
42bbad16aa changed design for notification settings
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Pint / pint (pull_request) Failing after 2m19s
Laravel Larastan / larastan (pull_request) Failing after 2m21s
2026-01-24 16:39:49 +05:30
dhanabalan
3fcc2de515 changed logic in rfq transporter
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Pint / pint (pull_request) Failing after 2m43s
Laravel Larastan / larastan (pull_request) Failing after 2m58s
2026-01-24 16:25:12 +05:30
dhanabalan
d14200e40e Added after create logic inside the rfq transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m14s
Laravel Pint / pint (pull_request) Failing after 2m15s
2026-01-24 15:36:56 +05:30
dhanabalan
a34322c87a Added after craete method in transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 2m27s
Laravel Pint / pint (pull_request) Failing after 2m42s
2026-01-24 15:06:54 +05:30
dhanabalan
547d73e1f4 changed logic in transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Pint / pint (pull_request) Failing after 2m18s
Laravel Larastan / larastan (pull_request) Failing after 2m22s
2026-01-24 15:00:41 +05:30
dhanabalan
d93d55bb69 removed dd in push notification
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Larastan / larastan (pull_request) Failing after 2m25s
Laravel Pint / pint (pull_request) Failing after 2m35s
2026-01-24 14:55:15 +05:30
dhanabalan
ad00321dff changed logic in bid transporter page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m19s
Laravel Pint / pint (pull_request) Failing after 2m29s
2026-01-24 14:53:56 +05:30
dhanabalan
d333324935 Addded dd fior checking web push
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 2m34s
Laravel Pint / pint (pull_request) Failing after 2m33s
2026-01-24 14:30:44 +05:30
dhanabalan
a1b8cc0eed changed logic in transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Laravel Larastan / larastan (pull_request) Failing after 2m8s
Laravel Pint / pint (pull_request) Failing after 2m26s
2026-01-24 13:06:45 +05:30
dhanabalan
d2a2a35410 changed logic in push alert notification page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Laravel Pint / pint (pull_request) Failing after 2m14s
Laravel Larastan / larastan (pull_request) Failing after 2m18s
2026-01-24 12:58:57 +05:30
dhanabalan
28503a25c3 chanaged logic for rfq transporter bid
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Laravel Pint / pint (pull_request) Failing after 2m20s
Laravel Larastan / larastan (pull_request) Failing after 2m22s
2026-01-24 12:41:06 +05:30
dhanabalan
20b17d446b changed logic in manifest.json
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m9s
Laravel Pint / pint (pull_request) Failing after 2m34s
2026-01-24 12:16:40 +05:30
dhanabalan
0de50e12b8 Added gcm sender id in manifest.json
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 16s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Pint / pint (pull_request) Failing after 2m8s
Laravel Larastan / larastan (pull_request) Failing after 2m19s
2026-01-24 12:12:04 +05:30
dhanabalan
9767e0547d Added service worker js
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m52s
Laravel Pint / pint (pull_request) Failing after 2m29s
2026-01-24 11:34:50 +05:30
dhanabalan
92da8af6d2 changed logic in service worker.js
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m31s
Laravel Pint / pint (pull_request) Failing after 2m54s
2026-01-24 11:28:29 +05:30
dhanabalan
1adfd59a0e Added has push subscriptions in user model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Pint / pint (pull_request) Failing after 2m26s
Laravel Larastan / larastan (pull_request) Failing after 2m35s
2026-01-24 10:17:32 +05:30
dhanabalan
92bf1ee401 changed logic in invoice validation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m33s
Laravel Pint / pint (pull_request) Failing after 2m34s
2026-01-24 10:13:24 +05:30
dhanabalan
99a605196d Added web push route in web.php
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Larastan / larastan (pull_request) Failing after 2m15s
Laravel Pint / pint (pull_request) Failing after 2m53s
2026-01-24 10:10:31 +05:30
dhanabalan
e075a510d9 Added push aert notification page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m3s
Laravel Pint / pint (pull_request) Failing after 2m9s
2026-01-24 10:05:23 +05:30
dhanabalan
2a7d012ec8 Added push alert notification sample
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Pint / pint (pull_request) Failing after 2m24s
Laravel Larastan / larastan (pull_request) Failing after 7m9s
2026-01-24 10:04:01 +05:30
dhanabalan
d63e8f7a37 added web push notification in pwa
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m11s
Laravel Larastan / larastan (pull_request) Failing after 2m28s
2026-01-24 09:55:23 +05:30
dhanabalan
9c4679a5a5 added package for web push notification
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Larastan / larastan (pull_request) Failing after 2m12s
Laravel Pint / pint (pull_request) Failing after 2m12s
2026-01-24 09:52:59 +05:30
dhanabalan
f6f3ab803c changed logic in push.js
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Larastan / larastan (pull_request) Failing after 2m23s
Laravel Pint / pint (pull_request) Failing after 2m46s
2026-01-24 09:47:32 +05:30
dhanabalan
64b5c129ee Added service worker js and push.js
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Larastan / larastan (pull_request) Failing after 2m28s
Laravel Pint / pint (pull_request) Failing after 2m37s
2026-01-23 18:16:05 +05:30
dhanabalan
955359b92c registered push.js in admin panel provider
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m31s
Laravel Pint / pint (pull_request) Failing after 2m42s
2026-01-23 18:12:54 +05:30
dhanabalan
3ffc59f0cf Added notification settings page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Pint / pint (pull_request) Failing after 2m43s
Laravel Larastan / larastan (pull_request) Failing after 2m47s
2026-01-23 18:11:00 +05:30
dhanabalan
9b5e66c834 removed route logic
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 16s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 24s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Laravel Larastan / larastan (pull_request) Failing after 2m34s
Laravel Pint / pint (pull_request) Failing after 2m28s
2026-01-23 17:01:04 +05:30
dhanabalan
7dbeb0afd3 Added admin welcome page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Laravel Pint / pint (pull_request) Failing after 2m36s
Laravel Larastan / larastan (pull_request) Failing after 2m54s
2026-01-23 16:50:07 +05:30
dhanabalan
fdfa5d0cf1 Added welcome page in cri
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Larastan / larastan (pull_request) Failing after 2m25s
Laravel Pint / pint (pull_request) Failing after 2m27s
2026-01-23 16:32:02 +05:30
dhanabalan
fce45b5386 changed logic in pwa install
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Pint / pint (pull_request) Failing after 2m33s
Laravel Larastan / larastan (pull_request) Failing after 2m37s
2026-01-23 15:39:48 +05:30
dhanabalan
6a62dce305 Added sticker validation policy file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Larastan / larastan (pull_request) Failing after 2m36s
Laravel Pint / pint (pull_request) Failing after 2m34s
2026-01-23 15:30:20 +05:30
dhanabalan
9ae6dede23 changed logic in rfq chart
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
2026-01-23 15:11:41 +05:30
dhanabalan
2f470c5c54 changed logic in rfq transporter bid resource page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m45s
Laravel Larastan / larastan (pull_request) Failing after 2m57s
2026-01-23 15:10:16 +05:30
dhanabalan
529850f0b9 Added spot rate transport master policy file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m29s
Laravel Pint / pint (pull_request) Failing after 2m39s
2026-01-23 15:09:00 +05:30
dhanabalan
61637bc3d5 Added request quotation policy file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 13s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Pint / pint (pull_request) Failing after 2m35s
Laravel Larastan / larastan (pull_request) Failing after 2m56s
2026-01-23 15:07:54 +05:30
dhanabalan
c248a42a9d Added rfq transporter bid policy file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Laravel Larastan / larastan (pull_request) Failing after 2m22s
Laravel Pint / pint (pull_request) Failing after 2m37s
2026-01-23 15:07:05 +05:30
dhanabalan
aeb49c40eb Added rfq chart and rfq rank chart widgets page
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Laravel Larastan / larastan (pull_request) Failing after 2m23s
Laravel Pint / pint (pull_request) Failing after 2m30s
2026-01-23 12:57:18 +05:30
dhanabalan
8fc963dc0a Added rfq dashboard and rfq overview pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m24s
Laravel Pint / pint (pull_request) Failing after 2m35s
2026-01-23 12:56:11 +05:30
dhanabalan
14844f1e1e Added rfq transporter bid resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Pint / pint (pull_request) Failing after 2m42s
Laravel Larastan / larastan (pull_request) Failing after 3m9s
2026-01-23 12:53:17 +05:30
dhanabalan
cfc5845768 Added rfq transporter bid model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m26s
Laravel Pint / pint (pull_request) Failing after 2m46s
2026-01-23 12:51:46 +05:30
dhanabalan
f0a6f924d9 Added rfq transporter bid migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m36s
Laravel Pint / pint (pull_request) Failing after 2m24s
2026-01-23 12:50:34 +05:30
dhanabalan
f6f9730587 Added request quotation resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Laravel Larastan / larastan (pull_request) Failing after 2m37s
Laravel Pint / pint (pull_request) Failing after 3m16s
2026-01-23 12:47:53 +05:30
dhanabalan
b42f5ffe84 Added request quotation model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m49s
Laravel Pint / pint (pull_request) Failing after 2m36s
2026-01-23 12:46:11 +05:30
dhanabalan
376899e277 added request quotation migartion file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m31s
Laravel Larastan / larastan (pull_request) Failing after 3m33s
2026-01-23 12:44:55 +05:30
dhanabalan
e53fb15c01 Added spot rate transport master resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m42s
Laravel Pint / pint (pull_request) Failing after 2m35s
2026-01-23 12:40:53 +05:30
dhanabalan
953999459e Added spot rate master model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Pint / pint (pull_request) Failing after 2m34s
Laravel Larastan / larastan (pull_request) Failing after 3m1s
2026-01-23 12:37:59 +05:30
dhanabalan
d2ab947b1d Added spot rate transport master migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Laravel Pint / pint (pull_request) Failing after 2m28s
Laravel Larastan / larastan (pull_request) Failing after 2m39s
2026-01-23 11:39:26 +05:30
dhanabalan
9b965d5a2b changed logic for safari browser pwa
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 9s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Larastan / larastan (pull_request) Failing after 2m7s
Laravel Pint / pint (pull_request) Failing after 2m15s
2026-01-01 14:04:46 +05:30
dhanabalan
f48de19e0d Added logo for the application of pwa
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Laravel Larastan / larastan (pull_request) Failing after 2m3s
Laravel Pint / pint (pull_request) Failing after 2m3s
2026-01-01 11:19:38 +05:30
dhanabalan
48ce416cbf Added manifest.json and changed name of teh application
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Laravel Pint / pint (pull_request) Failing after 2m5s
Laravel Larastan / larastan (pull_request) Failing after 2m19s
2026-01-01 10:44:01 +05:30
dhanabalan
39b5cf77e4 changed logic for kiosk mode to take print out in sticker validation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 15s
Laravel Larastan / larastan (pull_request) Failing after 2m17s
Laravel Pint / pint (pull_request) Failing after 2m19s
2025-12-30 16:36:26 +05:30
dhanabalan
46649d0459 Added pdf generate method for sticker validation
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Pint / pint (pull_request) Failing after 2m38s
Laravel Larastan / larastan (pull_request) Failing after 13m18s
2025-12-30 14:49:11 +05:30
dhanabalan
2f593326e5 Added sticker validation livewire pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m8s
Laravel Pint / pint (pull_request) Failing after 2m23s
2025-12-30 14:28:49 +05:30
dhanabalan
bb9c344e5b Added sticker validation blade file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Larastan / larastan (pull_request) Failing after 2m14s
Laravel Pint / pint (pull_request) Failing after 2m8s
2025-12-30 14:27:37 +05:30
dhanabalan
10e595a4a3 Added Sticker Validation resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Pint / pint (pull_request) Failing after 2m39s
Laravel Larastan / larastan (pull_request) Failing after 2m57s
2025-12-30 14:26:13 +05:30
dhanabalan
31ab56d46c Added Sticker Validation model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m18s
Laravel Larastan / larastan (pull_request) Failing after 2m25s
2025-12-30 14:24:55 +05:30
dhanabalan
a8f566ca04 Added sticker validation migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 12s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 17s
Laravel Larastan / larastan (pull_request) Failing after 2m19s
Laravel Pint / pint (pull_request) Failing after 2m27s
2025-12-30 14:23:41 +05:30
dhanabalan
00d9cfc291 Added sticker mapping master importer and exporter
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m13s
Laravel Larastan / larastan (pull_request) Failing after 2m20s
2025-12-30 14:16:41 +05:30
dhanabalan
7e45c611a0 Added sticker mapping master resource pages
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Laravel Pint / pint (pull_request) Failing after 2m7s
Laravel Larastan / larastan (pull_request) Failing after 2m10s
2025-12-30 14:10:21 +05:30
dhanabalan
7cab79c7e0 Added sticker mapping master model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 14s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 19s
Laravel Larastan / larastan (pull_request) Failing after 2m13s
Laravel Pint / pint (pull_request) Failing after 2m11s
2025-12-30 14:08:59 +05:30
dhanabalan
2d81d41f34 Added sticker mapping master migration file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 52s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 1m20s
Laravel Larastan / larastan (pull_request) Failing after 2m28s
Laravel Pint / pint (pull_request) Failing after 2m24s
2025-12-30 14:07:53 +05:30
dhanabalan
4daa72db8e Added validation in importer and exporter of sticker structure details
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 9s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 14s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Laravel Pint / pint (pull_request) Failing after 2m7s
Laravel Larastan / larastan (pull_request) Failing after 2m13s
2025-12-25 17:33:33 +05:30
dhanabalan
504c125ca8 Added validation logic in sticker detail for importer and exporter
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 16s
Laravel Larastan / larastan (pull_request) Failing after 2m8s
Laravel Pint / pint (pull_request) Failing after 2m44s
2025-12-25 17:30:59 +05:30
dhanabalan
79f91bf444 Remove dimage path and image type in sticker detail model file
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Gemini PR Review / Gemini PR Review (pull_request) Successful in 18s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 9s
Laravel Larastan / larastan (pull_request) Failing after 2m20s
Laravel Pint / pint (pull_request) Failing after 2m20s
2025-12-25 13:13:29 +05:30
125 changed files with 13300 additions and 2148 deletions

View File

@@ -0,0 +1,62 @@
<?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;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Filament\Exports;
use App\Models\EmployeeMaster;
use Filament\Actions\Exports\ExportColumn;
use Filament\Actions\Exports\Exporter;
use Filament\Actions\Exports\Models\Export;
class EmployeeMasterExporter extends Exporter
{
protected static ?string $model = EmployeeMaster::class;
public static function getColumns(): array
{
return [
ExportColumn::make('id')
->label('ID'),
ExportColumn::make('plant.name'),
ExportColumn::make('name'),
ExportColumn::make('code'),
ExportColumn::make('department'),
ExportColumn::make('designation'),
ExportColumn::make('email'),
ExportColumn::make('mobile_number'),
ExportColumn::make('created_at'),
ExportColumn::make('updated_at'),
ExportColumn::make('created_by'),
ExportColumn::make('updated_by'),
ExportColumn::make('deleted_at'),
];
}
public static function getCompletedNotificationBody(Export $export): string
{
$body = 'Your employee master export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
if ($failedRowsCount = $export->getFailedRowsCount()) {
$body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
}
return $body;
}
}

View File

@@ -29,6 +29,8 @@ class StickerDetailExporter extends Exporter
->label('ELEMENT ID'),
ExportColumn::make('element_type')
->label('ELEMENT TYPE'),
ExportColumn::make('characteristics_type')
->label('CHARACTERISTICS TYPE'),
ExportColumn::make('string_value')
->label('STRING VALUE'),
ExportColumn::make('string_font')
@@ -47,6 +49,8 @@ class StickerDetailExporter extends Exporter
->label('SHAPE NAME'),
ExportColumn::make('shape_pen_size')
->label('SHAPE PEN SIZE'),
ExportColumn::make('curve_radius')
->label('CURVE RADIUS'),
ExportColumn::make('shape_x1_value')
->label('SHAPE X1 VALUE'),
ExportColumn::make('shape_y1_value')
@@ -55,10 +59,6 @@ class StickerDetailExporter extends Exporter
->label('SHAPE X2 VALUE'),
ExportColumn::make('shape_y2_value')
->label('SHAPE Y2 VALUE'),
ExportColumn::make('image_path')
->label('IMAGE PATH'),
ExportColumn::make('image_type')
->label('IMAGE TYPE'),
ExportColumn::make('image_x')
->label('IMAGE X'),
ExportColumn::make('image_y')

View File

@@ -21,28 +21,117 @@ class StickerMappingMasterExporter extends Exporter
// Increment and return the row number
return ++$rowNumber;
}),
ExportColumn::make('plant.name')
->label('PLANT'),
ExportColumn::make('item.code')
ExportColumn::make('plant.code')
->label('PLANT CODE'),
ExportColumn::make('itemCharacteristic.item.code')
->label('ITEM CODE'),
ExportColumn::make('sticker1')
->label('STICKER LABEL 1'),
ExportColumn::make('sticker2')
->label('STICKER LABEL 2'),
ExportColumn::make('sticker3')
->label('STICKER LABEL 3'),
ExportColumn::make('sticker4')
->label('STICKER LABEL 4'),
ExportColumn::make('sticker5')
->label('STICKER LABEL 5'),
ExportColumn::make('sticker_structure1_id')
->label('STICKER 1')
->getStateUsing(function ($record) {
return $record->sticker1Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker1_machine_id')
->label('WC STICKER 1')
->getStateUsing(function ($record) {
return $record->sticker1Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker1_print_ip')
->label('STICKER 1 PRINT IP'),
ExportColumn::make('sticker_structure2_id')
->label('STICKER 2')
->getStateUsing(function ($record) {
return $record->sticker2Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker2_machine_id')
->label('WC STICKER 2')
->getStateUsing(function ($record) {
return $record->sticker2Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker2_print_ip')
->label('STICKER 2 PRINT IP'),
ExportColumn::make('sticker_structure3_id')
->label('STICKER 3')
->getStateUsing(function ($record) {
return $record->sticker3Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker3_machine_id')
->label('WC STICKER 3')
->getStateUsing(function ($record) {
return $record->sticker3Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker3_print_ip')
->label('STICKER 3 PRINT IP'),
ExportColumn::make('sticker_structure4_id')
->label('STICKER 4')
->getStateUsing(function ($record) {
return $record->sticker4Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker4_machine_id')
->label('WC STICKER 4')
->getStateUsing(function ($record) {
return $record->sticker4Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker4_print_ip')
->label('STICKER 4 PRINT IP'),
ExportColumn::make('sticker_structure5_id')
->label('STICKER 5')
->getStateUsing(function ($record) {
return $record->sticker5Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker5_machine_id')
->label('WC STICKER 5')
->getStateUsing(function ($record) {
return $record->sticker5Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker5_print_ip')
->label('STICKER 5 PRINT IP'),
ExportColumn::make('sticker_structure6_id')
->label('STICKER 6')
->getStateUsing(function ($record) {
return $record->sticker6Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker6_machine_id')
->label('WC STICKER 6')
->getStateUsing(function ($record) {
return $record->sticker6Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker6_print_ip')
->label('STICKER 6 PRINT IP'),
ExportColumn::make('sticker_structure7_id')
->label('STICKER 7')
->getStateUsing(function ($record) {
return $record->sticker7Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker7_machine_id')
->label('WC STICKER 7')
->getStateUsing(function ($record) {
return $record->sticker2Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker7_print_ip')
->label('STICKER 7 PRINT IP'),
ExportColumn::make('sticker_structure8_id')
->label('STICKER 8')
->getStateUsing(function ($record) {
return $record->sticker8Structure?->sticker_id ?? '-';
}),
ExportColumn::make('sticker8_machine_id')
->label('WC STICKER 8')
->getStateUsing(function ($record) {
return $record->sticker8Machine?->work_center ?? '-';
}),
ExportColumn::make('sticker8_print_ip')
->label('STICKER 8 PRINT IP'),
ExportColumn::make('created_at')
->label('CREATED AT'),
ExportColumn::make('updated_at')
->label('UPDATED AT'),
ExportColumn::make('created_by')
->label('CREATED BY'),
ExportColumn::make('updated_by'),
ExportColumn::make('deleted_at'),
ExportColumn::make('updated_by')
->label('UPDATED BY'),
ExportColumn::make('deleted_at')
->enabledByDefault(false)
->label('DELETED AT'),
];
}

View File

@@ -13,21 +13,43 @@ class StickerStructureDetailExporter extends Exporter
public static function getColumns(): array
{
static $rowNumber = 0;
return [
ExportColumn::make('id')
->label('ID'),
ExportColumn::make('sticker_id'),
ExportColumn::make('sticker_width'),
ExportColumn::make('sticker_height'),
ExportColumn::make('sticker_lmargin'),
ExportColumn::make('sticker_rmargin'),
ExportColumn::make('sticker_tmargin'),
ExportColumn::make('sticker_bmargin'),
ExportColumn::make('created_at'),
ExportColumn::make('updated_at'),
ExportColumn::make('created_by'),
ExportColumn::make('updated_by'),
ExportColumn::make('deleted_at'),
ExportColumn::make('no')
->label('NO')
->state(function ($record) use (&$rowNumber) {
// Increment and return the row number
return ++$rowNumber;
}),
ExportColumn::make('plant.code')
->label('PLANT CODE'),
ExportColumn::make('itemCharacteristic.item.code')
->label('ITEM CODE'),
ExportColumn::make('sticker_id')
->label('STICKER ID'),
ExportColumn::make('sticker_width')
->label('STICKER WIDTH'),
ExportColumn::make('sticker_height')
->label('STICKER HEIGHT'),
ExportColumn::make('sticker_lmargin')
->label('STICKER LEFT MARGIN'),
ExportColumn::make('sticker_rmargin')
->label('STICKER RIGHT MARGIN'),
ExportColumn::make('sticker_tmargin')
->label('STICKER TOP MARGIN'),
ExportColumn::make('sticker_bmargin')
->label('STICKER BOTTOM MARGIN'),
ExportColumn::make('created_at')
->label('CREATED AT'),
ExportColumn::make('updated_at')
->label('UPDATED AT'),
ExportColumn::make('created_by')
->label('CREATED BY'),
ExportColumn::make('updated_by')
->label('UPDATED BY'),
ExportColumn::make('deleted_at')
->enabledByDefault(false)
->label('DELETED AT'),
];
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Filament\Imports;
use App\Models\EmployeeMaster;
use Filament\Actions\Imports\ImportColumn;
use Filament\Actions\Imports\Importer;
use Filament\Actions\Imports\Models\Import;
class EmployeeMasterImporter extends Importer
{
protected static ?string $model = EmployeeMaster::class;
public static function getColumns(): array
{
return [
ImportColumn::make('plant')
->requiredMapping()
->relationship()
->rules(['required']),
ImportColumn::make('name'),
ImportColumn::make('code'),
ImportColumn::make('department'),
ImportColumn::make('designation'),
ImportColumn::make('email')
->rules(['email']),
ImportColumn::make('mobile_number'),
ImportColumn::make('created_by'),
ImportColumn::make('updated_by'),
];
}
public function resolveRecord(): ?EmployeeMaster
{
// return EmployeeMaster::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
return new EmployeeMaster();
}
public static function getCompletedNotificationBody(Import $import): string
{
$body = 'Your employee master import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.';
if ($failedRowsCount = $import->getFailedRowsCount()) {
$body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.';
}
return $body;
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Filament\Imports;
use App\Models\Block;
use App\Models\Item;
use App\Models\Line;
use App\Models\Plant;
use App\Models\ProductionPlan;
@@ -23,11 +24,33 @@ class ProductionPlanImporter extends Importer
public static function getColumns(): array
{
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()
->exampleHeader('Created DateTime')
->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
->label('Created DateTime')
->exampleHeader('Plant Code')
->example(['1000', '1000'])
->label('Plant Code')
->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']),
ImportColumn::make('plan_quantity')
->requiredMapping()
@@ -36,175 +59,111 @@ class ProductionPlanImporter extends Importer
->label('Plan Quantity')
->numeric()
->rules(['required', 'integer']),
ImportColumn::make('production_quantity')
->requiredMapping()
->exampleHeader('Production Quantity')
->example(['0', '0'])
->label('Production Quantity')
->numeric()
->rules(['required', 'integer']),
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('block_reference')
->requiredMapping() // Or optionalMapping() if not always present
->exampleHeader('Block Name')
->example(['Block A', 'Block A'])
->label('Block Name')
->rules(['required']), // Or remove if not required
ImportColumn::make('shift')
->requiredMapping()
->exampleHeader('Shift Name') //ID
->example(['Day', 'Night']) //'2', '7'
->label('Shift Name') // ID
->relationship(resolveUsing: 'name')
->rules(['required']),
ImportColumn::make('plant')
->requiredMapping()
->exampleHeader('Plant Name')
->example(['Ransar Industries-I', 'Ransar Industries-I'])
->label('Plant Name')
->relationship(resolveUsing:'name')
->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']),
// ImportColumn::make('production_quantity')
// ->requiredMapping()
// ->exampleHeader('Production Quantity')
// ->example(['0', '0'])
// ->label('Production Quantity')
// ->numeric()
// ->rules(['required', 'integer']),
// ImportColumn::make('block_reference')
// ->requiredMapping() // Or optionalMapping() if not always present
// ->exampleHeader('Block Name')
// ->example(['Block A', 'Block A'])
// ->label('Block Name')
// ->rules(['required']), // Or remove if not required
// ImportColumn::make('shift')
// ->requiredMapping()
// ->exampleHeader('Shift Name') // ID
// ->example(['Day', 'Night']) // '2', '7'
// ->label('Shift Name') // ID
// ->relationship(resolveUsing: 'name')
// ->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
{
$warnMsg = [];
$plant = Plant::where('name', $this->data['plant'])->first();
$plantCod = $this->data['plant'];
$itemCod = $this->data['item'];
$plant = null;
$line = null;
$block = null;
if (!$plant) {
$warnMsg[] = "Plant not found";
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();
}
else {
if (! $plant) {
$warnMsg[] = 'Plant not found';
} else {
$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) {
$warnMsg[] = "Line not found";
}
$shift = null;
if (!$block) {
$warnMsg[] = "Block not found";
}
else {
$shift = Shift::where('name', $this->data['shift'])->where('plant_id', $plant->id)->where('block_id', $block->id)->first();
}
//$shift = Shift::where('id', $this->data['shift'])->where('plant_id', $plant->id)->first();
if (!$shift) {
$warnMsg[] = "Shift not found";
}
if (Str::length($this->data['plan_quantity']) < 0 || !is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) {
$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";
}
if (! $line) {
$warnMsg[] = 'Line not found';
}
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";
}
if (Str::length($itemCod) < 6 || ! is_numeric($itemCod)) {
$warnMsg[] = 'Invalid item code found';
} else {
$item = Item::where('code', $itemCod)->first();
}
$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 (! $item) {
$warnMsg[] = 'Item not found';
}
if (isset($fdateTime) && isset($tdateTime)) {
if ($fdateTime->greaterThan($tdateTime)) {
$warnMsg[] = "'Created DataTime' is greater than 'Updated DateTime'.";
}
$plantId = $plant->id;
$itemAgaPlant = Item::where('plant_id', $plantId)->where('code', $itemCod)->first();
if(!$itemAgaPlant){
$warnMsg[] = 'Item not found against plant code';
}
// 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 = Filament::auth()->user();
$user = User::where('name', $this->data['operator_id'])->first();
if (!$user) {
$warnMsg[] = "Operator ID not found";
$operatorName = $user->name;
if (Str::length($this->data['plan_quantity']) < 0 || ! is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) {
$warnMsg[] = 'Invalid plan quantity found';
}
if (!empty($warnMsg)) {
if (! empty($warnMsg)) {
throw new RowImportFailedException(implode(', ', $warnMsg));
}
else { //if (empty($warnMsg))
} else {
$productionPlan = ProductionPlan::where('plant_id', $plant->id)
->where('shift_id', $shift->id)
->where('line_id', $line->id)
->whereDate('created_at', $fDateOnly)
// ->where('plan_quantity', $productionQuantity->plan_quantity)
->latest()
->first();
->where('line_id', $line->id)
->where('item_id', $itemAgaPlant->id)
->latest()
->first();
if ($productionPlan) {
// if($productionPlan->production_quantity)
// {
// throw new RowImportFailedException("{$productionPlan->created_at}, {$productionPlan->production_quantity}");
// }
// $warnMsg[] = "Production plan already exist on '{$fDateOnly}'!";
$productionPlan->update([
'plan_quantity' => $this->data['plan_quantity'],
// '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'],
'operator_id' => $operatorName,
]);
$productionPlan->save();
return null;
}
}
@@ -212,28 +171,23 @@ class ProductionPlanImporter extends Importer
ProductionPlan::updateOrCreate([
'plant_id' => $plant->id,
'line_id' => $line->id,
'shift_id' => $shift->id,
'item_id' => $itemAgaPlant->id,
// 'shift_id' => $shift->id,
'plan_quantity' => $this->data['plan_quantity'],
'production_quantity' => $this->data['production_quantity'],
'created_at' => $fdateTime->format('Y-m-d H:i:s'),//$this->data['created_at'],
'updated_at' => $tdateTime->format('Y-m-d H:i:s'),//$this->data['updated_at'],
'operator_id' => $this->data['operator_id'],
'created_at' =>now(),
'updated_at' => now(),
'operator_id' => $operatorName,
]);
return null;
// return ProductionPlan::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
// return new ProductionPlan();
return null;
}
public static function getCompletedNotificationBody(Import $import): string
{
$body = 'Your production plan import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.';
$body = 'Your production plan import has completed and '.number_format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.';
if ($failedRowsCount = $import->getFailedRowsCount()) {
$body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.';
$body .= ' '.number_format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.';
}
return $body;

View File

@@ -2,10 +2,14 @@
namespace App\Filament\Imports;
use App\Models\Plant;
use App\Models\StickerDetail;
use App\Models\StickerStructureDetail;
use App\Models\User;
use Filament\Actions\Imports\ImportColumn;
use Filament\Actions\Imports\Importer;
use Filament\Actions\Imports\Models\Import;
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
class StickerDetailImporter extends Importer
{
@@ -14,50 +18,295 @@ class StickerDetailImporter extends Importer
public static function getColumns(): array
{
return [
ImportColumn::make('stickerStructureDetail')
ImportColumn::make('sticker_structure_detail_id')
->requiredMapping()
->relationship()
->exampleHeader('Sticker ID')
->example('123456')
->label('STICKER ID')
->rules(['required']),
ImportColumn::make('design_element_type'),
ImportColumn::make('element_id'),
ImportColumn::make('element_type'),
ImportColumn::make('string_value'),
ImportColumn::make('string_font'),
ImportColumn::make('string_size'),
ImportColumn::make('element_colour'),
ImportColumn::make('string_align'),
ImportColumn::make('string_x_value'),
ImportColumn::make('string_y_value'),
ImportColumn::make('shape_name'),
ImportColumn::make('shape_pen_size'),
ImportColumn::make('shape_x1_value'),
ImportColumn::make('shape_y1_value'),
ImportColumn::make('shape_x2_value'),
ImportColumn::make('shape_y2_value'),
ImportColumn::make('image_path'),
ImportColumn::make('image_type'),
ImportColumn::make('image_x'),
ImportColumn::make('image_y'),
ImportColumn::make('image_width'),
ImportColumn::make('image_height'),
ImportColumn::make('qr_value'),
ImportColumn::make('qr_align'),
ImportColumn::make('qr_size'),
ImportColumn::make('qr_x_value'),
ImportColumn::make('qr_y_value'),
ImportColumn::make('created_by'),
ImportColumn::make('updated_by'),
ImportColumn::make('design_element_type')
->requiredMapping()
->exampleHeader('Design Element Type')
->label('DESIGN ELEMENT TYPE')
->example('Text/Shape/Image/QR'),
ImportColumn::make('element_id')
->requiredMapping()
->exampleHeader('Element ID')
->label('ELEMENT ID')
->example('001'),
ImportColumn::make('element_type')
->requiredMapping()
->exampleHeader('Element Type')
->label('ELEMENT TYPE')
->example('Static/Dynamic'),
ImportColumn::make('characteristics_type')
->requiredMapping()
->exampleHeader('Characteristics Type')
->label('CHARACTERISTICS TYPE')
->example('ZMM values'),
ImportColumn::make('string_value')
->requiredMapping()
->exampleHeader('String Value')
->label('STRING VALUE')
->example('1'),
ImportColumn::make('string_font')
->requiredMapping()
->exampleHeader('String Font')
->label('STRING FONT')
->example('Arial'),
ImportColumn::make('string_size')
->requiredMapping()
->exampleHeader('String Size')
->label('STRING SIZE')
->example('12'),
ImportColumn::make('element_colour')
->requiredMapping()
->exampleHeader('Element Colour')
->label('ELEMENT COLOUR')
->example('Black'),
ImportColumn::make('string_align')
->requiredMapping()
->exampleHeader('String Align')
->label('STRING ALIGN')
->example('Left/Center/Right'),
ImportColumn::make('string_x_value')
->requiredMapping()
->exampleHeader('String X Value')
->label('STRING X VALUE')
->example('10'),
ImportColumn::make('string_y_value')
->requiredMapping()
->exampleHeader('String Y Value')
->label('STRING Y VALUE')
->example('20'),
ImportColumn::make('shape_name')
->requiredMapping()
->exampleHeader('Shape Name')
->label('SHAPE NAME')
->example('Line/Rectangle/CurvedRectangle'),
ImportColumn::make('shape_pen_size')
->requiredMapping()
->exampleHeader('Shape Pen Size')
->label('SHAPE PEN SIZE')
->example('0.3'),
ImportColumn::make('curve_radius')
->requiredMapping()
->exampleHeader('Curve Radius')
->label('CURVE RADIUS')
->example('3'),
ImportColumn::make('shape_x1_value')
->requiredMapping()
->exampleHeader('Shape X1 Value')
->label('SHAPE X1 VALUE')
->example('10'),
ImportColumn::make('shape_y1_value')
->requiredMapping()
->exampleHeader('Shape Y1 Value')
->label('SHAPE Y1 VALUE')
->example('20'),
ImportColumn::make('shape_x2_value')
->requiredMapping()
->exampleHeader('Shape X2 Value')
->label('SHAPE X2 VALUE')
->example('30'),
ImportColumn::make('shape_y2_value')
->requiredMapping()
->exampleHeader('Shape Y2 Value')
->label('SHAPE Y2 VALUE')
->example('40'),
ImportColumn::make('image_x')
->requiredMapping()
->exampleHeader('Image X')
->label('IMAGE X')
->example('15'),
ImportColumn::make('image_y')
->requiredMapping()
->exampleHeader('Image Y')
->label('IMAGE Y')
->example('25'),
ImportColumn::make('image_width')
->requiredMapping()
->exampleHeader('Image Width')
->label('IMAGE WIDTH')
->example('100'),
ImportColumn::make('image_height')
->requiredMapping()
->exampleHeader('Image Height')
->label('IMAGE HEIGHT')
->example('100'),
ImportColumn::make('qr_value')
->requiredMapping()
->exampleHeader('QR Value')
->label('QR VALUE')
->example('246118|53246735267'),
ImportColumn::make('qr_align')
->requiredMapping()
->exampleHeader('QR Align')
->label('QR ALIGN')
->example('Left/Center/Right'),
ImportColumn::make('qr_size')
->requiredMapping()
->exampleHeader('QR Size')
->label('QR SIZE')
->example('10'),
ImportColumn::make('qr_x_value')
->requiredMapping()
->exampleHeader('QR X Value')
->label('QR X VALUE')
->example('30'),
ImportColumn::make('qr_y_value')
->requiredMapping()
->exampleHeader('QR Y Value')
->label('QR Y VALUE')
->example('40'),
ImportColumn::make('created_by')
->requiredMapping()
->exampleHeader('Created By')
->label('CREATED BY')
->example('RAW001234'),
];
}
public function resolveRecord(): ?StickerDetail
{
// return StickerDetail::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
return new StickerDetail();
$warnMsg = [];
$sticker = $this->data['sticker_structure_detail_id'] ?? null;
$stickerCode = StickerStructureDetail::where('sticker_id', $sticker)->first();
if (!$stickerCode) {
$warnMsg[] = "Sticker Id not found in Sticker Structure Detail";
}
$stickerId = $stickerCode->id;
$user = User::where('name', $this->data['created_by'])->first();
if (!$user) {
$warnMsg[] = "User not found";
}
$designType = strtolower($this->data['design_element_type'] ?? '');
$rules = [
'text' => [
'required' => ['string_x_value', 'string_y_value'],
'allowed' => [
'string_value',
'string_font',
'string_size',
'string_align',
'string_colour',
'string_x_value',
'string_y_value',
],
],
'image' => [
'required' => ['image_x', 'image_y', 'image_width', 'image_height'],
'allowed' => [
'image_x',
'image_y',
'image_width',
'image_height',
],
],
'shape' => [
'required' => ['shape_name', 'shape_pen_size', 'shape_x1_value', 'shape_y1_value', 'shape_x2_value', 'shape_y2_value'],
'allowed' => [
'shape_name',
'shape_pen_size',
'curve_radius',
'shape_x1_value',
'shape_y1_value',
'shape_x2_value',
'shape_y2_value',
],
],
'qr' => [
'required' => ['qr_value', 'qr_x_value', 'qr_y_value', 'qr_size'],
'allowed' => [
'qr_value',
'qr_align',
'qr_size',
'qr_x_value',
'qr_y_value',
],
],
];
if (!isset($rules[$designType])) {
$warnMsg[] = "Invalid Design Element Type: {$designType}";
}
if (isset($rules[$designType])) {
foreach ($rules[$designType]['required'] as $field) {
if (empty($this->data[$field])) {
$warnMsg[] = ucfirst($designType) . " requires {$field}";
}
}
}
$allElementFields = [
'string_value','string_font','string_size','string_align','string_colour',
'string_x_value','string_y_value',
'image_x','image_y','image_width','image_height',
'shape_name','shape_pen_size','curve_radius',
'shape_x1_value','shape_y1_value','shape_x2_value','shape_y2_value',
'qr_value','qr_align','qr_size','qr_x_value','qr_y_value',
];
if (isset($rules[$designType])) {
$allowed = $rules[$designType]['allowed'];
foreach ($allElementFields as $field) {
if (!in_array($field, $allowed, true) && !empty($this->data[$field])) {
$warnMsg[] = "Field {$field} is not allowed for {$designType} element";
}
}
}
if (!empty($warnMsg)) {
throw new RowImportFailedException(implode(' | ', $warnMsg));
}
StickerDetail::Create([
'sticker_structure_detail_id' => $stickerId,
'design_element_type' => $this->data['design_element_type'],
'element_id' => $this->data['element_id'],
'element_type' => $this->data['element_type'],
'characteristics_type' => $this->data['characteristics_type'],
'string_value' => $this->data['string_value'],
'string_font' => $this->data['string_font'],
'string_size' => $this->data['string_size'],
'element_colour' => $this->data['element_colour'],
'string_align' => $this->data['string_align'],
'string_x_value' => $this->data['string_x_value'],
'string_y_value' => $this->data['string_y_value'],
'shape_name' => $this->data['shape_name'],
'shape_pen_size' => $this->data['shape_pen_size'],
'curve_radius' => $this->data['curve_radius'],
'shape_x1_value' => $this->data['shape_x1_value'],
'shape_y1_value' => $this->data['shape_y1_value'],
'shape_x2_value' => $this->data['shape_x2_value'],
'shape_y2_value' => $this->data['shape_y2_value'],
'image_x' => $this->data['image_x'],
'image_y' => $this->data['image_y'],
'image_width' => $this->data['image_width'],
'image_height' => $this->data['image_height'],
'qr_value' => $this->data['qr_value'],
'qr_align' => $this->data['qr_align'],
'qr_size' => $this->data['qr_size'],
'qr_x_value' => $this->data['qr_x_value'],
'qr_y_value' => $this->data['qr_y_value'],
'created_by' => $this->data['created_by'],
]);
return null;
//return new StickerDetail();
}
public static function getCompletedNotificationBody(Import $import): string

View File

@@ -14,43 +14,56 @@ class StickerMappingMasterImporter extends Importer
public static function getColumns(): array
{
return [
ImportColumn::make('plant')
ImportColumn::make('plant_id')
->requiredMapping()
->exampleHeader('Plant Name')
->example('Ransar Industries-I')
->label('Plant Name')
->relationship(resolveUsing:'name')
->rules(['required']),
ImportColumn::make('item')
->numeric()
->rules(['required', 'integer']),
ImportColumn::make('item_id')
->requiredMapping()
->exampleHeader('Item Code')
->example('630987')
->label('Item Code')
->relationship(resolveUsing:'code')
->rules(['required']),
ImportColumn::make('sticker1')
->label('Sticker Label 1')
->exampleHeader('Sticker Label 1')
->example('Label 1')
->rules(['required']),
ImportColumn::make('sticker2')
->label('Sticker Label 2')
->exampleHeader('Sticker Label 2')
->example('601468'),
ImportColumn::make('sticker3')
->label('Sticker Label 3')
->exampleHeader('Sticker Label 3'),
//->example(''),
ImportColumn::make('sticker4')
->label('Sticker Label 4')
->exampleHeader('Sticker Label 4'),
ImportColumn::make('sticker5')
->label('Sticker Label 5')
->exampleHeader('Sticker Label 5'),
ImportColumn::make('created_by')
->label('Created By')
->exampleHeader('Created By'),
// ImportColumn::make('updated_by'),
->numeric()
->rules(['required', 'integer']),
ImportColumn::make('sticker1'),
ImportColumn::make('sticker1_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker1_print_ip'),
ImportColumn::make('sticker2'),
ImportColumn::make('sticker2_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker2_print_ip'),
ImportColumn::make('sticker3'),
ImportColumn::make('sticker3_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker3_print_ip'),
ImportColumn::make('sticker4'),
ImportColumn::make('sticker4_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker4_print_ip'),
ImportColumn::make('sticker5'),
ImportColumn::make('sticker5_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker5_print_ip'),
ImportColumn::make('sticker6'),
ImportColumn::make('sticker6_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker6_print_ip'),
ImportColumn::make('sticker7'),
ImportColumn::make('sticker7_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker7_print_ip'),
ImportColumn::make('sticker8'),
ImportColumn::make('sticker8_machine_id')
->numeric()
->rules(['integer']),
ImportColumn::make('sticker8_print_ip'),
ImportColumn::make('created_by'),
ImportColumn::make('updated_by'),
];
}

View File

@@ -2,10 +2,15 @@
namespace App\Filament\Imports;
use App\Models\Item;
use App\Models\ItemCharacteristic;
use App\Models\Plant;
use App\Models\StickerStructureDetail;
use App\Models\User;
use Filament\Actions\Imports\ImportColumn;
use Filament\Actions\Imports\Importer;
use Filament\Actions\Imports\Models\Import;
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
class StickerStructureDetailImporter extends Importer
{
@@ -14,26 +19,133 @@ class StickerStructureDetailImporter extends Importer
public static function getColumns(): array
{
return [
ImportColumn::make('sticker_id'),
ImportColumn::make('sticker_width'),
ImportColumn::make('sticker_height'),
ImportColumn::make('sticker_lmargin'),
ImportColumn::make('sticker_rmargin'),
ImportColumn::make('sticker_tmargin'),
ImportColumn::make('sticker_bmargin'),
ImportColumn::make('created_by'),
ImportColumn::make('updated_by'),
ImportColumn::make('plant')
->requiredMapping()
->exampleHeader('Plant Code')
->example('1000')
->label('PLANT CODE')
->relationship(resolveUsing: 'code')
->rules(['required']),
ImportColumn::make('item_characteristic_id')
->requiredMapping()
->exampleHeader('Item Code')
->example('123456')
->label('ITEM CODE')
->rules(['required']),
ImportColumn::make('sticker_id')
->requiredMapping()
->exampleHeader('Sticker ID')
->example('123456')
->label('STICKER ID')
->rules(['required']),
ImportColumn::make('sticker_width')
->requiredMapping()
->exampleHeader('Sticker Width')
->example('90')
->label('STICKER WIDTH')
->rules(['required']),
ImportColumn::make('sticker_height')
->requiredMapping()
->exampleHeader('Sticker Height')
->example('90')
->label('STICKER HEIGHT')
->rules(['required']),
ImportColumn::make('sticker_lmargin')
->requiredMapping()
->exampleHeader('Sticker Left Margin')
->example('0')
->label('STICKER LEFT MARGIN')
->rules(['required']),
ImportColumn::make('sticker_rmargin')
->requiredMapping()
->exampleHeader('Sticker Right Margin')
->example('0')
->label('STICKER RIGHT MARGIN')
->rules(['required']),
ImportColumn::make('sticker_tmargin')
->requiredMapping()
->exampleHeader('Sticker Top Margin')
->example('0')
->label('STICKER TOP MARGIN')
->rules(['required']),
ImportColumn::make('sticker_bmargin')
->requiredMapping()
->exampleHeader('Sticker Bottom Margin')
->example('0')
->label('STICKER BOTTOM MARGIN')
->rules(['required']),
ImportColumn::make('created_by')
->requiredMapping()
->exampleHeader('Created By')
->example('RAW001234')
->label('CREATED BY')
->rules(['required']),
];
}
public function resolveRecord(): ?StickerStructureDetail
{
// return StickerStructureDetail::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
$warnMsg = [];
return new StickerStructureDetail();
$plant = Plant::where('code', $this->data['plant'])->first();
if (!$plant) {
$warnMsg[] = "Plant code not found";
}
// $item = null;
// if ($plant) {
// $item = Item::where('code', $this->data['itemCharacteristic.item'])->where('plant_id', $plant->id)->first();
// }
// if (!$item) {
// $warnMsg[] = "Item not found";
// }
$itemCode = $this->data['item_characteristic_id'] ?? null;
$item = Item::where('code', $itemCode)->first();
if (!$item) {
$warnMsg[] = "Item not found";
}
$itemChar = ItemCharacteristic::where('item_id', $item->id)->first();
if (!$itemChar) {
$warnMsg[] = "Item not found in item characteristic";
}
$user = User::where('name', $this->data['created_by'])->first();
if (!$user) {
$warnMsg[] = "User not found";
}
if (!empty($warnMsg)) {
throw new RowImportFailedException(implode(', ', $warnMsg));
}
else { //if (empty($warnMsg))
$stickerId = StickerStructureDetail::where('sticker_id', $this->data['sticker_id'])
->first();
if ($stickerId) {
throw new RowImportFailedException("Sticker ID already exist!");
}
}
StickerStructureDetail::Create([
'plant_id' => $plant->id,
'item_characteristic_id' => $itemChar->id,
'sticker_id' => $this->data['sticker_id'],
'sticker_width' => $this->data['sticker_width'],
'sticker_height' => $this->data['sticker_height'],
'sticker_lmargin' => $this->data['sticker_lmargin'],
'sticker_rmargin' => $this->data['sticker_rmargin'],
'sticker_tmargin' => $this->data['sticker_tmargin'],
'sticker_bmargin' => $this->data['sticker_bmargin'],
'created_by' => $this->data['created_by'],
]);
return null;
//return new StickerStructureDetail();
}
public static function getCompletedNotificationBody(Import $import): string

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
class NotificationSettings extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.notification-settings';
public static function getScripts(): array
{
return [
asset('js/push.js')
];
}
}

View File

@@ -0,0 +1,163 @@
<?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

View File

@@ -0,0 +1,173 @@
<?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);
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Pages\Dashboard\Concerns\HasFiltersForm;
use Filament\Tables\Filters\SelectFilter;
use Illuminate\Support\Facades\DB;
use App\Models\Plant;
use App\Models\RequestQuotation;
use App\Models\RfqTransporterBid;
use Filament\Facades\Filament;
use Filament\Widgets\Widget;
use Illuminate\Support\Facades\Auth;
use Filament\Forms\Components\Section;
use Filament\Forms;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
class RfqDashboard extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.rfq-dashboard';
protected static ?string $navigationGroup = 'RFQ Dashboard';
use HasFiltersForm;
public function mount(): void
{
session()->forget(['transport_name']);
session()->forget(['rfq_number']);
$this->filtersForm->fill([
'transport_name' => null,
'rfq_number' => null
]);
}
public function filtersForm(Form $form): Form
{
return $form
->statePath('filters')
->schema([
Section::make('')
->schema([
// Select::make('plant')
// ->label('Select 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();
// })
// ->afterStateUpdated(function ($state,callable $set) {
// session(['selected_plant' => $state]);
// // $set('rfq_number', null);
// session()->forget('rfq_number');
// }),
Select::make('rfq_number')
->label('Select RFQ Number')
->reactive()
->options(function (callable $get) {
return RequestQuotation::orderBy('rfq_number')
->pluck('rfq_number', 'id')
->toArray();
})
->afterStateUpdated(function ($state, callable $set) {
session(['rfq_id' => $state]);
$set('transport_name', null);
session()->forget('transport_name');
}),
Select::make('transport_name')
->label('User name')
->reactive()
->options(function (callable $get) {
$rfqId = $get('rfq_number');
if (!$rfqId) {
return [];
}
$user = Filament::auth()->user();
if ($user->hasRole(['Super Admin', 'Rfq Supervisor'])) {
return RfqTransporterBid::query()
->where('request_quotation_id', $rfqId)
->whereNotNull('transporter_name')
->distinct()
->pluck('transporter_name', 'transporter_name')
->toArray();
}
return RfqTransporterBid::query()
->where('request_quotation_id', $rfqId)
->where('transporter_name', $user->name)
->distinct()
->pluck('transporter_name', 'transporter_name')
->toArray();
})
->afterStateUpdated(function ($state, callable $set) {
session(['transport_name' => $state]);
}),
// Select::make('transport_name')
// ->label('User name')
// ->reactive()
// ->options(function () {
// $user = Filament::auth()->user();
// if ($user->hasRole(['Super Admin', 'Rfq Supervisor'])) {
// return RfqTransporterBid::query()
// ->whereNotNull('transporter_name')
// ->distinct()
// ->pluck('transporter_name', 'transporter_name')
// ->toArray();
// }
// return RfqTransporterBid::query()
// ->where('transporter_name', $user->name)
// ->distinct()
// ->pluck('transporter_name', 'transporter_name')
// ->toArray();
// })
// ->afterStateUpdated(function ($state, callable $set) {
// session(['transport_name' => $state]);
// $set('rfq_number', null);
// session()->forget('rfq_number');
// }),
// Select::make('rfq_number')
// ->label('Select RFQ Number')
// ->reactive()
// ->options(function (callable $get) {
// $transportName = $get('transport_name');
// if (!$transportName) {
// return [];
// }
// return RequestQuotation::where('transporter_name', $transportName)
// ->pluck('rfq_number', 'rfq_number')
// ->toArray();
// })
// ->afterStateUpdated(function ($state) {
// session(['rfq_number' => $state]);
// }),
])
->columns(2),
]);
}
public static function canAccess(): bool
{
return Auth::check() && Auth::user()->can('view rfq dashboard');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Filament\Pages;
use App\Models\RequestQuotation;
use Filament\Pages\Page;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Pages\Dashboard\Concerns\HasFiltersForm;
use Illuminate\Support\Facades\Auth;
class RfqOverview extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.rfq-overview';
protected static ?string $navigationGroup = 'RFQ Dashboard';
use HasFiltersForm;
public function mount(): void
{
session()->forget(['rfq_id']);
$this->filtersForm->fill([
'rfq_id' => null
]);
}
public function filtersForm(Form $form): Form
{
return $form
->statePath('filters')
->schema([
Section::make('')
->schema([
Select::make('rfq_number')
->label('Select RFQ Number')
->reactive()
->options(function (callable $get) {
return RequestQuotation::orderBy('rfq_number')
->pluck('rfq_number', 'id')
->toArray();
})
->afterStateUpdated(function ($state, callable $set) {
session(['rfq_id' => $state]);
}),
])
->columns(1),
]);
}
public static function canAccess(): bool
{
return Auth::check() && Auth::user()->can('view rfq overview dashboard');
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Filament\Pages;
use Filament\Pages\Page;
class Welcome extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.welcome';
public function getHeading(): string
{
return '';
}
}

View File

@@ -0,0 +1,217 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Exports\EmployeeMasterExporter;
use App\Filament\Imports\EmployeeMasterImporter;
use App\Filament\Resources\EmployeeMasterResource\Pages;
use App\Filament\Resources\EmployeeMasterResource\RelationManagers;
use App\Models\EmployeeMaster;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Actions\ImportAction;
use Illuminate\Validation\Rule;
class EmployeeMasterResource extends Resource
{
protected static ?string $model = EmployeeMaster::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Select::make('plant_id')
->label('Plant')
->relationship('plant', 'name')
->required(),
Forms\Components\TextInput::make('name')
->label('Name')
->required()
->reactive()
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^a-zA-Z\s]/g, "")',
]),
Forms\Components\TextInput::make('code')
->label('ID')
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^a-zA-Z0-9]/g, "")',])
->required()
->unique(
table: 'employee_masters',
column: 'code',
ignoreRecord: true
)
->validationMessages([
'unique' => 'Duplicate employee code already exists.',
]),
Forms\Components\TextInput::make('department')
->label('Department')
->required(),
Forms\Components\TextInput::make('designation')
->label('Designation')
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^a-zA-Z\s]/g, "")',])
->required(),
Forms\Components\TextInput::make('email')
->label('Email')
->email()
->required(),
Forms\Components\TextInput::make('mobile_number')
->label('Mobile Number')
->length(10)
->reactive()
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^0-9]/g, "").slice(0, 10)', // blocks non-numbers + limits to 10 chars
'maxlength' => 10,
])
->required()
->unique(
table: 'employee_masters',
column: 'mobile_number',
ignoreRecord: true
)
->validationMessages([
'unique' => 'Duplicate mobile number already exists.',
]),
Forms\Components\Hidden::make('created_by')
->label('Created by')
->default(Filament::auth()->user()?->name ?? ''),
Forms\Components\Hidden::make('updated_by')
->label('Updated by')
->default(Filament::auth()->user()?->name ?? ''),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
})
->alignCenter(),
Tables\Columns\TextColumn::make('plant.name')
->numeric()
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('name')
->label('Name')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('code')
->label('Employee ID')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('department')
->label('Department')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('designation')
->label('Designation')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('email')
->label('Email')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('mobile_number')
->label('Mobile Number')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
Tables\Columns\TextColumn::make('created_by')
->label('Created by')
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('updated_by')
->label('Updated by')
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
])
->headerActions([
ImportAction::make()
->importer(EmployeeMasterImporter::class)
->visible(function() {
return Filament::auth()->user()->can('view import employee master');
}),
ExportAction::make()
->exporter(EmployeeMasterExporter::class)
->visible(function() {
return Filament::auth()->user()->can('view export employee master');
}),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListEmployeeMasters::route('/'),
'create' => Pages\CreateEmployeeMaster::route('/create'),
'view' => Pages\ViewEmployeeMaster::route('/{record}'),
'edit' => Pages\EditEmployeeMaster::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\EmployeeMasterResource\Pages;
use App\Filament\Resources\EmployeeMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateEmployeeMaster extends CreateRecord
{
protected static string $resource = EmployeeMasterResource::class;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Filament\Resources\EmployeeMasterResource\Pages;
use App\Filament\Resources\EmployeeMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditEmployeeMaster extends EditRecord
{
protected static string $resource = EmployeeMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\EmployeeMasterResource\Pages;
use App\Filament\Resources\EmployeeMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListEmployeeMasters extends ListRecords
{
protected static string $resource = EmployeeMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\EmployeeMasterResource\Pages;
use App\Filament\Resources\EmployeeMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewEmployeeMaster extends ViewRecord
{
protected static string $resource = EmployeeMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -10,6 +10,7 @@ use App\Models\InvoiceValidation;
use App\Models\Item;
use App\Models\Plant;
use App\Models\StickerMaster;
use App\Notifications\PushAlertNotification;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Pages\Concerns\ExposesTableToWidgets;
@@ -136,6 +137,10 @@ class CreateInvoiceValidation extends CreateRecord
->send();
$this->dispatch('playNotificationSound');
$user1 = Filament::auth()->user();
$user1->notify(new PushAlertNotification());
$this->form->fill([
'plant_id' => $plantId,
'invoice_number' => $invoiceNumber,
@@ -2285,6 +2290,7 @@ class CreateInvoiceValidation extends CreateRecord
->danger()
->seconds(3)
->send();
$this->dispatch('playWarnSound');
$this->form->fill([

View File

@@ -82,6 +82,9 @@ class ItemResource extends Resource
Forms\Components\TextInput::make('category')
->label('Category')
->placeholder('Scan the Category'),
Forms\Components\TextInput::make('category')
->label('Category')
->placeholder('Scan the Category'),
Forms\Components\TextInput::make('code')
->required()
->placeholder('Scan the valid code')

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ use AlperenErsoy\FilamentExport\Actions\FilamentExportBulkAction;
use App\Filament\Exports\ProductionPlanExporter;
use App\Filament\Imports\ProductionPlanImporter;
use App\Filament\Resources\ProductionPlanResource\Pages;
use App\Filament\Resources\ProductionPlanResource\RelationManagers;
use App\Models\Block;
use App\Models\Item;
use App\Models\Line;
use App\Models\Plant;
use App\Models\ProductionPlan;
@@ -16,19 +16,18 @@ use Carbon\Carbon;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
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
{
@@ -55,22 +54,22 @@ class ProductionPlanResource extends Resource
->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();
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
})
->default(function () {
return optional(ProductionPlan::latest()->first())->plant_id;
})
->disabled(fn (Get $get) => !empty($get('id')))
->disabled(fn (Get $get) => ! empty($get('id')))
// ->afterStateUpdated(fn ($set) => $set('block_name', null))
->afterStateUpdated(function ($state, callable $set, callable $get) {
$plantId = $get('plant_id');
$set('block_name', null);
if (!$plantId) {
if (! $plantId) {
$set('ppPlantError', 'Please select a plant first.');
return;
}
else
{
} else {
$set('ppPlantError', null);
}
})
@@ -79,102 +78,6 @@ class ProductionPlanResource extends Resource
])
->hint(fn ($get) => $get('ppPlantError') ? $get('ppPlantError') : null)
->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')
->relationship('line', 'name')
->required()
@@ -185,7 +88,7 @@ class ProductionPlanResource extends Resource
// ->toArray() // Convert collection to array
// )
->options(function (callable $get) {
if (!$get('plant_id') || !$get('block_name') || !$get('shift_id')) {
if (! $get('plant_id')) {
return [];
}
@@ -197,248 +100,257 @@ class ProductionPlanResource extends Resource
// ->default(function () {
// return optional(ProductionPlan::latest()->first())->line_id;
// })
// ->afterStateUpdated(function ($state, callable $set, callable $get) {
// if ($get('id')) {
// $getShift = ProductionPlan::where('id', $get('id'))->first();
// if ($getShift->line_id) {
// $set('line_id', $getShift->line_id);
// $set('ppLineError', null);
// }
// } else {
// $currentDT = Carbon::now()->toDateTimeString();
// $set('created_at', $currentDT);
// $set('update_date', null);
// }
// $lineId = $get('line_id');
// // $set('plan_quantity', null);
// if (! $lineId) {
// $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;
// }
// // $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;
// // }
// }
// }
// $set('ppLineError', null);
// }
// })
->afterStateUpdated(function ($state, callable $set, callable $get) {
if($get('id'))
{
$getShift = ProductionPlan::where('id', $get('id'))->first();
if($getShift->line_id)
{
$set('line_id', $getShift->line_id);
$set('ppLineError', null);
}
}
else
{
$currentDT = Carbon::now()->toDateTimeString();
$set('created_at', $currentDT);
$set('update_date', null);
}
$lineId = $get('line_id');
// $set('plan_quantity', null);
if (!$lineId) {
$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;
}
// $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;
// }
}
}
$set('ppLineError', null);
}
$set('item_id', null);
$set('plan_quantity', null);
})
->extraAttributes(fn ($get) => [
'class' => $get('ppLineError') ? 'border-red-500' : '',
])
->hint(fn ($get) => $get('ppLineError') ? $get('ppLineError') : null)
->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')
->required()
->integer()
@@ -449,21 +361,18 @@ class ProductionPlanResource extends Resource
->afterStateUpdated(function ($state, callable $set, callable $get) {
$planQuan = $get('plan_quantity');
if(!$get('update_date') )
{
if(!$get('id'))
{
if (! $get('update_date')) {
if (! $get('id')) {
$currentDT = Carbon::now()->toDateTimeString();
$set('created_at', $currentDT);
}
}
if (!$planQuan) {
if (! $planQuan) {
$set('ppPlanQuanError', 'Scan the valid plan quantity.');
return;
}
else
{
} else {
$set('ppPlanQuanError', null);
}
})
@@ -472,29 +381,19 @@ class ProductionPlanResource extends Resource
])
->hint(fn ($get) => $get('ppPlanQuanError') ? $get('ppPlanQuanError') : null)
->hintColor('danger'),
Forms\Components\TextInput::make('production_quantity')
->required()
->integer()
->label('Production Quantity')
->readOnly(fn (callable $get) => !$get('id'))
->default(0),
// Forms\Components\TextInput::make('production_quantity')
// ->required()
// ->integer()
// ->label('Production Quantity')
// ->readOnly(fn (callable $get) => ! $get('id'))
// ->default(0),
Forms\Components\TextInput::make('id')
->hidden()
->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')
->default(Filament::auth()->user()->name),
])
->columns(2),
])
->columns(4),
]);
}
@@ -529,34 +428,55 @@ class ProductionPlanResource extends Resource
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('plant.name')
->label('Plant')
->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')
->label('Plan Quantity')
->alignCenter()
->numeric()
->sortable(),
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('working_days')
->label('Working Days')
->alignCenter()
->numeric()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('production_quantity')
->label('Production Quantity')
->alignCenter()
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('line.name')
->label('Line')
->alignCenter()
->sortable(),// ->searchable(),
Tables\Columns\TextColumn::make('shift.block.name')
->label('Block')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('shift.name')
->label('Shift')
->alignCenter()
->sortable(),// ->searchable(),
Tables\Columns\TextColumn::make('plant.name')
->label('Plant')
->alignCenter()
->sortable(),// ->searchable(),
->sortable()
->searchable(),
// Tables\Columns\TextColumn::make('line.name')
// ->label('Line')
// ->alignCenter()
// ->sortable(), // ->searchable(),
// Tables\Columns\TextColumn::make('shift.block.name')
// ->label('Block')
// ->alignCenter()
// ->sortable(),
// Tables\Columns\TextColumn::make('shift.name')
// ->label('Shift')
// ->alignCenter()
// ->sortable(), // ->searchable(),
Tables\Columns\TextColumn::make('created_at')
->label('Created At')
->dateTime()
@@ -584,7 +504,7 @@ class ProductionPlanResource extends Resource
Filter::make('advanced_filters')
->label('Advanced Filters')
->form([
//plant
// plant
Select::make('Plant')
->label('Select Plant')
->nullable()
@@ -593,7 +513,8 @@ class ProductionPlanResource extends Resource
// })
->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();
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
})
->reactive()
->afterStateUpdated(function ($state, callable $set, callable $get) {
@@ -602,31 +523,33 @@ class ProductionPlanResource extends Resource
$set('Shift', null);
}),
//line
// line
Select::make('Line')
->label('Select line')
->nullable()
->options(function (callable $get) {
$plantId = $get('Plant');
if (!$plantId ) {
if (! $plantId) {
return [];
}
return Line::where('plant_id', $plantId)
->pluck('name', 'id');
->pluck('name', 'id');
})
->reactive(),
//block
// block
Select::make('Block')
->label('Select Block')
->nullable()
->options(function (callable $get) {
$plantId = $get('Plant');
if (!$plantId ) {
if (! $plantId) {
return [];
}
return Block::where('plant_id', $get('Plant'))->pluck('name', 'id');
})
->reactive()
@@ -634,7 +557,7 @@ class ProductionPlanResource extends Resource
$set('Shift', null);
}),
//shift
// shift
Select::make('Shift')
->label('Select Shift')
->nullable()
@@ -642,7 +565,7 @@ class ProductionPlanResource extends Resource
$plantId = $get('Plant');
$blockId = $get('Block');
if (!$plantId || !$blockId) {
if (! $plantId || ! $blockId) {
return []; // Return empty if plant or block is not selected
}
@@ -669,52 +592,66 @@ class ProductionPlanResource extends Resource
return $query->whereRaw('1 = 0');
}
if ($plant = $data['Plant'] ?? null) {
$query->where('plant_id', $plant);
if (! empty($data['Plant'])) {// if ($plant = $data['Plant'] ?? null) {
$query->where('plant_id', $data['Plant']);
} else {
$userHas = Filament::auth()->user()->plant_id;
if ($userHas && strlen($userHas) > 0) {
return $query->whereRaw('1 = 0');
}
}
if ($shift = $data['Shift'] ?? null) {
$query->where('shift_id', $shift);
if (! empty($data['Shift'])) {// if ($shift = $data['Shift'] ?? null) {
$query->where('shift_id', $data['Shift']);
}
if ($line = $data['Line'] ?? null) {
$query->where('line_id', $line);
if (! empty($data['Line'])) {// if ($line = $data['Line'] ?? null) {
$query->where('line_id', $data['Line']);
}
if ($from = $data['created_from'] ?? null) {
$query->where('created_at', '>=', $from);
if (! empty($data['created_from'])) {// if ($from = $data['created_from'] ?? null) {
$query->where('created_at', '>=', $data['created_from']);
}
if ($to = $data['created_to'] ?? null) {
$query->where('created_at', '<=', $to);
if (! empty($data['created_to'])) {// if ($to = $data['created_to'] ?? null) {
$query->where('created_at', '<=', $data['created_to']);
}
return $query;
})
->indicateUsing(function (array $data) {
$indicators = [];
if (!empty($data['Plant'])) {
$indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name');
if (! empty($data['Plant'])) {
$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'])) {
$indicators[] = 'Shift: ' . Shift::where('id', $data['Shift'])->value('name');
if (! empty($data['Shift'])) {
$indicators[] = 'Shift: '.Shift::where('id', $data['Shift'])->value('name');
}
if (!empty($data['Line'])) {
$indicators[] = 'Line: ' . Line::where('id', $data['Line'])->value('name');
if (! empty($data['Line'])) {
$indicators[] = 'Line: '.Line::where('id', $data['Line'])->value('name');
}
if (!empty($data['created_from'])) {
$indicators[] = 'From: ' . $data['created_from'];
if (! empty($data['created_from'])) {
$indicators[] = 'From: '.$data['created_from'];
}
if (!empty($data['created_to'])) {
$indicators[] = 'To: ' . $data['created_to'];
if (! empty($data['created_to'])) {
$indicators[] = 'To: '.$data['created_to'];
}
return $indicators;
})
}),
])
->filtersFormMaxHeight('280px')
->actions([
@@ -726,7 +663,7 @@ class ProductionPlanResource extends Resource
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
FilamentExportBulkAction::make('export')
FilamentExportBulkAction::make('export'),
]),
])
->headerActions([
@@ -734,14 +671,14 @@ class ProductionPlanResource extends Resource
->label('Import Production Plans')
->color('warning')
->importer(ProductionPlanImporter::class)
->visible(function() {
->visible(function () {
return Filament::auth()->user()->can('view import production plan');
}),
ExportAction::make()
->label('Export Production Plans')
->color('warning')
->exporter(ProductionPlanExporter::class)
->visible(function() {
->visible(function () {
return Filament::auth()->user()->can('view export production plan');
}),
]);

View File

@@ -0,0 +1,295 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Exports\RequestQuotationExporter;
use App\Filament\Imports\RequestQuotationImporter;
use App\Filament\Resources\RequestQuotationResource\Pages;
use App\Filament\Resources\RequestQuotationResource\RelationManagers;
use App\Models\RequestQuotation;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Facades\Auth;
use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Actions\ExportAction;
class RequestQuotationResource extends Resource
{
protected static ?string $model = RequestQuotation::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Request For Quotation';
public static function form(Form $form): Form
{
$canEdit = Auth::user()?->can('edit_transport_fields');
$canEditData = Auth::user()?->can('edit_all_transport_fields');
return $form
->schema([
Forms\Components\TextInput::make('rfq_number')
->label('RFQ Number')
->readOnly()
->required(),
Forms\Components\Select::make('spot_rate_transport_master_id')
->label('Group Name')
->relationship('spotRateTransportMaster', 'group_name')
->required(),
Forms\Components\DateTimePicker::make('rfq_date_time')
->label('RFQ Date Time')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('pickup_address')
->label('PickUp Address')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('delivery_address')
->label('Delivery Address')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('weight')
->label('Weight')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('volumetrice_size_inch')
->label('Volumetrice')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('type_of_vehicle')
->label('Type of Vehicle')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('special_type')
->label('Special Type')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('no_of_vehicle')
->label('No of Vehicle')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('product_name')
->label('Product Name')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('loading_by')
->label('Loading By')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('unloading_by')
->label('Unloading By')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('pick_and_delivery')
->label('Pick and Delivery')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('payment_term')
->label('Payment Term')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\TextInput::make('paid_topay')
->label('Paid Today')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\DateTimePicker::make('require_date_time')
->label('Require Date Time')
->disabled(fn () => !$canEditData)
->required(),
Forms\Components\DateTimePicker::make('rfq_rec_on_or_before')
->label('RFQ Receive On Or Before')
->disabled(fn () => !$canEditData)
->required(),
// Forms\Components\TextInput::make('transporter_name')
// ->label('Transporter Name')
// ->disabled(fn () => !($canEdit || $canEditData)),
// Forms\Components\TextInput::make('total_freight_charge')
// ->label('Total Freight Charge')
// ->disabled(fn () => !($canEdit || $canEditData)),
// Forms\Components\TextInput::make('transit_day')
// ->label('Transit Day')
// ->disabled(fn () => !($canEdit || $canEditData)),
Forms\Components\Hidden::make('created_by')
->label('Created By'),
Forms\Components\Hidden::make('updated_by')
->label('Updated By'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->alignCenter()
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('rfq_number')
->label('RFQ Number')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('spotRateTransportMaster.group_name')
->label('Group Name')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('rfq_date_time')
->label('RFQ Date Time')
->alignCenter()
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('pickup_address')
->label('PickUp Address')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('delivery_address')
->label('Delivery Address')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('weight')
->label('Weight')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('volumetrice_size_inch')
->label('Volumetrice Size Inch')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('type_of_vehicle')
->label('Type Of Vehicle')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('product_name')
->label('Product Name')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('loading_by')
->label('Loading By')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('unloading_by')
->label('Unloading By')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('unloading_by')
->label('Unloading By')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('pick_and_delivery')
->label('Pick and Delivery')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('payment_term')
->label('Payment Term')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('paid_topay')
->label('Paid Today')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('require_date_time')
->label('Require Date Time')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('rfq_rec_on_or_before')
->label('RFQ Receive On Or Before')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('transporter_name')
->label('Transporter Name')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('total_freight_charge')
->label('Total Freight Charge')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('transit_day')
->label('Transit Day')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->label('Created At')
->alignCenter()
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->label('Updated At')
->alignCenter()
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('deleted_at')
->label('Deleted At')
->alignCenter()
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
])
->headerActions([
ImportAction::make()
->label('Import RequestQuotation')
->color('warning')
->importer(RequestQuotationImporter::class)
->visible(function() {
return Filament::auth()->user()->can('view import request quotation');
}),
ExportAction::make()
->label('Export RequestQuotation')
->color('warning')
->exporter(RequestQuotationExporter::class)
->visible(function() {
return Filament::auth()->user()->can('view export request quotation');
}),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRequestQuotations::route('/'),
'create' => Pages\CreateRequestQuotation::route('/create'),
'view' => Pages\ViewRequestQuotation::route('/{record}'),
'edit' => Pages\EditRequestQuotation::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Filament\Resources\RequestQuotationResource\Pages;
use App\Filament\Resources\RequestQuotationResource;
use App\Models\RequestQuotation;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateRequestQuotation extends CreateRecord
{
protected static string $resource = RequestQuotationResource::class;
public function mount(): void
{
parent::mount();
$this->form->fill([
'rfq_number' => $this->generateRfqNumber(),
]);
}
protected function generateRfqNumber(): string
{
$year = now()->year;
$lastRfq = RequestQuotation::whereYear('created_at', $year)
->orderBy('id', 'desc')
->value('rfq_number');
if ($lastRfq) {
$lastNumber = (int) substr($lastRfq, -3);
$nextNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
} else {
$nextNumber = '001';
}
return "C.R.I-{$year}-{$nextNumber}";
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Filament\Resources\RequestQuotationResource\Pages;
use App\Filament\Resources\RequestQuotationResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditRequestQuotation extends EditRecord
{
protected static string $resource = RequestQuotationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\RequestQuotationResource\Pages;
use App\Filament\Resources\RequestQuotationResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListRequestQuotations extends ListRecords
{
protected static string $resource = RequestQuotationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\RequestQuotationResource\Pages;
use App\Filament\Resources\RequestQuotationResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewRequestQuotation extends ViewRecord
{
protected static string $resource = RequestQuotationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,345 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource\RelationManagers;
use App\Models\RfqTransporterBid;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Validation\Rule;
class RfqTransporterBidResource extends Resource
{
protected static ?string $model = RfqTransporterBid::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Request For Quotation';
public static function getEloquentQuery(): Builder
{
$user = Filament::auth()->user();
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
])
->when(
! $user->hasAnyRole(['Super Admin', 'TransporterBidSupervisor']),
fn (Builder $query) => $query
->where('transporter_name', $user->name)
->whereNotNull('request_quotation_id')
);
}
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Select::make('request_quotation_id')
->label('RFQ Number')
// ->relationship('requestQuotation', 'rfq_number')
->relationship(
'requestQuotation',
'rfq_number',
function (Builder $query) {
$userName = Filament::auth()->user()?->name;
$masterIds = \App\Models\SpotRateTransportMaster::whereRaw(
"user_name::jsonb @> ?",
[json_encode([$userName])]
)
->pluck('id')
->unique()
->toArray();
if (empty($masterIds)) {
$query->whereRaw('1 = 0');
return;
}
$query->whereIn('spot_rate_transport_master_id', $masterIds);
}
)
->reactive()
// ->disabled(fn ($record) => !Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor']))
->disabled(fn ($record) =>
!Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor'])
&& $record
)
->afterStateUpdated(function ($state, callable $set) {
$rfq = \App\Models\RequestQuotation::find($state);
if ($rfq) {
$set('pickup_address', $rfq->pickup_address);
$set('delivery_address', $rfq->delivery_address);
$set('type_of_vehicle', $rfq->type_of_vehicle);
$set('weight', $rfq->weight);
$set('volumetrice_size_inch', $rfq->volumetrice_size_inch);
$set('no_of_vehicle', $rfq->no_of_vehicle);
$set('product_name', $rfq->product_name);
$set('pick_and_delivery', $rfq->pick_and_delivery);
$set('payment_term', $rfq->payment_term);
$set('paid_topay', $rfq->paid_topay);
$set('loading_by', $rfq->loading_by);
$set('unloading_by', $rfq->unloading_by);
$set('special_type', $rfq->special_type);
$set('rfq_date_time', $rfq->rfq_date_time);
$set('require_date_time', $rfq->require_date_time);
$set('rfq_rec_on_or_before', $rfq->rfq_rec_on_or_before);
}
})
->afterStateHydrated(function ($state, callable $set) {
$rfq = \App\Models\RequestQuotation::find($state);
if ($rfq) {
$set('pickup_address', $rfq->pickup_address);
$set('delivery_address', $rfq->delivery_address);
$set('type_of_vehicle', $rfq->type_of_vehicle);
$set('weight', $rfq->weight);
$set('volumetrice_size_inch', $rfq->volumetrice_size_inch);
$set('no_of_vehicle', $rfq->no_of_vehicle);
$set('product_name', $rfq->product_name);
$set('pick_and_delivery', $rfq->pick_and_delivery);
$set('payment_term', $rfq->payment_term);
$set('paid_topay', $rfq->paid_topay);
$set('loading_by', $rfq->loading_by);
$set('unloading_by', $rfq->unloading_by);
$set('special_type', $rfq->special_type);
$set('rfq_date_time', $rfq->rfq_date_time);
$set('require_date_time', $rfq->require_date_time);
$set('rfq_rec_on_or_before', $rfq->rfq_rec_on_or_before);
}
})
->required(),
Forms\Components\Section::make('RFQ Details')
->visible(fn ($get) => $get('request_quotation_id') != null)
->reactive()
->schema([
TextInput::make('pickup_address')->label('Pickup Address')->disabled(),
TextInput::make('delivery_address')->label('Delivery Address')->disabled(),
TextInput::make('type_of_vehicle')->label('Vehicle Type')->disabled(),
TextInput::make('weight')->label('Weight')->disabled(),
TextInput::make('volumetrice_size_inch')->label('Volumetric Size')->disabled(),
TextInput::make('no_of_vehicle')->label('No. of Vehicle')->disabled(),
TextInput::make('product_name')->label('Product Name')->disabled(),
TextInput::make('pick_and_delivery')->label('Pick & Delivery')->disabled(),
TextInput::make('payment_term')->label('Payment Term')->disabled(),
TextInput::make('paid_topay')->label('Paid / To Pay')->disabled(),
TextInput::make('loading_by')->label('Loading By')->disabled(),
TextInput::make('unloading_by')->label('Unloading By')->disabled(),
TextInput::make('special_type')->label('Special Type')->disabled(),
TextInput::make('rfq_date_time')->label('RFQ Created On')->disabled(),
TextInput::make('require_date_time')->label('Required Date')->disabled(),
TextInput::make('rfq_rec_on_or_before')->label('RFQ Received On/Before')->disabled(),
]),
Forms\Components\TextInput::make('transporter_name')
->label('Transporter Name')
->readOnly()
->default(Filament::auth()->user()?->name)
->required()
->rule(function (callable $get) {
return Rule::unique('rfq_transporter_bids', 'transporter_name')
->where('request_quotation_id', $get('request_quotation_id'))
->ignore($get('id'));
}),
Forms\Components\TextInput::make('total_freight_charge')
->label('Total Freight Charge')
->required()
// ->disabled(fn ($record) => $record && $record->created_by != Filament::auth()->user()?->name)
// ->hidden(fn ($record) => !Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor']) && $record->transporter_name != Filament::auth()->user()?->name),
->hidden(fn ($record) =>
!Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor'])
&& ($record && $record->transporter_name != Filament::auth()->user()?->name) // Ensure $record is not null before checking transporter_name
),
Forms\Components\TextInput::make('transit_day')
->label('Transit Day')
->required()
// ->disabled(fn ($record) => $record && $record->created_by != Filament::auth()->user()?->name),
// ->hidden(fn ($record) => !Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor']) && $record->transporter_name != Filament::auth()->user()?->name),
->hidden(fn ($record) =>
!Filament::auth()->user()?->hasAnyRole(['Super Admin', 'TransporterBidSupervisor'])
&& ($record && $record->transporter_name != Filament::auth()->user()?->name) // Ensure $record is not null before checking transporter_name
),
Forms\Components\Hidden::make('created_by')
->label('Created By')
->default(Filament::auth()->user()?->name),
Forms\Components\Hidden::make('updated_by')
->label('Updated By')
->default(Filament::auth()->user()?->name),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->alignCenter()
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('requestQuotation.rfq_number')
->label('RFQ Number')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.pickup_address')
->label('PickUp Address')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.delivery_address')
->label('Delivery Address')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.type_of_vehicle')
->label('Type Of Vehicle')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.weight')
->label('Weight')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.volumetrice_size_inch')
->label('Volumetrice Size Inch')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.no_of_vehicle')
->label('No Of Vehicle')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.product_name')
->label('Product Name')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.pick_and_delivery')
->label('Pick And Delivery')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.payment_term')
->label('Payment Term')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.paid_topay')
->label('Paid Today')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.loading_by')
->label('Loading By')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.unloading_by')
->label('Unloading By')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.special_type')
->label('Special Type')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.rfq_date_time')
->label('RFQ DateTime')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.require_date_time')
->label('RFQ Require DateTime')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('requestQuotation.rfq_rec_on_or_before')
->label('RFQ Rec On Or Before')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('created_by')
->label('Created By')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_by')
->label('Updated By')
->searchable()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListRfqTransporterBids::route('/'),
'create' => Pages\CreateRfqTransporterBid::route('/create'),
'view' => Pages\ViewRfqTransporterBid::route('/{record}'),
'edit' => Pages\EditRfqTransporterBid::route('/{record}/edit'),
];
}
// public static function getEloquentQuery(): Builder
// {
// return parent::getEloquentQuery()
// ->withoutGlobalScopes([
// SoftDeletingScope::class,
// ]);
// }
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource;
use App\Models\RfqTransporterBid;
use App\Models\User;
use App\Notifications\PushAlertNotification;
use Filament\Actions;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreateRfqTransporterBid extends CreateRecord
{
protected static string $resource = RfqTransporterBidResource::class;
protected function afterCreate(): void
{
$record = $this->record;
// Calculate rank based on total_freight_charge
$rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id)
->orderBy('total_freight_charge')
->pluck('id')
->search($this->record->id) + 1;
$recipients = User::role(['Super Admin', 'Rfq Supervisor'])->get();
$currentUser = Filament::auth()->user();
if ($currentUser && ! $recipients->contains('id', $currentUser->id)) {
$recipients->push($currentUser);
}
// $user1 = Filament::auth()->user();
$rfqNumber = $this->record->requestQuotation->rfq_number;
$body = "{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.";
Notification::make()
->title('Rank Updated')
->body("{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.")
->success()
->sendToDatabase($recipients);
// Push notification
foreach ($recipients as $user) {
$user->notify(
new PushAlertNotification(
'New Bid Added',
$body
)
);
}
\Log::info('Create bid notification sent', [
'bid_id' => $record->id,
'rank' => $rank,
'recipients' => $recipients->pluck('id'),
]);
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource;
use App\Models\RequestQuotation;
use App\Models\RfqTransporterBid;
use App\Models\SpotRateTransportMaster;
use App\Models\User;
use App\Notifications\PushAlertNotification;
use Filament\Actions;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Log;
class EditRfqTransporterBid extends EditRecord
{
protected static string $resource = RfqTransporterBidResource::class;
protected function afterSave(): void
{
if (! $this->record->wasChanged('total_freight_charge')) {
return;
}
$rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id)
->orderBy('total_freight_charge')
->pluck('id')
->search($this->record->id) + 1;
$requestQuotation = RequestQuotation::findOrFail(
$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();
// if ($currentUser && ! $recipients1->contains('id', $currentUser->id)) {
// $recipients1->push($currentUser);
// }
// if ($currentUser && ! $recipients->contains('id', $currentUser->id)) {
// $recipients->push($currentUser);
// }
// $user1 = Filament::auth()->user();
$rfqNumber = $this->record->requestQuotation->rfq_number;
$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()
->title('Rank Updated')
->body("{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.")
->success()
->sendToDatabase($users);
// foreach ($recipients1 as $user) {
// $user->notify(
// new PushAlertNotification(
// '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) {
Log::warning('User has NO push subscription', [
'user_id' => $user->id,
]);
continue;
}
Log::info('Sending push notification', [
'user_id' => $user->id,
]);
// ✅ THIS IS ALL YOU NEED
$user->notify(new PushAlertNotification(
'Rank Updated',
$body
));
}
}
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListRfqTransporterBids extends ListRecords
{
protected static string $resource = RfqTransporterBidResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewRfqTransporterBid extends ViewRecord
{
protected static string $resource = RfqTransporterBidResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\SpotRateTransportMasterResource\Pages;
use App\Filament\Resources\SpotRateTransportMasterResource\RelationManagers;
use App\Models\SpotRateTransportMaster;
use App\Models\User;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
class SpotRateTransportMasterResource extends Resource
{
protected static ?string $model = SpotRateTransportMaster::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Request For Quotation';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('group_name')
->label('Group Name')
->required(),
Forms\Components\Select::make('user_name')
->label('User')
->multiple()
->preload()
->reactive()
->options(
User::pluck('name', 'name')->toArray()
)
->searchable()
->required(),
Forms\Components\Hidden::make('id')
->label('id'),
Forms\Components\Hidden::make('created_by')
->label('Created By')
->default(Filament::auth()->user()?->name),
Forms\Components\Hidden::make('updated_by')
->label('Updated By')
->default(Filament::auth()->user()?->name),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('group_name')
->label('Group Name')
->sortable(),
Tables\Columns\TextColumn::make('user_name')
->label('User Name')
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->label('Created At')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->label('Updated At')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListSpotRateTransportMasters::route('/'),
'create' => Pages\CreateSpotRateTransportMaster::route('/create'),
'view' => Pages\ViewSpotRateTransportMaster::route('/{record}'),
'edit' => Pages\EditSpotRateTransportMaster::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Filament\Resources\SpotRateTransportMasterResource\Pages;
use App\Filament\Resources\SpotRateTransportMasterResource;
use App\Models\SpotRateTransportMaster;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreateSpotRateTransportMaster extends CreateRecord
{
protected static string $resource = SpotRateTransportMasterResource::class;
protected function beforeCreate(): void
{
$groupName = $this->data['group_name'] ?? null;
$userNames = $this->data['user_name'] ?? [];
foreach ($userNames as $userName) {
$query = SpotRateTransportMaster::where('group_name', $groupName)
->whereJsonContains('user_name', $userName);
if ($query->exists()) {
Notification::make()
->title('Duplicate User')
->body("User {$userName} already exists in this group.")
->danger()
->persistent()
->send();
// Prevent create
$this->halt();
return;
}
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Filament\Resources\SpotRateTransportMasterResource\Pages;
use App\Filament\Resources\SpotRateTransportMasterResource;
use App\Models\SpotRateTransportMaster;
use Filament\Actions;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
class EditSpotRateTransportMaster extends EditRecord
{
protected static string $resource = SpotRateTransportMasterResource::class;
protected function beforeSave(): void
{
$groupName = $this->data['group_name'] ?? null;
$userNames = $this->data['user_name'] ?? [];
$recordId = $this->record->id ?? null;
foreach ($userNames as $userName) {
$query = SpotRateTransportMaster::where('group_name', $groupName)
->whereJsonContains('user_name', $userName);
// Exclude current record for update
if ($recordId) {
$query->where('id', '!=', $recordId);
}
if ($query->exists()) {
Notification::make()
->title('Duplicate User')
->body("User {$userName} already exists in this group.")
->danger()
->persistent()
->send();
// Prevent save/update
$this->halt();
return;
}
}
}
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SpotRateTransportMasterResource\Pages;
use App\Filament\Resources\SpotRateTransportMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListSpotRateTransportMasters extends ListRecords
{
protected static string $resource = SpotRateTransportMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\SpotRateTransportMasterResource\Pages;
use App\Filament\Resources\SpotRateTransportMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewSpotRateTransportMaster extends ViewRecord
{
protected static string $resource = SpotRateTransportMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,478 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Exports\StickerMappingMasterExporter;
use App\Filament\Imports\StickerMappingMasterImporter;
use App\Filament\Resources\StickerMappingMasterResource\Pages;
use App\Filament\Resources\StickerMappingMasterResource\RelationManagers;
use App\Models\Item;
use App\Models\ItemCharacteristic;
use App\Models\Machine;
use App\Models\StickerMappingMaster;
use App\Models\StickerStructureDetail;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Actions\ImportAction;
class StickerMappingMasterResource extends Resource
{
protected static ?string $model = StickerMappingMaster::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Customized Sticker Printing';
// public static function form(Form $form): Form
// {
// return $form
// ->schema([
// Forms\Components\TextInput::make('plant_id')
// ->required()
// ->numeric(),
// Forms\Components\TextInput::make('item_id')
// ->required()
// ->numeric(),
// Forms\Components\Textarea::make('sticker1')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('sticker2')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('sticker3')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('sticker4')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('sticker5')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('created_by')
// ->columnSpanFull(),
// Forms\Components\Textarea::make('updated_by')
// ->columnSpanFull(),
// ]);
// }
public static function form(Form $form): Form
{
return $form
->schema([
/* -------- Basic Mapping -------- */
Section::make('Basic Mapping')
->schema([
Select::make('plant_id')
->label('Plant')
->reactive()
->relationship('plant', 'name')
->required(),
Select::make('item_characteristic_id')
->label('Item Code')
->reactive()
//->relationship('item', 'code')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (! $plantId) {
return [];
}
return ItemCharacteristic::query()
->whereHas('item', function ($query) use ($plantId) {
$query->where('plant_id', $plantId);
})
->with('item')
->get()
->mapWithKeys(fn ($ic) => [
$ic->id => $ic->item->code,
]);
})
->searchable()
->required(),
])
->columns(2),
/* -------- Sticker 1 -------- */
Section::make('Sticker 1')
->schema([
Select::make('sticker_structure1_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker1_machine_id')
->label('Work Center')
//->relationship('sticker1Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker1_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 2 -------- */
Section::make('Sticker 2')
->schema([
Select::make('sticker_structure2_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker2_machine_id')
->label('Work Center')
// ->relationship('sticker2Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker2_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 3 -------- */
Section::make('Sticker 3')
->schema([
Select::make('sticker_structure3_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker3_machine_id')
->label('Work Center')
//->relationship('sticker3Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker3_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 4 -------- */
Section::make('Sticker 4')
->schema([
Select::make('sticker_structure4_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker4_machine_id')
->label('Work Center')
//->relationship('sticker4Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker4_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 5 -------- */
Section::make('Sticker 5')
->schema([
Select::make('sticker_structure5_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker5_machine_id')
->label('Work Center')
//->relationship('sticker5Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker5_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 6 -------- */
Section::make('Sticker 6')
->schema([
Select::make('sticker_structure6_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker6_machine_id')
->label('Work Center')
//->relationship('sticker6Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker6_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 7 -------- */
Section::make('Sticker 7')
->schema([
Select::make('sticker_structure7_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker7_machine_id')
->label('Work Center')
//->relationship('sticker7Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker7_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
/* -------- Sticker 8 -------- */
Section::make('Sticker 8')
->schema([
Select::make('sticker_structure8_id')
->label('Sticker ID')
->options(function (callable $get) {
return StickerStructureDetail::pluck('sticker_id', 'id');
})
->searchable(),
Select::make('sticker8_machine_id')
->label('Work Center')
//->relationship('sticker8Machine', 'name')
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
TextInput::make('sticker8_print_ip')
->label('Printer IP')
->ipv4(),
])
->columns(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('plant.name')
->label('Plant')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('itemCharacteristic.item.code')
->label('Item')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('sticker1Structure.sticker_id')
->label('Sticker 1')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker1Machine.work_center')
->label('WC 1')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker1_print_ip')
->label('Printer IP 1')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker2Structure.sticker_id')
->label('Sticker 2')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker2Machine.work_center')
->label('WC 2')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker2_print_ip')
->label('Printer IP 2')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker3Structure.sticker_id')
->label('Sticker 3')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker3Machine.work_center')
->label('WC 3')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker3_print_ip')
->label('Printer IP 3')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker4Structure.sticker_id')
->label('Sticker 4')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker4Machine.work_center')
->label('WC 4')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker4_print_ip')
->label('Printer IP 4')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker5Structure.sticker_id')
->label('Sticker 5')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker5Machine.work_center')
->label('WC 5')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker5_print_ip')
->label('Printer IP 5')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker6Structure.sticker_id')
->label('Sticker 6')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker6Machine.work_center')
->label('WC 6')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker6_print_ip')
->label('Printer IP 6')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker7Structure.sticker_id')
->label('Sticker 7')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker7Machine.work_center')
->label('WC 7')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker7_print_ip')
->label('Printer IP 7')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker8Structure.sticker_id')
->label('Sticker 8')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker8Machine.work_center')
->label('WC 8')
->alignCenter(),
Tables\Columns\TextColumn::make('sticker8_print_ip')
->label('Printer IP 8')
->alignCenter(),
Tables\Columns\TextColumn::make('created_at')
->label('Created At')
->alignCenter()
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->label('Updated At')
->alignCenter()
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
])
->headerActions([
ImportAction::make()
->importer(StickerMappingMasterImporter::class)
->label('Import Sticker Mapping Master')
->color('warning')
->visible(function () {
return Filament::auth()->user()->can('view import sticker mapping master');
}),
ExportAction::make()
->exporter(StickerMappingMasterExporter::class)
->label('Export Sticker Mapping Master')
->color('warning')
->visible(function () {
return Filament::auth()->user()->can('view export sticker mapping master');
}),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListStickerMappingMasters::route('/'),
'create' => Pages\CreateStickerMappingMaster::route('/create'),
'view' => Pages\ViewStickerMappingMaster::route('/{record}'),
'edit' => Pages\EditStickerMappingMaster::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\StickerMappingMasterResource\Pages;
use App\Filament\Resources\StickerMappingMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateStickerMappingMaster extends CreateRecord
{
protected static string $resource = StickerMappingMasterResource::class;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Filament\Resources\StickerMappingMasterResource\Pages;
use App\Filament\Resources\StickerMappingMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditStickerMappingMaster extends EditRecord
{
protected static string $resource = StickerMappingMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\StickerMappingMasterResource\Pages;
use App\Filament\Resources\StickerMappingMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListStickerMappingMasters extends ListRecords
{
protected static string $resource = StickerMappingMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\StickerMappingMasterResource\Pages;
use App\Filament\Resources\StickerMappingMasterResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewStickerMappingMaster extends ViewRecord
{
protected static string $resource = StickerMappingMasterResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\StickerValidationResource\Pages;
use App\Filament\Resources\StickerValidationResource\RelationManagers;
use App\Models\Machine;
use App\Models\StickerValidation;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class StickerValidationResource extends Resource
{
protected static ?string $model = StickerValidation::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?string $navigationGroup = 'Customized Sticker Printing';
public static function form(Form $form): Form
{
return $form
->schema([
Section::make('')
->schema([
Forms\Components\Select::make('plant_id')
->label('Plant')
->reactive()
->relationship('plant', 'name')
->required(),
Forms\Components\Select::make('machine_id')
->label('Work Center')
->required()
->reactive()
->options(function (callable $get) {
$plantId = $get('plant_id');
if (empty($plantId)) {
return [];
}
return Machine::where('plant_id', $plantId)->pluck('work_center', 'id');
})
->searchable(),
Forms\Components\TextInput::make('production_order')
->label('Production Order')
->reactive()
->extraAttributes([
'id' => 'production_order_input',
'x-data' => '{ value: "" }',
'x-model' => 'value',
'wire:keydown.enter.prevent' => 'processProOrder(value)',
]),
Forms\Components\TextInput::make('serial_number')
->label('Serial Number')
->reactive()
->extraAttributes([
'id' => 'serial_number_input',
'x-data' => '{ value: "" }',
'x-model' => 'value',
'wire:keydown.enter.prevent' => 'processSno(value)',
]),
Forms\Components\Hidden::make('created_by')
->label('Created By')
->default(Filament::auth()->user()?->name),
Forms\Components\Hidden::make('updated_by')
->label('Updated By')
->default(Filament::auth()->user()?->name),
])
->columns(4),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('No.')
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\TextColumn::make('plant.code')
->label('Plant')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('machine.work_center')
->label('Work Center')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('production_order')
->label('Production Order')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('serial_number')
->label('Serial Number')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('status')
->label('Status')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListStickerValidations::route('/'),
'create' => Pages\CreateStickerValidation::route('/create'),
'view' => Pages\ViewStickerValidation::route('/{record}'),
'edit' => Pages\EditStickerValidation::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,416 @@
<?php
namespace App\Filament\Resources\StickerValidationResource\Pages;
use App\Filament\Resources\StickerValidationResource;
use App\Models\Item;
use App\Models\ItemCharacteristic;
use App\Models\ProductionQuantity;
use App\Models\StickerDetail;
use App\Models\StickerMappingMaster;
use App\Models\StickerStructureDetail;
use App\Models\StickerValidation;
use App\Services\StickerPdfService;
use Filament\Actions;
use Filament\Facades\Filament;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
class CreateStickerValidation extends CreateRecord
{
protected static string $resource = StickerValidationResource::class;
protected static string $view = 'filament.resources.sticker-validation-resource.create-sticker-validation';
public $ref_number;
public $plantId;
public $workCenter;
public $serNo;
public function getFormActions(): array
{
return [
$this->getCancelFormAction(),
];
}
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('create');
}
public function processProOrder($value)
{
$plantId = $this->form->getState()['plant_id'];
$this->plantId = $plantId;
$this->ref_number = $value;
$this->dispatch('refreshEmptySticker', $plantId, $value);
$this->dispatch('focus-serial-number');
}
public function processSno($serNo)
{
$plantId = $this->form->getState()['plant_id'];
$this->plantId = $plantId;
$workCenter = $this->form->getState()['machine_id'];
$this->workCenter = $workCenter;
$this->ref_number = $this->form->getState()['production_order'];
$this->serNo = $serNo;
$user = Filament::auth()->user();
$operatorName = $user->name;
if (! preg_match('/^([a-zA-Z0-9]{6,})\|([1-9][a-zA-Z0-9]{8,})\|?$/', $this->serNo, $matches)) {
Notification::make()
->danger()
->title('Invalid Serial QR Format')
->body('Scan valid Serial QR code proceed!<br>Sample formats are:<br>123456|1234567890123| OR 123456|1234567890123')
->seconds(3)
->send();
$this->dispatch('playWarnSound');
$this->form->fill([
'plant_id' => $this->plantId,
'machine_id' => $this->workCenter,
'production_order' => $this->ref_number,
'serial_number' => null,
]);
$this->dispatch('focus-serial-number');
return;
}
else {
$itemCode = $matches[1];
$serialNumber = $matches[2];
$recFound = ProductionQuantity::where('plant_id', $this->plantId)
->where('production_order', $this->ref_number)
->where('serial_number', $serialNumber)
->first();
if(!$recFound){
Notification::make()
->danger()
->title('Unknown Serial Number')
->body("Scanned serial number '$serialNumber' not found for the given plant and production order")
->seconds(3)
->send();
$this->form->fill([
'plant_id' => $this->plantId,
'machine_id' => $this->workCenter,
'production_order' => $this->ref_number,
'serial_number' => null,
]);
return;
}
$duplicate = StickerValidation::where('plant_id', $this->plantId)
->where('production_order', $this->ref_number)
->where('serial_number', $serialNumber)
->first();
if ($duplicate) {
Notification::make()
->danger()
->title('Duplicate Serial Number')
->body("Serial number $serialNumber already exists for this plant and production order!")
->seconds(3)
->send();
$this->form->fill([
'plant_id' => $this->plantId,
'machine_id' => $this->workCenter,
'production_order' => $this->ref_number,
'serial_number' => null,
]);
return;
}
$itemC = Item::where('code', $itemCode)
->where('plant_id',$this->plantId)
->first();
$itemId = $itemC->id;
$item = ItemCharacteristic::where('item_id', $itemId)
->where('plant_id',$this->plantId)
->first();
$itemI = $item->id;
$mapping = StickerMappingMaster::where('plant_id', $this->plantId)
->where('item_characteristic_id', $itemI)
->first();
if (!$mapping) {
Notification::make()
->danger()
->title('Sticker Mapping Not Found')
->body("No sticker mapping found for this item and plant.")
->send();
return;
}
$stickers = [];
for ($i = 1; $i <= 8; $i++) {
$machineColumn = "sticker{$i}_machine_id";
$ipColumn = "sticker{$i}_print_ip";
$stickerColumn = "sticker_structure{$i}_id";
$itemColumn = "item_characteristic_id";
if (
!empty($mapping->$machineColumn) &&
!empty($mapping->$stickerColumn)
) {
$stickers[] = [
'machine_id' => $mapping->$machineColumn,
'sticker_id' => $mapping->$stickerColumn,
'item_characteristic' => $mapping->$itemColumn,
'print_ip' => $mapping->$ipColumn,
];
}
}
if (empty($stickers)) {
Notification::make()
->danger()
->title('No Sticker Configuration Found')
->body('No sticker and machine mappings configured for this item and plant.')
->send();
return;
}
StickerValidation::create([
'plant_id' => $this->plantId,
'machine_id' => $this->workCenter,
'production_order' => $this->ref_number ?? null,
'serial_number' => $serialNumber,
'status' => 'Printed',
// 'sticker_id' => $matchedSticker,
'created_by' => $operatorName,
'created_at' => now(),
'updated_at' => now(),
]);
Notification::make()
->success()
->title('Sticker Recorded')
->body("Item: $itemCode, Serial: $serialNumber recorded successfully!")
->seconds(3)
->send();
$this->form->fill([
'plant_id' => $this->plantId,
'machine_id' => $this->workCenter,
'production_order' => $this->ref_number,
'serial_number' => null,
]);
$this->dispatch('refreshEmptySticker', $plantId, $this->ref_number);
// foreach ($stickers as $sticker) {
// // $printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']);
// \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)
{
\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){
$parts = explode(':', $line, 2);
if (count($parts) < 2) continue;
$printerName = trim(str_replace('device for', '', $parts[0]));
$deviceUri = trim($parts[1]);
if (str_contains($deviceUri, $ip)) {
return $printerName;
}
}
return null;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Filament\Resources\StickerValidationResource\Pages;
use App\Filament\Resources\StickerValidationResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditStickerValidation extends EditRecord
{
protected static string $resource = StickerValidationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\StickerValidationResource\Pages;
use App\Filament\Resources\StickerValidationResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListStickerValidations extends ListRecords
{
protected static string $resource = StickerValidationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\StickerValidationResource\Pages;
use App\Filament\Resources\StickerValidationResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewStickerValidation extends ViewRecord
{
protected static string $resource = StickerValidationResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,276 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\VisitorEntryResource\Pages;
use App\Filament\Resources\VisitorEntryResource\RelationManagers;
use App\Models\VisitorEntry;
use Filament\Facades\Filament;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class VisitorEntryResource extends Resource
{
protected static ?string $model = VisitorEntry::class;
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('mobile_number')
->label('Mobile Number')
->length(10)
->reactive()
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^0-9]/g, "").slice(0, 10)', // blocks non-numbers + limits to 10 chars
'maxlength' => 10,
])
->required()
->extraAttributes([
'id' => 'mobile_number_input',
'x-data' => '{ value: "" }',
'x-model' => 'value',
'wire:keydown.enter.prevent' => 'processMobile(value)',
]),
Forms\Components\TextInput::make('name')
->label('Name')
->required()
->reactive()
->extraInputAttributes([
'oninput' => 'this.value = this.value.replace(/[^a-zA-Z\s]/g, "")',
]),
Forms\Components\Select::make('type')
->label('Type')
->reactive()
->options([
'Student' => 'Student',
'Consultant' => 'Consultant',
'Vendor' => 'Vendor',
'Other' => 'Other',
])
->required()
->dehydrateStateUsing(function ($state, callable $get) {
return $state == 'Other'
? $get('other_type')
: $state;
}),
Forms\Components\TextInput::make('other_type')
->label('Specify Type')
->reactive()
->visible(fn (callable $get) => $get('type') == 'Other')
->required(fn (callable $get) => $get('type') == 'Other')
->dehydrated(false),
Forms\Components\TextInput::make('company')
->label('Company')
->required(),
Forms\Components\Select::make('department')
->label('Employee Department')
->options(
\App\Models\EmployeeMaster::distinct()
->pluck('department', 'department')
)
->required()
->reactive()
->afterStateUpdated(function (callable $set) {
$set('employee_master_id', null);
$set('code', null);
}),
// Forms\Components\Select::make('employee_master_id')
// ->label('Recipient Employee')
// ->required()
// ->options(function (callable $get) {
// $department = $get('department');
// if (!$department) {
// return [];
// }
// return \App\Models\EmployeeMaster::where('department', $department)
// ->pluck('name', 'id');
// })
// ->reactive()
// ->afterStateUpdated(function (callable $set, callable $get, ?string $state) {
// $department = $get('department');
// $employee = \App\Models\EmployeeMaster::where('id', $state)
// ->where('department', $department)
// ->first();
// $set('code', $employee ? $employee->code : '');
// }),
Forms\Components\Select::make('employee_master_id')
->label('Recipient Employee')
->required()
->options(function (callable $get) {
$department = $get('department');
// Always load ALL employees, filter by department if set
if ($department) {
return \App\Models\EmployeeMaster::where('department', $department)
->pluck('name', 'id');
}
// Fallback: load all so fill() can always match the ID
return \App\Models\EmployeeMaster::pluck('name', 'id');
})
->reactive()
->afterStateUpdated(function (callable $set, ?string $state) {
$employee = \App\Models\EmployeeMaster::find($state);
$set('code', $employee?->code ?? '');
}),
Forms\Components\TextInput::make('code')
->label('Employee Code')
->readOnly(),
Forms\Components\Textarea::make('purpose_of_visit')
->label('Purpose of Visit')
->required(),
Forms\Components\TextInput::make('number_of_person')
->numeric()
->default(1)
->required(),
Forms\Components\DateTimePicker::make('in_time')
->label('In Time'),
Forms\Components\DateTimePicker::make('out_time')
->label('Out Time'),
Forms\Components\View::make('components.webcam-field')
->columnSpanFull(),
Forms\Components\Hidden::make('photo'),
Forms\Components\Hidden::make('created_by')
->label('created_by')
->default(Filament::auth()->user()?->name),
Forms\Components\Hidden::make('updated_by')
->label('updated_by')
->default(Filament::auth()->user()?->name),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('No.')
->label('NO')
->alignCenter()
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
$paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}),
Tables\Columns\ImageColumn::make('photo')
->label('Photo')
->disk('public')
->height(50)
->width(50)
// ->defaultImageUrl('https://ui-avatars.com/api/?name=Visitor&background=555&color=fff')
->defaultImageUrl(asset('images/profile.png'))
->alignCenter()
->extraImgAttributes(['style' => 'border-radius: 6px; object-fit: cover;']),
Tables\Columns\TextColumn::make('type')
->label('Visitor Type')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('name')
->label('Visitor Name')
->sortable()
->alignCenter()
->searchable(),
Tables\Columns\TextColumn::make('mobile_number')
->label('Visitor Mobile Number')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('employeeMaster.name')
->label('Recipient Name')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('employeeMaster.code')
->label('Receipient ID')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('employeeMaster.department')
->label('Receipient Department')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('number_of_person')
->label('Number of Person')
->numeric()
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('in_time')
->label('In Time')
->dateTime()
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('out_time')
->label('Out Time')
->dateTime()
->sortable()
->alignCenter(),
Tables\Columns\TextColumn::make('created_at')
->label('Created At')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
Tables\Columns\TextColumn::make('updated_at')
->label('Updated At')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
Tables\Columns\TextColumn::make('deleted_at')
->label('Deleted At')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true)
->alignCenter(),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListVisitorEntries::route('/'),
'create' => Pages\CreateVisitorEntry::route('/create'),
'view' => Pages\ViewVisitorEntry::route('/{record}'),
'edit' => Pages\EditVisitorEntry::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\Filament\Resources\VisitorEntryResource\Pages;
use App\Filament\Resources\VisitorEntryResource;
use App\Models\EmployeeMaster;
use App\Models\VisitorEntry;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
use Livewire\Attributes\On;
use Storage;
class CreateVisitorEntry extends CreateRecord
{
protected static string $resource = VisitorEntryResource::class;
public $capturedPhoto;
// #[On('photo-captured')]
// public function handlePhotoCapture(string $photo): void
// {
// // Puts the Base64 photo into the form's data array
// $this->data['photo'] = $photo;
// }
public function processMobile($mobile)
{
$visitor = VisitorEntry::where('mobile_number', $mobile)->latest()->first();
if ($visitor) {
$employee = EmployeeMaster::where('id', $visitor->employee_master_id)->first();
$this->form->fill([
'mobile_number' => $mobile ?? '',
'name' => $visitor->name ?? '',
'company' => $visitor->company ?? '',
'type' => $visitor->type ?? '',
'department' => $employee->department ?? '',
'employee_master_id' => $visitor->employee_master_id->name ?? '',
'code' => $employee->code ?? '',
]);
}
else {
$this->form->fill([
'mobile_number' => $mobile ?? '',
'name' => $visitor->name ?? '',
'company' => $visitor->company ?? '',
'type' => $visitor->type ?? '',
'department' => $employee->department ?? '',
'employee_master_id' => $visitor->employee_master_id->name ?? '',
'code' => $employee->code ?? '',
]);
}
}
// protected function mutateFormDataBeforeCreate(array $data): array
// {
// if (
// !empty($data['photo']) &&
// str_starts_with($data['photo'], 'data:image')
// ) {
// // Step A: Strip the "data:image/jpeg;base64," prefix
// $imageData = explode(',', $data['photo'])[1];
// // Step B: Generate a unique filename
// $filename = 'visitor_' . time() . '_' . uniqid() . '.jpg';
// // Step C: Decode Base64 and save as a real .jpg file
// $path = 'visitor-photos/' . $filename;
// Storage::disk('public')->put($path, base64_decode($imageData));
// // Step D: Replace the Base64 string with just the file path
// $data['photo'] = $path;
// }
// return $data;
// }
#[On('photo-captured')]
public function handlePhotoCapture(string $photo): void
{
$this->data['photo'] = $photo;
\Log::info('WEBCAM: photo-captured event received, length: ' . strlen($photo));
}
// protected function mutateFormDataBeforeCreate(array $data): array
// {
// \Log::info('WEBCAM: mutateFormDataBeforeCreate called, photo value: ' . substr($data['photo'] ?? 'NULL', 0, 50));
// if (
// !empty($data['photo']) &&
// str_starts_with($data['photo'], 'data:image')
// ) {
// $imageData = explode(',', $data['photo'])[1];
// $filename = 'visitor_' . time() . '_' . uniqid() . '.jpg';
// $path = 'visitor-photos/' . $filename;
// Storage::disk('public')->put($path, base64_decode($imageData));
// $data['photo'] = $path;
// \Log::info('WEBCAM: photo saved to ' . $path);
// }
// return $data;
// }
protected function mutateFormDataBeforeCreate(array $data): array
{
if (
!empty($data['photo']) &&
str_starts_with($data['photo'], 'data:image')
) {
try {
$imageData = explode(',', $data['photo'])[1];
$filename = 'visitor_' . time() . '_' . uniqid() . '.jpg';
$path = 'visitor-photos/' . $filename;
$decoded = base64_decode($imageData);
$saved = Storage::disk('public')->put($path, $decoded);
\Log::info('PHOTO UPLOAD (PUBLIC):', [
'filename' => $filename,
'path' => $path,
'size_bytes' => strlen($decoded),
'saved' => $saved ? 'SUCCESS' : 'FAILED',
]);
$data['photo'] = $path;
} catch (\Exception $e) {
\Log::error('PHOTO UPLOAD ERROR: ' . $e->getMessage());
}
}
return $data;
}
public function setPhoto(string $photo): void
{
$this->capturedPhoto = $photo;
// Change this ↓ to dispatch to parent explicitly
$this->dispatch('photo-captured', photo: $photo)->to(\App\Filament\Resources\VisitorEntryResource\Pages\CreateVisitorEntry::class);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Filament\Resources\VisitorEntryResource\Pages;
use App\Filament\Resources\VisitorEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Livewire\Attributes\On;
use Storage;
class EditVisitorEntry extends EditRecord
{
protected static string $resource = VisitorEntryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\ViewAction::make(),
Actions\DeleteAction::make(),
Actions\ForceDeleteAction::make(),
Actions\RestoreAction::make(),
];
}
#[On('photo-captured')]
public function handlePhotoCapture(string $photo): void
{
$this->data['photo'] = $photo;
}
protected function mutateFormDataBeforeSave(array $data): array
{
if (
!empty($data['photo']) &&
str_starts_with($data['photo'], 'data:image')
) {
// Delete the old photo file if one exists
$oldPhoto = $this->record->photo;
if ($oldPhoto && Storage::disk('public')->exists($oldPhoto)) {
Storage::disk('public')->delete($oldPhoto);
}
// Save the new photo
$imageData = explode(',', $data['photo'])[1];
$filename = 'visitor_' . time() . '_' . uniqid() . '.jpg';
$path = 'visitor-photos/' . $filename;
Storage::disk('public')->put($path, base64_decode($imageData));
$data['photo'] = $path;
}
return $data;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\VisitorEntryResource\Pages;
use App\Filament\Resources\VisitorEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListVisitorEntries extends ListRecords
{
protected static string $resource = VisitorEntryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\VisitorEntryResource\Pages;
use App\Filament\Resources\VisitorEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\ViewRecord;
class ViewVisitorEntry extends ViewRecord
{
protected static string $resource = VisitorEntryResource::class;
protected function getHeaderActions(): array
{
return [
Actions\EditAction::make(),
];
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Filament\Widgets;
use App\Models\RequestQuotation;
use App\Models\RfqTransporterBid;
use Filament\Facades\Filament;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class RfqChart extends BaseWidget
{
// protected function getStats(): array
// {
// $transporter = session('transport_name');
// $rfqNumber = session('rfq_number');
// if (!$transporter || !$rfqNumber) {
// return [
// Stat::make('Total Freight Charge', '-'),
// Stat::make('Rank', '-'),
// ];
// }
// $selectedRfq = RequestQuotation::query()
// ->where('transporter_name', $transporter)
// ->where('rfq_number', $rfqNumber)
// ->first();
// if (!$selectedRfq) {
// return [
// Stat::make('Total Freight Charge', '-'),
// Stat::make('Rank', '-'),
// ];
// }
// $myAmount = (float) $selectedRfq->total_freight_charge;
// $rank = RequestQuotation::query()
// ->whereRaw(
// 'CAST(total_freight_charge AS DECIMAL(10,2)) < ?',
// [$myAmount]
// )
// ->selectRaw('CAST(total_freight_charge AS DECIMAL(10,2))')
// ->distinct()
// ->count() + 1;
// $medal = match (true) {
// $rank == 1 => '🥇',
// $rank == 2 => '🥈',
// $rank == 3 => '🥉',
// default => '',
// };
// return [
// Stat::make(
// 'Total Freight Charge',
// number_format($selectedRfq->total_freight_charge, 2)
// )
// ->description('Transporter: ' . $selectedRfq->transporter_name)
// ->color($rank == 1 ? 'success' : 'primary'),
// Stat::make(
// 'Rank',
// trim("{$medal} #{$rank}")
// )
// ->description('Among all transporters')
// ->color(
// $rank == 1 ? 'success' :
// ($rank <= 3 ? 'warning' : 'gray')
// ),
// ];
// }
protected function getStats(): array
{
$transporter = session('transport_name');
$rfqNumber = session('rfq_id');
if (!$transporter || !$rfqNumber) {
return [
Stat::make('Total Freight Charge', '-'),
Stat::make('Rank', '-'),
];
}
$selectedRfq = RfqTransporterBid::query()
->where('transporter_name', $transporter)
->where('request_quotation_id', $rfqNumber)
->first();
if (!$selectedRfq) {
return [
Stat::make('Total Freight Charge', '-'),
Stat::make('Rank', '-'),
];
}
$myAmount = (float) $selectedRfq->total_freight_charge;
// $rank = RfqTransporterBid::query()
// ->whereRaw(
// 'CAST(total_freight_charge AS DECIMAL(10,2)) < ?',
// [$myAmount]
// )
// ->selectRaw('CAST(total_freight_charge AS DECIMAL(10,2))')
// ->distinct()
// ->count() + 1;
$rank = RfqTransporterBid::query()
->where('request_quotation_id', $rfqNumber) // 🔥 MISSING CONDITION
->whereRaw(
'CAST(total_freight_charge AS DECIMAL(10,2)) < ?',
[$myAmount]
)
->selectRaw('CAST(total_freight_charge AS DECIMAL(10,2))')
->distinct()
->count() + 1;
$medal = match (true) {
$rank == 1 => '🥇',
$rank == 2 => '🥈',
$rank == 3 => '🥉',
default => '',
};
return [
Stat::make(
'Total Freight Charge',
number_format($selectedRfq->total_freight_charge, 2)
)
->description('Transporter: ' . $selectedRfq->transporter_name)
->color($rank == 1 ? 'success' : 'primary'),
Stat::make(
'Rank',
trim("{$medal} #{$rank}")
)
->description('Among all transporters')
->color(
$rank == 1 ? 'success' :
($rank <= 3 ? 'warning' : 'gray')
),
];
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace App\Filament\Widgets;
use App\Models\RfqTransporterBid;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Js;
class RfqRankChart extends ChartWidget
{
protected static ?string $heading = 'Chart';
// protected function getData(): array
// {
// $rfqId = session('rfq_id');
// if (!$rfqId) {
// return [
// 'datasets' => [],
// 'labels' => [],
// ];
// }
// // Get bids ordered by lowest freight charge
// $bids = RfqTransporterBid::query()
// ->where('request_quotation_id', $rfqId)
// ->orderByRaw('CAST(total_freight_charge AS DECIMAL(10,2)) ASC')
// ->get();
// // $labels = [];
// // $ranks = [];
// // $rank = 1;
// // foreach ($bids as $bid) {
// // $labels[] = $bid->transporter_name;
// // $ranks[] = $rank++;
// // }
// // return [
// // 'datasets' => [
// // [
// // 'label' => 'Rank (Lower is Better)',
// // 'data' => $ranks,
// // 'fill' => false,
// // 'tension' => 0.3,
// // ],
// // ],
// // 'labels' => $labels,
// // ];
// $labels = [];
// $ranks = [];
// $colors = [];
// $rank = 1;
// foreach ($bids as $bid) {
// $labels[] = $bid->transporter_name;
// $ranks[] = $rank;
// // Rank-based colors
// $colors[] = match ($rank) {
// 1 => '#FFD700', // Gold
// 2 => '#C0C0C0', // Silver
// 3 => '#CD7F32', // Bronze
// default => '#3B82F6', // Blue
// };
// $rank++;
// }
// return [
// 'datasets' => [
// [
// 'label' => 'Rank (1 = Best)',
// 'data' => $ranks,
// // 🎨 Styling
// 'borderColor' => '#3B82F6',
// 'backgroundColor' => $colors,
// 'pointBackgroundColor' => $colors,
// 'pointBorderColor' => '#000',
// 'pointRadius' => 7,
// 'pointHoverRadius' => 10,
// 'borderWidth' => 3,
// 'tension' => 0.4,
// 'fill' => false,
// ],
// ],
// 'labels' => $labels,
// ];
// }
protected function getData(): array
{
$rfqId = session('rfq_id');
if (!$rfqId) {
return [
'datasets' => [],
'labels' => [],
];
}
/**
* STEP 1: Get bids sorted by freight charge (for ranking)
*/
$rankedBids = RfqTransporterBid::query()
->where('request_quotation_id', $rfqId)
->orderByRaw('CAST(total_freight_charge AS DECIMAL(10,2)) ASC')
->get();
$rankMap = [];
$rank = 1;
foreach ($rankedBids as $bid) {
$rankMap[$bid->id] = $rank++;
}
/**
* STEP 2: Get bids in natural order (for wave effect)
* You can change orderBy to:
* - created_at
* - transporter_name
*/
$chartBids = RfqTransporterBid::query()
->where('request_quotation_id', $rfqId)
->orderBy('id')
->get();
$labels = [];
$amounts = [];
$colors = [];
$ranks = [];
foreach ($chartBids as $bid) {
$labels[] = $bid->transporter_name;
$amounts[] = (float) $bid->total_freight_charge;
$rank = $rankMap[$bid->id];
$ranks[] = $rank;
$colors[] = match ($rank) {
1 => '#FFD700',
2 => '#C0C0C0',
3 => '#CD7F32',
default => '#2563EB',
};
}
return [
'datasets' => [
[
'label' => 'Freight Charge',
'data' => $amounts,
'rankData' => $ranks,
'borderColor' => '#2563EB',
'backgroundColor' => $colors,
'pointBackgroundColor' => $colors,
'pointBorderColor' => '#000',
'pointRadius' => 7,
'pointHoverRadius' => 11,
'borderWidth' => 3,
'tension' => 0.45,
'fill' => false,
],
],
'labels' => $labels,
];
}
protected function getOptions(): array
{
return [
'plugins' => [
'datalabels' => [
'anchor' => 'start',
'align' => 'start',
'offset' => -15,
'color' => '#000',
'font' => [
'weight' => 'bold',
],
'formatter' => Js::from("function(value) { return Number(value); }"),
],
],
'scales' => [
'y' => [
'beginAtZero' => true,
'ticks' => [
'stepSize' => 0.5,
],
],
],
];
}
protected function getType(): string
{
return 'bar';
}
}

View File

@@ -913,6 +913,8 @@ class PdfController extends Controller
$itemCharacteristicId = $request->query('item_characteristic_id');
$stickerId = trim($stickerId);
//dd($plantId,$itemCharacteristicId,$stickerId);
// Normalize "empty" or "" to null
$plantId = ($plantId && $plantId != 'empty') ? $plantId : null;
$itemCharacteristicId = ($itemCharacteristicId && $itemCharacteristicId != 'empty') ? $itemCharacteristicId : null;
@@ -956,4 +958,91 @@ class PdfController extends Controller
return $pdfService->generate($stickerId, $elements->toArray());
}
}
// public function generatePdf(Request $request, $stickerId)
// {
// $plantId = $request->query('plant_id');
// $itemCharacteristicId = $request->query('item_characteristic_id');
// $stickerId = trim($stickerId);
// //dd($plantId,$itemCharacteristicId,$stickerId);
// // Normalize "empty" or "" to null
// $plantId = ($plantId && $plantId != 'empty') ? $plantId : null;
// $itemCharacteristicId = ($itemCharacteristicId && $itemCharacteristicId != 'empty') ? $itemCharacteristicId : null;
// if ($plantId && $itemCharacteristicId)
// {
// $plantId = $request->query('plant_id');
// $itemCharacteristicId = $request->query('item_characteristic_id');
// $sticId = StickerStructureDetail::where('id', $stickerId)->first();
// $sId = $sticId->id;
// $stickerId = $sticId->sticker_id;
// $dynamicElements = StickerDetail::where('sticker_structure_detail_id', $sId)
// ->where('element_type', 'Dynamic')
// ->get();
// $itemCharacteristic = null;
// if ($plantId && $itemCharacteristicId) {
// $itemCharacteristic = ItemCharacteristic::where('plant_id', $plantId)
// ->where('id', $itemCharacteristicId)
// ->first();
// }
// $pdfService = new StickerPdfService();
// return $pdfService->generatePdf1(
// $stickerId,
// $dynamicElements,
// $itemCharacteristic
// );
// }
// else
// {
// $elements = StickerStructureDetail::where('sticker_id', $stickerId)
// ->first();
// $pdfService = new StickerPdfService();
// return $pdfService->generate($stickerId, $elements->toArray());
// }
// }
public function generatePdf(Request $request, $stickerId)
{
$plantId = $request->query('plant_id');
$itemCharacteristicId = $request->query('item_characteristic_id');
$serialNumber = $request->query('serial_number');
if ($plantId && $itemCharacteristicId) {
$structure = StickerStructureDetail::findOrFail($stickerId);
$dynamicElements = StickerDetail::where(
'sticker_structure_detail_id',
$structure->id
)->where('element_type', 'Dynamic')->get();
$itemCharacteristic = ItemCharacteristic::where('plant_id', $plantId)
->where('id', $itemCharacteristicId)
->first();
return (new StickerPdfService())->generatePdf1(
$structure->sticker_id,
$dynamicElements,
$itemCharacteristic,
$serialNumber
);
}
return abort(404);
}
}

482
app/Livewire/ChatBot.php Normal file
View File

@@ -0,0 +1,482 @@
<?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');
}
}

View File

@@ -0,0 +1,236 @@
<?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,
]);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Livewire;
use App\Models\ProductionQuantity;
use Livewire\Component;
class StickerValidation extends Component
{
public $plantId;
public $refNumber;
public $serialNumber;
public bool $materialInvoice = false;
public $records = [];
protected $listeners = [
'refreshEmptySticker' => 'loadStickerData',
'addStickerToList' => 'loadSticker'
];
public function loadStickerData($plantId, $refNumber)
{
$this->plantId = $plantId;
$this->refNumber = $refNumber;
// $this->records = ProductionQuantity::where('plant_id', $plantId)
// ->where('production_order', $refNumber)
// ->orderBy('created_at', 'asc')
// ->get(['serial_number', 'operator_id']);
$this->records = ProductionQuantity::query()
->where('production_quantities.plant_id', $plantId)
->where('production_quantities.production_order', $refNumber)
->leftJoin(
'sticker_validations',
'sticker_validations.serial_number',
'=',
'production_quantities.serial_number'
)
->orderBy('production_quantities.created_at', 'asc')
->get([
'production_quantities.serial_number',
'production_quantities.operator_id',
'sticker_validations.status',
'sticker_validations.sticker_id',
])
->map(function ($row) {
$row->status = $row->status ?? '';
return $row;
});
//dd($this->records);
}
public function render()
{
return view('livewire.sticker-validation');
}
}

32
app/Livewire/Webcam.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Webcam extends Component
{
public string $capturedPhoto = '';
// Called from JavaScript when a photo is taken
public function setPhoto(string $photo): void
{
$this->capturedPhoto = $photo;
// Fires a browser event that the Filament form will listen to
$this->dispatch('photo-captured', photo: $photo);
}
// Called from JavaScript when user clicks "Retake"
public function clearPhoto(): void
{
$this->capturedPhoto = '';
$this->dispatch('photo-captured', photo: '');
}
public function render()
{
return view('livewire.webcam');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class EmployeeMaster extends Model
{
use SoftDeletes;
protected $fillable = [
'plant_id',
'name',
'code',
'department',
'designation',
'email',
'mobile_number',
'created_at',
'updated_at',
'created_by',
'updated_by',
];
public function plant(): BelongsTo
{
return $this->belongsTo(Plant::class);
}
}

View File

@@ -12,11 +12,13 @@ class Item extends Model
protected $fillable = [
'plant_id',
'line_id',
'category',
'code',
'description',
'hourly_quantity',
'uom',
'line_capacity',
];
public function plant(): BelongsTo
@@ -24,6 +26,11 @@ class Item extends Model
return $this->belongsTo(Plant::class);
}
public function line(): BelongsTo
{
return $this->belongsTo(Line::class);
}
public function stickerMasters()
{
return $this->hasMany(StickerMaster::class, 'item_id', 'id');

View File

@@ -13,6 +13,7 @@ class Line extends Model
protected $fillable = [
"plant_id",
"block_id",
"name",
"type",
"group_work_center",
@@ -34,6 +35,11 @@ class Line extends Model
return $this->belongsTo(Plant::class);
}
public function block(): BelongsTo
{
return $this->belongsTo(Block::class);
}
public function testingPanelReadings()
{
return $this->hasMany(TestingPanelReading::class);

View File

@@ -12,12 +12,15 @@ class ProductionPlan extends Model
protected $fillable = [
"plant_id",
"item_id",
"shift_id",
"created_at",
"line_id",
"plan_quantity",
"production_quantity",
"operator_id",
"working_days",
"leave_dates",
];
public function plant(): BelongsTo
@@ -34,4 +37,9 @@ class ProductionPlan extends Model
{
return $this->belongsTo(Line::class);
}
public function item(): BelongsTo
{
return $this->belongsTo(Item::class);
}
}

View File

@@ -16,6 +16,7 @@ class ProductionQuantity extends Model
protected $fillable = [
"plant_id",
"machine_id",
"shift_id",
"line_id",
"item_id",
@@ -50,6 +51,11 @@ class ProductionQuantity extends Model
return $this->belongsTo(Item::class);
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
protected static function booted()
{
static::created(function ($productionQuantity) {

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class RequestQuotation extends Model
{
use SoftDeletes;
protected $fillable = [
'rfq_number',
'rfq_date_time',
'pickup_address',
'delivery_address',
'weight',
'volumetrice_size_inch',
'type_of_vehicle',
'special_type',
'no_of_vehicle',
'product_name',
'loading_by',
'unloading_by',
'pick_and_delivery',
'payment_term',
'paid_topay',
'require_date_time',
'transporter_name',
'total_freight_charge',
'transit_day',
'spot_rate_transport_master_id',
'created_at',
'updated_at',
'created_by',
'updated_by',
];
public function spotRateTransportMaster(): BelongsTo
{
return $this->belongsTo(SpotRateTransportMaster::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use phpseclib3\Crypt\Common\Formats\Signature\Raw;
class RfqTransporterBid extends Model
{
use SoftDeletes;
protected $fillable = [
'request_quotation_id',
'transporter_name',
'total_freight_charge',
'transit_day',
'created_at',
'updated_at',
'created_by',
'updated_by',
];
public function requestQuotation(): BelongsTo
{
return $this->belongsTo(RequestQuotation::class);
// return $this->belongsTo(RequestQuotation::class, 'request_quotation_id');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class SpotRateTransportMaster extends Model
{
use SoftDeletes;
protected $casts = [
'user_name' => 'array',
];
protected $fillable = [
'group_name',
'user_name',
'created_at',
'updated_at',
'created_by',
'updated_by',
];
}

View File

@@ -30,8 +30,8 @@ class StickerDetail extends Model
'shape_y1_value',
'shape_x2_value',
'shape_y2_value',
'image_path',
'image_type',
// 'image_path',
// 'image_type',
'image_x',
'image_y',
'image_width',

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class StickerMappingMaster extends Model
{
use SoftDeletes;
protected $fillable = [
'plant_id',
'item_characteristic_id',
'sticker_structure1_id',
'sticker1_machine_id',
'sticker1_print_ip',
'sticker_structure2_id',
'sticker2_machine_id',
'sticker2_print_ip',
'sticker_structure3_id',
'sticker3_machine_id',
'sticker3_print_ip',
'sticker_structure4_id',
'sticker4_machine_id',
'sticker4_print_ip',
'sticker_structure5_id',
'sticker5_machine_id',
'sticker5_print_ip',
'sticker_structure7_id',
'sticker6_machine_id',
'sticker6_print_ip',
'sticker_structure7_id',
'sticker7_machine_id',
'sticker7_print_ip',
'sticker_structure8_id',
'sticker8_machine_id',
'sticker8_print_ip',
'created_at',
'updated_at',
'created_by',
'updated_by',
];
public function plant()
{
return $this->belongsTo(Plant::class);
}
public function itemCharacteristic()
{
return $this->belongsTo(ItemCharacteristic::class);
}
public function item(): BelongsTo
{
return $this->belongsTo(Item::class, 'item_id');
}
public function sticker1Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure1_id');
}
public function sticker2Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure2_id');
}
public function sticker3Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure3_id');
}
public function sticker4Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure4_id');
}
public function sticker5Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure5_id');
}
public function sticker6Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure6_id');
}
public function sticker7Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure7_id');
}
public function sticker8Structure()
{
return $this->belongsTo(StickerStructureDetail::class, 'sticker_structure8_id');
}
// Machine relationships (per sticker)
public function sticker1Machine()
{
return $this->belongsTo(Machine::class, 'sticker1_machine_id');
}
public function sticker2Machine()
{
return $this->belongsTo(Machine::class, 'sticker2_machine_id');
}
public function sticker3Machine()
{
return $this->belongsTo(Machine::class, 'sticker3_machine_id');
}
public function sticker4Machine()
{
return $this->belongsTo(Machine::class, 'sticker4_machine_id');
}
public function sticker5Machine()
{
return $this->belongsTo(Machine::class, 'sticker5_machine_id');
}
public function sticker6Machine()
{
return $this->belongsTo(Machine::class, 'sticker6_machine_id');
}
public function sticker7Machine()
{
return $this->belongsTo(Machine::class, 'sticker7_machine_id');
}
public function sticker8Machine()
{
return $this->belongsTo(Machine::class, 'sticker8_machine_id');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class StickerValidation extends Model
{
use SoftDeletes;
protected $fillable = [
'plant_id',
'machine_id',
'sticker_id',
'production_order',
'serial_number',
'status',
'created_by',
'updated_by',
'deleted_at',
];
public function plant()
{
return $this->belongsTo(Plant::class);
}
public function machine()
{
return $this->belongsTo(Machine::class);
}
}

View File

@@ -13,11 +13,13 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use NotificationChannels\WebPush\HasPushSubscriptions;
use NotificationChannels\WebPush\PushSubscription;
class User extends Authenticatable implements FilamentUser
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, HasRoles, Notifiable, SoftDeletes, HasSuperAdmin;
use HasFactory, HasRoles, Notifiable, SoftDeletes, HasSuperAdmin, HasPushSubscriptions;
/**
* The attributes that are mass assignable.
@@ -63,4 +65,9 @@ class User extends Authenticatable implements FilamentUser
{
return $this->belongsTo(Plant::class);
}
public function pushSubscriptions()
{
return $this->morphMany(PushSubscription::class, 'subscribable');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class VisitorEntry extends Model
{
use SoftDeletes;
protected $fillable = [
'mobile_number',
'name',
'company',
'purpose_of_visit',
'type',
'in_time',
'out_time',
'photo',
'employee_master_id',
'number_of_person'
];
public function employeeMaster(): BelongsTo
{
return $this->belongsTo(EmployeeMaster::class);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class WebPushSubscription extends Model
{
protected $table = 'push_subscriptions';
protected $fillable = [
'subscribable_type',
'subscribable_id',
'endpoint',
'public_key',
'auth_token',
'content_encoding',
];
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\WebPush\WebPushMessage;
use NotificationChannels\WebPush\WebPushChannel;
class PushAlertNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*/
// public function __construct()
// {
// //
// }
public $title;
public $body;
public function __construct($title, $body)
{
$this->title = $title;
$this->body = $body;
}
// public function via($notifiable)
// {
// return [WebPushChannel::class];
// }
public function via($notifiable)
{
return [
'database', // ✅ Filament toast
WebPushChannel::class // ✅ Browser / PWA push
];
}
public function toDatabase($notifiable): array
{
return [
'title' => $this->title,
'body' => $this->body,
];
}
// public function toWebPush($notifiable, $notification)
// {
// return (new WebPushMessage)
// ->title('New Alert 🚨')
// ->icon('/pwa-192x192.png')
// ->body('You have a new notification')
// ->action('Open App', 'open_app')
// ->data(['url' => '/admin']);
// }
public function toWebPush($notifiable, $notification)
{
return (new WebPushMessage)
->title($this->title)
->icon('/pwa-192x192.png')
->body($this->body)
->action('Open App', 'open_app')
->data(['url' => '/admin']);
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
// public function via(object $notifiable): array
// {
// return ['mail'];
// }
/**
* Get the mail representation of the notification.
*/
// public function toMail(object $notifiable): MailMessage
// {
// return (new MailMessage)
// ->line('The introduction to the notification.')
// ->action('Notification Action', url('/'))
// ->line('Thank you for using our application!');
// }
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Policies;
use Illuminate\Auth\Access\Response;
use App\Models\RequestQuotation;
use App\Models\User;
class RequestQuotationPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->checkPermissionTo('view-any RequestQuotation');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('view RequestQuotation');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->checkPermissionTo('create RequestQuotation');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('update RequestQuotation');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('delete RequestQuotation');
}
/**
* Determine whether the user can delete any models.
*/
public function deleteAny(User $user): bool
{
return $user->checkPermissionTo('delete-any RequestQuotation');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('restore RequestQuotation');
}
/**
* Determine whether the user can restore any models.
*/
public function restoreAny(User $user): bool
{
return $user->checkPermissionTo('restore-any RequestQuotation');
}
/**
* Determine whether the user can replicate the model.
*/
public function replicate(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('replicate RequestQuotation');
}
/**
* Determine whether the user can reorder the models.
*/
public function reorder(User $user): bool
{
return $user->checkPermissionTo('reorder RequestQuotation');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, RequestQuotation $requestquotation): bool
{
return $user->checkPermissionTo('force-delete RequestQuotation');
}
/**
* Determine whether the user can permanently delete any models.
*/
public function forceDeleteAny(User $user): bool
{
return $user->checkPermissionTo('force-delete-any RequestQuotation');
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Policies;
use Illuminate\Auth\Access\Response;
use App\Models\RfqTransporterBid;
use App\Models\User;
class RfqTransporterBidPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->checkPermissionTo('view-any RfqTransporterBid');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('view RfqTransporterBid');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->checkPermissionTo('create RfqTransporterBid');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('update RfqTransporterBid');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('delete RfqTransporterBid');
}
/**
* Determine whether the user can delete any models.
*/
public function deleteAny(User $user): bool
{
return $user->checkPermissionTo('delete-any RfqTransporterBid');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('restore RfqTransporterBid');
}
/**
* Determine whether the user can restore any models.
*/
public function restoreAny(User $user): bool
{
return $user->checkPermissionTo('restore-any RfqTransporterBid');
}
/**
* Determine whether the user can replicate the model.
*/
public function replicate(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('replicate RfqTransporterBid');
}
/**
* Determine whether the user can reorder the models.
*/
public function reorder(User $user): bool
{
return $user->checkPermissionTo('reorder RfqTransporterBid');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, RfqTransporterBid $rfqtransporterbid): bool
{
return $user->checkPermissionTo('force-delete RfqTransporterBid');
}
/**
* Determine whether the user can permanently delete any models.
*/
public function forceDeleteAny(User $user): bool
{
return $user->checkPermissionTo('force-delete-any RfqTransporterBid');
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Policies;
use Illuminate\Auth\Access\Response;
use App\Models\SpotRateTransportMaster;
use App\Models\User;
class SpotRateTransportMasterPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->checkPermissionTo('view-any SpotRateTransportMaster');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('view SpotRateTransportMaster');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->checkPermissionTo('create SpotRateTransportMaster');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('update SpotRateTransportMaster');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('delete SpotRateTransportMaster');
}
/**
* Determine whether the user can delete any models.
*/
public function deleteAny(User $user): bool
{
return $user->checkPermissionTo('delete-any SpotRateTransportMaster');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('restore SpotRateTransportMaster');
}
/**
* Determine whether the user can restore any models.
*/
public function restoreAny(User $user): bool
{
return $user->checkPermissionTo('restore-any SpotRateTransportMaster');
}
/**
* Determine whether the user can replicate the model.
*/
public function replicate(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('replicate SpotRateTransportMaster');
}
/**
* Determine whether the user can reorder the models.
*/
public function reorder(User $user): bool
{
return $user->checkPermissionTo('reorder SpotRateTransportMaster');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, SpotRateTransportMaster $spotratetransportmaster): bool
{
return $user->checkPermissionTo('force-delete SpotRateTransportMaster');
}
/**
* Determine whether the user can permanently delete any models.
*/
public function forceDeleteAny(User $user): bool
{
return $user->checkPermissionTo('force-delete-any SpotRateTransportMaster');
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Policies;
use Illuminate\Auth\Access\Response;
use App\Models\StickerValidation;
use App\Models\User;
class StickerValidationPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->checkPermissionTo('view-any StickerValidation');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('view StickerValidation');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->checkPermissionTo('create StickerValidation');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('update StickerValidation');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('delete StickerValidation');
}
/**
* Determine whether the user can delete any models.
*/
public function deleteAny(User $user): bool
{
return $user->checkPermissionTo('delete-any StickerValidation');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('restore StickerValidation');
}
/**
* Determine whether the user can restore any models.
*/
public function restoreAny(User $user): bool
{
return $user->checkPermissionTo('restore-any StickerValidation');
}
/**
* Determine whether the user can replicate the model.
*/
public function replicate(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('replicate StickerValidation');
}
/**
* Determine whether the user can reorder the models.
*/
public function reorder(User $user): bool
{
return $user->checkPermissionTo('reorder StickerValidation');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, StickerValidation $stickervalidation): bool
{
return $user->checkPermissionTo('force-delete StickerValidation');
}
/**
* Determine whether the user can permanently delete any models.
*/
public function forceDeleteAny(User $user): bool
{
return $user->checkPermissionTo('force-delete-any StickerValidation');
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Providers\Filament;
use Althinect\FilamentSpatieRolesPermissions\FilamentSpatieRolesPermissionsPlugin;
use App\Filament\Pages\InvoiceDashboard;
use App\Filament\Pages\NotificationSettings;
use Filament\Facades\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
@@ -28,6 +29,7 @@ use App\Filament\Auth\CustomLogin as AuthCustomLogin;
use App\Filament\Pages\CustomLogin;
use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView;
use Illuminate\Support\Facades\Blade;
class AdminPanelProvider extends PanelProvider
@@ -59,6 +61,7 @@ class AdminPanelProvider extends PanelProvider
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
// ->widgets([
@@ -123,6 +126,15 @@ class AdminPanelProvider extends PanelProvider
public function boot(): void
{
FilamentView::registerRenderHook('panels::body.end', function () {
if (url()->current() == config('app.url') . '/admin') {
return '<script src="' . asset('js/push.js') . '"></script>';
}
return '';
});
FilamentView::registerRenderHook('panels::head.end', function () {
// Only inject on the "home" page (or specific route)
if (url()->current() == config('app.url') . '/admin') {
@@ -149,5 +161,10 @@ class AdminPanelProvider extends PanelProvider
}
return '';
});
FilamentView::registerRenderHook(
PanelsRenderHook::BODY_END,
fn (): string => Blade::render("@livewire('chat-bot')"),
);
}
}

View File

@@ -0,0 +1,403 @@
<?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.';
}
}

View File

@@ -0,0 +1,819 @@
<?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?";
}
}

View File

@@ -2,11 +2,16 @@
namespace App\Services;
use App\Models\Item;
use App\Models\ItemCharacteristic;
use App\Models\ProductionQuantity;
use App\Models\StickerDetail;
use App\Models\StickerMappingMaster;
use App\Models\StickerStructureDetail;
use App\Models\StickerValidation;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use TCPDF;
@@ -447,11 +452,10 @@ class StickerPdfService
}
// return $pdf->Output('sticker.pdf', 'S');
$pdfContent = $pdf->Output('sticker.pdf', 'S');
$pdfContent = $pdf->Output('', 'S'); // 'S' returns string
return (new Response($pdfContent, 200))
->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="sticker.pdf"');
// Encode as base64
return base64_encode($pdfContent);
}
@@ -788,6 +792,349 @@ class StickerPdfService
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
}
public function generatePdf1(string $stickerId, Collection $dynamicElements, ?ItemCharacteristic $itemCharacteristic, ?string $serialNumber, $serNo)
{
$dynamicValueMap = [];
$itemCode = $itemCharacteristic?->item?->code ?? '';
foreach ($dynamicElements as $element) {
$column = $element->characteristics_type;
$value = '';
if (
$itemCharacteristic &&
$column &&
Schema::hasColumn('item_characteristics', $column)
) {
$value = $itemCharacteristic->{$column};
}
$dynamicValueMap[$element->id] = [
'design_type' => $element->design_element_type, // Text / Image
'value' => $value,
];
}
$structure = StickerStructureDetail::where('sticker_id', $stickerId)
->first();
$structureId = $structure->id;
$elements = StickerDetail::where(
'sticker_structure_detail_id',
$structureId
)->get();
$width = (float) $structure->sticker_width;
$height = (float) $structure->sticker_height;
$pdf = new TCPDF('P', 'mm', [$width, $height], true, 'UTF-8', false);
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->SetMargins(
(float) $structure->sticker_lmargin,
(float) $structure->sticker_tmargin,
(float) $structure->sticker_rmargin,
(float) $structure->sticker_bmargin,
);
$pdf->SetAutoPageBreak(false, 0);
$pdf->AddPage();
// if (!empty($serialNumber)) {
// $pdf->SetFont('helvetica', 'B', 10);
// $pdf->SetTextColor(0, 0, 0);
// // HARD-CODED POSITION (mm)
// $x = 40; // change as needed
// $y = 60; // change as needed
// $pdf->Text($x, $y, (string) $serialNumber);
// }
$pdf->SetFont('helvetica', 'B', 10);
foreach ($elements as $row) {
switch ($row->design_element_type) {
case 'Text':
$pdf->SetFont(
$row->string_font ?? 'helvetica',
'',
(int) ($row->string_size ?? 10)
);
$pdf->SetTextColor(
...$this->hexToRgb($row->element_colour ?? '#000000')
);
$textValue = $row->string_value ?? '';
if (
$row->element_type == 'Dynamic' &&
isset($dynamicValueMap[$row->id])
) {
$textValue = $dynamicValueMap[$row->id]['value'];
}
$pdf->Text(
(float) ($row->string_x_value ?? 0),
(float) ($row->string_y_value ?? 0),
(string) $textValue
);
break;
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(
$row->qr_value ?? '',
'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;
}
case 'Image':
$imagePath = null;
if (
$row->element_type == 'Dynamic' &&
isset($dynamicValueMap[$row->id]) &&
! empty($dynamicValueMap[$row->id]['value'])
) {
$imageName = strtolower($dynamicValueMap[$row->id]['value']).'.png';
$imagePath = public_path('images/'.ltrim($imageName, '/'));
}
$pdf->Image(
$imagePath,
(float) ($row->image_x ?? 0),
(float) ($row->image_y ?? 0),
(float) ($row->image_width ?? 0),
(float) ($row->image_height ?? 0)
);
break;
case 'Shape':
if ($row->shape_name == 'Line') {
$pdf->SetLineWidth((float) ($row->shape_pen_size ?? 0.3));
if (isset($row->element_colour)) {
$rgb = $this->hexToRgb($row->element_colour);
$pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
} else {
$pdf->SetDrawColor(0, 0, 0);
}
$pdf->Line(
(float) $row->shape_x1_value,
(float) $row->shape_y1_value,
(float) $row->shape_x2_value,
(float) $row->shape_y2_value
);
} elseif ($row->shape_name == 'Rectangle') {
$pdf->SetLineWidth((float) ($row->shape_pen_size ?? 0.3));
if (isset($row->element_colour)) {
$rgb = $this->hexToRgb($row->element_colour);
$pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
} else {
$pdf->SetDrawColor(0, 0, 0);
}
$x1 = (float) $row->shape_x1_value;
$y1 = (float) $row->shape_y1_value;
$x2 = (float) $row->shape_x2_value;
$y2 = (float) $row->shape_y2_value;
$x = min($x1, $x2);
$y = min($y1, $y2);
$width = abs($x2 - $x1);
$height = abs($y2 - $y1);
$pdf->Rect($x, $y, $width, $height, 'D');
} elseif ($row->shape_name == 'CurvedRectangle') {
$pdf->SetLineWidth((float) ($row->shape_pen_size ?? 0.3));
if (isset($row->element_colour)) {
$rgb = $this->hexToRgb($row->element_colour);
$pdf->SetDrawColor($rgb[0], $rgb[1], $rgb[2]);
} else {
$pdf->SetDrawColor(0, 0, 0);
}
$x1 = (float) $row->shape_x1_value;
$y1 = (float) $row->shape_y1_value;
$x2 = (float) $row->shape_x2_value;
$y2 = (float) $row->shape_y2_value;
$x = min($x1, $x2);
$y = min($y1, $y2);
$width = abs($x2 - $x1);
$height = abs($y2 - $y1);
// radius in mm
// $radius = 3;
$radius = (float) $row->curve_radius;
$pdf->RoundedRect(
$x,
$y,
$width,
$height,
$radius,
'1111', // ← round all 4 corners (default)
'D'
);
}
break;
}
}
// $pdfContent = $pdf->Output('', 'S'); // 'S' returns string
// // Encode as base64
// return base64_encode($pdfContent);
// $pdfContent = $pdf->Output('', 'S');
// return response($pdfContent)
// ->header('Content-Type', 'application/pdf')
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
$pdfContent = $pdf->Output('', 'S'); // 'S' returns the PDF as a string
// 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-Disposition', 'inline; filename="sticker.pdf"');
} catch (\Exception $e) {
Log::error('PDF generation failed: '.$e->getMessage());
abort(500, 'Failed to generate PDF');
}
}
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)
// {
// $hex = ltrim($hex, '#');
@@ -797,6 +1144,7 @@ class StickerPdfService
// hexdec(substr($hex, 4, 2)),
// ];
// }
private function hexToRgb($hex)
{
if (! is_string($hex) || ($hex = trim($hex)) === '') {

View File

@@ -11,9 +11,11 @@
"althinect/filament-spatie-roles-permissions": "^2.3",
"diogogpinto/filament-auth-ui-enhancer": "^1.0",
"erag/laravel-pwa": "^1.9",
"ffhs/filament-package_ffhs_approvals": "^1.0",
"filament/filament": "^3.3",
"intervention/image": "^3.11",
"irazasyed/telegram-bot-sdk": "^3.15",
"laravel-notification-channels/webpush": "^10.4",
"laravel/framework": "^11.31",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",

477
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "69ede7e9877dd08efdeb795bfb6b7d29",
"content-hash": "0e64d9b0a3c4d596ff8cd51b521c0565",
"packages": [
{
"name": "alperenersoy/filament-export",
@@ -1810,6 +1810,85 @@
},
"time": "2025-10-17T16:34:55+00:00"
},
{
"name": "ffhs/filament-package_ffhs_approvals",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/ffhs/filament-package_ffhs_approvals.git",
"reference": "712475522b63bf45a9e63a649d391cfe22132818"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ffhs/filament-package_ffhs_approvals/zipball/712475522b63bf45a9e63a649d391cfe22132818",
"reference": "712475522b63bf45a9e63a649d391cfe22132818",
"shasum": ""
},
"require": {
"filament/filament": "^3.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.15.0"
},
"require-dev": {
"larastan/larastan": "^3.0",
"laravel/pint": "^1.0",
"nunomaduro/collision": "^8.0",
"orchestra/testbench": "^9.9",
"pestphp/pest": "^3.7",
"pestphp/pest-plugin-arch": "^3.0",
"pestphp/pest-plugin-laravel": "^3.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"spatie/laravel-ray": "^1.26"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Approvals": "Ffhs\\Approvals\\Facades\\Approvals"
},
"providers": [
"Ffhs\\Approvals\\ApprovalsServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Ffhs\\Approvals\\": "src/",
"Ffhs\\Approvals\\Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kromer Luc",
"email": "luc.kromer@ffhs.ch",
"role": "Developer"
},
{
"name": "Matthew Ballou",
"email": "mballou@kirschbaumdevelopment.com",
"role": "Developer"
}
],
"description": "This is my package filament-package_ffhs_approvals",
"homepage": "https://github.com/ffhs/filament-package_ffhs_approvals",
"keywords": [
"ffhs",
"filament-package_ffhs_approvals",
"kirschbaum-development",
"laravel"
],
"support": {
"issues": "https://github.com/ffhs/filament-package_ffhs_approvals/issues",
"source": "https://github.com/ffhs/filament-package_ffhs_approvals"
},
"time": "2025-07-24T14:32:41+00:00"
},
{
"name": "filament/actions",
"version": "v3.3.45",
@@ -3076,6 +3155,72 @@
},
"time": "2025-11-13T14:57:49+00:00"
},
{
"name": "laravel-notification-channels/webpush",
"version": "10.4.0",
"source": {
"type": "git",
"url": "https://github.com/laravel-notification-channels/webpush.git",
"reference": "a504bcbdd6258091b1fafdef6ca95b0891a47c9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-notification-channels/webpush/zipball/a504bcbdd6258091b1fafdef6ca95b0891a47c9e",
"reference": "a504bcbdd6258091b1fafdef6ca95b0891a47c9e",
"shasum": ""
},
"require": {
"illuminate/notifications": "^11.0|^12.0",
"illuminate/support": "^11.0|^12.0",
"minishlink/web-push": "^10.0",
"php": "^8.2"
},
"require-dev": {
"larastan/larastan": "^3.1",
"laravel/pint": "^1.25",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^9.2|^10.0",
"phpunit/phpunit": "^10.5|^11.5.3",
"rector/rector": "^2.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"NotificationChannels\\WebPush\\WebPushServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"NotificationChannels\\WebPush\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Cretu Eusebiu",
"email": "me@cretueusebiu.com",
"homepage": "http://cretueusebiu.com",
"role": "Developer"
},
{
"name": "Joost de Bruijn",
"email": "joost@aqualabs.nl",
"role": "Maintainer"
}
],
"description": "Web Push Notifications driver for Laravel.",
"homepage": "https://github.com/laravel-notification-channels/webpush",
"support": {
"issues": "https://github.com/laravel-notification-channels/webpush/issues",
"source": "https://github.com/laravel-notification-channels/webpush/tree/10.4.0"
},
"time": "2025-12-19T15:47:27+00:00"
},
{
"name": "laravel/framework",
"version": "v11.46.1",
@@ -4880,6 +5025,73 @@
},
"time": "2019-10-05T02:44:33+00:00"
},
{
"name": "minishlink/web-push",
"version": "v10.0.1",
"source": {
"type": "git",
"url": "https://github.com/web-push-libs/web-push-php.git",
"reference": "08463189d3501cbd78a8625c87ab6680a7397aad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/08463189d3501cbd78a8625c87ab6680a7397aad",
"reference": "08463189d3501cbd78a8625c87ab6680a7397aad",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"guzzlehttp/guzzle": "^7.9.2",
"php": ">=8.2",
"spomky-labs/base64url": "^2.0.4",
"web-token/jwt-library": "^3.4.9|^4.0.6"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v3.91.3",
"phpstan/phpstan": "^2.1.33",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^11.5.46|^12.5.2",
"symfony/polyfill-iconv": "^1.33"
},
"suggest": {
"ext-bcmath": "Optional for performance.",
"ext-gmp": "Optional for performance."
},
"type": "library",
"autoload": {
"psr-4": {
"Minishlink\\WebPush\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Louis Lagrange",
"email": "lagrange.louis@gmail.com",
"homepage": "https://github.com/Minishlink"
}
],
"description": "Web Push library for PHP",
"homepage": "https://github.com/web-push-libs/web-push-php",
"keywords": [
"Push API",
"WebPush",
"notifications",
"push",
"web"
],
"support": {
"issues": "https://github.com/web-push-libs/web-push-php/issues",
"source": "https://github.com/web-push-libs/web-push-php/tree/v10.0.1"
},
"time": "2025-12-15T10:04:28+00:00"
},
{
"name": "monolog/monolog",
"version": "3.9.0",
@@ -7696,6 +7908,180 @@
],
"time": "2025-09-24T06:40:28+00:00"
},
{
"name": "spomky-labs/base64url",
"version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/base64url.git",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.11|^0.12",
"phpstan/phpstan-beberlei-assert": "^0.11|^0.12",
"phpstan/phpstan-deprecation-rules": "^0.11|^0.12",
"phpstan/phpstan-phpunit": "^0.11|^0.12",
"phpstan/phpstan-strict-rules": "^0.11|^0.12"
},
"type": "library",
"autoload": {
"psr-4": {
"Base64Url\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky-Labs/base64url/contributors"
}
],
"description": "Base 64 URL Safe Encoding/Decoding PHP Library",
"homepage": "https://github.com/Spomky-Labs/base64url",
"keywords": [
"base64",
"rfc4648",
"safe",
"url"
],
"support": {
"issues": "https://github.com/Spomky-Labs/base64url/issues",
"source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2020-11-03T09:10:25+00:00"
},
{
"name": "spomky-labs/pki-framework",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/pki-framework.git",
"reference": "f0e9a548df4e3942886adc9b7830581a46334631"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/f0e9a548df4e3942886adc9b7830581a46334631",
"reference": "f0e9a548df4e3942886adc9b7830581a46334631",
"shasum": ""
},
"require": {
"brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14",
"ext-mbstring": "*",
"php": ">=8.1"
},
"require-dev": {
"ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
"ext-gmp": "*",
"ext-openssl": "*",
"infection/infection": "^0.28|^0.29|^0.31",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpstan/extension-installer": "^1.3|^2.0",
"phpstan/phpstan": "^1.8|^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
"phpstan/phpstan-phpunit": "^1.1|^2.0",
"phpstan/phpstan-strict-rules": "^1.3|^2.0",
"phpunit/phpunit": "^10.1|^11.0|^12.0",
"rector/rector": "^1.0|^2.0",
"roave/security-advisories": "dev-latest",
"symfony/string": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0",
"symplify/easy-coding-standard": "^12.0"
},
"suggest": {
"ext-bcmath": "For better performance (or GMP)",
"ext-gmp": "For better performance (or BCMath)",
"ext-openssl": "For OpenSSL based cyphering"
},
"type": "library",
"autoload": {
"psr-4": {
"SpomkyLabs\\Pki\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joni Eskelinen",
"email": "jonieske@gmail.com",
"role": "Original developer"
},
{
"name": "Florent Morselli",
"email": "florent.morselli@spomky-labs.com",
"role": "Spomky-Labs PKI Framework developer"
}
],
"description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.",
"homepage": "https://github.com/spomky-labs/pki-framework",
"keywords": [
"DER",
"Private Key",
"ac",
"algorithm identifier",
"asn.1",
"asn1",
"attribute certificate",
"certificate",
"certification request",
"cryptography",
"csr",
"decrypt",
"ec",
"encrypt",
"pem",
"pkcs",
"public key",
"rsa",
"sign",
"signature",
"verify",
"x.509",
"x.690",
"x509",
"x690"
],
"support": {
"issues": "https://github.com/Spomky-Labs/pki-framework/issues",
"source": "https://github.com/Spomky-Labs/pki-framework/tree/1.4.1"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2025-12-20T12:57:40+00:00"
},
{
"name": "symfony/clock",
"version": "v7.3.0",
@@ -10504,6 +10890,95 @@
}
],
"time": "2024-11-21T01:49:47+00:00"
},
{
"name": "web-token/jwt-library",
"version": "4.1.3",
"source": {
"type": "git",
"url": "https://github.com/web-token/jwt-library.git",
"reference": "690d4dd47b78f423cb90457f858e4106e1deb728"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/web-token/jwt-library/zipball/690d4dd47b78f423cb90457f858e4106e1deb728",
"reference": "690d4dd47b78f423cb90457f858e4106e1deb728",
"shasum": ""
},
"require": {
"brick/math": "^0.12|^0.13|^0.14",
"php": ">=8.2",
"psr/clock": "^1.0",
"spomky-labs/pki-framework": "^1.2.1"
},
"conflict": {
"spomky-labs/jose": "*"
},
"suggest": {
"ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance",
"ext-gmp": "GMP or BCMath is highly recommended to improve the library performance",
"ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)",
"ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
"paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
"spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (AxxxKW, AxxxGCMKW, PBES2-HSxxx+AyyyKW...)",
"symfony/console": "Needed to use console commands",
"symfony/http-client": "To enable JKU/X5U support."
},
"type": "library",
"autoload": {
"psr-4": {
"Jose\\Component\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/web-token/jwt-framework/contributors"
}
],
"description": "JWT library",
"homepage": "https://github.com/web-token",
"keywords": [
"JOSE",
"JWE",
"JWK",
"JWKSet",
"JWS",
"Jot",
"RFC7515",
"RFC7516",
"RFC7517",
"RFC7518",
"RFC7519",
"RFC7520",
"bundle",
"jwa",
"jwt",
"symfony"
],
"support": {
"issues": "https://github.com/web-token/jwt-library/issues",
"source": "https://github.com/web-token/jwt-library/tree/4.1.3"
},
"funding": [
{
"url": "https://github.com/Spomky",
"type": "github"
},
{
"url": "https://www.patreon.com/FlorentMorselli",
"type": "patreon"
}
],
"time": "2025-12-18T14:27:35+00:00"
}
],
"packages-dev": [

View File

@@ -18,6 +18,11 @@ return [
'token' => env('POSTMARK_TOKEN'),
],
'gemini' => [
'api_key' => env('GEMINI_API_KEY'),
'model' => env('GEMINI_MODEL', 'gemini-3-flash-preview'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),

View File

@@ -0,0 +1,91 @@
<?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'
CREATE TABLE sticker_mapping_masters (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
plant_id BIGINT NOT NULL,
item_characteristic_id BIGINT DEFAULT NULL,
sticker_structure1_id BIGINT DEFAULT NULL,
sticker1_machine_id BIGINT DEFAULT NULL,
sticker1_print_ip TEXT DEFAULT NULL,
sticker_structure2_id BIGINT DEFAULT NULL,
sticker2_machine_id BIGINT DEFAULT NULL,
sticker2_print_ip TEXT DEFAULT NULL,
sticker_structure3_id BIGINT DEFAULT NULL,
sticker3_machine_id BIGINT DEFAULT NULL,
sticker3_print_ip TEXT DEFAULT NULL,
sticker_structure4_id BIGINT DEFAULT NULL,
sticker4_machine_id BIGINT DEFAULT NULL,
sticker4_print_ip TEXT DEFAULT NULL,
sticker_structure5_id BIGINT DEFAULT NULL,
sticker5_machine_id BIGINT DEFAULT NULL,
sticker5_print_ip TEXT DEFAULT NULL,
sticker_structure6_id BIGINT DEFAULT NULL,
sticker6_machine_id BIGINT DEFAULT NULL,
sticker6_print_ip TEXT DEFAULT NULL,
sticker_structure7_id BIGINT DEFAULT NULL,
sticker7_machine_id BIGINT DEFAULT NULL,
sticker7_print_ip TEXT DEFAULT NULL,
sticker_structure8_id BIGINT DEFAULT NULL,
sticker8_machine_id BIGINT DEFAULT NULL,
sticker8_print_ip TEXT DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by TEXT DEFAULT NULL,
updated_by TEXT DEFAULT NULL,
deleted_at TIMESTAMP,
FOREIGN KEY (plant_id) REFERENCES plants(id),
FOREIGN KEY (item_characteristic_id) REFERENCES item_characteristics(id),
FOREIGN KEY (sticker_structure1_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure2_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure3_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure4_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure5_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure6_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure7_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker_structure8_id) REFERENCES sticker_structure_details(id),
FOREIGN KEY (sticker1_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker2_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker3_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker4_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker5_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker6_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker7_machine_id) REFERENCES machines(id),
FOREIGN KEY (sticker8_machine_id) REFERENCES machines(id)
);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sticker_mapping_masters');
}
};

View File

@@ -0,0 +1,44 @@
<?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'
CREATE TABLE sticker_validations (
id BIGINT GENERATED always AS IDENTITY PRIMARY KEY,
plant_id BIGINT NOT NULL,
machine_id BIGINT NOT NULL,
production_order TEXT DEFAULT NULL,
serial_number TEXT DEFAULT NULL,
status TEXT DEFAULT NULL,
sticker_id TEXT DEFAULT NULL
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by TEXT DEFAULT NULL,
updated_by TEXT DEFAULT NULL,
deleted_at TIMESTAMP,
FOREIGN KEY (plant_id) REFERENCES plants (id),
FOREIGN KEY (machine_id) REFERENCES machines (id)
);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sticker_validations');
}
};

View File

@@ -0,0 +1,38 @@
<?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'
CREATE TABLE spot_rate_transport_masters (
id BIGINT GENERATED always AS IDENTITY PRIMARY KEY,
group_name TEXT NOT NULL,
user_name TEXT DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by TEXT DEFAULT NULL,
updated_by TEXT DEFAULT NULL,
deleted_at TIMESTAMP
);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('spot_rate_transport_masters');
}
};

View File

@@ -0,0 +1,60 @@
<?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'
CREATE TABLE request_quotations (
id BIGINT GENERATED always AS IDENTITY PRIMARY KEY,
spot_rate_transport_master_id BIGINT NOT NULL,
rfq_number TEXT NOT NULL,
rfq_date_time TIMESTAMP NOT NULL DEFAULT NOW(),
pickup_address TEXT NOT NULL,
delivery_address TEXT NOT NULL,
weight TEXT NOT NULL,
volumetrice_size_inch TEXT NOT NULL,
type_of_vehicle TEXT NOT NULL,
special_type TEXT NOT NULL,
no_of_vehicle TEXT NOT NULL,
product_name TEXT NOT NULL,
loading_by TEXT NOT NULL,
unloading_by TEXT NOT NULL,
pick_and_delivery TEXT NOT NULL,
payment_term TEXT NOT NULL,
paid_topay TEXT NOT NULL,
require_date_time TIMESTAMP NOT NULL DEFAULT NOW(),
rfq_rec_on_or_before TIMESTAMP NOT NULL DEFAULT NOW(),
transporter_name TEXT NULL,
total_freight_charge TEXT NULL,
transit_day TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by TEXT DEFAULT NULL,
updated_by TEXT DEFAULT NULL,
deleted_at TIMESTAMP,
FOREIGN KEY (spot_rate_transport_master_id) REFERENCES spot_rate_transport_masters (id)
);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('request_quotations');
}
};

View File

@@ -0,0 +1,44 @@
<?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'
CREATE TABLE rfq_transporter_bids (
id BIGINT GENERATED always AS IDENTITY PRIMARY KEY,
request_quotation_id BIGINT NOT NULL,
transporter_name TEXT NULL,
total_freight_charge TEXT NULL,
transit_day TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by TEXT DEFAULT NULL,
updated_by TEXT DEFAULT NULL,
deleted_at TIMESTAMP,
FOREIGN KEY (request_quotation_id) REFERENCES request_quotations(id)
);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('rfq_transporter_bids');
}
};

View File

@@ -0,0 +1,34 @@
<?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) {
// //
// });
}
};

View File

@@ -0,0 +1,33 @@
<?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) {
// //
// });
}
};

View File

@@ -0,0 +1,40 @@
<?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) {
// //
// });
}
};

View File

@@ -0,0 +1,33 @@
<?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) {
// //
// });
}
};

View File

@@ -0,0 +1,31 @@
<?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) {
// //
// });
}
};

View File

@@ -0,0 +1,31 @@
<?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) {
// //
// });
}
};

Some files were not shown because too many files have changed in this diff Show More