137 Commits

Author SHA1 Message Date
eae7e76ea4 Merge pull request 'changed registerPush method logic in push.js' (#138) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #138
2026-01-27 06:00:10 +00:00
30e4415af9 Merge pull request 'change dlogin in web.php and added web push subscription model file' (#137) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Reviewed-on: #137
2026-01-27 05:31:41 +00:00
40014570c9 Merge pull request 'changed logic in edit transporter bid' (#136) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 2m22s
Reviewed-on: #136
2026-01-27 05:01:14 +00:00
c5e45cd6dd Merge pull request 'changed design for notification settings' (#135) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #135
2026-01-24 11:10:03 +00:00
2d40e51e78 Merge pull request 'changed logic in rfq transporter' (#134) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #134
2026-01-24 10:55:25 +00:00
98a3c5bc8e Merge pull request 'Added after create logic inside the rfq transporter bid' (#133) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #133
2026-01-24 10:07:07 +00:00
e969e54f59 Merge pull request 'Added after craete method in transporter bid' (#132) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #132
2026-01-24 09:37:08 +00:00
6bad120e70 Merge pull request 'changed logic in transporter bid' (#131) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #131
2026-01-24 09:30:53 +00:00
02dea13222 Merge pull request 'removed dd in push notification' (#130) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #130
2026-01-24 09:25:29 +00:00
07c642e3bd Merge pull request 'changed logic in bid transporter page' (#129) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #129
2026-01-24 09:24:07 +00:00
33e3b247b3 Merge pull request 'Addded dd fior checking web push' (#128) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #128
2026-01-24 09:00:56 +00:00
0bcc8d2113 Merge pull request 'changed logic in transporter bid' (#127) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #127
2026-01-24 07:37:14 +00:00
ff607695bc Merge pull request 'changed logic in push alert notification page' (#126) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #126
2026-01-24 07:29:31 +00:00
006857ec89 Merge pull request 'chanaged logic for rfq transporter bid' (#125) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #125
2026-01-24 07:11:20 +00:00
107e8cf505 Merge pull request 'changed logic in manifest.json' (#124) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #124
2026-01-24 06:46:50 +00:00
49d5dfb664 Merge pull request 'Added gcm sender id in manifest.json' (#123) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #123
2026-01-24 06:42:31 +00:00
cf097255a0 Merge pull request 'Added service worker js' (#122) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #122
2026-01-24 06:05:03 +00:00
27223612d3 Merge pull request 'changed logic in service worker.js' (#121) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #121
2026-01-24 05:58:44 +00:00
22de1ba081 Merge pull request 'Added has push subscriptions in user model file' (#120) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #120
2026-01-24 04:47:44 +00:00
d7c2fc3ff9 Merge pull request 'changed logic in invoice validation' (#119) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #119
2026-01-24 04:43:38 +00:00
29a7a66ff4 Merge pull request 'Added web push route in web.php' (#118) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #118
2026-01-24 04:40:45 +00:00
5f87467dbf Merge pull request 'Added push aert notification page' (#117) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #117
2026-01-24 04:35:34 +00:00
c01df679a0 Merge pull request 'Added push alert notification sample' (#116) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #116
2026-01-24 04:34:14 +00:00
4b653f6bb0 Merge pull request 'added web push notification in pwa' (#115) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #115
2026-01-24 04:25:38 +00:00
a877623567 Merge pull request 'added package for web push notification' (#114) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #114
2026-01-24 04:23:12 +00:00
e8a6e88eac Merge pull request 'changed logic in push.js' (#113) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #113
2026-01-24 04:17:44 +00:00
ab49a0fc7c Merge pull request 'Added service worker js and push.js' (#112) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #112
2026-01-23 12:46:22 +00:00
e320e4486f Merge pull request 'registered push.js in admin panel provider' (#111) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #111
2026-01-23 12:43:05 +00:00
4b5cdef55a Merge pull request 'Added notification settings page' (#110) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #110
2026-01-23 12:41:20 +00:00
82a022d3b4 Merge pull request 'removed route logic' (#109) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #109
2026-01-23 11:31:20 +00:00
7ec116ca0b Merge pull request 'Added admin welcome page' (#108) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #108
2026-01-23 11:20:21 +00:00
048f069dd7 Merge pull request 'Added welcome page in cri' (#107) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #107
2026-01-23 11:02:24 +00:00
b83e26c9d6 Merge pull request 'changed logic in pwa install' (#106) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #106
2026-01-23 10:10:01 +00:00
945d50c504 Merge pull request 'ranjith-dev' (#105) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #105
2026-01-23 10:00:31 +00:00
ae0758a9c0 Merge pull request 'changed logic in rfq transporter bid resource page' (#104) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #104
2026-01-23 09:40:28 +00:00
238d7bdfa7 Merge pull request 'Added spot rate transport master policy file' (#103) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #103
2026-01-23 09:39:14 +00:00
be1e7f36b2 Merge pull request 'Added request quotation policy file' (#102) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #102
2026-01-23 09:38:06 +00:00
165b7e6a77 Merge pull request 'Added rfq transporter bid policy file' (#101) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #101
2026-01-23 09:37:20 +00:00
d0269a646c Merge pull request 'Added rfq chart and rfq rank chart widgets page' (#100) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #100
2026-01-23 07:27:29 +00:00
3eccb1c7d5 Merge pull request 'Added rfq dashboard and rfq overview pages' (#99) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #99
2026-01-23 07:26:21 +00:00
83888054e9 Merge pull request 'Added rfq transporter bid resource pages' (#98) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #98
2026-01-23 07:23:30 +00:00
22e2fbd25d Merge pull request 'Added rfq transporter bid model file' (#97) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #97
2026-01-23 07:22:02 +00:00
d5d1243d5c Merge pull request 'Added rfq transporter bid migration file' (#96) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #96
2026-01-23 07:20:47 +00:00
2d2d3ac8c5 Merge pull request 'Added request quotation resource pages' (#95) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #95
2026-01-23 07:18:05 +00:00
6e9e57682c Merge pull request 'Added request quotation model file' (#94) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #94
2026-01-23 07:16:21 +00:00
3fa6ca578b Merge pull request 'added request quotation migartion file' (#93) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #93
2026-01-23 07:15:06 +00:00
bc43d5ddf1 Merge pull request 'Added spot rate transport master resource pages' (#92) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #92
2026-01-23 07:11:06 +00:00
201c55121e Merge pull request 'Added spot rate master model file' (#91) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #91
2026-01-23 07:08:24 +00:00
aa91bd80de Merge pull request 'Added spot rate transport master migration file' (#90) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #90
2026-01-23 06:09:48 +00:00
d4fc6f6f33 Merge pull request 'changed logic for safari browser pwa' (#89) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #89
2026-01-01 08:35:02 +00:00
dade6a3c0c Merge pull request 'Added logo for the application of pwa' (#88) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #88
2026-01-01 05:49:55 +00:00
d8aea01b26 Merge pull request 'Added manifest.json and changed name of teh application' (#87) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #87
2026-01-01 05:14:18 +00:00
a690faf6d7 Merge pull request 'changed logic for kiosk mode to take print out in sticker validation' (#86) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #86
2025-12-30 11:06:58 +00:00
dd6cbd92a2 Merge pull request 'Added pdf generate method for sticker validation' (#85) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #85
2025-12-30 09:19:24 +00:00
b54a9af78b Merge pull request 'Added sticker validation livewire pages' (#84) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #84
2025-12-30 08:59:00 +00:00
57f059aca8 Merge pull request 'Added sticker validation blade file' (#83) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #83
2025-12-30 08:57:51 +00:00
5688e93862 Merge pull request 'Added Sticker Validation resource pages' (#82) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #82
2025-12-30 08:56:26 +00:00
8b556d3c9c Merge pull request 'Added Sticker Validation model file' (#81) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #81
2025-12-30 08:55:06 +00:00
091aaaa0d7 Merge pull request 'Added sticker validation migration file' (#80) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #80
2025-12-30 08:53:55 +00:00
9dd5d04b3c Merge pull request 'Added sticker mapping master importer and exporter' (#79) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #79
2025-12-30 08:46:59 +00:00
4ac3eb4f2f Merge pull request 'Added sticker mapping master resource pages' (#78) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #78
2025-12-30 08:40:35 +00:00
bcd493901c Merge pull request 'Added sticker mapping master model file' (#77) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #77
2025-12-30 08:39:11 +00:00
af3259a842 Merge pull request 'Added sticker mapping master migration file' (#76) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #76
2025-12-30 08:38:43 +00:00
6f41238b1e Merge pull request 'Added validation in importer and exporter of sticker structure details' (#75) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #75
2025-12-25 12:03:45 +00:00
8779e66a18 Merge pull request 'Added validation logic in sticker detail for importer and exporter' (#74) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #74
2025-12-25 12:01:11 +00:00
e0384780dc Merge pull request 'Remove dimage path and image type in sticker detail model file' (#73) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #73
2025-12-25 07:43:39 +00:00
afbbfe2aca Merge pull request 'Removed image path and image type in sticker detail resource' (#72) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #72
2025-12-25 07:42:34 +00:00
4fb04c9fa8 Merge pull request 'removed image type column from sticker details migration' (#71) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #71
2025-12-25 07:40:24 +00:00
656c58999c Merge pull request 'removed image path column in sticker details' (#70) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #70
2025-12-25 07:36:56 +00:00
102bc62805 Merge pull request 'Commented heading tag on preview' (#69) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #69
2025-12-23 12:23:14 +00:00
ef1ad62749 Merge pull request 'removed unwanted file in fields' (#68) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #68
2025-12-23 12:22:21 +00:00
1e0193e6c9 Merge pull request 'Added item and plant models' (#67) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #67
2025-12-23 12:19:20 +00:00
18c203552c Merge pull request 'Commented Blade File' (#66) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #66
2025-12-23 12:18:05 +00:00
61190edcce Merge pull request 'changed logic in sticker preview' (#65) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #65
2025-12-23 12:17:23 +00:00
62774aec0f Merge pull request 'changed logic in sticker pdf service' (#64) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #64
2025-12-23 12:16:15 +00:00
44e58d2d78 Merge pull request 'Added show preview method in sticker pdf service' (#63) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 14s
Reviewed-on: #63
2025-12-23 12:04:02 +00:00
3ac4f88155 Merge pull request 'Added sticker structure preview page blade file' (#62) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #62
2025-12-23 12:00:01 +00:00
818f8a3e08 Merge pull request 'Added Sticker Preview page custom filament page' (#61) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #61
2025-12-23 11:58:45 +00:00
86798212d0 Merge pull request 'Removed sticker mapping master resource files' (#60) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #60
2025-12-23 11:25:53 +00:00
837bd58177 Merge pull request 'Removed model file of sticker mapping master' (#59) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #59
2025-12-23 11:23:13 +00:00
d8b29ba000 Merge pull request 'removed sticker mapping migration file' (#58) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #58
2025-12-23 11:21:54 +00:00
d1db3e0dad Merge pull request 'added null for design element type in resource page' (#57) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #57
2025-12-23 10:57:01 +00:00
f274de00e1 Merge pull request 'added visible to all text boxes based on design element type' (#56) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #56
2025-12-23 10:51:15 +00:00
f9c6405b61 Merge pull request 'Added curved rectangle in sticker pdf service' (#55) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 13s
Reviewed-on: #55
2025-12-23 09:54:48 +00:00
7ec34a04fc Merge pull request 'Added curve radius in sticker details resource pages' (#54) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #54
2025-12-23 09:53:19 +00:00
9256e1ed12 Merge pull request 'added curve radius in model file of sticker details' (#53) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #53
2025-12-23 09:52:27 +00:00
b334ba60fa Merge pull request 'Added curve radius column in sticker details' (#52) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #52
2025-12-23 09:48:11 +00:00
8c81c140d1 Merge pull request 'Added logic in pdf controller' (#51) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #51
2025-12-23 06:19:16 +00:00
5ff8acac74 Merge pull request 'Modified logic in generate template' (#50) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #50
2025-12-23 05:56:29 +00:00
2f2bab37d4 Merge pull request 'Added plant and item characteritivcs id in resource page' (#49) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #49
2025-12-23 05:51:19 +00:00
a8d72923ae Merge pull request 'ranjith-dev' (#48) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #48
2025-12-23 05:50:34 +00:00
e39ab23142 Merge pull request 'Added dynamic logic in pdf controller' (#47) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #47
2025-12-23 05:44:55 +00:00
2373e1d427 Merge pull request 'Added Sticker pdf service logic for dynamic values' (#46) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #46
2025-12-23 05:44:01 +00:00
baa165e285 Merge pull request 'Added route file in web.php' (#45) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #45
2025-12-23 05:42:43 +00:00
53e671d612 Merge pull request 'Added characteritics type in resource page' (#44) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #44
2025-12-23 05:41:34 +00:00
58be303cfe Merge pull request 'Added characteristics type in model file' (#43) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #43
2025-12-23 05:40:18 +00:00
a1c39c5f0e Merge pull request 'Added characteritics type column in sticker detail' (#42) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #42
2025-12-23 05:39:12 +00:00
41e8c04b9d Merge pull request 'hidden the sticker_id_live form in resource' (#41) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 12s
Reviewed-on: #41
2025-12-22 12:28:02 +00:00
6a30fbc8f2 Merge pull request 'Added logic for validation in generate temp' (#40) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #40
2025-12-22 12:26:51 +00:00
9e57461d3a Merge pull request 'Added validation part for generate pdf' (#39) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #39
2025-12-22 12:19:39 +00:00
ab5a6b94c9 Merge pull request 'Added string value in forms and tables' (#38) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #38
2025-12-22 12:09:51 +00:00
84b78ea0c1 Merge pull request 'changed logic in sticker pdf service' (#37) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #37
2025-12-22 12:08:43 +00:00
5e46b080c5 Merge pull request 'Added sticker pdf service file' (#36) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #36
2025-12-22 11:57:49 +00:00
17f337e8db Merge pull request 'Added sticker generate route in web file' (#35) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #35
2025-12-22 11:53:40 +00:00
2781d57e3f Merge pull request 'Added generate method in pdf controller' (#34) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #34
2025-12-22 11:52:32 +00:00
5628e8abd4 Merge pull request 'Added generate pdf blade file' (#33) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #33
2025-12-22 11:51:41 +00:00
e56733ce44 Merge pull request 'Added item characteristics policy file' (#32) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #32
2025-12-22 11:49:18 +00:00
b3f32f6813 Merge pull request 'Added item characteristics resource files' (#31) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #31
2025-12-22 11:47:17 +00:00
b2937ccfa8 Merge pull request 'Added item characteristics model file' (#30) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #30
2025-12-22 11:46:06 +00:00
026e4982b8 Merge pull request 'Added item characteristics migration file' (#29) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #29
2025-12-22 11:45:27 +00:00
6478bc3722 Merge pull request 'Added sticker detail policy file' (#28) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #28
2025-12-22 11:43:41 +00:00
1ab7ed2da9 Merge pull request 'Added sticker deatil importer and exporter' (#27) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #27
2025-12-22 11:41:57 +00:00
9ecd04f0e3 Merge pull request 'Added sticker detail resource page' (#26) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #26
2025-12-22 11:41:04 +00:00
53179ea538 Merge pull request 'Added sticker detail model file' (#25) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #25
2025-12-22 11:39:46 +00:00
b4e24c581e Merge pull request 'Added sticker details migration file' (#24) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #24
2025-12-22 11:38:28 +00:00
4938fa62a7 Merge pull request 'Added sticker structure policy file' (#23) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #23
2025-12-22 11:34:44 +00:00
01f6288c63 Merge pull request 'Added importer and exporter of sticker structure detail' (#22) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #22
2025-12-22 11:33:09 +00:00
c9c7c38088 Merge pull request 'Added sticker structure detail resource pages' (#21) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #21
2025-12-22 11:32:08 +00:00
90cfe9ef0d Merge pull request 'Added sticker structure detail model file' (#20) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #20
2025-12-22 11:30:56 +00:00
aa806f2fe3 Merge pull request 'Added sticker structure detail migration file' (#19) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #19
2025-12-22 11:30:11 +00:00
0eb6f76ca6 Merge pull request 'Enhance Gemini review step to handle errors and provide feedback on review status' (#18) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #18
2025-12-18 06:34:16 +00:00
73366ccd70 Merge pull request 'Update Gemini review step to use gemini-pro model for code analysis' (#17) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #17
2025-12-18 06:31:07 +00:00
acbecedce9 Merge pull request 'Update Gemini review step to use gemini-1.5-flash model for code analysis' (#16) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #16
2025-12-18 06:28:15 +00:00
614d89932b Merge pull request 'Update Gemini review step to use GPT-5-mini model for code analysis' (#15) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #15
2025-12-18 06:26:56 +00:00
c839c6fdbc Merge pull request 'Remove unnecessary blank line in Gemini PR Review workflow' (#14) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 11s
Reviewed-on: #14
2025-12-18 05:16:54 +00:00
d9445a9d4b Merge pull request 'Refactor Gemini PR Review workflow' (#13) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #13
2025-12-18 04:48:44 +00:00
50476c8a2c Merge pull request 'Added nav group name in ocr validation resource page' (#12) from ranjith-dev into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #12
2025-12-17 06:22:14 +00:00
c57cfe71ca Merge pull request 'Added nav group for web capture page' (#11) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #11
2025-12-17 06:21:40 +00:00
96c08c34c1 Merge pull request 'Added nav group in reject reason resource page' (#10) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #10
2025-12-17 06:20:01 +00:00
19cf7c8edd Merge pull request 'Added nav group for production reject reason resource page' (#9) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #9
2025-12-17 06:19:03 +00:00
66086dd2d7 Merge pull request 'Added nav group in product characteristics resource page' (#8) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #8
2025-12-17 06:16:38 +00:00
5da724c9a4 Merge pull request 'Added nav group in gr master resource page' (#7) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #7
2025-12-17 06:15:52 +00:00
6b723c0929 Merge pull request 'Added navigation sort for sticker mapping resource page' (#6) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #6
2025-12-17 06:13:44 +00:00
8e20d0732d Merge pull request 'Added nav group for sticker printing resource page' (#5) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #5
2025-12-17 06:11:56 +00:00
1d8fb1c9aa Merge pull request 'Added nav group for sticker master mapping resource' (#4) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #4
2025-12-17 06:11:07 +00:00
33bbce47ba Merge pull request 'Added navigation group for class characteristics' (#3) from ranjith-dev into master
Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
Reviewed-on: #3
2025-12-17 06:08:01 +00:00
860ff96134 Merge pull request 'Added driver master migration file' (#2) from fix-migration into master
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 10s
Reviewed-on: #2
2025-12-17 03:52:20 +00:00
38 changed files with 2159 additions and 6179 deletions

View File

@@ -1,62 +0,0 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ProductionPlanExport implements FromArray, WithHeadings, WithMapping
{
protected array $data;
protected array $dates;
public function __construct(array $data, array $dates)
{
$this->data = $data;
$this->dates = $dates;
}
public function array(): array
{
return $this->data;
}
public function headings(): array
{
$headings = [
'Plant Name',
'Line Name',
'Item Code',
];
// Add dynamic headings for each date: Target / Produced
foreach ($this->dates as $date) {
$headings[] = $date . ' - Target Plan';
$headings[] = $date . ' - Produced Quantity';
}
return $headings;
}
public function map($row): array
{
$mapped = [
$row['plant_name'] ?? '',
$row['line_name'] ?? '',
$row['item_code'] ?? '',
];
// Add daily target and produced quantity for each date
foreach ($this->dates as $date) {
// $mapped[] = $row['daily_target_dynamic'] ?? 0;
$mapped[] = $row['daily_target_dynamic'][$date] ?? '-';
$mapped[] = $row['produced_quantity'][$date] ?? 0;
}
return $mapped;
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Filament\Imports; namespace App\Filament\Imports;
use App\Models\Block; use App\Models\Block;
use App\Models\Item;
use App\Models\Line; use App\Models\Line;
use App\Models\Plant; use App\Models\Plant;
use App\Models\ProductionPlan; use App\Models\ProductionPlan;
@@ -24,33 +23,11 @@ class ProductionPlanImporter extends Importer
public static function getColumns(): array public static function getColumns(): array
{ {
return [ return [
// ImportColumn::make('created_at') ImportColumn::make('created_at')
// ->requiredMapping()
// ->exampleHeader('Created DateTime')
// ->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
// ->label('Created DateTime')
// ->rules(['required']),
ImportColumn::make('plant')
->requiredMapping() ->requiredMapping()
->exampleHeader('Plant Code') ->exampleHeader('Created DateTime')
->example(['1000', '1000']) ->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
->label('Plant Code') ->label('Created DateTime')
->relationship(resolveUsing: 'code')
->rules(['required']),
ImportColumn::make('line')
->requiredMapping()
->exampleHeader('Line Name')
->example(['4 inch pump line', '4 inch pump line'])
->label('Line Name')
->relationship(resolveUsing: 'name')
->rules(['required']),
ImportColumn::make('item')
->requiredMapping()
->exampleHeader('Item Code')
->example(['123456', '210987'])
->label('Item Code')
->relationship(resolveUsing: 'code')
->rules(['required']), ->rules(['required']),
ImportColumn::make('plan_quantity') ImportColumn::make('plan_quantity')
->requiredMapping() ->requiredMapping()
@@ -59,111 +36,175 @@ class ProductionPlanImporter extends Importer
->label('Plan Quantity') ->label('Plan Quantity')
->numeric() ->numeric()
->rules(['required', 'integer']), ->rules(['required', 'integer']),
// ImportColumn::make('production_quantity') ImportColumn::make('production_quantity')
// ->requiredMapping() ->requiredMapping()
// ->exampleHeader('Production Quantity') ->exampleHeader('Production Quantity')
// ->example(['0', '0']) ->example(['0', '0'])
// ->label('Production Quantity') ->label('Production Quantity')
// ->numeric() ->numeric()
// ->rules(['required', 'integer']), ->rules(['required', 'integer']),
ImportColumn::make('line')
// ImportColumn::make('block_reference') ->requiredMapping()
// ->requiredMapping() // Or optionalMapping() if not always present ->exampleHeader('Line Name')
// ->exampleHeader('Block Name') ->example(['4 inch pump line', '4 inch pump line'])
// ->example(['Block A', 'Block A']) ->label('Line Name')
// ->label('Block Name') ->relationship(resolveUsing:'name')
// ->rules(['required']), // Or remove if not required ->rules(['required']),
// ImportColumn::make('shift') ImportColumn::make('block_reference')
// ->requiredMapping() ->requiredMapping() // Or optionalMapping() if not always present
// ->exampleHeader('Shift Name') // ID ->exampleHeader('Block Name')
// ->example(['Day', 'Night']) // '2', '7' ->example(['Block A', 'Block A'])
// ->label('Shift Name') // ID ->label('Block Name')
// ->relationship(resolveUsing: 'name') ->rules(['required']), // Or remove if not required
// ->rules(['required']), ImportColumn::make('shift')
->requiredMapping()
// ImportColumn::make('updated_at') ->exampleHeader('Shift Name') //ID
// ->requiredMapping() ->example(['Day', 'Night']) //'2', '7'
// ->exampleHeader('Updated DateTime') ->label('Shift Name') // ID
// ->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00']) ->relationship(resolveUsing: 'name')
// ->label('Updated DateTime') ->rules(['required']),
// ->rules(['required']), ImportColumn::make('plant')
// ImportColumn::make('operator_id') ->requiredMapping()
// ->requiredMapping() ->exampleHeader('Plant Name')
// ->exampleHeader('Operator ID') ->example(['Ransar Industries-I', 'Ransar Industries-I'])
// ->example([Filament::auth()->user()->name, Filament::auth()->user()->name]) ->label('Plant Name')
// ->label('Operator ID') ->relationship(resolveUsing:'name')
// ->rules(['required']), ->rules(['required']),
ImportColumn::make('updated_at')
->requiredMapping()
->exampleHeader('Updated DateTime')
->example(['01-01-2025 08:00:00', '01-01-2025 19:30:00'])
->label('Updated DateTime')
->rules(['required']),
ImportColumn::make('operator_id')
->requiredMapping()
->exampleHeader('Operator ID')
->example([Filament::auth()->user()->name, Filament::auth()->user()->name])
->label('Operator ID')
->rules(['required']),
]; ];
} }
public function resolveRecord(): ?ProductionPlan public function resolveRecord(): ?ProductionPlan
{ {
$warnMsg = []; $warnMsg = [];
$plantCod = $this->data['plant']; $plant = Plant::where('name', $this->data['plant'])->first();
$itemCod = $this->data['item'];
$plant = null;
$line = null; $line = null;
$block = null; $block = null;
if (Str::length($plantCod) < 4 || ! is_numeric($plantCod) || ! preg_match('/^[1-9]\d{3,}$/', $plantCod)) {
$warnMsg[] = 'Invalid plant code found';
} else {
$plant = Plant::where('code', $plantCod)->first();
}
if (!$plant) { if (!$plant) {
$warnMsg[] = 'Plant not found'; $warnMsg[] = "Plant not found";
} else { }
else {
$line = Line::where('name', $this->data['line'])->where('plant_id', $plant->id)->first(); $line = Line::where('name', $this->data['line'])->where('plant_id', $plant->id)->first();
//block_reference
$block = Block::where('name', $this->data['block_reference'])->where('plant_id', $plant->id)->first();
} }
if (!$line) { if (!$line) {
$warnMsg[] = 'Line not found'; $warnMsg[] = "Line not found";
} }
$shift = null;
if (Str::length($itemCod) < 6 || ! is_numeric($itemCod)) { if (!$block) {
$warnMsg[] = 'Invalid item code found'; $warnMsg[] = "Block not found";
} else {
$item = Item::where('code', $itemCod)->first();
} }
else {
if (! $item) { $shift = Shift::where('name', $this->data['shift'])->where('plant_id', $plant->id)->where('block_id', $block->id)->first();
$warnMsg[] = 'Item not found';
} }
//$shift = Shift::where('id', $this->data['shift'])->where('plant_id', $plant->id)->first();
$plantId = $plant->id; if (!$shift) {
$warnMsg[] = "Shift not found";
$itemAgaPlant = Item::where('plant_id', $plantId)->where('code', $itemCod)->first();
if(!$itemAgaPlant){
$warnMsg[] = 'Item not found against plant code';
} }
$user = Filament::auth()->user();
$operatorName = $user->name;
if (Str::length($this->data['plan_quantity']) < 0 || !is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) { if (Str::length($this->data['plan_quantity']) < 0 || !is_numeric($this->data['plan_quantity']) || $this->data['plan_quantity'] <= 0) {
$warnMsg[] = 'Invalid plan quantity found'; $warnMsg[] = "Invalid plan quantity found";
}
if (Str::length($this->data['production_quantity']) < 0 || !is_numeric($this->data['production_quantity']) || $this->data['production_quantity'] < 0) {
$warnMsg[] = "Invalid production quantity found";
}
$fromDate = $this->data['created_at'];
$toDate = $this->data['updated_at'];
$formats = ['d-m-Y H:i', 'd-m-Y H:i:s']; //'07-05-2025 08:00' or '07-05-2025 08:00:00'
$fdateTime = null;
$tdateTime = null;
// Try parsing with multiple formats
foreach ($formats as $format) {
try {
$fdateTime = Carbon::createFromFormat($format, $fromDate);
break;
} catch (\Exception $e) {
// Optionally collect warning messages
// $warnMsg[] = "Date format mismatch with format: $format";
}
}
foreach ($formats as $format) {
try {
$tdateTime = Carbon::createFromFormat($format, $toDate);
break;
} catch (\Exception $e) {
// Optionally collect warning messages
// $warnMsg[] = "Date format mismatch with format: $format";
}
}
$fDateOnly = '';
if (!isset($fdateTime)) {
// throw new \Exception('Invalid date time format');
$warnMsg[] = "Invalid 'Created DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
}
else {
$fDateOnly = $fdateTime->toDateString();
}
if (!isset($tdateTime)) {
$warnMsg[] = "Invalid 'Updated DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
}
if (isset($fdateTime) && isset($tdateTime)) {
if ($fdateTime->greaterThan($tdateTime)) {
$warnMsg[] = "'Created DataTime' is greater than 'Updated DateTime'.";
}
}
// if (!$fromDate) {
// $warnMsg[] = "Invalid 'Created DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
// }
// else if (!$toDate) {
// $warnMsg[] = "Invalid 'Updated DateTime' format. Expected DD-MM-YYYY HH:MM:SS";
// }
$user = User::where('name', $this->data['operator_id'])->first();
if (!$user) {
$warnMsg[] = "Operator ID not found";
} }
if (!empty($warnMsg)) { if (!empty($warnMsg)) {
throw new RowImportFailedException(implode(', ', $warnMsg)); throw new RowImportFailedException(implode(', ', $warnMsg));
} else { }
else { //if (empty($warnMsg))
$productionPlan = ProductionPlan::where('plant_id', $plant->id) $productionPlan = ProductionPlan::where('plant_id', $plant->id)
->where('shift_id', $shift->id)
->where('line_id', $line->id) ->where('line_id', $line->id)
->where('item_id', $itemAgaPlant->id) ->whereDate('created_at', $fDateOnly)
// ->where('plan_quantity', $productionQuantity->plan_quantity)
->latest() ->latest()
->first(); ->first();
if ($productionPlan) { if ($productionPlan) {
// if($productionPlan->production_quantity)
// {
// throw new RowImportFailedException("{$productionPlan->created_at}, {$productionPlan->production_quantity}");
// }
// $warnMsg[] = "Production plan already exist on '{$fDateOnly}'!";
$productionPlan->update([ $productionPlan->update([
'plan_quantity' => $this->data['plan_quantity'], 'plan_quantity' => $this->data['plan_quantity'],
'operator_id' => $operatorName, // 'production_quantity' => $productionPlan->production_quantity,
// 'created_at' => $productionPlan->created_at,//$fdateTime->format('Y-m-d H:i:s'),
// 'updated_at' => $tdateTime->format('Y-m-d H:i:s'),
'operator_id' => $this->data['operator_id'],
]); ]);
$productionPlan->save(); $productionPlan->save();
return null; return null;
} }
} }
@@ -171,15 +212,20 @@ class ProductionPlanImporter extends Importer
ProductionPlan::updateOrCreate([ ProductionPlan::updateOrCreate([
'plant_id' => $plant->id, 'plant_id' => $plant->id,
'line_id' => $line->id, 'line_id' => $line->id,
'item_id' => $itemAgaPlant->id, 'shift_id' => $shift->id,
// 'shift_id' => $shift->id,
'plan_quantity' => $this->data['plan_quantity'], 'plan_quantity' => $this->data['plan_quantity'],
'created_at' =>now(), 'production_quantity' => $this->data['production_quantity'],
'updated_at' => now(), 'created_at' => $fdateTime->format('Y-m-d H:i:s'),//$this->data['created_at'],
'operator_id' => $operatorName, 'updated_at' => $tdateTime->format('Y-m-d H:i:s'),//$this->data['updated_at'],
'operator_id' => $this->data['operator_id'],
]); ]);
return null; return null;
// return ProductionPlan::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
// return new ProductionPlan();
} }
public static function getCompletedNotificationBody(Import $import): string public static function getCompletedNotificationBody(Import $import): string

View File

@@ -1,163 +0,0 @@
<?php
namespace App\Filament\Pages;
use App\Models\CustomerPoMaster;
use App\Models\Plant;
use App\Models\ProductionPlan;
use App\Models\WireMasterPacking;
use Filament\Facades\Filament;
use Filament\Pages\Page;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Form;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ViewField;
use Filament\Notifications\Notification;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Hidden;
class ProductionCalender extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.production-calender';
use InteractsWithForms;
protected $listeners = ['setWorkingDays'];
public $pId;
public array $filters = [];
public function setWorkingDays($days = null)
{
$this->form->fill([
'working_days' => $days ?? 0,
]);
}
public function form(Form $form): Form
{
return $form
->statePath('filters')
->schema([
Section::make('')
->schema([
Select::make('plant_id')
->label('Plant')
->reactive()
//->options(Plant::pluck('name', 'id'))
->options(function (callable $get) {
$userHas = Filament::auth()->user()->plant_id;
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
})
->columnSpan(['default' => 10, 'sm' => 7])
->required()
->afterStateUpdated(function ($state, callable $set, callable $get) {
$set('working_days', null);
}),
TextInput::make('working_days')
->label('No. of Working Days')
->numeric()
->readOnly()
->columnSpan(['default' => 10, 'sm' => 2])
->required()
->minValue(0)
->maxValue(31)
->placeholder('Enter working days')
->id('working_days'),
Hidden::make('month')
->label('Month')
->id('month'),
Hidden::make('year')
->label('Year')
->id('year'),
Hidden::make('selected_dates')
->label('Selected Dates')
->id('selected_dates'),
ViewField::make('save')
->view('forms.save')
->columnSpan(['default' => 10, 'sm' => 1]),
ViewField::make('calendar')
->view('forms.calendar')
->columnspan(10),
])
->columns(10)
]);
}
public function saveWorkingDays(){
$plantId = $this->filters['plant_id'] ?? null;
$workingDays = $this->filters['working_days'] ?? null;
$month = $this->filters['month'] ?? null;
$year = $this->filters['year'] ?? null;
$dates = $this->filters['selected_dates'] ?? null;
if (!$plantId) {
Notification::make()
->title('Unknown Plant')
->body("Please select a plant first!")
->danger()
->send();
return;
}
else if (!$workingDays) {
Notification::make()
->title('Unknown Working Days')
->body("Working days can't be empty!")
->danger()
->send();
return;
}
else if (!$month) {
Notification::make()
->title('Unknown Month')
->body("month can't be empty!")
->danger()
->send();
return;
}
else if (!$year) {
Notification::make()
->title('Unknown Year')
->body("Year can't be empty!")
->danger()
->send();
return;
}
$updated = ProductionPlan::where('plant_id', $plantId)
->whereMonth('created_at', $month)
->whereYear('created_at', $year)
->update([
'working_days' => $workingDays,
'leave_dates' => $dates,
'updated_at' => now(),
]);
if ($updated) {
Notification::make()
->title('Success')
->body("Working days updated successfully!")
->success()
->send();
} else {
Notification::make()
->title('No Records Updated')
->body("No production plans found for this plant and month.")
->warning()
->send();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,173 +0,0 @@
<?php
namespace App\Filament\Pages;
use App\Models\Plant;
use Filament\Facades\Filament;
use Filament\Forms\Components\DatePicker;
use Filament\Pages\Page;
use Filament\Forms\Form;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
class ProductionTarget extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static string $view = 'filament.pages.production-target';
public array $filters = [];
public function form(Form $form): Form
{
return $form
->statePath('filters')
->schema([
Section::make('')
->schema([
Select::make('plant_id')
->label('Plant')
->reactive()
->options(function (callable $get) {
$userHas = Filament::auth()->user()->plant_id;
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
})
->required()
->afterStateUpdated(function ($state, callable $get, $set) {
$set('line_id', null);
$set('year', null);
$set('month', null);
$this->dispatch('loadData',$state, '', '', '');
}),
Select::make('line_id')
->label('Line')
->required()
->columnSpan(1)
->options(function (callable $get) {
if (!$get('plant_id')) {
return [];
}
return \App\Models\Line::where('plant_id', $get('plant_id'))
->pluck('name', 'id')
->toArray();
})
->reactive()
->afterStateUpdated(function ($state, callable $get, $set) {
$plantId = $get('plant_id');
$set('year', null);
$set('month', null);
$this->dispatch('loadData',$plantId, $state, '', '');
}),
Select::make('year')
->label('Year')
->reactive()
->options([
'2026' => '2026',
'2027' => '2027',
'2028' => '2028',
'2029' => '2029',
'2030' => '2030',
'2031' => '2031',
'2032' => '2032',
'2033' => '2033',
'2034' => '2034',
'2035' => '2035',
'2036' => '2036',
'2037' => '2037',
'2038' => '2038',
'2039' => '2039',
'2040' => '2040',
])
->required()
->afterStateUpdated(function ($state, callable $get, $set) {
$set('month', null);
$plantId = $get('plant_id');
$lineId = $get('line_id');
$this->dispatch('loadData',$plantId, $lineId, $state, '');
}),
Select::make('month')
->label('Month')
->reactive()
->options([
'01' => 'January',
'02' => 'February',
'03' => 'March',
'04' => 'April',
'05' => 'May',
'06' => 'June',
'07' => 'July',
'08' => 'August',
'09' => 'September',
'10' => 'October',
'11' => 'November',
'12' => 'December',
])
->required()
->afterStateUpdated(function ($state, callable $get) {
$plantId = $get('plant_id');
$lineId = $get('line_id');
// $month = $get('month');
$year = $get('year');
$month = (int) $get('month');
if (!$month) {
return;
}
$this->dispatch('loadData', $plantId, $lineId, $month, $year);
}),
])
->columns(4)
]);
}
public function export(){
$plantId = $this->filters['plant_id'] ?? null;
$lineId = $this->filters['line_id'] ?? null;
$year = $this->filters['year'] ?? null;
$month = $this->filters['month'] ?? null;
if (! $plantId) {
Notification::make()
->title('Plant')
->body("please select plant to export data..!")
->danger()
->send();
return;
}
else if (! $lineId) {
Notification::make()
->title('Line')
->body("please select line to export data..!")
->danger()
->send();
return;
}
else if (! $year) {
Notification::make()
->title('Year')
->body("please select year to export data..!")
->danger()
->send();
return;
}
else if (! $month) {
Notification::make()
->title('Month')
->body("please select month to export data..!")
->danger()
->send();
return;
}
$this->dispatch('loadData1' ,$plantId, $lineId, $year, $month);
}
}

View File

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

View File

@@ -5,23 +5,23 @@ namespace App\Filament\Resources;
use App\Filament\Exports\LineExporter; use App\Filament\Exports\LineExporter;
use App\Filament\Imports\LineImporter; use App\Filament\Imports\LineImporter;
use App\Filament\Resources\LineResource\Pages; use App\Filament\Resources\LineResource\Pages;
use App\Models\Block; use App\Filament\Resources\LineResource\RelationManagers;
use App\Models\Line; use App\Models\Line;
use App\Models\Plant; use App\Models\Plant;
use App\Models\WorkGroupMaster; use App\Models\WorkGroupMaster;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Forms\Get; use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Actions\ImportAction; use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Forms\Components\Section;
use Filament\Forms\Set;
use Filament\Tables\Actions\ExportAction;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Unique; use Illuminate\Validation\Rules\Unique;
@@ -48,8 +48,7 @@ class LineResource extends Resource
->reactive() ->reactive()
->options(function (callable $get) { ->options(function (callable $get) {
$userHas = Filament::auth()->user()->plant_id; $userHas = Filament::auth()->user()->plant_id;
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
}) })
->default(function () { ->default(function () {
return optional(Line::latest()->first())->plant_id; return optional(Line::latest()->first())->plant_id;
@@ -61,9 +60,10 @@ class LineResource extends Resource
// Ensure `linestop_id` is not cleared // Ensure `linestop_id` is not cleared
if (!$plantId) { if (!$plantId) {
$set('lPlantError', 'Please select a plant first.'); $set('lPlantError', 'Please select a plant first.');
return; return;
} else { }
else
{
$set('lPlantError', null); $set('lPlantError', null);
} }
}) })
@@ -72,39 +72,6 @@ class LineResource extends Resource
]) ])
->hint(fn ($get) => $get('lPlantError') ? $get('lPlantError') : null) ->hint(fn ($get) => $get('lPlantError') ? $get('lPlantError') : null)
->hintColor('danger'), ->hintColor('danger'),
Forms\Components\Select::make('block_id')
->label('Block')
->relationship('block', 'name')
->required()
// ->nullable(),
->reactive()
->options(function (callable $get) {
if (! $get('plant_id')) {
return [];
}
return Block::where('plant_id', $get('plant_id'))
->pluck('name', 'id')
->toArray();
})
->default(function () {
return optional(Block::latest()->first())->plant_id;
})
->afterStateUpdated(function ($state, callable $set, callable $get) {
$blockId = $get('block_id');
if (! $blockId) {
$set('lblockError', 'Please select a Block first.');
return;
} else {
$set('lblockError', null);
}
})
->extraAttributes(fn ($get) => [
'class' => $get('lblockError') ? 'border-red-500' : '',
])
->hint(fn ($get) => $get('lblockError') ? $get('lblockError') : null)
->hintColor('danger'),
Forms\Components\TextInput::make('name') Forms\Components\TextInput::make('name')
->required() ->required()
->placeholder('Scan the valid name') ->placeholder('Scan the valid name')
@@ -132,9 +99,10 @@ class LineResource extends Resource
// Ensure `linestop_id` is not cleared // Ensure `linestop_id` is not cleared
if (!$lineNam) { if (!$lineNam) {
$set('lNameError', 'Scan the valid name.'); $set('lNameError', 'Scan the valid name.');
return; return;
} else { }
else
{
// $exists = Line::where('name', $lineNam) // $exists = Line::where('name', $lineNam)
// ->where('plant_id', $get('plant_id')) // ->where('plant_id', $get('plant_id'))
// ->exists(); // ->exists();
@@ -188,9 +156,6 @@ class LineResource extends Resource
'Base FG Line' => 'Base FG Line', 'Base FG Line' => 'Base FG Line',
'SFG Line' => 'SFG Line', 'SFG Line' => 'SFG Line',
'FG Line' => 'FG Line', 'FG Line' => 'FG Line',
'Process Base FG Line' => 'Process Base FG Line',
'Process SFG Line' => 'Process SFG Line',
'Process FG Line' => 'Process FG Line',
'Machining Cell' => 'Machining Cell', 'Machining Cell' => 'Machining Cell',
'Blanking Cell' => 'Blanking Cell', 'Blanking Cell' => 'Blanking Cell',
'Forming Cell' => 'Forming Cell', 'Forming Cell' => 'Forming Cell',
@@ -205,9 +170,10 @@ class LineResource extends Resource
// Ensure `linestop_id` is not cleared // Ensure `linestop_id` is not cleared
if (!$lineTyp) { if (!$lineTyp) {
$set('lTypeError', 'Scan the valid type.'); $set('lTypeError', 'Scan the valid type.');
return; return;
} else { }
else
{
$set('lTypeError', null); $set('lTypeError', null);
} }
}) })
@@ -238,7 +204,7 @@ class LineResource extends Resource
$partValidDispColumns = [ $partValidDispColumns = [
'work_group1_actual_id', 'work_group2_actual_id', 'work_group3_actual_id', 'work_group4_actual_id', 'work_group5_actual_id', 'work_group1_actual_id', 'work_group2_actual_id', 'work_group3_actual_id', 'work_group4_actual_id', 'work_group5_actual_id',
'work_group6_actual_id', 'work_group7_actual_id', 'work_group8_actual_id', 'work_group9_actual_id', 'work_group10_actual_id', 'work_group6_actual_id', 'work_group7_actual_id', 'work_group8_actual_id', 'work_group9_actual_id', 'work_group10_actual_id'
]; ];
foreach ($partValidDispColumns as $column) { foreach ($partValidDispColumns as $column) {
@@ -289,7 +255,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group1_id_error', null); $set('work_group1_id_error', null);
$set('work_group1_id', null); $set('work_group1_id', null);
return; return;
} }
@@ -298,7 +263,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group1_id_error', 'Invalid plant name.'); $set('work_group1_id_error', 'Invalid plant name.');
return; return;
} }
@@ -308,7 +272,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group1_id_error', 'Work group does not exist for this plant in master.'); $set('work_group1_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -334,9 +297,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group1_actual_id', ''); $set('work_group1_actual_id', '');
$set('work_group1_id', null); $set('work_group1_id', null);
return; return;
} else { }
else
{
$set('work_group1_id_error', null); $set('work_group1_id_error', null);
$set('work_group1_id', $workGroupRecord->id); $set('work_group1_id', $workGroupRecord->id);
} }
@@ -368,7 +332,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group2_id_error', null); $set('work_group2_id_error', null);
$set('work_group2_id', null); $set('work_group2_id', null);
return; return;
} }
@@ -377,7 +340,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group2_id_error', 'Invalid plant name.'); $set('work_group2_id_error', 'Invalid plant name.');
return; return;
} }
@@ -387,7 +349,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group2_id_error', 'Work group does not exist for this plant in master.'); $set('work_group2_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -408,9 +369,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group2_actual_id', ''); $set('work_group2_actual_id', '');
$set('work_group2_id', null); $set('work_group2_id', null);
return; return;
} else { }
else
{
$set('work_group2_id_error', null); $set('work_group2_id_error', null);
$set('work_group2_id', $workGroupRecord->id); $set('work_group2_id', $workGroupRecord->id);
} }
@@ -442,7 +404,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group3_id_error', null); $set('work_group3_id_error', null);
$set('work_group3_id', null); $set('work_group3_id', null);
return; return;
} }
@@ -451,7 +412,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group3_id_error', 'Invalid plant name.'); $set('work_group3_id_error', 'Invalid plant name.');
return; return;
} }
@@ -461,7 +421,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group3_id_error', 'Work group does not exist for this plant in master.'); $set('work_group3_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -482,9 +441,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group3_actual_id', ''); $set('work_group3_actual_id', '');
$set('work_group3_id', null); $set('work_group3_id', null);
return; return;
} else { }
else
{
$set('work_group3_id_error', null); $set('work_group3_id_error', null);
$set('work_group3_id', $workGroupRecord->id); $set('work_group3_id', $workGroupRecord->id);
} }
@@ -516,7 +476,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group4_id_error', null); $set('work_group4_id_error', null);
$set('work_group4_id', null); $set('work_group4_id', null);
return; return;
} }
@@ -525,7 +484,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group4_id_error', 'Invalid plant name.'); $set('work_group4_id_error', 'Invalid plant name.');
return; return;
} }
@@ -535,7 +493,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group4_id_error', 'Work group does not exist for this plant in master.'); $set('work_group4_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -556,9 +513,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group4_actual_id', ''); $set('work_group4_actual_id', '');
$set('work_group4_id', null); $set('work_group4_id', null);
return; return;
} else { }
else
{
$set('work_group4_id_error', null); $set('work_group4_id_error', null);
$set('work_group4_id', $workGroupRecord->id); $set('work_group4_id', $workGroupRecord->id);
} }
@@ -590,7 +548,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group5_id_error', null); $set('work_group5_id_error', null);
$set('work_group5_id', null); $set('work_group5_id', null);
return; return;
} }
@@ -599,7 +556,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group5_id_error', 'Invalid plant name.'); $set('work_group5_id_error', 'Invalid plant name.');
return; return;
} }
@@ -609,7 +565,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group5_id_error', 'Work group does not exist for this plant in master.'); $set('work_group5_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -630,9 +585,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group5_actual_id', ''); $set('work_group5_actual_id', '');
$set('work_group5_id', null); $set('work_group5_id', null);
return; return;
} else { }
else
{
$set('work_group5_id_error', null); $set('work_group5_id_error', null);
$set('work_group5_id', $workGroupRecord->id); $set('work_group5_id', $workGroupRecord->id);
} }
@@ -664,7 +620,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group6_id_error', null); $set('work_group6_id_error', null);
$set('work_group6_id', null); $set('work_group6_id', null);
return; return;
} }
@@ -673,7 +628,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group6_id_error', 'Invalid plant name.'); $set('work_group6_id_error', 'Invalid plant name.');
return; return;
} }
@@ -683,7 +637,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group6_id_error', 'Work group does not exist for this plant in master.'); $set('work_group6_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -704,9 +657,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group6_actual_id', ''); $set('work_group6_actual_id', '');
$set('work_group6_id', null); $set('work_group6_id', null);
return; return;
} else { }
else
{
$set('work_group6_id_error', null); $set('work_group6_id_error', null);
$set('work_group6_id', $workGroupRecord->id); $set('work_group6_id', $workGroupRecord->id);
} }
@@ -738,7 +692,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group7_id_error', null); $set('work_group7_id_error', null);
$set('work_group7_id', null); $set('work_group7_id', null);
return; return;
} }
@@ -747,7 +700,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group7_id_error', 'Invalid plant name.'); $set('work_group7_id_error', 'Invalid plant name.');
return; return;
} }
@@ -757,7 +709,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group7_id_error', 'Work group does not exist for this plant in master.'); $set('work_group7_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -778,9 +729,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group7_actual_id', ''); $set('work_group7_actual_id', '');
$set('work_group7_id', null); $set('work_group7_id', null);
return; return;
} else { }
else
{
$set('work_group7_id_error', null); $set('work_group7_id_error', null);
$set('work_group7_id', $workGroupRecord->id); $set('work_group7_id', $workGroupRecord->id);
} }
@@ -812,7 +764,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group8_id_error', null); $set('work_group8_id_error', null);
$set('work_group8_id', null); $set('work_group8_id', null);
return; return;
} }
@@ -821,7 +772,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group8_id_error', 'Invalid plant name.'); $set('work_group8_id_error', 'Invalid plant name.');
return; return;
} }
@@ -831,7 +781,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group8_id_error', 'Work group does not exist for this plant in master.'); $set('work_group8_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -852,9 +801,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group8_actual_id', ''); $set('work_group8_actual_id', '');
$set('work_group8_id', null); $set('work_group8_id', null);
return; return;
} else { }
else
{
$set('work_group8_id_error', null); $set('work_group8_id_error', null);
$set('work_group8_id', $workGroupRecord->id); $set('work_group8_id', $workGroupRecord->id);
} }
@@ -886,7 +836,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group9_id_error', null); $set('work_group9_id_error', null);
$set('work_group9_id', null); $set('work_group9_id', null);
return; return;
} }
@@ -895,7 +844,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group9_id_error', 'Invalid plant name.'); $set('work_group9_id_error', 'Invalid plant name.');
return; return;
} }
@@ -905,7 +853,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group9_id_error', 'Work group does not exist for this plant in master.'); $set('work_group9_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -926,9 +873,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group9_actual_id', ''); $set('work_group9_actual_id', '');
$set('work_group9_id', null); $set('work_group9_id', null);
return; return;
} else { }
else
{
$set('work_group9_id_error', null); $set('work_group9_id_error', null);
$set('work_group9_id', $workGroupRecord->id); $set('work_group9_id', $workGroupRecord->id);
} }
@@ -960,7 +908,6 @@ class LineResource extends Resource
if ($state == null || trim($state) == '') { if ($state == null || trim($state) == '') {
$set('work_group10_id_error', null); $set('work_group10_id_error', null);
$set('work_group10_id', null); $set('work_group10_id', null);
return; return;
} }
@@ -969,7 +916,6 @@ class LineResource extends Resource
if (!$plantId) { if (!$plantId) {
$set('work_group10_id_error', 'Invalid plant name.'); $set('work_group10_id_error', 'Invalid plant name.');
return; return;
} }
@@ -979,7 +925,6 @@ class LineResource extends Resource
if (!$workGroupRecord) { if (!$workGroupRecord) {
$set('work_group10_id_error', 'Work group does not exist for this plant in master.'); $set('work_group10_id_error', 'Work group does not exist for this plant in master.');
return; return;
} }
@@ -1000,9 +945,10 @@ class LineResource extends Resource
->send(); ->send();
$set('work_group10_actual_id', ''); $set('work_group10_actual_id', '');
$set('work_group10_id', null); $set('work_group10_id', null);
return; return;
} else { }
else
{
$set('work_group10_id_error', null); $set('work_group10_id_error', null);
$set('work_group10_id', $workGroupRecord->id); $set('work_group10_id', $workGroupRecord->id);
} }
@@ -1038,7 +984,6 @@ class LineResource extends Resource
$paginator = $livewire->getTableRecords(); $paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10; $perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1; $currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration; return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}), }),
Tables\Columns\TextColumn::make('plant.name') Tables\Columns\TextColumn::make('plant.name')
@@ -1046,11 +991,6 @@ class LineResource extends Resource
->alignCenter() ->alignCenter()
->sortable() ->sortable()
->searchable(), ->searchable(),
Tables\Columns\TextColumn::make('block.name')
->label('Block')
->alignCenter()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('name') Tables\Columns\TextColumn::make('name')
->label('Line') ->label('Line')
->alignCenter() ->alignCenter()

View File

@@ -6,8 +6,8 @@ use AlperenErsoy\FilamentExport\Actions\FilamentExportBulkAction;
use App\Filament\Exports\ProductionPlanExporter; use App\Filament\Exports\ProductionPlanExporter;
use App\Filament\Imports\ProductionPlanImporter; use App\Filament\Imports\ProductionPlanImporter;
use App\Filament\Resources\ProductionPlanResource\Pages; use App\Filament\Resources\ProductionPlanResource\Pages;
use App\Filament\Resources\ProductionPlanResource\RelationManagers;
use App\Models\Block; use App\Models\Block;
use App\Models\Item;
use App\Models\Line; use App\Models\Line;
use App\Models\Plant; use App\Models\Plant;
use App\Models\ProductionPlan; use App\Models\ProductionPlan;
@@ -16,18 +16,19 @@ use Carbon\Carbon;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\DateTimePicker; use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Forms\Get; use Filament\Forms\Get;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Actions\ImportAction; use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope; use Illuminate\Database\Eloquent\SoftDeletingScope;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Tables\Actions\ExportAction;
use Filament\Tables\Filters\Filter;
use Illuminate\Support\Facades\Request;
class ProductionPlanResource extends Resource class ProductionPlanResource extends Resource
{ {
@@ -54,8 +55,7 @@ class ProductionPlanResource extends Resource
->reactive() ->reactive()
->options(function (callable $get) { ->options(function (callable $get) {
$userHas = Filament::auth()->user()->plant_id; $userHas = Filament::auth()->user()->plant_id;
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
}) })
->default(function () { ->default(function () {
return optional(ProductionPlan::latest()->first())->plant_id; return optional(ProductionPlan::latest()->first())->plant_id;
@@ -67,9 +67,10 @@ class ProductionPlanResource extends Resource
$set('block_name', null); $set('block_name', null);
if (!$plantId) { if (!$plantId) {
$set('ppPlantError', 'Please select a plant first.'); $set('ppPlantError', 'Please select a plant first.');
return; return;
} else { }
else
{
$set('ppPlantError', null); $set('ppPlantError', null);
} }
}) })
@@ -78,6 +79,102 @@ class ProductionPlanResource extends Resource
]) ])
->hint(fn ($get) => $get('ppPlantError') ? $get('ppPlantError') : null) ->hint(fn ($get) => $get('ppPlantError') ? $get('ppPlantError') : null)
->hintColor('danger'), ->hintColor('danger'),
Forms\Components\Select::make('block_name')
->required()
// ->nullable()
->label('Block')
->options(function (callable $get) {
if (!$get('plant_id')) {
return [];
}
return Block::where('plant_id', $get('plant_id'))
->pluck('name', 'id')
->toArray();
})
->reactive()
->default(function () {
$latestShiftId = optional(ProductionPlan::latest()->first())->shift_id;
return optional(Shift::where('id', $latestShiftId)->first())->block_id;
})
//->afterStateUpdated(fn ($set) => $set('shift_id', null))
->afterStateUpdated(function ($state, callable $set, callable $get) {
if($get('id'))
{
$getShift = ProductionPlan::where('id', $get('id'))->first();
$getBlock = Shift::where('id', $getShift->shift_id)->first();
if($getBlock->block_id)
{
$set('block_name', $getBlock->block_id);
$set('ppBlockError', null);
}
}
$blockId = $get('block_name');
$set('shift_id', null);
if (!$blockId) {
$set('ppBlockError', 'Please select a block first.');
return;
}
else
{
$set('ppBlockError', null);
}
})
->extraAttributes(fn ($get) => [
'class' => $get('ppBlockError') ? 'border-red-500' : '',
])
->hint(fn ($get) => $get('ppBlockError') ? $get('ppBlockError') : null)
->hintColor('danger'),
Forms\Components\Select::make('shift_id')
->relationship('shift', 'name')
->required()
// ->nullable()
->autofocus(true)
->options(function (callable $get) {
if (!$get('plant_id') || !$get('block_name')) {
return [];
}
return Shift::where('plant_id', $get('plant_id'))
->where('block_id', $get('block_name'))
->pluck('name', 'id')
->toArray();
})
->reactive()
->default(function () {
return optional(ProductionPlan::latest()->first())->shift_id;
})
// ->afterStateUpdated(fn ($set) => $set('line_id', null))
->afterStateUpdated(function ($state, callable $set, callable $get) {
if($get('id'))
{
$getShift = ProductionPlan::where('id', $get('id'))->first();
if($getShift->shift_id)
{
$set('shift_id', $getShift->shift_id);
$set('ppShiftError', null);
}
}
$curShiftId = $get('shift_id');
$set('line_id', null);
if (!$curShiftId) {
$set('ppShiftError', 'Please select a shift first.');
return;
}
else
{
$set('ppShiftError', null);
}
})
->extraAttributes(fn ($get) => [
'class' => $get('ppShiftError') ? 'border-red-500' : '',
])
->hint(fn ($get) => $get('ppShiftError') ? $get('ppShiftError') : null)
->hintColor('danger'),
Forms\Components\Select::make('line_id') Forms\Components\Select::make('line_id')
->relationship('line', 'name') ->relationship('line', 'name')
->required() ->required()
@@ -88,7 +185,7 @@ class ProductionPlanResource extends Resource
// ->toArray() // Convert collection to array // ->toArray() // Convert collection to array
// ) // )
->options(function (callable $get) { ->options(function (callable $get) {
if (! $get('plant_id')) { if (!$get('plant_id') || !$get('block_name') || !$get('shift_id')) {
return []; return [];
} }
@@ -100,54 +197,207 @@ class ProductionPlanResource extends Resource
// ->default(function () { // ->default(function () {
// return optional(ProductionPlan::latest()->first())->line_id; // return optional(ProductionPlan::latest()->first())->line_id;
// }) // })
// ->afterStateUpdated(function ($state, callable $set, callable $get) { ->afterStateUpdated(function ($state, callable $set, callable $get) {
// if ($get('id')) { if($get('id'))
// $getShift = ProductionPlan::where('id', $get('id'))->first(); {
// if ($getShift->line_id) { $getShift = ProductionPlan::where('id', $get('id'))->first();
// $set('line_id', $getShift->line_id); if($getShift->line_id)
// $set('ppLineError', null); {
// } $set('line_id', $getShift->line_id);
// } else { $set('ppLineError', null);
// $currentDT = Carbon::now()->toDateTimeString(); }
// $set('created_at', $currentDT); }
// $set('update_date', null); else
// } {
$currentDT = Carbon::now()->toDateTimeString();
$set('created_at', $currentDT);
$set('update_date', null);
}
// $lineId = $get('line_id'); $lineId = $get('line_id');
// // $set('plan_quantity', null); // $set('plan_quantity', null);
// if (! $lineId) { if (!$lineId) {
// $set('ppLineError', 'Please select a line first.'); $set('ppLineError', 'Please select a line first.');
return;
}
else
{
$isUpdate = !empty($get('id'));
if (!$isUpdate)
{
$exists = ProductionPlan::where('plant_id', $get('plant_id'))
->where('shift_id', $get('shift_id'))
->where('line_id', $get('line_id'))
->whereDate('created_at', today())
->latest()
->exists();
if ($exists)
{
$set('line_id', null);
$set('ppLineError', 'Production plan already updated.');
return;
}
else
{
$existShifts = ProductionPlan::where('plant_id', $get('plant_id'))
->where('shift_id', $get('shift_id'))
->where('line_id', $get('line_id'))
->whereDate('created_at', Carbon::yesterday())
->latest()
->exists();
if ($existShifts) //if ($existShifts->count() > 0)
{
//$currentDate = date('Y-m-d');
$yesterday = date('Y-m-d', strtotime('-1 days'));
$shiftId = Shift::where('id', $get('shift_id'))
->first();
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
$hRs = (int) $hRs;
// $miNs = (int) $miNs;-*/
$totalMinutes = $hRs * 60 + $miNs;
$from_dt = $yesterday . ' ' . $shiftId->start_time;
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
$currentDateTime = date('Y-m-d H:i:s');
// Check if current date time is within the range
if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
//echo "Choosed a valid shift...";
$set('line_id', null);
$set('ppLineError', 'Production plan already updated.');
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
return;
}
else
{
$currentDate = date('Y-m-d');
$shiftId = Shift::where('id', $get('shift_id'))
->first();
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
$hRs = (int) $hRs;
// $miNs = (int) $miNs;-*/
$totalMinutes = $hRs * 60 + $miNs;
$from_dt = $currentDate . ' ' . $shiftId->start_time;
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
$currentDateTime = date('Y-m-d H:i:s');
// Check if current date time is within the range
if (!($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
//echo "Choosed a valid shift...";
$set('line_id', null);
$set('ppLineError', 'Choosed a invalid shift.');
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
return;
}
}
$set('ppLineError', null);
return;
}
else
{
//$currentDate = date('Y-m-d');
$yesterday = date('Y-m-d', strtotime('-1 days'));
$shiftId = Shift::where('id', $get('shift_id'))
->first();
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
$hRs = (int) $hRs;
// $miNs = (int) $miNs;-*/
$totalMinutes = $hRs * 60 + $miNs;
$from_dt = $yesterday . ' ' . $shiftId->start_time;
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
$currentDateTime = date('Y-m-d H:i:s');
// Check if current date time is within the range
if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
//echo "Choosed a valid shift...";
// here i'm updating created as yesterday
$set('created_at', $from_dt);
$set('update_date', '1');
$set('ppLineError', null);
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
return;
}
else
{
$currentDate = date('Y-m-d');
$shiftId = Shift::where('id', $get('shift_id'))
->first();
[$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
$hRs = (int) $hRs;
// $miNs = (int) $miNs;-*/
$totalMinutes = $hRs * 60 + $miNs;
$from_dt = $currentDate . ' ' . $shiftId->start_time;
$to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
$currentDateTime = date('Y-m-d H:i:s');
// Check if current date time is within the range
if (!($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
//echo "Choosed a valid shift...";
$set('line_id', null);
$set('ppLineError', 'Choosed a invalid shift.');
// $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
return;
}
}
$set('ppLineError', null);
return;
}
// return;
// } else {
// $isUpdate = ! empty($get('id'));
// if (! $isUpdate) {
// $exists = ProductionPlan::where('plant_id', $get('plant_id')) // $exists = ProductionPlan::where('plant_id', $get('plant_id'))
// ->where('shift_id', $get('shift_id')) // //->where('shift_id', $get('shift_id'))
// ->where('line_id', $get('line_id')) // ->where('line_id', $get('line_id'))
// ->whereDate('created_at', today()) // ->whereDate('created_at', today())
// ->latest() // ->latest() // Orders by created_at DESC
// ->exists(); // ->first();
// if ($exists) { // if ($exists)
// $set('line_id', null); // {
// $set('ppLineError', 'Production plan already updated.'); // $existingShifts = ProductionPlan::where('plant_id', $get('plant_id'))
// //->where('shift_id', $get('shift_id'))
// return;
// } else {
// $existShifts = ProductionPlan::where('plant_id', $get('plant_id'))
// ->where('shift_id', $get('shift_id'))
// ->where('line_id', $get('line_id')) // ->where('line_id', $get('line_id'))
// ->whereDate('created_at', Carbon::yesterday()) // // ->whereDate('created_at', today())
// ->latest() // ->whereDate('created_at', today())
// ->exists(); // ->get();
// if ($existShifts) { // if ($existShifts->count() > 0) // foreach ($existingShifts as $shift) {
// // $currentDate = date('Y-m-d'); // $curShiftId = $shift->shift_id;
// $yesterday = date('Y-m-d', strtotime('-1 days'));
// $shiftId = Shift::where('id', $get('shift_id')) // $currentDate = date('Y-m-d');
// $shiftId = \App\Models\Shift::where('id', $curShiftId)
// ->first(); // ->first();
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0]; // [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
@@ -156,7 +406,7 @@ class ProductionPlanResource extends Resource
// $totalMinutes = $hRs * 60 + $miNs; // $totalMinutes = $hRs * 60 + $miNs;
// $from_dt = $yesterday.' '.$shiftId->start_time; // $from_dt = $currentDate . ' ' . $shiftId->start_time;
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes")); // $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
@@ -165,192 +415,30 @@ class ProductionPlanResource extends Resource
// // Check if current date time is within the range // // Check if current date time is within the range
// if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) { // if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
// //echo "Choosed a valid shift..."; // //echo "Choosed a valid shift...";
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// $set('line_id', null); // $set('line_id', null);
// $set('ppLineError', 'Production plan already updated.'); // $set('ppLineError', 'Production plan already updated.');
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// return;
// } else {
// $currentDate = date('Y-m-d');
// $shiftId = Shift::where('id', $get('shift_id'))
// ->first();
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
// $hRs = (int) $hRs;
// // $miNs = (int) $miNs;-*/
// $totalMinutes = $hRs * 60 + $miNs;
// $from_dt = $currentDate.' '.$shiftId->start_time;
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
// $currentDateTime = date('Y-m-d H:i:s');
// // Check if current date time is within the range
// if (! ($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
// // echo "Choosed a valid shift...";
// $set('line_id', null);
// $set('ppLineError', 'Choosed a invalid shift.');
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// return; // return;
// } // }
// } // // else {
// // $set('ppLineError', 'Choosed a invalid shift...');
// $set('ppLineError', null);
// return;
// } else {
// // $currentDate = date('Y-m-d');
// $yesterday = date('Y-m-d', strtotime('-1 days'));
// $shiftId = Shift::where('id', $get('shift_id'))
// ->first();
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
// $hRs = (int) $hRs;
// // $miNs = (int) $miNs;-*/
// $totalMinutes = $hRs * 60 + $miNs;
// $from_dt = $yesterday.' '.$shiftId->start_time;
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
// $currentDateTime = date('Y-m-d H:i:s');
// // Check if current date time is within the range
// if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
// // echo "Choosed a valid shift...";
// // here i'm updating created as yesterday
// $set('created_at', $from_dt);
// $set('update_date', '1');
// $set('ppLineError', null);
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// return;
// } else {
// $currentDate = date('Y-m-d');
// $shiftId = Shift::where('id', $get('shift_id'))
// ->first();
// [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
// $hRs = (int) $hRs;
// // $miNs = (int) $miNs;-*/
// $totalMinutes = $hRs * 60 + $miNs;
// $from_dt = $currentDate.' '.$shiftId->start_time;
// $to_dt = date('Y-m-d H:i:s', strtotime($from_dt." + $totalMinutes minutes"));
// $currentDateTime = date('Y-m-d H:i:s');
// // Check if current date time is within the range
// if (! ($currentDateTime >= $from_dt && $currentDateTime < $to_dt)) {
// // echo "Choosed a valid shift...";
// $set('line_id', null);
// $set('ppLineError', 'Choosed a invalid shift.');
// // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// return;
// }
// }
// $set('ppLineError', null);
// return;
// }
// // $exists = ProductionPlan::where('plant_id', $get('plant_id'))
// // //->where('shift_id', $get('shift_id'))
// // ->where('line_id', $get('line_id'))
// // ->whereDate('created_at', today())
// // ->latest() // Orders by created_at DESC
// // ->first();
// // if ($exists)
// // {
// // $existingShifts = ProductionPlan::where('plant_id', $get('plant_id'))
// // //->where('shift_id', $get('shift_id'))
// // ->where('line_id', $get('line_id'))
// // // ->whereDate('created_at', today())
// // ->whereDate('created_at', today())
// // ->get();
// // foreach ($existingShifts as $shift) {
// // $curShiftId = $shift->shift_id;
// // $currentDate = date('Y-m-d');
// // $shiftId = \App\Models\Shift::where('id', $curShiftId)
// // ->first();
// // [$hRs, $miNs] = explode('.', $shiftId->duration) + [0, 0];
// // $hRs = (int) $hRs;
// // // $miNs = (int) $miNs;-*/
// // $totalMinutes = $hRs * 60 + $miNs;
// // $from_dt = $currentDate . ' ' . $shiftId->start_time;
// // $to_dt = date('Y-m-d H:i:s', strtotime($from_dt . " + $totalMinutes minutes"));
// // $currentDateTime = date('Y-m-d H:i:s');
// // // Check if current date time is within the range
// // if ($currentDateTime >= $from_dt && $currentDateTime < $to_dt) {
// // //echo "Choosed a valid shift...";
// // // $set('ppLineError', 'Valid (From: '.$from_dt.', To: '.$to_dt.')');
// // $set('line_id', null);
// // $set('ppLineError', 'Production plan already updated.');
// // return;
// // }
// // // else {
// // // $set('ppLineError', 'Choosed a invalid shift...');
// // // return;
// // // }
// // }
// // $set('ppLineError', null);
// // return; // // return;
// // } // // }
// } // }
// }
// $set('ppLineError', null); // $set('ppLineError', null);
// return;
// } // }
// }) }
->afterStateUpdated(function ($state, callable $set, callable $get) { }
$set('item_id', null); $set('ppLineError', null);
$set('plan_quantity', null); }
}) })
->extraAttributes(fn ($get) => [ ->extraAttributes(fn ($get) => [
'class' => $get('ppLineError') ? 'border-red-500' : '', 'class' => $get('ppLineError') ? 'border-red-500' : '',
]) ])
->hint(fn ($get) => $get('ppLineError') ? $get('ppLineError') : null) ->hint(fn ($get) => $get('ppLineError') ? $get('ppLineError') : null)
->hintColor('danger'), ->hintColor('danger'),
Forms\Components\Select::make('item_id')
->label('Item')
->reactive()
->searchable()
->required()
->options(function (callable $get) {
if (! $get('plant_id')) {
return [];
}
return Item::where('plant_id', $get('plant_id'))
->pluck('code', 'id')
->toArray();
}),
Forms\Components\TextInput::make('plan_quantity') Forms\Components\TextInput::make('plan_quantity')
->required() ->required()
->integer() ->integer()
@@ -361,8 +449,10 @@ class ProductionPlanResource extends Resource
->afterStateUpdated(function ($state, callable $set, callable $get) { ->afterStateUpdated(function ($state, callable $set, callable $get) {
$planQuan = $get('plan_quantity'); $planQuan = $get('plan_quantity');
if (! $get('update_date')) { if(!$get('update_date') )
if (! $get('id')) { {
if(!$get('id'))
{
$currentDT = Carbon::now()->toDateTimeString(); $currentDT = Carbon::now()->toDateTimeString();
$set('created_at', $currentDT); $set('created_at', $currentDT);
} }
@@ -370,9 +460,10 @@ class ProductionPlanResource extends Resource
if (!$planQuan) { if (!$planQuan) {
$set('ppPlanQuanError', 'Scan the valid plan quantity.'); $set('ppPlanQuanError', 'Scan the valid plan quantity.');
return; return;
} else { }
else
{
$set('ppPlanQuanError', null); $set('ppPlanQuanError', null);
} }
}) })
@@ -381,19 +472,29 @@ class ProductionPlanResource extends Resource
]) ])
->hint(fn ($get) => $get('ppPlanQuanError') ? $get('ppPlanQuanError') : null) ->hint(fn ($get) => $get('ppPlanQuanError') ? $get('ppPlanQuanError') : null)
->hintColor('danger'), ->hintColor('danger'),
// Forms\Components\TextInput::make('production_quantity') Forms\Components\TextInput::make('production_quantity')
// ->required() ->required()
// ->integer() ->integer()
// ->label('Production Quantity') ->label('Production Quantity')
// ->readOnly(fn (callable $get) => ! $get('id')) ->readOnly(fn (callable $get) => !$get('id'))
// ->default(0), ->default(0),
Forms\Components\TextInput::make('id') Forms\Components\TextInput::make('id')
->hidden() ->hidden()
->readOnly(), ->readOnly(),
Forms\Components\TextInput::make('update_date')
->hidden()
->reactive()
->readOnly(),
Forms\Components\DateTimePicker::make('created_at')
->label('Created DateTime')
->hidden()
->reactive()
->required()
->readOnly(),
Forms\Components\Hidden::make('operator_id') Forms\Components\Hidden::make('operator_id')
->default(Filament::auth()->user()->name), ->default(Filament::auth()->user()->name),
]) ])
->columns(4), ->columns(2),
]); ]);
} }
@@ -428,55 +529,34 @@ class ProductionPlanResource extends Resource
$paginator = $livewire->getTableRecords(); $paginator = $livewire->getTableRecords();
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10; $perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1; $currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
return ($currentPage - 1) * $perPage + $rowLoop->iteration; return ($currentPage - 1) * $perPage + $rowLoop->iteration;
}), }),
Tables\Columns\TextColumn::make('plant.name')
->label('Plant')
->alignCenter()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('line.name')
->label('Plant')
->alignCenter()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('item.code')
->label('Item')
->alignCenter()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('plan_quantity') Tables\Columns\TextColumn::make('plan_quantity')
->label('Plan Quantity') ->label('Plan Quantity')
->alignCenter() ->alignCenter()
->numeric() ->numeric()
->sortable() ->sortable(),
->searchable(),
Tables\Columns\TextColumn::make('working_days')
->label('Working Days')
->alignCenter()
->numeric()
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('production_quantity') Tables\Columns\TextColumn::make('production_quantity')
->label('Production Quantity') ->label('Production Quantity')
->alignCenter() ->alignCenter()
->numeric() ->numeric()
->sortable() ->sortable(),
->searchable(), Tables\Columns\TextColumn::make('line.name')
// Tables\Columns\TextColumn::make('line.name') ->label('Line')
// ->label('Line') ->alignCenter()
// ->alignCenter() ->sortable(),// ->searchable(),
// ->sortable(), // ->searchable(), Tables\Columns\TextColumn::make('shift.block.name')
// Tables\Columns\TextColumn::make('shift.block.name') ->label('Block')
// ->label('Block') ->alignCenter()
// ->alignCenter() ->sortable(),
// ->sortable(), Tables\Columns\TextColumn::make('shift.name')
// Tables\Columns\TextColumn::make('shift.name') ->label('Shift')
// ->label('Shift') ->alignCenter()
// ->alignCenter() ->sortable(),// ->searchable(),
// ->sortable(), // ->searchable(), Tables\Columns\TextColumn::make('plant.name')
->label('Plant')
->alignCenter()
->sortable(),// ->searchable(),
Tables\Columns\TextColumn::make('created_at') Tables\Columns\TextColumn::make('created_at')
->label('Created At') ->label('Created At')
->dateTime() ->dateTime()
@@ -513,8 +593,7 @@ class ProductionPlanResource extends Resource
// }) // })
->options(function (callable $get) { ->options(function (callable $get) {
$userHas = Filament::auth()->user()->plant_id; $userHas = Filament::auth()->user()->plant_id;
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray();
return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray();
}) })
->reactive() ->reactive()
->afterStateUpdated(function ($state, callable $set, callable $get) { ->afterStateUpdated(function ($state, callable $set, callable $get) {
@@ -533,7 +612,6 @@ class ProductionPlanResource extends Resource
if (!$plantId ) { if (!$plantId ) {
return []; return [];
} }
return Line::where('plant_id', $plantId) return Line::where('plant_id', $plantId)
->pluck('name', 'id'); ->pluck('name', 'id');
}) })
@@ -549,7 +627,6 @@ class ProductionPlanResource extends Resource
if (!$plantId ) { if (!$plantId ) {
return []; return [];
} }
return Block::where('plant_id', $get('Plant'))->pluck('name', 'id'); return Block::where('plant_id', $get('Plant'))->pluck('name', 'id');
}) })
->reactive() ->reactive()
@@ -592,33 +669,25 @@ class ProductionPlanResource extends Resource
return $query->whereRaw('1 = 0'); return $query->whereRaw('1 = 0');
} }
if (! empty($data['Plant'])) {// if ($plant = $data['Plant'] ?? null) { if ($plant = $data['Plant'] ?? null) {
$query->where('plant_id', $data['Plant']); $query->where('plant_id', $plant);
} else {
$userHas = Filament::auth()->user()->plant_id;
if ($userHas && strlen($userHas) > 0) {
return $query->whereRaw('1 = 0');
}
} }
if (! empty($data['Shift'])) {// if ($shift = $data['Shift'] ?? null) { if ($shift = $data['Shift'] ?? null) {
$query->where('shift_id', $data['Shift']); $query->where('shift_id', $shift);
} }
if ($line = $data['Line'] ?? null) {
if (! empty($data['Line'])) {// if ($line = $data['Line'] ?? null) { $query->where('line_id', $line);
$query->where('line_id', $data['Line']);
} }
if (! empty($data['created_from'])) {// if ($from = $data['created_from'] ?? null) { if ($from = $data['created_from'] ?? null) {
$query->where('created_at', '>=', $data['created_from']); $query->where('created_at', '>=', $from);
} }
if (! empty($data['created_to'])) {// if ($to = $data['created_to'] ?? null) { if ($to = $data['created_to'] ?? null) {
$query->where('created_at', '<=', $data['created_to']); $query->where('created_at', '<=', $to);
} }
return $query; return $query;
}) })
->indicateUsing(function (array $data) { ->indicateUsing(function (array $data) {
@@ -626,12 +695,6 @@ class ProductionPlanResource extends Resource
if (!empty($data['Plant'])) { if (!empty($data['Plant'])) {
$indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name'); $indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name');
} else {
$userHas = Filament::auth()->user()->plant_id;
if ($userHas && strlen($userHas) > 0) {
return 'Plant: Choose plant to filter records.';
}
} }
if (!empty($data['Shift'])) { if (!empty($data['Shift'])) {
@@ -651,7 +714,7 @@ class ProductionPlanResource extends Resource
} }
return $indicators; return $indicators;
}), })
]) ])
->filtersFormMaxHeight('280px') ->filtersFormMaxHeight('280px')
->actions([ ->actions([
@@ -663,7 +726,7 @@ class ProductionPlanResource extends Resource
Tables\Actions\DeleteBulkAction::make(), Tables\Actions\DeleteBulkAction::make(),
Tables\Actions\ForceDeleteBulkAction::make(), Tables\Actions\ForceDeleteBulkAction::make(),
Tables\Actions\RestoreBulkAction::make(), Tables\Actions\RestoreBulkAction::make(),
FilamentExportBulkAction::make('export'), FilamentExportBulkAction::make('export')
]), ]),
]) ])
->headerActions([ ->headerActions([

View File

@@ -3,16 +3,13 @@
namespace App\Filament\Resources\RfqTransporterBidResource\Pages; namespace App\Filament\Resources\RfqTransporterBidResource\Pages;
use App\Filament\Resources\RfqTransporterBidResource; use App\Filament\Resources\RfqTransporterBidResource;
use App\Models\RequestQuotation;
use App\Models\RfqTransporterBid; use App\Models\RfqTransporterBid;
use App\Models\SpotRateTransportMaster;
use App\Models\User; use App\Models\User;
use App\Notifications\PushAlertNotification; use App\Notifications\PushAlertNotification;
use Filament\Actions; use Filament\Actions;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Illuminate\Support\Facades\Log;
class EditRfqTransporterBid extends EditRecord class EditRfqTransporterBid extends EditRecord
{ {
@@ -20,147 +17,54 @@ class EditRfqTransporterBid extends EditRecord
protected function afterSave(): void protected function afterSave(): void
{ {
if (! $this->record->wasChanged('total_freight_charge')) { if (! $this->record->wasChanged('total_freight_charge')) {
return; return;
} }
// dd($this->record);
$rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id) $rank = RfqTransporterBid::where('request_quotation_id', $this->record->request_quotation_id)
->orderBy('total_freight_charge') ->orderBy('total_freight_charge')
->pluck('id') ->pluck('id')
->search($this->record->id) + 1; ->search($this->record->id) + 1;
$requestQuotation = RequestQuotation::findOrFail( $recipients = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->get();
$this->record->request_quotation_id
);
$spotRateId = $requestQuotation->spot_rate_transport_master_id;
$spotRate = SpotRateTransportMaster::findOrFail($spotRateId);
$userNames = $spotRate->user_name;
Log::info('User names from spot rate', [
'user_name_raw' => $spotRate->user_name,
]);
if (!is_array($userNames)) {
Log::warning('user_name is not array, resetting', [
'user_name' => $userNames,
]);
$userNames = [];
}
$users = User::whereIn('name', $userNames)->get();
Log::info('Matched users', [
'count' => $users->count(),
'user_ids' => $users->pluck('id'),
]);
// $recipients = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->get();
// $recipients1 = User::role(['Super Admin', 'Rfq Supervisor', 'TransporterBid Employee'])->whereHas('pushSubscriptions')->get();
$currentUser = Filament::auth()->user(); $currentUser = Filament::auth()->user();
// if ($currentUser && ! $recipients1->contains('id', $currentUser->id)) {
// $recipients1->push($currentUser);
// }
// if ($currentUser && ! $recipients->contains('id', $currentUser->id)) { if ($currentUser && ! $recipients->contains('id', $currentUser->id)) {
// $recipients->push($currentUser); $recipients->push($currentUser);
// } }
// $user1 = Filament::auth()->user(); // $user1 = Filament::auth()->user();
$rfqNumber = $this->record->requestQuotation->rfq_number; $body = "{$currentUser->name} current rank is #{$rank}";
$body = "{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.";
// Notification::make()
// ->title('Rank Updated')
// ->body("{$currentUser->name} current rank is #{$rank}")
// ->success()
// ->sendToDatabase($recipients);
// \Log::info('Notification sent', [
// 'rank' => $rank,
// 'recipients' => $recipients->pluck('id'),
// ]);
Notification::make() Notification::make()
->title('Rank Updated') ->title('Rank Updated')
->body("{$currentUser->name} has updated the bid for RFQ No '{$rfqNumber}'. The current rank is #{$rank}.") ->body("{$currentUser->name} current rank is #{$rank}")
->success() ->success()
->sendToDatabase($users); ->sendToDatabase($recipients);
// foreach ($recipients1 as $user) { \Log::info('Notification sent', [
// $user->notify( 'rank' => $rank,
// new PushAlertNotification( 'recipients' => $recipients->pluck('id'),
// 'Rank Updated',
// $body
// )
// );
// }
// foreach ($users as $user) {
// Log::info('Checking push subscription for user', [
// 'user_id' => $user->id,
// 'name' => $user->name,
// 'subscription_count' => $user->pushSubscriptions()->count(),
// ]);
// if ($user->pushSubscriptions()->exists()) {
// Log::info('Sending push notification', [
// 'user_id' => $user->id,
// ]);
// $user->notify(
// new PushAlertNotification(
// 'Rank Updated',
// $body
// )
// );
// }
// else {
// Log::warning('User has NO push subscription', [
// 'user_id' => $user->id,
// ]);
// }
// }
foreach ($users as $user) {
$count = $user->pushSubscriptions()->count();
Log::info('Checking push subscription for user', [
'user_id' => $user->id,
'name' => $user->name,
'subscription_count' => $count,
]); ]);
if ($count == 0) { foreach ($recipients as $user) {
Log::warning('User has NO push subscription', [ $user->notify(
'user_id' => $user->id, new PushAlertNotification(
]);
continue;
}
Log::info('Sending push notification', [
'user_id' => $user->id,
]);
// ✅ THIS IS ALL YOU NEED
$user->notify(new PushAlertNotification(
'Rank Updated', 'Rank Updated',
$body $body
)); )
);
} }
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array

View File

@@ -6,11 +6,8 @@ use App\Filament\Resources\StickerValidationResource;
use App\Models\Item; use App\Models\Item;
use App\Models\ItemCharacteristic; use App\Models\ItemCharacteristic;
use App\Models\ProductionQuantity; use App\Models\ProductionQuantity;
use App\Models\StickerDetail;
use App\Models\StickerMappingMaster; use App\Models\StickerMappingMaster;
use App\Models\StickerStructureDetail;
use App\Models\StickerValidation; use App\Models\StickerValidation;
use App\Services\StickerPdfService;
use Filament\Actions; use Filament\Actions;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
@@ -95,7 +92,8 @@ class CreateStickerValidation extends CreateRecord
return; return;
} }
else {
if (preg_match('/^([a-zA-Z0-9]{6,})\|([1-9][a-zA-Z0-9]{8,})\|?$/', $this->serNo, $matches)) {
$itemCode = $matches[1]; $itemCode = $matches[1];
$serialNumber = $matches[2]; $serialNumber = $matches[2];
@@ -197,6 +195,58 @@ class CreateStickerValidation extends CreateRecord
return; return;
} }
$urls = [];
foreach ($stickers as $sticker) {
$urls[] = route('stickers1.pdf', [
'stickerId' => $sticker['sticker_id'],
'plant_id' => $this->plantId,
'item_characteristic_id' => $sticker['item_characteristic'],
'serial_number' => $serialNumber,
]);
}
$this->dispatch('open-stickers-sequence', urls: $urls);
// $pdfPath = storage_path('app/private/uploads/StickerTemplateOcr/multi.pdf');
// if (! file_exists($pdfPath)) {
// Notification::make()
// ->danger()
// ->title('Pdf Not Found')
// ->body("pdf file not exist.")
// ->send();
// return;
// }
// if (! $printerName) {
// Notification::make()
// ->danger()
// ->title('Printer Not Found')
// ->body("No CUPS printer configured for IP: $iotsPrintIp")
// ->send();
// return;
// }
// putenv('CUPS_SERVER=printer.iotsignin.com');
// $cmd = "lp -d " . escapeshellarg($printerName)
// . " -o fit-to-page "
// . escapeshellarg($pdfPath);
// exec($cmd, $out, $status);
// if ($status != 0) {
// Notification::make()
// ->danger()
// ->title('Print Failed')
// ->body('CUPS print command failed.')
// ->send();
// return;
// }
//dd($iotsPrintIp, $matchedSticker);
StickerValidation::create([ StickerValidation::create([
'plant_id' => $this->plantId, 'plant_id' => $this->plantId,
'machine_id' => $this->workCenter, 'machine_id' => $this->workCenter,
@@ -225,192 +275,24 @@ class CreateStickerValidation extends CreateRecord
$this->dispatch('refreshEmptySticker', $plantId, $this->ref_number); $this->dispatch('refreshEmptySticker', $plantId, $this->ref_number);
}
// foreach ($stickers as $sticker) { }
// // $printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']); private function getPrinterNameByIp(string $ip): ?string
// \Log::info("Looking up printer for IP: " . $sticker['print_ip']);
// $printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']);
// \Log::info("Found printer: " . ($printerName ?? 'NULL'));
// if (! $printerName) {
// Notification::make()
// ->danger()
// ->title('Printer Not Found')
// ->body("No CUPS printer configured for IP: {$sticker['print_ip']}")
// ->send();
// return;
// }
// $structure = StickerStructureDetail::findOrFail($sticker['sticker_id']);
// $itemCharacteristic = ItemCharacteristic::where('plant_id', $this->plantId)
// ->where('id', $sticker['item_characteristic'])
// ->firstOrFail();
// $dynamicElements = StickerDetail::where(
// 'sticker_structure_detail_id',
// $structure->id
// )->where('element_type', 'Dynamic')->get();
// /** STEP 3: Stream PDF to CUPS (STDIN) */
// $process = proc_open(
// 'lp -d ' . escapeshellarg($printerName) . ' -o fit-to-page -',
// [
// ['pipe', 'r'], // STDIN
// ['pipe', 'w'], // STDOUT
// ['pipe', 'w'], // STDERR
// ],
// $pipes
// );
// if (! is_resource($process)) {
// Notification::make()
// ->danger()
// ->title('Print Failed')
// ->body('Unable to start CUPS print process.')
// ->send();
// return;
// // continue;
// }
// $pdfContent = (new StickerPdfService())->generatePdf1(
// $structure->sticker_id,
// $dynamicElements,
// $itemCharacteristic,
// $serialNumber,
// $serNo
// );
// fwrite($pipes[0], $pdfContent);
// fclose($pipes[0]);
// $stderr = stream_get_contents($pipes[2]);
// fclose($pipes[1]);
// fclose($pipes[2]);
// $status = proc_close($process);
// if ($status != 0) {
// Notification::make()
// ->danger()
// ->title('Print Failed')
// ->body("CUPS error: {$stderr}")
// ->send();
// return;
// }
// }
foreach ($stickers as $sticker)
{ {
exec('lpstat -v', $output);
\Log::info("Looking up printer for IP: " . $sticker['print_ip']);
$printerName = $this->getCupsPrinterNameByIp($sticker['print_ip']);
\Log::info("Found printer: " . ($printerName ?? 'NULL'));
if (! $printerName) {
Notification::make()
->danger()
->title('Printer Not Found')
->body("No CUPS printer configured for IP: {$sticker['print_ip']}")
->send();
return;
}
$structure = StickerStructureDetail::findOrFail($sticker['sticker_id']);
$itemCharacteristic = ItemCharacteristic::where('plant_id', $this->plantId)
->where('id', $sticker['item_characteristic'])
->firstOrFail();
$dynamicElements = StickerDetail::where(
'sticker_structure_detail_id',
$structure->id
)->where('element_type', 'Dynamic')->get();
$pdfContent = (new StickerPdfService())->generatePdf1(
$structure->sticker_id,
$dynamicElements,
$itemCharacteristic,
$serialNumber,
$serNo
);
$tempPdfPath = storage_path('app/temp_sticker_' . uniqid() . '.pdf');
file_put_contents($tempPdfPath, $pdfContent);
exec(
"lp -d " . escapeshellarg($printerName) . " " . escapeshellarg($tempPdfPath),
$output,
$status
);
\Log::info("LP Output:", $output);
\Log::info("LP Status: " . $status);
if ($status != 0) {
Notification::make()
->danger()
->title('Print Failed')
->body("CUPS error while printing.")
->send();
if (file_exists($tempPdfPath)) {
unlink($tempPdfPath);
}
return;
}
if (file_exists($tempPdfPath)) {
unlink($tempPdfPath);
}
}
Notification::make()
->success()
->title('Sticker Printed')
->body("Sticker for Serial Number: $serialNumber printed successfully!")
->seconds(3)
->send();
// [$itemCode, $serialNumber] = explode('|', $serNo);
// $this->dispatch('open-sticker-pdf', [
// 'url' => url("/sticker/pdf/{$itemCode}/{$serialNumber}/$this->plantId/$this->ref_number")
// ]);
}
}
protected function getCupsPrinterNameByIp(string $ip): ?string
{
// exec('lpstat -v 2>&1', $output, $status);
exec('lpstat -h cups:631 -v 2>&1', $output, $status);
if ($status != 0 || empty($output)) {
return null;
}
foreach ($output as $line) { foreach ($output as $line) {
$parts = explode(':', $line, 2); // Example:
if (count($parts) < 2) continue; // device for TSC_WC_01: socket://192.168.1.50:9100
if (str_contains($line, $ip)) {
$printerName = trim(str_replace('device for', '', $parts[0])); preg_match('/device for (.+?):/', $line, $matches);
$deviceUri = trim($parts[1]); return $matches[1] ?? null;
if (str_contains($deviceUri, $ip)) {
return $printerName;
} }
} }
return null; return null;
} }
} }

View File

@@ -1,482 +0,0 @@
<?php
namespace App\Livewire;
use App\Services\ChatbotService;
use App\Services\GeminiChatbotService;
use Livewire\Component;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ChatBot extends Component
{
// ── Panel state ───────────────────────────────────────────────────────────
public bool $isOpen = false;
/**
* 'select' mode-picker screen shown first
* 'basic' structured query UI (production or invoice report)
* 'advanced' free-text natural-language UI (Gemini-powered)
*/
public string $mode = 'select';
// ── Basic mode — shared ───────────────────────────────────────────────────
/**
* Which report the user picked inside basic mode.
* '' report-type picker shown
* 'production' production form
* 'invoice' invoice type-lookup form
* 'invoice_status' invoice scan-status form
*/
public string $reportType = '';
public array $plants = [];
// ── Basic mode — Production report ───────────────────────────────────────
public string $result = '';
public bool $hasResult = false;
public ?int $selectedPlantId = null;
public ?int $selectedLineId = null;
public string $dateFrom = '';
public string $dateTo = '';
public array $lines = [];
// ── Basic mode — Invoice report (type lookup) ─────────────────────────────
public ?int $invoicePlantId = null;
public string $invoiceItemCode = '';
public string $invoiceResult = '';
public bool $hasInvoiceResult = false;
// ── Basic mode — Invoice status (scan status) ─────────────────────────────
public string $invoiceNumber = '';
public string $invoiceStatusResult = ''; // kept for simple error strings
public bool $hasInvoiceStatusResult = false;
/**
* Structured result from ChatbotService::getInvoiceData().
* Shape: type, message, invoice_number, total, scanned, not_scanned, unscanned_serials[]
*/
public array $invoiceStatusData = [];
/** Controls whether all unscanned serials are shown (vs the first 10). */
public bool $showAllUnscanned = false;
// ── Advanced mode ─────────────────────────────────────────────────────────
public string $advancedQuestion = '';
public string $advancedResult = '';
public bool $hasAdvancedResult = false;
public bool $isAdvancedLoading = false;
/**
* Conversation history shown in advanced mode.
* Each entry: ['role' => 'user'|'assistant', 'content' => '…']
*
* The full history is passed to GeminiChatbotService on every turn so
* Gemini can resolve follow-up messages (e.g. answering a clarification
* question) in context.
*/
public array $chatHistory = [];
// ─────────────────────────────────────────────────────────────────────────
public function mount(): void
{
$this->plants = DB::table('plants')
->whereNull('deleted_at')
->orderBy('name')
->get(['id', 'name'])
->toArray();
$this->dateFrom = now()->startOfMonth()->format('Y-m-d');
$this->dateTo = now()->format('Y-m-d');
}
// ── Mode switching ────────────────────────────────────────────────────────
public function setMode(string $mode): void
{
$this->mode = $mode;
}
public function setReportType(string $type): void
{
$this->reportType = $type;
// Clear previous results when switching report type
$this->result = '';
$this->hasResult = false;
$this->invoiceResult = '';
$this->hasInvoiceResult = false;
$this->invoiceStatusResult = '';
$this->hasInvoiceStatusResult = false;
$this->invoiceStatusData = [];
$this->showAllUnscanned = false;
}
// ── Basic mode — Production helpers ──────────────────────────────────────
public function updatedSelectedPlantId(): void
{
$this->selectedLineId = null;
$this->lines = [];
$this->result = '';
$this->hasResult = false;
if ($this->selectedPlantId) {
$this->lines = DB::table('lines')
->whereNull('deleted_at')
->where('plant_id', $this->selectedPlantId)
->orderBy('name')
->get(['id', 'name'])
->toArray();
}
}
public function updatedSelectedLineId(): void
{
$this->result = '';
$this->hasResult = false;
}
public function fetchProduction(): void
{
if (! $this->selectedPlantId) {
$this->result = 'Please select a plant.';
$this->hasResult = true;
return;
}
$query = DB::table('production_quantities')
->whereNull('deleted_at')
->where('plant_id', $this->selectedPlantId)
->whereDate('created_at', '>=', $this->dateFrom)
->whereDate('created_at', '<=', $this->dateTo);
if ($this->selectedLineId) {
$query->where('line_id', $this->selectedLineId);
}
$count = $query->count();
$plantName = collect($this->plants)
->firstWhere('id', $this->selectedPlantId)?->name ?? 'Unknown Plant';
$lineName = $this->selectedLineId
? (collect($this->lines)->firstWhere('id', $this->selectedLineId)?->name ?? 'Unknown Line')
: 'All Lines';
$from = \Carbon\Carbon::parse($this->dateFrom)->format('d M Y');
$to = \Carbon\Carbon::parse($this->dateTo)->format('d M Y');
$this->result = "Production count for {$plantName} / {$lineName} from {$from} to {$to}: {$count} records.";
$this->hasResult = true;
}
// ── Basic mode — Invoice report (type lookup) ─────────────────────────────
public function updatedInvoicePlantId(): void
{
$this->invoiceResult = '';
$this->hasInvoiceResult = false;
}
public function updatedInvoiceItemCode(): void
{
$this->invoiceResult = '';
$this->hasInvoiceResult = false;
}
public function fetchInvoiceReport(): void
{
if (! $this->invoicePlantId) {
$this->invoiceResult = 'Please select a plant.';
$this->hasInvoiceResult = true;
return;
}
$itemCode = trim($this->invoiceItemCode);
if ($itemCode === '') {
$this->invoiceResult = 'Please enter an item code.';
$this->hasInvoiceResult = true;
return;
}
$plantName = collect($this->plants)
->firstWhere('id', $this->invoicePlantId)?->name ?? 'Unknown Plant';
try {
$rows = DB::select("
WITH plant_item AS (
SELECT ? AS user_plant,
? AS user_item_code
),
t1 AS (
SELECT
plants.id AS plant_id,
plants.name AS plant_name,
ARRAY_AGG(items.code) AS item_codes
FROM plants
LEFT JOIN items ON plants.id = items.plant_id
GROUP BY plants.id, plants.name
),
t2 AS (
SELECT
t1.plant_id,
t1.plant_name,
CASE
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
ELSE 0
END AS exists_flag
FROM t1
CROSS JOIN plant_item
WHERE t1.plant_name = plant_item.user_plant
),
t3 AS (
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
plant_item.user_item_code
FROM t2
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
),
t4 AS (
SELECT items.id AS item_id,
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
FROM t3
LEFT JOIN items
ON t3.plant_id = items.plant_id
AND t3.user_item_code = items.code
)
SELECT
t4.item_id,
t4.plant_id,
t4.plant_name,
t4.exists_flag,
t4.user_item_code,
COALESCE(sticker_masters.material_type, 0) AS material_type,
CASE
WHEN sticker_masters.item_id IS NULL
THEN 'no match found'
WHEN COALESCE(sticker_masters.material_type, 0) = 0
THEN 'serial invoice'
ELSE 'material invoice'
END AS invoice_description
FROM t4
LEFT JOIN sticker_masters
ON sticker_masters.plant_id = t4.plant_id
AND sticker_masters.item_id = t4.item_id
", [$plantName, $itemCode]);
} catch (\Exception $e) {
Log::error('ChatBot: invoice report query failed', [
'plant' => $plantName,
'item_code' => $itemCode,
'error' => $e->getMessage(),
]);
$this->invoiceResult = "Sorry, I couldn't fetch data. Please try again or contact support.";
$this->hasInvoiceResult = true;
return;
}
if (empty($rows)) {
$this->invoiceResult = "No data found for plant \"{$plantName}\". Please verify the plant selection.";
$this->hasInvoiceResult = true;
return;
}
$row = $rows[0];
if ((int) $row->exists_flag === 0) {
$this->invoiceResult = 'Provided item code does not exist in the item table.';
} else {
switch ($row->invoice_description) {
case 'no match found':
$this->invoiceResult = "Item not found in sticker master for the plant {$row->plant_name}.";
break;
case 'serial invoice':
$this->invoiceResult = 'It is a serial invoice item.';
break;
case 'material invoice':
$this->invoiceResult = 'It is a material invoice item.';
break;
default:
$this->invoiceResult = 'Unexpected result. Please contact support.';
}
}
$this->hasInvoiceResult = true;
}
// ── Basic mode — Invoice status (scan status) ─────────────────────────────
public function updatedInvoiceNumber(): void
{
$this->invoiceStatusResult = '';
$this->hasInvoiceStatusResult = false;
$this->invoiceStatusData = [];
$this->showAllUnscanned = false;
}
/**
* Looks up how many serials within an invoice have been scanned / not scanned.
* Stores structured data in $invoiceStatusData so the blade can render
* a "show more" serial-number list without dumping 70+ serials in one blob.
*/
public function fetchInvoiceStatus(): void
{
$invoiceNumber = trim(preg_replace('/\s+/', '', $this->invoiceNumber));
if (empty($invoiceNumber)) {
$this->invoiceStatusResult = 'Please enter a valid invoice number.';
$this->invoiceStatusData = [];
$this->showAllUnscanned = false;
$this->hasInvoiceStatusResult = true;
return;
}
try {
/** @var \App\Services\ChatbotService $service */
$service = app(\App\Services\ChatbotService::class);
$data = $service->getInvoiceData($invoiceNumber);
} catch (\Throwable $e) {
Log::error('ChatBot: invoice status fetch failed', [
'invoice' => $invoiceNumber,
'error' => $e->getMessage(),
]);
$data = [
'type' => 'error',
'message' => "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. "
. 'Please try again or contact support.',
'invoice_number' => $invoiceNumber,
'total' => 0,
'scanned' => 0,
'not_scanned' => 0,
'unscanned_serials' => [],
];
}
$this->invoiceStatusData = $data;
$this->invoiceStatusResult = $data['message']; // fallback plain-text copy
$this->showAllUnscanned = false;
$this->hasInvoiceStatusResult = true;
}
/**
* Toggles the "show all / show less" state for unscanned serial numbers
* in the Basic Invoice Status result card.
*/
public function toggleShowAllUnscanned(): void
{
$this->showAllUnscanned = ! $this->showAllUnscanned;
}
// ── Advanced mode (Gemini-powered) ────────────────────────────────────────
/**
* Handles a free-text user message in advanced mode.
*
* Steps:
* 1. Appends the user message to chatHistory immediately (UI feedback).
* 2. Calls GeminiChatbotService with the full prior history for context.
* 3. Gemini classifies the intent, extracts params, and either:
* a) runs the appropriate DB query and returns the result, or
* b) returns a clarification question if intent is ambiguous.
* 4. Appends the assistant reply to chatHistory.
*/
public function askAdvanced(): void
{
$question = trim($this->advancedQuestion);
if (empty($question)) {
return;
}
// Show the user's message in the chat bubble immediately
$this->chatHistory[] = [
'role' => 'user',
'content' => $question,
];
$this->advancedQuestion = '';
$this->isAdvancedLoading = true;
try {
/** @var GeminiChatbotService $gemini */
$gemini = app(GeminiChatbotService::class);
// Pass history *without* the turn we just appended — the new user
// message is passed separately so Gemini sees it as the latest turn.
$priorHistory = array_slice($this->chatHistory, 0, -1);
$answer = $gemini->processMessage($priorHistory, $question);
} catch (\Throwable $e) {
Log::error('ChatBot: advanced ask failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
$answer = 'Sorry, something went wrong. Please try again.';
}
$this->chatHistory[] = [
'role' => 'assistant',
'content' => $answer,
];
$this->isAdvancedLoading = false;
}
public function clearAdvancedChat(): void
{
$this->chatHistory = [];
$this->advancedQuestion = '';
$this->isAdvancedLoading = false;
}
// ── Panel controls ────────────────────────────────────────────────────────
public function toggleChat(): void
{
$this->isOpen = ! $this->isOpen;
}
public function resetForm(): void
{
// Basic mode — shared
$this->reportType = '';
// Basic mode — production
$this->selectedPlantId = null;
$this->selectedLineId = null;
$this->lines = [];
$this->result = '';
$this->hasResult = false;
$this->dateFrom = now()->startOfMonth()->format('Y-m-d');
$this->dateTo = now()->format('Y-m-d');
// Basic mode — invoice type lookup
$this->invoicePlantId = null;
$this->invoiceItemCode = '';
$this->invoiceResult = '';
$this->hasInvoiceResult = false;
// Basic mode — invoice scan status
$this->invoiceNumber = '';
$this->invoiceStatusResult = '';
$this->hasInvoiceStatusResult = false;
$this->invoiceStatusData = [];
$this->showAllUnscanned = false;
// Advanced mode
$this->clearAdvancedChat();
// Go back to mode selector
$this->mode = 'select';
}
public function render()
{
return view('livewire.chat-bot');
}
}

View File

@@ -1,236 +0,0 @@
<?php
namespace App\Livewire;
use App\Exports\ProductionPlanExport;
use App\Models\ProductionPlan;
use App\Models\ProductionQuantity;
use Livewire\Component;
use Carbon\Carbon;
use DB;
use Maatwebsite\Excel\Facades\Excel;
class ProductionTargetPlan extends Component
{
public $plantId, $lineId, $month, $year;
public $records = [];
public $dates = [];
public $leaveDates = [];
public $productionPlanDates = '';
protected $listeners = [
'loadData' => 'loadProductionData',
'loadData1' => 'exportProductionData',
];
public function getMonthDates($month, $year)
{
$start = Carbon::createFromDate($year, $month, 1);
$days = $start->daysInMonth;
$dates = [];
for ($i = 1; $i <= $days; $i++) {
$dates[] = Carbon::createFromDate($year, $month, $i)
->format('Y-m-d');
}
return $dates;
}
public function loadProductionData($plantId, $lineId, $month, $year){
if (!$plantId || !$lineId || !$month || !$year) {
$this->records = [];
$this->dates = [];
$this->leaveDates = [];
return;
}
$this->dates = $this->getMonthDates($month, $year);
$data = ProductionPlan::query()
->join('items', 'items.id', '=', 'production_plans.item_id')
->join('lines', 'lines.id', '=', 'production_plans.line_id')
->join('plants', 'plants.id', '=', 'production_plans.plant_id')
->where('production_plans.plant_id', $plantId)
->where('production_plans.line_id', $lineId)
->whereMonth('production_plans.created_at', $month)
->whereYear('production_plans.created_at', $year)
->select(
'production_plans.created_at',
'production_plans.operator_id',
'plants.name as plant',
'items.code as item_code',
'items.description as item_description',
'lines.name as line_name',
'production_plans.leave_dates'
)
->first();
if ($data && $data->leave_dates) {
$this->leaveDates = array_map('trim', explode(',', $data->leave_dates));
}
$producedData = ProductionQuantity::selectRaw("
plant_id,
line_id,
item_id,
DATE(created_at) as prod_date,
COUNT(*) as total_qty
")
->where('plant_id', $plantId)
->where('line_id', $lineId)
->whereMonth('created_at', $month)
->whereYear('created_at', $year)
->groupBy('plant_id', 'line_id', 'item_id', DB::raw('DATE(created_at)'))
->get()
->groupBy(function ($row) {
return $row->plant_id . '_' . $row->line_id . '_' . $row->item_id;
})
->map(function ($group) {
return $group->keyBy('prod_date');
});
$this->records = ProductionPlan::query()
->join('items', 'items.id', '=', 'production_plans.item_id')
->join('lines', 'lines.id', '=', 'production_plans.line_id')
->join('plants', 'plants.id', '=', 'production_plans.plant_id')
->where('production_plans.plant_id', $plantId)
->where('production_plans.line_id', $lineId)
->whereMonth('production_plans.created_at', $month)
->whereYear('production_plans.created_at', $year)
->select(
'production_plans.item_id',
'production_plans.plant_id',
'production_plans.line_id',
'production_plans.plan_quantity',
'production_plans.working_days',
'items.code as item_code',
'items.description as item_description',
'lines.name as line_name',
'plants.name as plant_name'
)
->get()
// ->map(function ($row) use ($producedData) {
// $row = $row->toArray();
// $row['daily_target'] = ($row['working_days'] > 0)
// ? round($row['plan_quantity'] / $row['working_days'], 2)
// : 0;
// // $key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
// // foreach ($this->dates as $date) {
// // $found = $producedData[$key][$date] ?? null;
// // $row['produced_quantity'][$date] = $found->total_qty ?? 0;
// // }
// $remainingDays = $row['working_days'];
// $pendingQty = 0;
// $row['daily_target_dynamic'] = [];
// $row['produced_quantity'] = [];
// $key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
// foreach ($this->dates as $date) {
// $found = $producedData[$key][$date] ?? null;
// $producedQty = $found->total_qty ?? 0;
// // today's adjusted target
// $todayTarget = $baseDailyTarget;
// if ($remainingDays > 1 && $pendingQty > 0) {
// $todayTarget += $pendingQty / $remainingDays;
// }
// $row['daily_target_dynamic'][$date] = round($todayTarget, 2);
// $row['produced_quantity'][$date] = $producedQty;
// // calculate today's shortfall
// $pendingQty += ($todayTarget - $producedQty);
// if ($pendingQty < 0) {
// $pendingQty = 0;
// }
// $remainingDays--;
// }
// return $row;
// })
->map(function ($row) use ($producedData) {
$row = $row->toArray();
$remainingQty = $row['plan_quantity'];
$remainingDays = $row['working_days'];
$row['daily_target_dynamic'] = [];
$row['produced_quantity'] = [];
$key = $row['plant_id'].'_'.$row['line_id'].'_'.$row['item_id'];
foreach ($this->dates as $date) {
// Skip leave dates
if (in_array($date, $this->leaveDates)) {
$row['daily_target_dynamic'][$date] = '-';
$row['produced_quantity'][$date] = '-';
continue;
}
$todayTarget = $remainingDays > 0
? round($remainingQty / $remainingDays, 2)
: 0;
//$todayTarget = $remainingDays > 0
// ? $remainingQty / $remainingDays
// : 0;
$producedQty = isset($producedData[$key][$date])
? $producedData[$key][$date]->total_qty
: 0;
$row['daily_target_dynamic'][$date] = $todayTarget;
$row['produced_quantity'][$date] = $producedQty;
// Carry forward pending
$remainingQty -= $producedQty;
if ($remainingQty < 0) {
$remainingQty = 0;
}
$remainingDays--;
}
return $row;
})
->toArray();
}
public function exportProductionData()
{
return Excel::download(
new ProductionPlanExport($this->records, $this->dates),
'production_plan_data.xlsx'
);
}
public function render()
{
// return view('livewire.production-target-plan');
return view('livewire.production-target-plan', [
'records' => $this->records,
'dates' => $this->dates,
]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use NotificationChannels\WebPush\HasPushSubscriptions; use NotificationChannels\WebPush\HasPushSubscriptions;
use NotificationChannels\WebPush\PushSubscription;
class User extends Authenticatable implements FilamentUser class User extends Authenticatable implements FilamentUser
{ {
@@ -65,9 +64,4 @@ class User extends Authenticatable implements FilamentUser
{ {
return $this->belongsTo(Plant::class); return $this->belongsTo(Plant::class);
} }
public function pushSubscriptions()
{
return $this->morphMany(PushSubscription::class, 'subscribable');
}
} }

View File

@@ -29,7 +29,6 @@ use App\Filament\Auth\CustomLogin as AuthCustomLogin;
use App\Filament\Pages\CustomLogin; use App\Filament\Pages\CustomLogin;
use Filament\View\PanelsRenderHook; use Filament\View\PanelsRenderHook;
use Filament\Support\Facades\FilamentView; use Filament\Support\Facades\FilamentView;
use Illuminate\Support\Facades\Blade;
class AdminPanelProvider extends PanelProvider class AdminPanelProvider extends PanelProvider
@@ -161,10 +160,5 @@ class AdminPanelProvider extends PanelProvider
} }
return ''; return '';
}); });
FilamentView::registerRenderHook(
PanelsRenderHook::BODY_END,
fn (): string => Blade::render("@livewire('chat-bot')"),
);
} }
} }

View File

@@ -1,403 +0,0 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* ChatbotService (Advanced Mode)
* ─────────────────────────────────────────────────────────────────────────────
* Parses STRUCTURED user inputs via regex no LLM involved.
*
* HOW TO ADD A NEW COMMAND:
* 1. Add an entry to $handlers with a regex pattern and a handler method name.
* 2. Write the private handler method (receives the captured group as $value).
* 3. Add a usage example to unknownCommand() so users know about it.
*
* Pattern convention: /keyword\s*=\s*(.+)/i
* - The first capture group is the raw value after "=".
* - The handler receives it already trimmed.
*/
class ChatbotService
{
/**
* Registry of structured-command handlers.
*
* Each entry:
* 'pattern' PCRE regex; capture group 1 is the extracted value.
* 'handler' name of the private method that processes the command.
*
* For two-value commands (e.g. invoice report), group 1 and group 2 are
* both passed to the handler; single-value handlers simply ignore $value2.
*/
private array $handlers = [
[
'pattern' => '/inv(?:oice)?(?:\s*(?:number|num|no\.?))?\s*(?:=|is| |equal\s+to)\s*([^\s,\.]+)/i',
'handler' => 'handleInvoice',
],
// ── Invoice report: item type lookup ──────────────────────────────────
// Accepts patterns like:
// item = 674071 plant = Vahinie Unit 2
// item code = 674071 plant = Vahinie Unit 2
// check item 674071 for plant Vahinie Unit 2
[
'pattern' => '/item(?:\s*code)?\s*(?:=|is|:)?\s*([^\s,]+)\s+(?:for\s+)?plant\s*(?:=|is|:)?\s*(.+)/i',
'handler' => 'handleInvoiceReport',
],
// ── Add more commands here ────────────────────────────────────────────
// Example:
// [
// 'pattern' => '/^\s*serial\s*=\s*(.+)/i',
// 'handler' => 'handleSerial',
// ],
];
// ─────────────────────────────────────────────────────────────────────────
// Public entry point
// ─────────────────────────────────────────────────────────────────────────
/**
* Dispatch the user's input to the matching handler.
*/
public function ask(string $input): string
{
$input = trim($input);
foreach ($this->handlers as $entry) {
if (preg_match($entry['pattern'], $input, $matches)) {
$value = trim($matches[1] ?? '');
$value2 = trim($matches[2] ?? '');
return $this->{$entry['handler']}($value, $value2);
}
}
return $this->unknownCommand($input);
}
// ─────────────────────────────────────────────────────────────────────────
// Handler: invoice = <invoice_number>
// ─────────────────────────────────────────────────────────────────────────
/**
* Looks up scan status for an invoice number in invoice_validations.
* Returns a plain-English string (used by the Advanced / free-text path).
* Structured callers should use getInvoiceData() directly.
*/
private function handleInvoice(string $invoiceNumber, string $_unused = ''): string
{
$data = $this->getInvoiceData($invoiceNumber);
// For the plain-text path (advanced mode / ChatbotService::ask()),
// reassemble a human-readable sentence from the structured data.
if (in_array($data['type'], ['invalid', 'error', 'not_found'], true)) {
return $data['message'];
}
if ($data['type'] === 'all_scanned') {
$n = $data['total'];
$itemWord = $n === 1 ? 'serial number' : 'serial numbers';
return "For invoice number {$data['invoice_number']}, all {$n} {$itemWord} "
. ($n === 1 ? 'has' : 'have') . ' been scanned. ✅';
}
// partial or none_scanned
$total = $data['total'];
$scanned = $data['scanned'];
$notScan = $data['not_scanned'];
$inv = $data['invoice_number'];
$itemWord = $total === 1 ? 'serial number' : 'serial numbers';
if ($scanned === 0) {
$msg = "For invoice number {$inv}, there "
. ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} "
. 'and none have been scanned.';
} else {
$msg = "For invoice number {$inv}, there "
. ($total === 1 ? 'is' : 'are') . " {$total} {$itemWord} in total. "
. "Out of which {$scanned} "
. ($scanned === 1 ? 'has' : 'have') . ' been scanned and '
. "{$notScan} "
. ($notScan === 1 ? 'has' : 'have') . ' not been scanned.';
}
if (! empty($data['unscanned_serials'])) {
$msg .= ' Unscanned serial numbers are: '
. implode(', ', $data['unscanned_serials']) . '.';
}
return $msg;
}
// ─────────────────────────────────────────────────────────────────────────
// Public structured accessor — used by ChatBot (Basic mode)
// ─────────────────────────────────────────────────────────────────────────
/**
* Returns structured scan-status data for an invoice number.
*
* Return shape:
* [
* 'type' => 'all_scanned' | 'partial' | 'none_scanned'
* | 'not_found' | 'error' | 'invalid',
* 'message' => string, // one-line human summary (no serial list)
* 'invoice_number' => string,
* 'total' => int,
* 'scanned' => int,
* 'not_scanned' => int,
* 'unscanned_serials' => string[], // full list — may be large
* ]
*/
public function getInvoiceData(string $invoiceNumber): array
{
$invoiceNumber = preg_replace('/\s+/', '', $invoiceNumber);
if (empty($invoiceNumber)) {
return [
'type' => 'invalid',
'message' => 'Please provide a valid invoice number. Example: invoice = 3RA0013333',
'invoice_number' => '',
'total' => 0,
'scanned' => 0,
'not_scanned' => 0,
'unscanned_serials' => [],
];
}
try {
$rows = DB::select("
SELECT
COALESCE(scanned_status, 'not scanned') AS status,
COUNT(*) AS total_count,
STRING_AGG(
CASE
WHEN scanned_status IS NULL THEN serial_number::text
END,
', '
) AS serial_numbers_not_scanned
FROM invoice_validations
WHERE invoice_number = ?
GROUP BY scanned_status
", [$invoiceNumber]);
} catch (\Exception $e) {
Log::error('ChatbotService: invoice query failed', [
'invoice' => $invoiceNumber,
'error' => $e->getMessage(),
]);
return [
'type' => 'error',
'message' => "Sorry, I couldn't fetch data for invoice {$invoiceNumber}. "
. 'Please try again or contact support if this keeps happening.',
'invoice_number' => $invoiceNumber,
'total' => 0,
'scanned' => 0,
'not_scanned' => 0,
'unscanned_serials' => [],
];
}
if (empty($rows)) {
return [
'type' => 'not_found',
'message' => "No records found for invoice number {$invoiceNumber}. "
. 'Please double-check the invoice number and try again.',
'invoice_number' => $invoiceNumber,
'total' => 0,
'scanned' => 0,
'not_scanned' => 0,
'unscanned_serials' => [],
];
}
// ── Aggregate rows ────────────────────────────────────────────────────
$totalScanned = 0;
$totalNotScanned = 0;
$unscannedSerials = [];
foreach ($rows as $row) {
if ($row->status === 'not scanned') {
$totalNotScanned = (int) $row->total_count;
if (! empty($row->serial_numbers_not_scanned)) {
$unscannedSerials = array_values(
array_filter(
array_map('trim', explode(',', $row->serial_numbers_not_scanned))
)
);
}
} else {
$totalScanned += (int) $row->total_count;
}
}
$grandTotal = $totalScanned + $totalNotScanned;
// ── All scanned ───────────────────────────────────────────────────────
if ($totalNotScanned === 0) {
$n = $grandTotal;
$itemWord = $n === 1 ? 'serial number' : 'serial numbers';
return [
'type' => 'all_scanned',
'message' => "All {$n} {$itemWord} scanned for invoice {$invoiceNumber}. ✅",
'invoice_number' => $invoiceNumber,
'total' => $grandTotal,
'scanned' => $totalScanned,
'not_scanned' => 0,
'unscanned_serials' => [],
];
}
// ── None / partial scanned ────────────────────────────────────────────
$type = $totalScanned === 0 ? 'none_scanned' : 'partial';
$itemWord = $grandTotal === 1 ? 'serial number' : 'serial numbers';
if ($totalScanned === 0) {
$summary = "Invoice {$invoiceNumber}{$grandTotal} {$itemWord}, none scanned yet.";
} else {
$summary = "Invoice {$invoiceNumber}{$grandTotal} {$itemWord} total: "
. "{$totalScanned} scanned, {$totalNotScanned} not scanned.";
}
return [
'type' => $type,
'message' => $summary,
'invoice_number' => $invoiceNumber,
'total' => $grandTotal,
'scanned' => $totalScanned,
'not_scanned' => $totalNotScanned,
'unscanned_serials' => $unscannedSerials,
];
}
// ─────────────────────────────────────────────────────────────────────────
// Handler: item = <item_code> plant = <plant_name>
// ─────────────────────────────────────────────────────────────────────────
/**
* Determines whether an item is a serial invoice or material invoice
* for a given plant, using the sticker_masters table.
*
* @param string $itemCode Extracted item code (capture group 1)
* @param string $plantName Extracted plant name (capture group 2)
*/
private function handleInvoiceReport(string $itemCode, string $plantName): string
{
$itemCode = trim($itemCode);
$plantName = trim($plantName);
if (empty($itemCode)) {
return 'Please provide an item code. Example: item = 674071 plant = Vahinie Unit 2';
}
if (empty($plantName)) {
return 'Please provide a plant name. Example: item = 674071 plant = Vahinie Unit 2';
}
try {
$rows = DB::select("
WITH plant_item AS (
SELECT ? AS user_plant,
? AS user_item_code
),
t1 AS (
SELECT
plants.id AS plant_id,
plants.name AS plant_name,
ARRAY_AGG(items.code) AS item_codes
FROM plants
LEFT JOIN items ON plants.id = items.plant_id
GROUP BY plants.id, plants.name
),
t2 AS (
SELECT
t1.plant_id,
t1.plant_name,
CASE
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
ELSE 0
END AS exists_flag
FROM t1
CROSS JOIN plant_item
WHERE t1.plant_name = plant_item.user_plant
),
t3 AS (
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
plant_item.user_item_code
FROM t2
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
),
t4 AS (
SELECT items.id AS item_id,
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
FROM t3
LEFT JOIN items
ON t3.plant_id = items.plant_id
AND t3.user_item_code = items.code
)
SELECT
t4.item_id,
t4.plant_id,
t4.plant_name,
t4.exists_flag,
t4.user_item_code,
COALESCE(sticker_masters.material_type, 0) AS material_type,
CASE
WHEN sticker_masters.item_id IS NULL
THEN 'no match found'
WHEN COALESCE(sticker_masters.material_type, 0) = 0
THEN 'serial invoice'
ELSE 'material invoice'
END AS invoice_description
FROM t4
LEFT JOIN sticker_masters
ON sticker_masters.plant_id = t4.plant_id
AND sticker_masters.item_id = t4.item_id
", [$plantName, $itemCode]);
} catch (\Exception $e) {
Log::error('ChatbotService: invoice report query failed', [
'plant' => $plantName,
'item_code' => $itemCode,
'error' => $e->getMessage(),
]);
return "Sorry, I couldn't fetch data for item {$itemCode} in plant {$plantName}. "
. 'Please try again or contact support.';
}
if (empty($rows)) {
return "No data found for plant \"{$plantName}\". Please check the plant name and try again.";
}
$row = $rows[0];
if ((int) $row->exists_flag === 0) {
return 'Provided item code does not exist in the item table.';
}
return match ($row->invoice_description) {
'no match found' => "Item not found in sticker master for the plant {$row->plant_name}.",
'serial invoice' => 'It is a serial invoice item.',
'material invoice' => 'It is a material invoice item.',
default => 'Unexpected result. Please contact support.',
};
}
// ─────────────────────────────────────────────────────────────────────────
// Fallback for unrecognised input
// ─────────────────────────────────────────────────────────────────────────
private function unknownCommand(string $input): string
{
return "I didn't recognise that command. Please include a supported keyword with a value after '='.\n\n"
. "• Invoice scan status:\n"
. " invoice = 3RA0013333\n"
. " what is the status of invoice = 3RA0013333\n\n"
. "• Invoice type lookup:\n"
. " item = 674071 plant = Vahinie Unit 2\n"
. " item code = 674071 plant = Vahinie Unit 2\n\n"
. 'Any sentence containing keyword = value will work.';
}
}

View File

@@ -1,819 +0,0 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* GeminiChatbotService
* ─────────────────────────────────────────────────────────────────────────────
* Powers the "Advanced" chatbot mode with plain-English understanding.
*
* FLOW:
* 1. Takes the full chat history + new user message.
* 2. Sends them to Gemini with a structured system prompt.
* 3. Gemini returns JSON: { task, params, missing, clarification }.
* 4. If task is a known, complete query runs the matching DB handler.
* 5. If task is "unknown" sends the message to Gemini again as a free-form
* conversational assistant so it can answer or ask the user what they need.
* 6. If required params are missing Gemini's clarification question is returned.
*
* SUPPORTED TASKS:
* - invoice_status scan status of an invoice number
* - invoice_report serial/material invoice type for an item + plant
* - production_report production count for a plant / line / date range
* - unknown handled via a free-form Gemini conversation turn
*
* CONFIGURATION (.env):
* GEMINI_API_KEY=your_key_here
* GEMINI_MODEL=gemini-2.5-flash-preview set whichever model your key supports
*
* HOW TO ADD A NEW TASK:
* 1. Describe it in buildSystemPrompt() under TASKS.
* 2. Add a private handle*() method below.
* 3. Add a match arm in processMessage().
*/
class GeminiChatbotService
{
private string $apiKey;
/**
* Full REST endpoint model name is part of the URL path, e.g.:
* https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview:generateContent
*
* Built in __construct() from config('services.gemini.model').
* Change GEMINI_MODEL in .env to switch models without editing code.
*/
private string $apiUrl;
public function __construct()
{
$this->apiKey = config('services.gemini.api_key', '');
$model = config('services.gemini.model', 'gemini-3-flash-preview');
$this->apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent";
}
// ─────────────────────────────────────────────────────────────────────────
// Public entry point
// ─────────────────────────────────────────────────────────────────────────
/**
* Process a user message in context of the prior conversation.
*
* @param array $chatHistory Previous turns: [['role'=>'user'|'assistant','content'=>'…'], ]
* @param string $userInput The new message just typed.
* @return string Plain-text reply to show in the chat bubble.
*/
public function processMessage(array $chatHistory, string $userInput): string
{
if (empty($this->apiKey)) {
return '⚠️ AI features are not configured. Please set GEMINI_API_KEY in your .env file.';
}
// ── Step 1: Classify the intent ───────────────────────────────────────
try {
$classification = $this->classifyWithGemini($chatHistory, $userInput);
} catch (\RuntimeException $e) {
// Surface the specific error directly in the chat bubble
return $e->getMessage();
}
$task = $classification['task'] ?? 'unknown';
$params = $classification['params'] ?? [];
$missing = $classification['missing'] ?? [];
$clarification = $classification['clarification'] ?? null;
// ── Step 2: Missing required params → ask user for them ───────────────
if (! empty($missing) && ! empty($clarification)) {
return $clarification;
}
// ── Step 3: Dispatch to the appropriate handler ───────────────────────
return match ($task) {
'invoice_status' => $this->handleInvoiceStatus($params),
'invoice_report' => $this->handleInvoiceReport($params),
'production_report' => $this->handleProductionReport($params),
// ── Unknown intent: hand off to Gemini as a free-form assistant ──
'unknown' => $this->handleUnknown($chatHistory, $userInput, $clarification),
default => $this->handleUnknown($chatHistory, $userInput, null),
};
}
// ─────────────────────────────────────────────────────────────────────────
// Gemini API calls
// ─────────────────────────────────────────────────────────────────────────
/**
* Phase 1 Classify the user's intent and extract structured params.
*
* @return array|null Parsed JSON array, or null on failure.
*/
/**
* @throws \RuntimeException with a user-facing message describing exactly what failed.
*/
private function classifyWithGemini(array $history, string $userInput): array
{
$requestBody = [
'system_instruction' => [
'parts' => [['text' => $this->buildSystemPrompt()]],
],
'contents' => $this->buildGeminiContents($history, $userInput),
'generationConfig' => [
'temperature' => 0.1,
'responseMimeType' => 'application/json',
],
];
// ── HTTP call ─────────────────────────────────────────────────────────
try {
$response = Http::withHeaders(['Content-Type' => 'application/json'])
->timeout(15)
->post($this->apiUrl . '?key=' . $this->apiKey, $requestBody);
} catch (\Exception $e) {
Log::error('GeminiChatbotService: HTTP exception', ['error' => $e->getMessage()]);
throw new \RuntimeException(
'⚠️ Could not reach the Gemini API. Check your network or firewall. ('
. $e->getMessage() . ')'
);
}
// ── HTTP-level error ──────────────────────────────────────────────────
if (! $response->successful()) {
$status = $response->status();
$body = $response->body();
Log::error('GeminiChatbotService: API HTTP error', [
'status' => $status,
'body' => $body,
]);
// Parse Google's error message when available
$googleMsg = $response->json('error.message') ?? $body;
throw new \RuntimeException(
"⚠️ Gemini API returned HTTP {$status}: {$googleMsg}"
);
}
// ── Extract text from response ────────────────────────────────────────
$text = $response->json('candidates.0.content.parts.0.text');
if (empty($text)) {
// Check for prompt-blocking
$blockReason = $response->json('promptFeedback.blockReason');
$finishReason = $response->json('candidates.0.finishReason');
Log::error('GeminiChatbotService: empty response text', [
'blockReason' => $blockReason,
'finishReason' => $finishReason,
'full' => $response->json(),
]);
$hint = $blockReason
? "prompt was blocked (reason: {$blockReason})"
: ($finishReason ? "finish reason: {$finishReason}" : 'no text returned');
throw new \RuntimeException("⚠️ Gemini returned no content — {$hint}");
}
// ── JSON decode ───────────────────────────────────────────────────────
$clean = preg_replace('/^```json\s*/i', '', trim($text));
$clean = preg_replace('/\s*```$/i', '', $clean);
$parsed = json_decode($clean, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error('GeminiChatbotService: JSON decode failed', ['raw' => $text]);
throw new \RuntimeException(
'⚠️ Gemini returned a non-JSON response: ' . mb_substr($text, 0, 200)
);
}
return $parsed;
}
/**
* Phase 2 (unknown task only) Ask Gemini to respond conversationally.
*
* Sends the full chat history + new user message to Gemini as a friendly
* factory-operations assistant (no JSON constraint). Gemini can ask for
* clarification, answer general questions, or guide the user to one of the
* supported tasks.
*
* @param string|null $hintFromClassification Optional clarification from the
* classification step prepended as assistant context if present.
* @return string Plain-text reply from Gemini.
*/
private function callGeminiConversational(
array $history,
string $userInput,
?string $hintFromClassification = null
): ?string {
// If the classifier already produced a good clarification question, use it
// directly and skip the second API call to save latency + quota.
if (! empty($hintFromClassification)) {
return $hintFromClassification;
}
$requestBody = [
'system_instruction' => [
'parts' => [['text' => $this->buildConversationalSystemPrompt()]],
],
'contents' => $this->buildGeminiContents($history, $userInput),
'generationConfig' => [
'temperature' => 0.7, // more natural conversational tone
'maxOutputTokens' => 400,
],
];
try {
$response = Http::withHeaders(['Content-Type' => 'application/json'])
->timeout(20)
->post($this->apiUrl . '?key=' . $this->apiKey, $requestBody);
if (! $response->successful()) {
Log::error('GeminiChatbotService: API error (conversational)', [
'status' => $response->status(),
'body' => $response->body(),
]);
return null;
}
return $response->json('candidates.0.content.parts.0.text');
} catch (\Exception $e) {
Log::error('GeminiChatbotService: exception (conversational)', ['error' => $e->getMessage()]);
return null;
}
}
// ─────────────────────────────────────────────────────────────────────────
// Prompt builders
// ─────────────────────────────────────────────────────────────────────────
/**
* System prompt for Phase 1 (classification) forces JSON output.
*/
private function buildSystemPrompt(): string
{
$today = now()->format('Y-m-d');
$startOfMonth = now()->startOfMonth()->format('Y-m-d');
return <<<PROMPT
You are a factory operations assistant that classifies plain-English user queries into structured tasks.
TASKS:
1. "invoice_status"
The user wants to check how many serial numbers in an invoice have been scanned / not scanned.
Required params: invoice_number
Examples:
- "check invoice 3RA0013333"
- "is invoice 3RA0013333 fully scanned?"
- "what's the scan status of 3RA0013333"
- "show me unscanned serials for invoice ABC123"
2. "invoice_report"
The user wants to know whether an item is a serial invoice or material invoice for a given plant.
Required params: item_code, plant_name
Examples:
- "check item 674071 for plant Vahinie Unit 2"
- "is item 500100 a serial or material invoice in Chennai plant?"
- "what type is item code 200300 at Vahinie?"
3. "production_report"
The user wants the production count for a plant and optional line over a date range.
Required params: plant_name
Optional params: line_name, date_from, date_to
Default dates: date_from = {$startOfMonth}, date_to = {$today}
Interpret relative dates: "this month", "last week", "yesterday", "today", etc.
Examples:
- "show production for Chennai plant this month"
- "how many units were produced in line 1 of Vahinie Unit 2 last week?"
- "production report for all plants from 2024-01-01 to 2024-01-31"
4. "unknown"
The query cannot be clearly matched to any of the above tasks.
Use this when the user is asking something general, greeting, asking what the bot can do,
asking a follow-up question that doesn't map to a task, or if you need more context.
In this case, set "clarification" to a friendly, helpful response it may be a question,
a helpful explanation, or guidance toward the supported tasks.
CONVERSATION CONTEXT:
Consider the full conversation history. If the previous assistant message asked a clarifying question
(e.g. "Do you mean Invoice Status or Invoice Report?") and the user is now answering that question,
classify accordingly based on the combined context.
OUTPUT FORMAT (return ONLY this JSON, no markdown fences, no extra text):
{
"task": "invoice_status | invoice_report | production_report | unknown",
"params": {
"invoice_number": "...",
"item_code": "...",
"plant_name": "...",
"line_name": "...",
"date_from": "YYYY-MM-DD",
"date_to": "YYYY-MM-DD"
},
"missing": ["list of required params that were not found in the user input"],
"clarification": "Friendly response for unknown tasks or missing-param prompts. Set to null when task is clear and params are complete."
}
RULES:
- Only include params relevant to the detected task.
- If a required param is missing, add it to "missing" and set a helpful "clarification" asking only for that missing value.
- If task is "unknown", always set "missing" to [] and put your full helpful response in "clarification".
- When task is clear and all required params are present, set "missing" to [] and "clarification" to null.
PROMPT;
}
/**
* System prompt for Phase 2 (conversational fallback) plain-text output.
*
* Used only when the classifier returns "unknown" AND produced no clarification.
*/
private function buildConversationalSystemPrompt(): string
{
return <<<PROMPT
You are a helpful factory operations assistant integrated into an internal management panel.
You can perform these tasks when the user gives you enough information:
Invoice Status check how many serial numbers in an invoice have been scanned and list any unscanned ones. Requires: invoice number.
Invoice Report find out whether an item is a serial invoice or material invoice for a given plant. Requires: item code and plant name.
Production Report get the production count for a plant, optionally filtered by line and date range. Requires: plant name.
When the user's request does not match any of the above:
- Answer general questions helpfully and concisely.
- If you need more information to perform a task, ask for only the missing detail.
- Guide the user toward one of the supported tasks when relevant.
- Keep replies short and conversational (2-4 sentences max).
- Do NOT mention JSON, APIs, or technical internals.
PROMPT;
}
/**
* Convert Livewire chat history + new user message into Gemini's contents array.
* Gemini uses "model" for the assistant role (not "assistant").
*/
private function buildGeminiContents(array $history, string $userInput): array
{
$contents = [];
// Include the last 8 turns at most to stay within token limits
foreach (array_slice($history, -8) as $msg) {
$contents[] = [
'role' => $msg['role'] === 'user' ? 'user' : 'model',
'parts' => [['text' => $msg['content']]],
];
}
$contents[] = [
'role' => 'user',
'parts' => [['text' => $userInput]],
];
return $contents;
}
// ─────────────────────────────────────────────────────────────────────────
// Task handlers
// ─────────────────────────────────────────────────────────────────────────
/**
* Invoice Status delegate to ChatbotService (regex-based, same as basic mode).
*/
private function handleInvoiceStatus(array $params): string
{
$invoiceNumber = trim(preg_replace('/\s+/', '', $params['invoice_number'] ?? ''));
if (empty($invoiceNumber)) {
return 'I need the invoice number to check the scan status. What is the invoice number?';
}
/** @var ChatbotService $svc */
$svc = app(ChatbotService::class);
return $svc->ask("invoice = {$invoiceNumber}");
}
/**
* Invoice Report resolves plant name with fuzzy matching, then runs the
* same CTE query as ChatBot::fetchInvoiceReport() directly.
*
* We bypass ChatbotService::ask() here so that resolvePlant()'s multi-strategy
* fuzzy logic is applied rather than the simpler LIKE inside ChatbotService.
*/
private function handleInvoiceReport(array $params): string
{
$itemCode = trim($params['item_code'] ?? '');
$plantName = trim($params['plant_name'] ?? '');
if (empty($itemCode)) {
return 'I need the item code to look up the invoice type. What is the item code?';
}
if (empty($plantName)) {
return 'I need the plant name to look up the invoice type. Which plant are you asking about?';
}
// ── Fuzzy-resolve the plant name ──────────────────────────────────────
$plant = $this->resolvePlant($plantName);
if ($plant === null) {
return "I couldn't find a plant matching \"{$plantName}\". "
. 'Please check the plant name and try again.';
}
// ── Run the same CTE as ChatBot::fetchInvoiceReport() ─────────────────
try {
$rows = DB::select("
WITH plant_item AS (
SELECT ? AS user_plant,
? AS user_item_code
),
t1 AS (
SELECT
plants.id AS plant_id,
plants.name AS plant_name,
ARRAY_AGG(items.code) AS item_codes
FROM plants
LEFT JOIN items ON plants.id = items.plant_id
GROUP BY plants.id, plants.name
),
t2 AS (
SELECT
t1.plant_id,
t1.plant_name,
CASE
WHEN plant_item.user_item_code = ANY(t1.item_codes) THEN 1
ELSE 0
END AS exists_flag
FROM t1
CROSS JOIN plant_item
WHERE t1.plant_name = plant_item.user_plant
),
t3 AS (
SELECT t2.plant_id, t2.plant_name, t2.exists_flag,
plant_item.user_item_code
FROM t2
LEFT JOIN plant_item ON plant_item.user_plant = t2.plant_name
),
t4 AS (
SELECT items.id AS item_id,
t3.plant_id, t3.plant_name, t3.exists_flag, t3.user_item_code
FROM t3
LEFT JOIN items
ON t3.plant_id = items.plant_id
AND t3.user_item_code = items.code
)
SELECT
t4.item_id,
t4.plant_id,
t4.plant_name,
t4.exists_flag,
t4.user_item_code,
COALESCE(sticker_masters.material_type, 0) AS material_type,
CASE
WHEN sticker_masters.item_id IS NULL
THEN 'no match found'
WHEN COALESCE(sticker_masters.material_type, 0) = 0
THEN 'serial invoice'
ELSE 'material invoice'
END AS invoice_description
FROM t4
LEFT JOIN sticker_masters
ON sticker_masters.plant_id = t4.plant_id
AND sticker_masters.item_id = t4.item_id
", [$plant->name, $itemCode]);
} catch (\Exception $e) {
Log::error('GeminiChatbotService: invoice report query failed', [
'plant' => $plant->name,
'item_code' => $itemCode,
'error' => $e->getMessage(),
]);
return "Sorry, I couldn't fetch data. Please try again or contact support.";
}
if (empty($rows)) {
return "No data found for plant \"{$plant->name}\". Please verify the plant name.";
}
$row = $rows[0];
if ((int) $row->exists_flag === 0) {
return 'The provided item code does not exist in the item table.';
}
return match ($row->invoice_description) {
'serial invoice' => 'It is a serial invoice item.',
'material invoice' => 'It is a material invoice item.',
'no match found' => "Item not found in sticker master for plant {$plant->name}.",
default => 'Unexpected result. Please contact support.',
};
}
/**
* Production Report resolves plant/line names to IDs and runs the count query.
* Mirrors ChatBot::fetchProduction() but works with plain names instead of IDs,
* using resolvePlant() for robust fuzzy matching.
*/
private function handleProductionReport(array $params): string
{
$plantName = trim($params['plant_name'] ?? '');
$lineName = trim($params['line_name'] ?? '');
$dateFrom = $params['date_from'] ?? now()->startOfMonth()->format('Y-m-d');
$dateTo = $params['date_to'] ?? now()->format('Y-m-d');
if (empty($plantName)) {
return 'I need a plant name to fetch the production report. Which plant are you asking about?';
}
// ── Fuzzy-resolve the plant name ──────────────────────────────────────
$plant = $this->resolvePlant($plantName);
if ($plant === null) {
return "I couldn't find a plant matching \"{$plantName}\". "
. 'Please check the plant name and try again.';
}
// ── Base query ────────────────────────────────────────────────────────
$query = DB::table('production_quantities')
->whereNull('deleted_at')
->where('plant_id', $plant->id)
->whereDate('created_at', '>=', $dateFrom)
->whereDate('created_at', '<=', $dateTo);
$lineLabel = 'All Lines';
// ── Optionally filter by line (fuzzy LIKE match) ──────────────────────
if (! empty($lineName)) {
$line = $this->resolveLine($lineName, $plant->id);
if ($line === null) {
return "I couldn't find a line matching \"{$lineName}\" "
. "in plant \"{$plant->name}\". Please check the line name.";
}
$query->where('line_id', $line->id);
$lineLabel = $line->name;
}
try {
$count = $query->count();
} catch (\Exception $e) {
Log::error('GeminiChatbotService: production query failed', [
'plant' => $plant->name,
'line' => $lineLabel,
'error' => $e->getMessage(),
]);
return "Sorry, I couldn't fetch production data for {$plant->name}. "
. 'Please try again or contact support.';
}
$from = \Carbon\Carbon::parse($dateFrom)->format('d M Y');
$to = \Carbon\Carbon::parse($dateTo)->format('d M Y');
return "📊 Production count for {$plant->name} / {$lineLabel} "
. "from {$from} to {$to}: {$count} records.";
}
// ─────────────────────────────────────────────────────────────────────────
// Fuzzy name resolvers
// ─────────────────────────────────────────────────────────────────────────
/**
* Resolve a user-supplied plant name to the best matching DB row.
*
* Strategy cascade (stops at first hit):
* 1. Exact case-insensitive match "ransar industries-i" == "Ransar Industries-I"
* 2. Normalised LIKE match strips hyphens/spaces, swaps I↔1
* 3. Every significant word present (LIKE) "ransar unit 2" matches "Ransar Industries Unit 2"
* 4. Best token-overlap score picks the DB row sharing the most words
*
* @return object|null stdClass with {id, name} or null if no match.
*/
private function resolvePlant(string $userInput): ?object
{
$allPlants = DB::table('plants')
->whereNull('deleted_at')
->get(['id', 'name']);
$norm = $this->normaliseForMatching($userInput);
// ── Strategy 1: exact normalised match ────────────────────────────────
foreach ($allPlants as $plant) {
if ($this->normaliseForMatching($plant->name) === $norm) {
return $plant;
}
}
// ── Strategy 2: normalised LIKE (user input contained in plant name or vice-versa) ──
foreach ($allPlants as $plant) {
$dbNorm = $this->normaliseForMatching($plant->name);
if (str_contains($dbNorm, $norm) || str_contains($norm, $dbNorm)) {
return $plant;
}
}
// ── Strategy 3: all significant user words appear in the plant name ───
$userTokens = $this->significantTokens($norm);
if (count($userTokens) >= 1) {
foreach ($allPlants as $plant) {
$dbNorm = $this->normaliseForMatching($plant->name);
$allFound = true;
foreach ($userTokens as $token) {
if (! str_contains($dbNorm, $token)) {
$allFound = false;
break;
}
}
if ($allFound) {
return $plant;
}
}
}
// ── Strategy 4: best token-overlap score ─────────────────────────────
$bestPlant = null;
$bestScore = 0;
foreach ($allPlants as $plant) {
$dbTokens = $this->significantTokens($this->normaliseForMatching($plant->name));
$shared = count(array_intersect($userTokens, $dbTokens));
// Require at least half the user tokens to match to avoid false positives
$threshold = max(1, (int) ceil(count($userTokens) / 2));
if ($shared >= $threshold && $shared > $bestScore) {
$bestScore = $shared;
$bestPlant = $plant;
}
}
return $bestPlant;
}
/**
* Resolve a user-supplied line name within a specific plant.
* Uses the same normalisation + token strategies as resolvePlant().
*
* @return object|null stdClass with {id, name} or null if no match.
*/
private function resolveLine(string $userInput, int $plantId): ?object
{
$allLines = DB::table('lines')
->whereNull('deleted_at')
->where('plant_id', $plantId)
->get(['id', 'name']);
$norm = $this->normaliseForMatching($userInput);
// Strategy 1: exact normalised
foreach ($allLines as $line) {
if ($this->normaliseForMatching($line->name) === $norm) {
return $line;
}
}
// Strategy 2: normalised LIKE
foreach ($allLines as $line) {
$dbNorm = $this->normaliseForMatching($line->name);
if (str_contains($dbNorm, $norm) || str_contains($norm, $dbNorm)) {
return $line;
}
}
// Strategy 3: all user tokens found in line name
$userTokens = $this->significantTokens($norm);
foreach ($allLines as $line) {
$dbNorm = $this->normaliseForMatching($line->name);
$allFound = true;
foreach ($userTokens as $token) {
if (! str_contains($dbNorm, $token)) {
$allFound = false;
break;
}
}
if ($allFound) {
return $line;
}
}
// Strategy 4: best token-overlap
$bestLine = null;
$bestScore = 0;
foreach ($allLines as $line) {
$dbTokens = $this->significantTokens($this->normaliseForMatching($line->name));
$shared = count(array_intersect($userTokens, $dbTokens));
$threshold = max(1, (int) ceil(count($userTokens) / 2));
if ($shared >= $threshold && $shared > $bestScore) {
$bestScore = $shared;
$bestLine = $line;
}
}
return $bestLine;
}
/**
* Normalise a plant/line name for fuzzy comparison:
* - lowercase
* - replace Roman numeral suffixes I/II/III/IV 1/2/3/4 (and vice-versa digits numerals as a canonical form)
* - collapse hyphens, underscores, extra spaces into a single space
* - strip leading/trailing whitespace
*
* Both the user input AND the DB value are passed through this before comparing,
* so the comparison is always apples-to-apples.
*/
private function normaliseForMatching(string $value): string
{
$v = strtolower($value);
// 1. Punctuation/separators → space
$v = str_replace(['-', '_', '.', ','], ' ', $v);
// 2. Split any letter→digit or digit→letter boundary with a space.
// e.g. "industries1" → "industries 1", "unit2" → "unit 2", "2unit" → "2 unit"
// This must happen BEFORE Roman numeral conversion so isolated digits are
// already separated from words.
$v = preg_replace('/([a-z])(\d)/', '$1 $2', $v);
$v = preg_replace('/(\d)([a-z])/', '$1 $2', $v);
// 3. Convert standalone Roman numerals to digits.
// Applied AFTER splitting so "industries" is never touched —
// the \b boundary ensures only whole tokens are matched.
// Order matters: longer patterns first (iii before ii before i).
$romanMap = [
'/\bviii\b/' => '8',
'/\bvii\b/' => '7',
'/\bvi\b/' => '6',
'/\biv\b/' => '4',
'/\biii\b/' => '3',
'/\bii\b/' => '2',
'/\bv\b/' => '5',
'/\bi\b/' => '1', // last — single i only after all others consumed
];
foreach ($romanMap as $pattern => $digit) {
$v = preg_replace($pattern, $digit, $v);
}
// 4. Collapse multiple spaces
$v = preg_replace('/\s+/', ' ', $v);
return trim($v);
}
/**
* Split a normalised string into significant tokens (drops noise words).
*
* @return array<string>
*/
private function significantTokens(string $normalised): array
{
$stopWords = ['and', 'the', 'of', 'for', 'at', 'in', 'a'];
$tokens = explode(' ', $normalised);
return array_values(array_filter($tokens, function (string $t) use ($stopWords) {
return strlen($t) >= 2 && ! in_array($t, $stopWords, true);
}));
}
// ─────────────────────────────────────────────────────────────────────────
/**
* Unknown task let Gemini respond conversationally.
*
* If the classification step already produced a useful clarification string
* (e.g. "Could you clarify — are you asking about scan status or invoice type?"),
* we return that directly without a second API call.
* Otherwise we hit Gemini again with a conversational system prompt.
*/
private function handleUnknown(
array $chatHistory,
string $userInput,
?string $clarificationFromClassifier
): string {
$reply = $this->callGeminiConversational(
$chatHistory,
$userInput,
$clarificationFromClassifier
);
return $reply
?? "I'm not sure I understood that. I can help you with:\n\n"
. "• Invoice Status — scan progress of an invoice\n"
. "• Invoice Report — serial vs material type for an item\n"
. "• Production Report — unit count for a plant / line\n\n"
. "What would you like to check?";
}
}

View File

@@ -2,16 +2,11 @@
namespace App\Services; namespace App\Services;
use App\Models\Item;
use App\Models\ItemCharacteristic; use App\Models\ItemCharacteristic;
use App\Models\ProductionQuantity;
use App\Models\StickerDetail; use App\Models\StickerDetail;
use App\Models\StickerMappingMaster;
use App\Models\StickerStructureDetail; use App\Models\StickerStructureDetail;
use App\Models\StickerValidation;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use TCPDF; use TCPDF;
@@ -452,10 +447,11 @@ class StickerPdfService
} }
// return $pdf->Output('sticker.pdf', 'S'); // return $pdf->Output('sticker.pdf', 'S');
$pdfContent = $pdf->Output('', 'S'); // 'S' returns string $pdfContent = $pdf->Output('sticker.pdf', 'S');
// Encode as base64 return (new Response($pdfContent, 200))
return base64_encode($pdfContent); ->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="sticker.pdf"');
} }
@@ -792,11 +788,10 @@ class StickerPdfService
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"'); // ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
} }
public function generatePdf1(string $stickerId, Collection $dynamicElements, ?ItemCharacteristic $itemCharacteristic, ?string $serialNumber, $serNo) public function generatePdf1(string $stickerId, Collection $dynamicElements, ?ItemCharacteristic $itemCharacteristic, ?string $serialNumber)
{ {
$dynamicValueMap = []; $dynamicValueMap = [];
$itemCode = $itemCharacteristic?->item?->code ?? '';
foreach ($dynamicElements as $element) { foreach ($dynamicElements as $element) {
@@ -832,29 +827,42 @@ class StickerPdfService
$pdf = new TCPDF('P', 'mm', [$width, $height], true, 'UTF-8', false); $pdf = new TCPDF('P', 'mm', [$width, $height], true, 'UTF-8', false);
// $pdf->SetMargins(
// (float) $structure->sticker_lmargin,
// (float) $structure->sticker_tmargin,
// (float) $structure->sticker_rmargin,
// );
// //$pdf->SetAutoPageBreak(false, (float) $structure->sticker_bmargin);
// $pdf->SetAutoPageBreak(false, (float) $structure->sticker_bmargin);
$pdf->setPrintHeader(false); $pdf->setPrintHeader(false);
$pdf->setPrintFooter(false); $pdf->setPrintFooter(false);
// $pdf->setCellPaddings(0, 0, 0, 0);
// $pdf->setCellMargins(5, 5, 5, 5);
// Set margins
// $pdf->SetMargins(5, 5, 5); // left, top, right
$pdf->SetMargins( $pdf->SetMargins(
(float) $structure->sticker_lmargin, (float) $structure->sticker_lmargin,
(float) $structure->sticker_tmargin, (float) $structure->sticker_tmargin,
(float) $structure->sticker_rmargin, (float) $structure->sticker_rmargin,
(float) $structure->sticker_bmargin,
); );
$pdf->SetAutoPageBreak(false, 0); $pdf->SetAutoPageBreak(false, 0);
$pdf->AddPage(); $pdf->AddPage();
// if (!empty($serialNumber)) { if (!empty($serialNumber)) {
// $pdf->SetFont('helvetica', 'B', 10); $pdf->SetFont('helvetica', 'B', 10);
// $pdf->SetTextColor(0, 0, 0); $pdf->SetTextColor(0, 0, 0);
// // HARD-CODED POSITION (mm) // HARD-CODED POSITION (mm)
// $x = 40; // change as needed $x = 40; // change as needed
// $y = 60; // change as needed $y = 60; // change as needed
// $pdf->Text($x, $y, (string) $serialNumber); $pdf->Text($x, $y, (string) $serialNumber);
// } }
$pdf->SetFont('helvetica', 'B', 10); $pdf->SetFont('helvetica', 'B', 10);
@@ -892,21 +900,6 @@ class StickerPdfService
break; break;
case 'QR': case 'QR':
if (
($row->element_type) == 'Dynamic'
) {
$qrContent = $serNo ?? '';
$pdf->write2DBarcode(
$qrContent,
'QRCODE,H',
(float) ($row->qr_x_value ?? 0),
(float) ($row->qr_y_value ?? 0),
(float) ($row->qr_size ?? 10),
(float) ($row->qr_size ?? 10)
);
break;
}
else{
$pdf->write2DBarcode( $pdf->write2DBarcode(
$row->qr_value ?? '', $row->qr_value ?? '',
'QRCODE,H', 'QRCODE,H',
@@ -916,8 +909,6 @@ class StickerPdfService
(float) ($row->qr_size ?? 10) (float) ($row->qr_size ?? 10)
); );
break; break;
}
case 'Image': case 'Image':
@@ -1018,122 +1009,21 @@ class StickerPdfService
} }
} }
// $pdfContent = $pdf->Output('', 'S'); // 'S' returns string // return $pdf->Output('sticker.pdf', 'S');
$pdfContent = $pdf->Output('sticker1.pdf', 'S');
$filename = "sticker_{$stickerId}_" . time() . ".pdf";
// // Encode as base64 // return (new Response($pdfContent, 200))
// return base64_encode($pdfContent); // ->header('Content-Type', 'application/pdf');
// $pdfContent = $pdf->Output('', 'S');
// return response($pdfContent)
// ->header('Content-Type', 'application/pdf')
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"'); // ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
$pdfContent = $pdf->Output('', 'S'); // 'S' returns the PDF as a string return response($pdfContent, 200)
// Return the PDF as a response
try {
$pdfContent = $pdf->Output('', 'S'); // 'S' returns the PDF as a string
return response($pdfContent)
->header('Content-Type', 'application/pdf') ->header('Content-Type', 'application/pdf')
->header('Content-Disposition', 'inline; filename="sticker.pdf"'); ->header('Content-Disposition', 'inline; filename="'.$filename.'"')
} catch (\Exception $e) { ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
Log::error('PDF generation failed: '.$e->getMessage()); ->header('Pragma', 'no-cache')
abort(500, 'Failed to generate PDF'); ->header('Expires', '0');
} }
}
public function generatePdfBySerial($item, $serNo, $plantId, $refNumber)
{
$recFound = ProductionQuantity::where('plant_id', $plantId)
->where('production_order', $refNumber)
->where('serial_number', $serNo)
->first();
if (!$recFound) {
abort(404, 'Serial not found');
}
$duplicate = StickerValidation::where('plant_id', $plantId)
->where('production_order', $refNumber)
->where('serial_number', $serNo)
->first();
$itemC = Item::where('code', $item)
->where('plant_id', $plantId)
->first();
if (!$itemC) {
abort(404, 'Item not found');
}
$item = ItemCharacteristic::where('item_id', $itemC->id)
->where('plant_id', $plantId)
->first();
if (!$item) {
abort(404, 'Item characteristic not found');
}
$mapping = StickerMappingMaster::where('plant_id', $plantId)
->where('item_characteristic_id', $item->id)
->first();
if (!$mapping) {
abort(404, 'Sticker mapping not found');
}
$structure = StickerStructureDetail::findOrFail($mapping->sticker_structure1_id);
$dynamicElements = StickerDetail::where(
'sticker_structure_detail_id',
$structure->id
)->where('element_type', 'Dynamic')->get();
return $this->generatePdf1(
$structure->sticker_id,
$dynamicElements,
$item,
$serNo,
$serNo
);
// return response($pdf)
// ->header('Content-Type', 'application/pdf')
// ->header('Content-Disposition', 'inline; filename="sticker.pdf"');
}
// public function printStickersToUSB(array $stickers, int $plantId, ?string $serialNumber)
// {
// $printerPort = 'USB001';
// foreach ($stickers as $sticker) {
// $dynamicElements = StickerDetail::where('sticker_structure_detail_id', $sticker['sticker_id'])
// ->get();
// $itemCharacteristic = ItemCharacteristic::find(
// $sticker['item_characteristic']
// );
// $pdfContent = $this->generatePdf1(
// $sticker['sticker_id'],
// $dynamicElements,
// $itemCharacteristic,
// $serialNumber
// );
// $handle = fopen("{$printerPort}:", "wb");
// if (! $handle) {
// throw new \Exception("Cannot open printer port {$printerPort}");
// }
// fwrite($handle, $pdfContent);
// fclose($handle);
// }
// }
// private function hexToRgb($hex) // private function hexToRgb($hex)
// { // {
@@ -1144,7 +1034,6 @@ class StickerPdfService
// hexdec(substr($hex, 4, 2)), // hexdec(substr($hex, 4, 2)),
// ]; // ];
// } // }
private function hexToRgb($hex) private function hexToRgb($hex)
{ {
if (! is_string($hex) || ($hex = trim($hex)) === '') { if (! is_string($hex) || ($hex = trim($hex)) === '') {

View File

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

View File

@@ -1,34 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql = <<<'SQL'
ALTER TABLE production_plans
ADD COLUMN item_id BIGINT DEFAULT NULL,
ADD CONSTRAINT production_plans_item_id_fkey
FOREIGN KEY (item_id) REFERENCES items(id);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('production_plans', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql = <<<'SQL'
ALTER TABLE lines
ADD COLUMN block_id BIGINT DEFAULT NULL,
ADD CONSTRAINT lines_block_id_fkey
FOREIGN KEY (block_id) REFERENCES blocks(id);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('lines', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -1,40 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql1 = <<<'SQL'
ALTER TABLE items
ADD COLUMN line_id BIGINT DEFAULT NULL,
ADD CONSTRAINT items_line_id_fkey
FOREIGN KEY (line_id) REFERENCES lines(id);
SQL;
DB::statement($sql1);
$sql2 = <<<'SQL'
ALTER TABLE items
ADD COLUMN line_capacity TEXT DEFAULT NULL
SQL;
DB::statement($sql2);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('items', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql = <<<'SQL'
ALTER TABLE production_quantities
ADD COLUMN machine_id BIGINT DEFAULT NULL,
ADD CONSTRAINT production_quantities_machine_id_fkey
FOREIGN KEY (machine_id) REFERENCES machines(id);
SQL;
DB::statement($sql);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('production_quantities', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -1,31 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql1 = <<<'SQL'
ALTER TABLE production_plans
ADD COLUMN working_days TEXT DEFAULT NULL
SQL;
DB::statement($sql1);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('production_plans', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -1,31 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$sql1 = <<<'SQL'
ALTER TABLE production_plans
ADD COLUMN leave_dates TEXT DEFAULT NULL
SQL;
DB::statement($sql1);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Schema::table('production_plans', function (Blueprint $table) {
// //
// });
}
};

View File

@@ -65,6 +65,7 @@
}, },
body: JSON.stringify(subscription) body: JSON.stringify(subscription)
}); });
alert("Push notifications enabled ✅"); alert("Push notifications enabled ✅");
} }
</script> </script>

View File

@@ -1,13 +0,0 @@
<x-filament-panels::page>
<div class="space-y-4">
{{-- Render the Select form fields --}}
<div class="space-y-4">
{{ $this->form }}
</div>
</div>
</x-filament-panels::page>

View File

@@ -45,7 +45,7 @@
</div> --}} </div> --}}
<div class="flex gap-6 -mt-6"> <div class="flex gap-6 -mt-6">
<!-- Scan QR Code --> <!-- Scan QR Code -->
<div class="w-full"> <div class="w-1/2">
<label for="qr-scan-input" class="block text-sm font-medium text-gray-700 mb-2"> <label for="qr-scan-input" class="block text-sm font-medium text-gray-700 mb-2">
SCAN QR CODE SCAN QR CODE
</label> </label>
@@ -60,7 +60,7 @@
</div> </div>
<!-- Last Scanned QR --> <!-- Last Scanned QR -->
{{-- <div class="w-1/2"> <div class="w-1/2">
<label for="recent-qr-input" class="block text-sm font-medium text-gray-700 mb-2"> <label for="recent-qr-input" class="block text-sm font-medium text-gray-700 mb-2">
LAST SCANNED QR LAST SCANNED QR
</label> </label>
@@ -71,7 +71,7 @@
readonly readonly
wire:model="recent_qr" wire:model="recent_qr"
/> />
</div> --}} </div>
</div> </div>

View File

@@ -1,19 +0,0 @@
<x-filament-panels::page>
<div class="space-y-4">
{{-- Render the Select form fields --}}
<div class="space-y-4">
{{ $this->form }}
</div>
<x-filament::button
wire:click="export"
color="primary"
class="mt-4"
>
Export
</x-filament::button>
<div class="bg-white shadow rounded-xl p-4 mt-6">
<livewire:production-target-plan />
</div>
</div>
</x-filament-panels::page>

View File

@@ -1,151 +0,0 @@
<!-- <select id="yearSelect">
<option value="">Select Year</option>
</select> -->
<div id="calendar" wire:ignore></div>
<!-- <input type="text" name="working_days" placeholder="Working Days"> -->
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
let selectedDates = [];
let calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
height: 600,
showNonCurrentDates: true,
datesSet: function(info) {
// Clear previous month selections
selectedDates = [];
// Remove background events
calendar.removeAllEvents();
// Recalculate working days for new month
updateWorkingDays(info.view.currentStart);
},
dateClick: function(info) {
let viewMonth = calendar.view.currentStart.getMonth();
let clickedMonth = info.date.getMonth();
if (viewMonth != clickedMonth) return;
let dateStr = info.dateStr;
if (selectedDates.includes(dateStr)) {
selectedDates = selectedDates.filter(d => d !== dateStr);
calendar.getEvents().forEach(event => {
if (event.startStr == dateStr) event.remove();
});
} else {
selectedDates.push(dateStr);
calendar.addEvent({
start: dateStr,
display: 'background',
color: '#f03f17'
});
}
updateWorkingDays(info.date);
}
});
// var calendar = new FullCalendar.Calendar(calendarEl, {
// initialView: 'dayGridMonth',
// height: 600,
// showNonCurrentDates: true,
// dateClick: function(info) {
// let viewMonth = calendar.view.currentStart.getMonth();
// let clickedMonth = info.date.getMonth();
// // let month = info.date.getMonth() + 1; // JS month: 0-11 → 1-12
// // let year = info.date.getFullYear();
// if (viewMonth != clickedMonth) {
// return; // Ignore next/prev month dates
// }
// let dateStr = info.dateStr;
// if (selectedDates.includes(dateStr)) {
// selectedDates = selectedDates.filter(d => d !== dateStr);
// calendar.getEvents().forEach(event => {
// if (event.startStr == dateStr) {
// event.remove();
// }
// });
// } else {
// selectedDates.push(dateStr);
// calendar.addEvent({
// start: dateStr,
// display: 'background',
// color: '#f03f17'
// });
// }
// updateWorkingDays(info.date);
// }
// });
// yearSelect.addEventListener('change', function () {
// let year = this.value;
// if (!year) return;
// let currentDate = calendar.getDate();
// let newDate = new Date(year, currentDate.getMonth(), 1);
// calendar.gotoDate(newDate);
// });
function updateWorkingDays(date) {
let totalDays = new Date(
date.getFullYear(),
date.getMonth()+1,
0
).getDate();
let workingDays = totalDays - selectedDates.length;
// document.querySelector('input[name="working_days"]').value = workingDays;
const input = document.querySelector('#working_days');
input.value = workingDays;
input.dispatchEvent(new Event('input'));
const monthInput = document.querySelector('#month');
monthInput.value = date.getMonth() + 1; // 112 month number
monthInput.dispatchEvent(new Event('input'));
const yearInput = document.querySelector('#year');
yearInput.value = date.getFullYear();
yearInput.dispatchEvent(new Event('input'));
const selectedDatesInput = document.querySelector('#selected_dates');
selectedDatesInput.value = selectedDates.join(',');
selectedDatesInput.dispatchEvent(new Event('input'));
}
calendar.render();
});
</script>

View File

@@ -1,9 +0,0 @@
<div class="flex space-x-2 items-center">
<button
type="button"
class="inline-flex items-center px-3 py-1 bg-primary-600 text-white rounded hover:bg-primary-700"
wire:click="saveWorkingDays"
>
Save
</button>
</div>

View File

@@ -1,734 +0,0 @@
<div style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:9999;display:flex;flex-direction:column;align-items:flex-end;gap:0.75rem;">
<style>
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadeIn { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
.cb-mode-card {
background: #111827;
border: 1px solid #374151;
border-radius: 0.75rem;
padding: 1rem;
cursor: pointer;
transition: border-color .2s, background .2s;
text-align: center;
flex: 1;
}
.cb-mode-card:hover { border-color: #f59e0b; background: #1a2436; }
.cb-report-pill {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.4rem 0.875rem;
border-radius: 9999px;
border: 1px solid #374151;
background: #111827;
color: #9ca3af;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: border-color .15s, background .15s, color .15s;
}
.cb-report-pill:hover { border-color: #f59e0b; color: #f9fafb; background: #1a2436; }
.cb-report-pill.active-prod { border-color: #f59e0b; background: #f59e0b22; color: #f59e0b; }
.cb-report-pill.active-inv { border-color: #10b981; background: #10b98122; color: #10b981; }
.cb-report-pill.active-inv-stat { border-color: #3b82f6; background: #3b82f622; color: #3b82f6; }
.cb-chat-bubble-user {
align-self: flex-end;
background: #f59e0b;
color: #1f2937;
border-radius: 1rem 1rem 0.25rem 1rem;
padding: 0.625rem 0.875rem;
font-size: 0.8125rem;
max-width: 85%;
line-height: 1.5;
animation: fadeIn .2s ease;
word-break: break-word;
}
.cb-chat-bubble-assistant {
align-self: flex-start;
background: #1f2937;
border: 1px solid #374151;
color: #f9fafb;
border-radius: 1rem 1rem 1rem 0.25rem;
padding: 0.625rem 0.875rem;
font-size: 0.8125rem;
max-width: 90%;
line-height: 1.6;
animation: fadeIn .2s ease;
word-break: break-word;
white-space: pre-line;
}
.cb-typing-dot {
display:inline-block; width:6px; height:6px; border-radius:50%;
background:#f59e0b; animation: pulse 1.2s ease infinite;
}
.cb-typing-dot:nth-child(2) { animation-delay:.2s; }
.cb-typing-dot:nth-child(3) { animation-delay:.4s; }
</style>
{{-- ── Chat Panel ──────────────────────────────────────────────────────── --}}
@if($isOpen)
<div style="width:380px;background:#1f2937;border-radius:1rem;box-shadow:0 25px 50px -12px rgba(0,0,0,.5);border:1px solid #374151;display:flex;flex-direction:column;overflow:hidden;">
{{-- ── Header ── --}}
<div style="display:flex;align-items:center;justify-content:space-between;padding:0.75rem 1rem;background:#f59e0b;flex-shrink:0;">
<div style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1.25rem;height:1.25rem;color:#fff;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z" />
</svg>
<span style="font-weight:600;font-size:0.875rem;color:#fff;">Report Assistant ⒶⓇ</span>
{{-- Mode badge --}}
@if($mode !== 'select')
<span style="background:rgba(0,0,0,.2);color:#fff;font-size:0.65rem;font-weight:700;padding:0.15rem 0.5rem;border-radius:9999px;letter-spacing:.05em;text-transform:uppercase;">
{{ $mode }}
</span>
@endif
</div>
<div style="display:flex;align-items:center;gap:0.5rem;">
{{-- Back to mode select (only when in a mode) --}}
@if($mode !== 'select')
<button wire:click="setMode('select')" title="Switch Mode"
style="background:rgba(0,0,0,.2);border:none;cursor:pointer;padding:0.25rem 0.5rem;border-radius:0.375rem;color:#fff;font-size:0.7rem;font-weight:600;display:flex;align-items:center;gap:0.25rem;">
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
Switch
</button>
@endif
{{-- Reset --}}
<button wire:click="resetForm" title="Reset"
style="background:transparent;border:none;cursor:pointer;padding:0.25rem;border-radius:0.25rem;color:#fff;display:flex;align-items:center;">
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
</button>
{{-- Close --}}
<button wire:click="toggleChat" title="Close"
style="background:transparent;border:none;cursor:pointer;padding:0.25rem;border-radius:0.25rem;color:#fff;display:flex;align-items:center;">
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
{{-- ══════════════════════════════════════════════════════════════════ --}}
{{-- ── MODE SELECT SCREEN ── --}}
{{-- ══════════════════════════════════════════════════════════════════ --}}
@if($mode === 'select')
<div style="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;">
<div style="text-align:center;">
<p style="color:#9ca3af;font-size:0.8125rem;margin:0 0 0.25rem;">How would you like to query?</p>
<p style="color:#6b7280;font-size:0.7rem;margin:0;">Choose a mode to get started</p>
</div>
<div style="display:flex;gap:0.75rem;">
{{-- Basic card --}}
<button wire:click="setMode('basic')" class="cb-mode-card" style="border:none;width:50%;">
<div style="display:flex;justify-content:center;margin-bottom:0.625rem;">
<div style="background:#f59e0b22;border-radius:0.625rem;padding:0.625rem;">
<svg style="width:1.5rem;height:1.5rem;color:#f59e0b;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
</svg>
</div>
</div>
<p style="color:#f9fafb;font-size:0.875rem;font-weight:700;margin:0 0 0.25rem;">Basic</p>
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.4;">Input the required information to generate reports</p>
</button>
{{-- Advanced card --}}
<button wire:click="setMode('advanced')" class="cb-mode-card" style="border:none;width:50%;">
<div style="display:flex;justify-content:center;margin-bottom:0.625rem;">
<div style="background:#8b5cf622;border-radius:0.625rem;padding:0.625rem;">
<svg style="width:1.5rem;height:1.5rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
</div>
<p style="color:#f9fafb;font-size:0.875rem;font-weight:700;margin:0 0 0.25rem;">Advanced</p>
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.4;">Ask anything in plain English powered by Gemini AI</p>
</button>
</div>
{{-- Hint --}}
<div style="background:#111827;border-radius:0.5rem;padding:0.75rem;border:1px solid #374151;">
<p style="color:#6b7280;font-size:0.7rem;margin:0;line-height:1.5;">
💡 <strong style="color:#9ca3af;">Tip:</strong> Use <em>Advanced</em> to just describe what you need in plain English Gemini will figure out the rest
</p>
</div>
</div>
@endif
{{-- ══════════════════════════════════════════════════════════════════ --}}
{{-- ── BASIC MODE ── --}}
{{-- ══════════════════════════════════════════════════════════════════ --}}
@if($mode === 'basic')
<div style="padding:1rem;display:flex;flex-direction:column;gap:0.875rem;max-height:480px;overflow-y:auto;">
{{-- ── Report Type Selector (dropdown) ──────────────────────────── --}}
<div style="position:relative;">
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.5rem;">Report Type</label>
{{-- Custom wrapper gives us the chevron icon and accent border on selection --}}
<div style="position:relative;">
<select
wire:change="setReportType($event.target.value)"
style="
width:100%;
appearance:none;
-webkit-appearance:none;
background:#111827;
border:1px solid {{ $reportType !== '' ? '#f59e0b' : '#374151' }};
border-radius:0.5rem;
color:{{ $reportType !== '' ? '#f9fafb' : '#6b7280' }};
padding:0.55rem 2.25rem 0.55rem 0.875rem;
font-size:0.8125rem;
font-weight:{{ $reportType !== '' ? '600' : '400' }};
outline:none;
cursor:pointer;
transition:border-color .15s, color .15s;
">
<option value="" {{ $reportType === '' ? 'selected' : '' }}> Select Report Type </option>
<option value="production" {{ $reportType === 'production' ? 'selected' : '' }}>📊 Production Report</option>
<option value="invoice" {{ $reportType === 'invoice' ? 'selected' : '' }}>📄 Invoice Report</option>
<option value="invoice_status" {{ $reportType === 'invoice_status' ? 'selected' : '' }}>🔍 Invoice Status</option>
</select>
{{-- Chevron icon --}}
<div style="pointer-events:none;position:absolute;right:0.75rem;top:50%;transform:translateY(-50%);">
<svg style="width:0.875rem;height:0.875rem;color:{{ $reportType !== '' ? '#f59e0b' : '#6b7280' }};" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</div>
</div>
</div>
{{-- ── Placeholder when no report type selected ─────────────────── --}}
@if($reportType === '')
<div style="background:#111827;border:1px dashed #374151;border-radius:0.625rem;padding:1.25rem;text-align:center;">
<svg style="width:2rem;height:2rem;color:#4b5563;margin:0 auto 0.5rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
</svg>
<p style="color:#6b7280;font-size:0.8rem;margin:0;line-height:1.5;">Select a report type from the dropdown to get started</p>
</div>
@endif
{{-- ══════════════════════════════════════════════════════════════ --}}
{{-- ── PRODUCTION REPORT FORM ── --}}
{{-- ══════════════════════════════════════════════════════════════ --}}
@if($reportType === 'production')
{{-- Plant --}}
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Plant</label>
<select wire:model.live="selectedPlantId"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;">
<option value=""> Select Plant </option>
@foreach($plants as $plant)
<option value="{{ $plant->id }}">{{ $plant->name }}</option>
@endforeach
</select>
</div>
{{-- Line --}}
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">
Line <span style="color:#6b7280;font-weight:400;">(optional leave blank for all lines)</span>
</label>
<select wire:model.live="selectedLineId"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;"
@if(!$selectedPlantId) disabled @endif>
<option value=""> All Lines </option>
@foreach($lines as $line)
<option value="{{ $line->id }}">{{ $line->name }}</option>
@endforeach
</select>
@if(!$selectedPlantId)
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Select a plant first</p>
@endif
</div>
{{-- Date Range --}}
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">From</label>
<input type="date" wire:model="dateFrom"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;" />
</div>
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">To</label>
<input type="date" wire:model="dateTo"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;" />
</div>
</div>
{{-- Fetch Button --}}
<button wire:click="fetchProduction"
wire:loading.attr="disabled"
wire:target="fetchProduction"
style="width:100%;background:#f59e0b;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
<span wire:loading.remove wire:target="fetchProduction" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
</svg>
Get Production Count
</span>
<span wire:loading wire:target="fetchProduction" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
Fetching…
</span>
</button>
{{-- Production Result --}}
@if($hasResult)
<div style="background:#111827;border:1px solid #f59e0b;border-radius:0.5rem;padding:0.875rem;display:flex;gap:0.75rem;align-items:flex-start;animation:fadeIn .25s ease;">
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
</svg>
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.5;margin:0;">{{ $result }}</p>
</div>
@endif
@endif {{-- end production --}}
{{-- ══════════════════════════════════════════════════════════════ --}}
{{-- ── INVOICE REPORT FORM (type lookup) ── --}}
{{-- ══════════════════════════════════════════════════════════════ --}}
@if($reportType === 'invoice')
{{-- Plant --}}
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Plant</label>
<select wire:model.live="invoicePlantId"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;">
<option value=""> Select Plant </option>
@foreach($plants as $plant)
<option value="{{ $plant->id }}">{{ $plant->name }}</option>
@endforeach
</select>
</div>
{{-- Item Code --}}
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Item Code</label>
<input
type="text"
wire:model="invoiceItemCode"
wire:keydown.enter="fetchInvoiceReport"
placeholder="e.g. 674071"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;font-family:monospace;"
/>
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Press Enter or click the button below</p>
</div>
{{-- Fetch Button --}}
<button wire:click="fetchInvoiceReport"
wire:loading.attr="disabled"
wire:target="fetchInvoiceReport"
style="width:100%;background:#10b981;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
<span wire:loading.remove wire:target="fetchInvoiceReport" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
Check Invoice Type
</span>
<span wire:loading wire:target="fetchInvoiceReport" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
Fetching…
</span>
</button>
{{-- Invoice Report Result --}}
@if($hasInvoiceResult)
<div style="background:#111827;border:1px solid #10b981;border-radius:0.5rem;padding:0.875rem;display:flex;gap:0.75rem;align-items:flex-start;animation:fadeIn .25s ease;">
@if(str_contains($invoiceResult, 'serial invoice'))
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
</svg>
@elseif(str_contains($invoiceResult, 'material invoice'))
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
</svg>
@elseif(str_contains($invoiceResult, 'does not exist') || str_contains($invoiceResult, 'not found'))
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
@else
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
@endif
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.5;margin:0;">{{ $invoiceResult }}</p>
</div>
@endif
@endif {{-- end invoice report --}}
{{-- ══════════════════════════════════════════════════════════════ --}}
{{-- ── INVOICE STATUS FORM (scan status) ── NEW ── --}}
{{-- ══════════════════════════════════════════════════════════════ --}}
@if($reportType === 'invoice_status')
{{-- Description banner --}}
<div style="background:#1e3a5f;border:1px solid #3b82f633;border-radius:0.5rem;padding:0.625rem 0.75rem;display:flex;gap:0.5rem;align-items:flex-start;">
<svg style="width:1rem;height:1rem;color:#60a5fa;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
<p style="font-size:0.7rem;color:#93c5fd;line-height:1.5;margin:0;">
Enter an invoice number to see how many serial numbers have been scanned, how many are pending, and the list of unscanned serials.
</p>
</div>
{{-- Invoice Number input --}}
<div>
<label style="display:block;font-size:0.75rem;font-weight:500;color:#9ca3af;margin-bottom:0.375rem;">Invoice Number</label>
<input
type="text"
wire:model="invoiceNumber"
wire:keydown.enter="fetchInvoiceStatus"
placeholder="e.g. 3RA0013333"
style="width:100%;background:#111827;border:1px solid #374151;border-radius:0.5rem;color:#f9fafb;padding:0.5rem 0.75rem;font-size:0.8125rem;outline:none;box-sizing:border-box;font-family:monospace;letter-spacing:0.03em;"
/>
<p style="font-size:0.7rem;color:#6b7280;margin-top:0.25rem;">Press Enter or click the button below</p>
</div>
{{-- Fetch Button --}}
<button wire:click="fetchInvoiceStatus"
wire:loading.attr="disabled"
wire:target="fetchInvoiceStatus"
style="width:100%;background:#3b82f6;color:#fff;border:none;border-radius:0.5rem;padding:0.625rem;font-size:0.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:0.5rem;">
<span wire:loading.remove wire:target="fetchInvoiceStatus" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
</svg>
Check Scan Status
</span>
<span wire:loading wire:target="fetchInvoiceStatus" style="display:flex;align-items:center;gap:0.5rem;">
<svg style="width:1rem;height:1rem;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
Fetching…
</span>
</button>
{{-- Invoice Status Result --}}
@if($hasInvoiceStatusResult)
@php
$sd = $invoiceStatusData;
$sdType = $sd['type'] ?? 'error';
$sdTotal = $sd['total'] ?? 0;
$sdScanned = $sd['scanned'] ?? 0;
$sdNot = $sd['not_scanned'] ?? 0;
$sdSerials = $sd['unscanned_serials'] ?? [];
$sdCount = count($sdSerials);
$sdInv = $sd['invoice_number'] ?? '';
$isGood = $sdType === 'all_scanned';
$isWarn = in_array($sdType, ['error', 'not_found', 'invalid']);
$borderCol = $isGood ? '#10b981' : ($isWarn ? '#f59e0b' : '#3b82f6');
@endphp
<div style="background:#111827;border:1px solid {{ $borderCol }};border-radius:0.5rem;padding:0.875rem;animation:fadeIn .25s ease;">
{{-- ── Icon + summary row ── --}}
<div style="display:flex;gap:0.75rem;align-items:flex-start;">
@if($isGood)
<svg style="width:1.25rem;height:1.25rem;color:#10b981;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
@elseif($isWarn)
<svg style="width:1.25rem;height:1.25rem;color:#f59e0b;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
@else
<svg style="width:1.25rem;height:1.25rem;color:#3b82f6;flex-shrink:0;margin-top:0.1rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
</svg>
@endif
<div style="flex:1;min-width:0;">
<p style="font-size:0.8125rem;color:#f9fafb;line-height:1.6;margin:0;">
{{ $sd['message'] ?? $invoiceStatusResult }}
</p>
{{-- ── Count pills (only for real invoice data) ── --}}
@if($sdTotal > 0)
<div style="display:flex;gap:0.375rem;flex-wrap:wrap;margin-top:0.625rem;">
<span style="background:#1e3a2f;border:1px solid #10b98155;color:#6ee7b7;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
Total: {{ $sdTotal }}
</span>
<span style="background:#1a3350;border:1px solid #3b82f655;color:#93c5fd;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
Scanned: {{ $sdScanned }}
</span>
@if($sdNot > 0)
<span style="background:#3b1a1a;border:1px solid #ef444455;color:#fca5a5;font-size:0.68rem;font-weight:700;padding:0.2rem 0.55rem;border-radius:9999px;">
Not scanned: {{ $sdNot }}
</span>
@endif
</div>
@endif
</div>
</div>
{{-- ── Unscanned serial numbers section ── --}}
@if($sdCount > 0)
<div style="margin-top:0.875rem;border-top:1px solid #374151;padding-top:0.75rem;">
<p style="font-size:0.72rem;font-weight:600;color:#9ca3af;margin:0 0 0.5rem;text-transform:uppercase;letter-spacing:.05em;">
Unscanned serial numbers
<span style="background:#3b1a1a;color:#fca5a5;border-radius:9999px;padding:0.1rem 0.45rem;font-size:0.68rem;margin-left:0.25rem;">
{{ $sdCount }}
</span>
</p>
{{-- Serial chips first 10, or all when expanded --}}
@php
$visibleSerials = $showAllUnscanned
? $sdSerials
: array_slice($sdSerials, 0, 10);
$hiddenCount = $sdCount - 10;
@endphp
<div style="display:flex;flex-wrap:wrap;gap:0.35rem;
@if($showAllUnscanned) max-height:200px;overflow-y:auto;padding-right:2px; @endif">
@foreach($visibleSerials as $serial)
<span style="
display:inline-block;
background:#1e293b;
border:1px solid #475569;
color:#e2e8f0;
font-family:monospace;
font-size:0.72rem;
padding:0.2rem 0.5rem;
border-radius:0.3rem;
white-space:nowrap;
">{{ $serial }}</span>
@endforeach
</div>
{{-- Show more / Show less button --}}
@if($sdCount > 10)
<button
wire:click="toggleShowAllUnscanned"
style="
margin-top:0.625rem;
background:transparent;
border:1px solid #3b82f655;
border-radius:0.375rem;
color:#60a5fa;
font-size:0.75rem;
font-weight:600;
padding:0.3rem 0.75rem;
cursor:pointer;
display:flex;
align-items:center;
gap:0.35rem;
transition:background .15s, border-color .15s;
"
onmouseover="this.style.background='#1e3a5f';this.style.borderColor='#3b82f6';"
onmouseout="this.style.background='transparent';this.style.borderColor='#3b82f655';">
@if($showAllUnscanned)
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
</svg>
Show less
@else
<svg style="width:0.75rem;height:0.75rem;" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
Show {{ $hiddenCount }} more…
@endif
</button>
@endif
</div>
@endif
</div>
@endif
@endif {{-- end invoice_status --}}
</div>
@endif
{{-- ══════════════════════════════════════════════════════════════════ --}}
{{-- ── ADVANCED MODE ── --}}
{{-- ══════════════════════════════════════════════════════════════════ --}}
@if($mode === 'advanced')
<div style="display:flex;flex-direction:column;height:420px;">
{{-- ── Conversation area ── --}}
<div id="cb-scroll-area"
style="flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:0.75rem;scroll-behavior:smooth;"
x-data
x-init="
const el = document.getElementById('cb-scroll-area');
const observer = new MutationObserver(() => el.scrollTop = el.scrollHeight);
observer.observe(el, { childList:true, subtree:true });
">
@if(empty($chatHistory))
{{-- ── Empty state ── --}}
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0.75rem;padding:1rem 0;">
<div style="background:#8b5cf622;border-radius:50%;padding:1rem;">
<svg style="width:2rem;height:2rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
<div style="text-align:center;">
<p style="color:#f9fafb;font-size:0.875rem;font-weight:600;margin:0 0 0.25rem;">AI-Powered Assistant</p>
<p style="color:#6b7280;font-size:0.75rem;margin:0;line-height:1.5;">Ask anything in plain English tap an example below to get started</p>
</div>
{{-- Example prompt cards --}}
<div style="display:flex;flex-direction:column;gap:0.375rem;width:100%;">
{{-- Card 1 Invoice scan status --}}
<button
wire:click="$set('advancedQuestion', 'Is invoice 3RA0013333 fully scanned?')"
style="background:#111827;border:1px solid #8b5cf6;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
onmouseover="this.style.background='#1a1245'"
onmouseout="this.style.background='#111827'">
<span style="color:#8b5cf6;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📋 Invoice Scan Status</span>
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Is invoice 3RA0013333 fully scanned?"</span>
</button>
{{-- Card 2 Invoice type lookup --}}
<button
wire:click="$set('advancedQuestion', 'Is item 674071 a serial or material invoice for Chennai plant?')"
style="background:#111827;border:1px solid #10b981;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
onmouseover="this.style.background='#0d2e24'"
onmouseout="this.style.background='#111827'">
<span style="color:#10b981;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📄 Invoice Type Lookup</span>
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Is item 674071 serial or material for Chennai plant?"</span>
</button>
{{-- Card 3 Production report --}}
<button
wire:click="$set('advancedQuestion', 'Show production count for Vahinie Unit 2 this month')"
style="background:#111827;border:1px solid #f59e0b;border-radius:0.5rem;padding:0.625rem 0.75rem;color:#f9fafb;font-size:0.75rem;cursor:pointer;text-align:left;display:flex;flex-direction:column;gap:0.25rem;"
onmouseover="this.style.background='#2a1e05'"
onmouseout="this.style.background='#111827'">
<span style="color:#f59e0b;font-weight:700;font-size:0.65rem;text-transform:uppercase;letter-spacing:.06em;">📊 Production Report</span>
<span style="color:#d1d5db;line-height:1.5;font-style:italic;">"Show production count for Vahinie Unit 2 this month"</span>
</button>
</div>
</div>
@endif
{{-- ── Chat bubbles ── --}}
@foreach($chatHistory as $message)
@if($message['role'] === 'user')
<div style="display:flex;justify-content:flex-end;">
<div class="cb-chat-bubble-user">{{ $message['content'] }}</div>
</div>
@else
<div style="display:flex;justify-content:flex-start;gap:0.5rem;align-items:flex-start;">
<div style="background:#8b5cf622;border-radius:50%;padding:0.3rem;flex-shrink:0;margin-top:0.1rem;">
<svg style="width:0.875rem;height:0.875rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
<div class="cb-chat-bubble-assistant">{{ $message['content'] }}</div>
</div>
@endif
@endforeach
{{-- ── Typing indicator ── --}}
@if($isAdvancedLoading)
<div style="display:flex;justify-content:flex-start;gap:0.5rem;align-items:flex-start;">
<div style="background:#8b5cf622;border-radius:50%;padding:0.3rem;flex-shrink:0;margin-top:0.1rem;">
<svg style="width:0.875rem;height:0.875rem;color:#8b5cf6;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
<div class="cb-chat-bubble-assistant" style="display:flex;align-items:center;gap:0.375rem;padding:0.625rem 0.875rem;">
<span class="cb-typing-dot"></span>
<span class="cb-typing-dot"></span>
<span class="cb-typing-dot"></span>
</div>
</div>
@endif
</div>
{{-- ── Input bar ── --}}
<div style="border-top:1px solid #374151;padding:0.75rem;background:#1a2233;flex-shrink:0;">
<div style="display:flex;gap:0.5rem;align-items:flex-end;">
<textarea
wire:model="advancedQuestion"
wire:keydown.enter.prevent="askAdvanced"
placeholder="Ask anything, e.g. 'Is invoice 3RA0013333 scanned?'"
rows="1"
style="flex:1;background:#111827;border:1px solid #374151;border-radius:0.625rem;color:#f9fafb;padding:0.625rem 0.75rem;font-size:0.8125rem;outline:none;resize:none;line-height:1.5;font-family:monospace;max-height:80px;overflow-y:auto;"
oninput="this.style.height='auto';this.style.height=Math.min(this.scrollHeight,80)+'px'"
@if($isAdvancedLoading) disabled @endif
></textarea>
<button wire:click="askAdvanced"
wire:loading.attr="disabled"
wire:target="askAdvanced"
@if($isAdvancedLoading) disabled @endif
style="background:#8b5cf6;border:none;border-radius:0.625rem;padding:0.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;opacity:{{ $isAdvancedLoading ? '.5' : '1' }};">
<span wire:loading.remove wire:target="askAdvanced">
<svg style="width:1rem;height:1rem;color:#fff;" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</span>
<span wire:loading wire:target="askAdvanced">
<svg style="width:1rem;height:1rem;color:#fff;animation:spin 1s linear infinite;" fill="none" viewBox="0 0 24 24">
<circle style="opacity:.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path style="opacity:.75;" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
</svg>
</span>
</button>
</div>
<p style="color:#4b5563;font-size:0.65rem;margin:0.375rem 0 0;text-align:center;">
Press Enter to send · Ask anything in plain English
</p>
</div>
</div>
@endif
</div>
@endif
{{-- ── FAB Toggle Button ───────────────────────────────────────────────── --}}
<button wire:click="toggleChat" title="Production Assistant"
style="width:56px;height:56px;background-color:#f59e0b;color:#fff;border-radius:9999px;box-shadow:0 10px 15px -3px rgba(0,0,0,.3);display:flex;align-items:center;justify-content:center;border:none;cursor:pointer;">
@if($isOpen)
<svg style="width:1.5rem;height:1.5rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
@else
<svg style="width:1.6rem;height:1.6rem;" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 3C7.03 3 3 6.58 3 11c0 2.1.85 4 2.24 5.43L4.5 21l4.86-1.62A9.54 9.54 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" />
<circle cx="9" cy="11" r="0.85" fill="currentColor" stroke="none" />
<circle cx="12" cy="11" r="0.85" fill="currentColor" stroke="none" />
<circle cx="15" cy="11" r="0.85" fill="currentColor" stroke="none" />
</svg>
@endif
</button>
</div>

View File

@@ -1,85 +0,0 @@
<div class="p-4">
<h2 class="text-lg font-bold mb-4 text-gray-700 uppercase tracking-wider">
PRODUCTION PLAN TABLE:
</h2>
<div class="overflow-x-auto rounded-lg shadow">
<table class="w-full divide-y divide-gray-200 text-sm text-center">
{{-- <thead class="bg-gray-100 text-s font-semibold uppercase text-gray-700">
<tr>
<th class="border px-4 py-2">No</th>
<th class="border px-4 py-2">Created Datetime</th>
<th class="border px-4 py-2 whitespace-nowrap">Created By</th>
<th class="border px-4 py-2 whitespace-nowrap">Plant</th>
<th class="border px-4 py-2">Line</th>
<th class="border px-4 py-2 whitespace-nowrap">Item Code</th>
<th class="border px-4 py-2">Production Plan Dates</th>
</tr>
</thead> --}}
<thead class="bg-gray-100 text-s font-semibold uppercase text-gray-700">
<tr>
<th class="border px-4 py-2" rowspan="3">No</th>
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Plant</th>
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Line</th>
<th class="border px-4 py-2 whitespace-nowrap" rowspan="3">Item Code</th>
<th class="border px-4 py-2 whitespace-nowrap" colspan="{{ count($dates) * 2 }}" class="text-center">
Production Plan Dates
</th>
</tr>
<tr>
@foreach($dates as $date)
<th colspan="2" class="text-center">
{{ $date }}
</th>
@endforeach
</tr>
<tr>
@foreach($dates as $date)
<th class="border px-4 py-2 whitespace-nowrap">Target Plan</th>
<th class="border px-4 py-2 whitespace-nowrap">Produced Quantity</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse ($records as $index => $record)
<tr class="hover:bg-gray-50">
<td class="border px-4 py-2">{{ $index + 1 }}</td>
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['plant_name'] }}</td>
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['line_name'] }}</td>
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['item_code'] }}</td>
{{-- @foreach($dates as $date)
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['target_plan'][$date] ?? '-' }}</td>
<td class="border px-4 py-2 whitespace-nowrap">{{ $record['production_plan'][$date] ?? '-' }}</td>
@endforeach --}}
@foreach($dates as $date)
@if(in_array($date, $leaveDates))
<td class="border px-4 py-2 whitespace-nowrap">-</td>
<td class="border px-4 py-2 whitespace-nowrap">-</td>
@else
{{-- <td class="border px-4 py-2 whitespace-nowrap">{{ $record['daily_target'] ?? '-' }}</td> --}}
<td class="border px-4 py-2 whitespace-nowrap">
{{ $record['daily_target_dynamic'][$date] ?? '-' }}
</td>
<td class="border px-4 py-2 whitespace-nowrap">
{{ $record['produced_quantity'][$date] ?? '-' }}
</td>
{{-- <td class="border px-4 py-2 whitespace-nowrap">{{ $record['produced_quantity'] ?? '-' }}</td> --}}
@endif
@endforeach
</tr>
@empty
<tr>
<td colspan="9" class="px-4 py-4 text-center text-gray-500">
No production plan data found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>

View File

@@ -25,15 +25,12 @@ use App\Http\Controllers\ObdController;
use App\Http\Controllers\PalletController; use App\Http\Controllers\PalletController;
use App\Http\Controllers\PdfController; use App\Http\Controllers\PdfController;
use App\Http\Controllers\PlantController; use App\Http\Controllers\PlantController;
use App\Http\Controllers\PrintController;
use App\Http\Controllers\ProductionStickerReprintController; use App\Http\Controllers\ProductionStickerReprintController;
use App\Http\Controllers\SapFileController; use App\Http\Controllers\SapFileController;
use App\Http\Controllers\StickerMasterController; use App\Http\Controllers\StickerMasterController;
// use App\Http\Controllers\TelegramController; // use App\Http\Controllers\TelegramController;
use App\Http\Controllers\TestingPanelController; use App\Http\Controllers\TestingPanelController;
use App\Http\Controllers\UserController; use App\Http\Controllers\UserController;
use App\Models\WebPushSubscription;
use Filament\Facades\Filament;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@@ -188,46 +185,3 @@ Route::post('file/store', [SapFileController::class, 'store'])->name('file.store
// Route::post('send-telegram', [TelegramController::class, 'sendMessage']); // Route::post('send-telegram', [TelegramController::class, 'sendMessage']);
// Route::post('invoice-exit', [InvoiceValidationController::class, 'handle']); // Route::post('invoice-exit', [InvoiceValidationController::class, 'handle']);
Route::post('/print-pdf', [PrintController::class, 'print']);
Route::post('/push/subscribe', function (Request $request) {
$user = Filament::auth()->user();
abort_if(!$user, 401);
$request->validate([
'endpoint' => 'required|string',
'keys.p256dh' => 'required|string',
'keys.auth' => 'required|string',
]);
// WebPushSubscription::updateOrCreate(
// ['endpoint' => $request->endpoint],
// [
// 'subscribable_type' => get_class($user),
// 'subscribable_id' => $user->id,
// 'public_key' => $request->keys['p256dh'],
// 'auth_token' => $request->keys['auth'],
// 'content_encoding' => $request->contentEncoding ?? 'aesgcm',
// ]
// );
WebPushSubscription::updateOrCreate(
[
'endpoint' => $request->endpoint,
'subscribable_type' => get_class($user),
'subscribable_id' => $user->id,
],
[
'public_key' => $request->keys['p256dh'],
'auth_token' => $request->keys['auth'],
'content_encoding' => $request->contentEncoding ?? 'aesgcm',
]
);
return response()->json(['success' => true]);
});

View File

@@ -1,7 +1,6 @@
<?php <?php
use App\Http\Controllers\PdfController; use App\Http\Controllers\PdfController;
use App\Http\Controllers\PrintController;
use App\Mail\test; use App\Mail\test;
use App\Models\EquipmentMaster; use App\Models\EquipmentMaster;
use App\Models\InvoiceValidation; use App\Models\InvoiceValidation;
@@ -14,14 +13,11 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use thiagoalessio\TesseractOCR\TesseractOCR; use thiagoalessio\TesseractOCR\TesseractOCR;
use App\Http\Livewire\CustomLogin; use App\Http\Livewire\CustomLogin;
use App\Services\StickerPdfService;
Route::get('/', function () { Route::get('/', function () {
return redirect('/admin'); return redirect('/admin');
}); });
Route::get('/sticker/pdf/{item}/{serial}/{plantId}/{po}', [PrintController::class, 'generate']);
// Route::get('/admin', function () { // Route::get('/admin', function () {
// return redirect('/admin/welcome'); // return redirect('/admin/welcome');
// }); // });
@@ -63,9 +59,10 @@ use App\Services\StickerPdfService;
]); ]);
WebPushSubscription::updateOrCreate( WebPushSubscription::updateOrCreate(
// ✅ UNIQUE PER DEVICE
['endpoint' => $request->endpoint], ['endpoint' => $request->endpoint],
[ [
'subscribable_type' => get_class($user), 'subscribable_type' => get_class($user), // 🔥 important
'subscribable_id' => $user->id, 'subscribable_id' => $user->id,
'public_key' => $request->keys['p256dh'], 'public_key' => $request->keys['p256dh'],
'auth_token' => $request->keys['auth'], 'auth_token' => $request->keys['auth'],
@@ -73,20 +70,6 @@ use App\Services\StickerPdfService;
] ]
); );
// WebPushSubscription::updateOrCreate(
// [
// 'endpoint' => $request->endpoint,
// 'subscribable_type' => get_class($user),
// 'subscribable_id' => $user->id,
// ],
// [
// 'public_key' => $request->keys['p256dh'],
// 'auth_token' => $request->keys['auth'],
// 'content_encoding' => $request->contentEncoding ?? 'aesgcm',
// ]
// );
return response()->json(['success' => true]); return response()->json(['success' => true]);
}); });