297 Commits

Author SHA1 Message Date
d3c9dd6eb9 Merge branch 'main' into dev
All checks were successful
PR Checks / test-backend (pull_request) Successful in 30s
PR Checks / test-frontend (pull_request) Successful in 1m6s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 33s
Build and Deploy / test-backend (push) Successful in 29s
Build and Deploy / test-frontend (push) Successful in 1m8s
Build and Deploy / build-and-push (push) Successful in 32s
Build and Deploy / deploy (push) Successful in 22s
2026-03-22 23:03:08 +01:00
254ff36c50 fix(front-end): seo improvemnts
Some checks failed
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / test-frontend (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / test-frontend (pull_request) Successful in 1m6s
2026-03-22 23:02:59 +01:00
b317196217 fix(front-end): redirect
All checks were successful
Build and Deploy / test-backend (push) Successful in 40s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 1m19s
Build and Deploy / deploy (push) Successful in 22s
2026-03-22 22:41:12 +01:00
cc343ee27c fix(back-end): fix csrm and cors 2026-03-22 21:11:48 +01:00
74d1b16b7c fix(back-end): fix load product 2026-03-22 21:11:33 +01:00
adf6889712 Merge pull request 'dev' (#49) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 39s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 22s
Reviewed-on: #49
2026-03-21 18:57:57 +01:00
653082868a Merge pull request 'feat/brand-logo' (#50) from feat/brand-logo into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 35s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 10s
Build and Deploy / test-frontend (push) Successful in 1m7s
PR Checks / test-frontend (pull_request) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 1m21s
Build and Deploy / deploy (push) Successful in 19s
Reviewed-on: #50
2026-03-20 13:17:03 +01:00
997e770256 Merge remote-tracking branch 'origin/feat/brand-logo' into feat/brand-logo
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m3s
2026-03-20 11:45:15 +01:00
fb1a6456e6 fix(back-end) base url fix 2026-03-20 11:45:10 +01:00
43cd80600e Merge branch 'dev' into feat/brand-logo
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-backend (pull_request) Failing after 27s
PR Checks / test-frontend (pull_request) Successful in 1m2s
2026-03-20 11:28:10 +01:00
printcalc-ci
23e1abdbbb style: apply prettier formatting 2026-03-20 09:37:56 +00:00
e575021f53 feat(front-end): new logo edited
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 22s
PR Checks / security-sast (pull_request) Successful in 35s
PR Checks / test-backend (pull_request) Failing after 34s
PR Checks / test-frontend (pull_request) Successful in 1m1s
2026-03-20 10:36:50 +01:00
7e8c89ce45 feat(front-end): new logo edited 2026-03-19 14:33:29 +01:00
a40a8df894 feat(animation logo) 2026-03-18 17:30:53 +01:00
printcalc-ci
41f36ed18a style: apply prettier formatting 2026-03-17 08:03:30 +00:00
e04189bbfe Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
PR Checks / prettier-autofix (pull_request) Successful in 14s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / build-and-push (push) Successful in 1m46s
Build and Deploy / deploy (push) Successful in 22s
2026-03-17 09:01:34 +01:00
20988e425a fix(front-end): set fallback lang
Some checks failed
Build and Deploy / build-and-push (push) Successful in 30s
Build and Deploy / deploy (push) Successful in 21s
Build and Deploy / test-backend (push) Successful in 36s
Build and Deploy / test-frontend (push) Successful in 1m5s
PR Checks / prettier-autofix (pull_request) Failing after 18s
PR Checks / security-sast (pull_request) Successful in 33s
PR Checks / test-backend (pull_request) Successful in 30s
PR Checks / test-frontend (pull_request) Successful in 1m7s
2026-03-14 20:53:50 +01:00
df63937406 feat(front-end): faster load
All checks were successful
Build and Deploy / test-backend (push) Successful in 31s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 29s
Build and Deploy / deploy (push) Successful in 19s
2026-03-14 19:28:30 +01:00
4ba408859d Merge pull request 'dev' (#48) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 34s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 19s
Reviewed-on: #48
2026-03-14 19:18:15 +01:00
996e95f93c fix(back-end): quote line items
All checks were successful
Build and Deploy / test-frontend (push) Successful in 1m8s
PR Checks / security-sast (pull_request) Successful in 33s
Build and Deploy / test-backend (push) Successful in 32s
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / test-backend (pull_request) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / build-and-push (push) Successful in 1m25s
Build and Deploy / deploy (push) Successful in 21s
2026-03-14 19:15:44 +01:00
printcalc-ci
c4bd0b5a67 style: apply prettier formatting 2026-03-14 17:58:02 +00:00
5c43873ede Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 29s
PR Checks / prettier-autofix (pull_request) Successful in 15s
Build and Deploy / test-frontend (push) Successful in 1m2s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-backend (pull_request) Successful in 30s
Build and Deploy / build-and-push (push) Successful in 18s
PR Checks / test-frontend (pull_request) Successful in 1m4s
Build and Deploy / deploy (push) Successful in 27s
2026-03-14 18:56:07 +01:00
249645619e fix(deploy): common..env
Some checks failed
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 22s
Build and Deploy / deploy (push) Successful in 19s
PR Checks / prettier-autofix (pull_request) Failing after 11s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
2026-03-14 18:52:18 +01:00
be9f303b37 fix(deploy): common..env
Some checks failed
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Failing after 6s
Build and Deploy / test-backend (push) Successful in 29s
Build and Deploy / test-frontend (push) Successful in 1m1s
2026-03-14 18:42:53 +01:00
6da8b3b6e4 feat(back-end): new translation api with openai
Some checks failed
Build and Deploy / test-backend (push) Successful in 35s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 1m20s
Build and Deploy / deploy (push) Failing after 6s
2026-03-14 18:33:51 +01:00
a3cd451575 Merge pull request 'dev' (#47) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Successful in 31s
Build and Deploy / deploy (push) Successful in 20s
Reviewed-on: #47
2026-03-14 16:13:37 +01:00
printcalc-ci
6f700c923a style: apply prettier formatting
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 58s
2026-03-14 14:15:10 +00:00
46fd59ed71 Merge branch 'main' into dev
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 12s
PR Checks / security-sast (pull_request) Successful in 31s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / test-backend (push) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / build-and-push (push) Successful in 29s
Build and Deploy / deploy (push) Successful in 22s
2026-03-14 15:14:12 +01:00
ba49463ee7 fix(front-end): seo improvements with SSR
Some checks failed
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / test-frontend (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
PR Checks / prettier-autofix (pull_request) Failing after 13s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m6s
2026-03-14 15:13:54 +01:00
576380e9a0 fix(front-end): seo translated 2026-03-14 15:02:00 +01:00
cac534ccbb Merge pull request 'dev' (#46) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 18s
Reviewed-on: #46
2026-03-13 17:44:20 +01:00
2e68105da4 Merge remote-tracking branch 'origin/dev' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 31s
PR Checks / prettier-autofix (pull_request) Successful in 10s
Build and Deploy / test-frontend (push) Successful in 1m6s
PR Checks / security-sast (pull_request) Successful in 31s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / build-and-push (push) Successful in 30s
PR Checks / test-frontend (pull_request) Successful in 1m5s
Build and Deploy / deploy (push) Successful in 24s
2026-03-13 17:41:55 +01:00
ed7ed6636d fix(front-end): al categories translated 2026-03-13 17:41:25 +01:00
e190359041 Merge pull request 'dev' (#45) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 28s
Build and Deploy / deploy (push) Successful in 20s
Reviewed-on: #45
2026-03-13 16:36:42 +01:00
printcalc-ci
bed94790d4 style: apply prettier formatting
All checks were successful
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 59s
PR Checks / prettier-autofix (pull_request) Successful in 8s
2026-03-13 15:30:28 +00:00
d8ad61ec54 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 30s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m1s
Build and Deploy / test-frontend (push) Successful in 1m4s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 21s
2026-03-13 16:28:40 +01:00
7e9a1482d6 fix(front-end): maximum budget
Some checks failed
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 30s
Build and Deploy / deploy (push) Successful in 21s
PR Checks / prettier-autofix (pull_request) Failing after 11s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / test-frontend (push) Successful in 1m1s
2026-03-13 16:24:39 +01:00
aa0adbf993 feat(back-end front-end): shop improvements
Some checks failed
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Failing after 41s
Build and Deploy / deploy (push) Has been skipped
2026-03-13 16:21:57 +01:00
00af9a9701 feat(back-end front-end): shop improvements
Some checks failed
Build and Deploy / test-backend (push) Successful in 38s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Failing after 1m15s
Build and Deploy / deploy (push) Has been skipped
2026-03-13 16:16:49 +01:00
fcdede2dd6 chore(front-end):map color
Some checks failed
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Failing after 26s
Build and Deploy / deploy (push) Has been skipped
2026-03-12 16:43:00 +01:00
5d17b23c3a chore(front-end): new seo, and improvements in shop component
Some checks failed
Build and Deploy / test-backend (push) Successful in 29s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Failing after 1m16s
Build and Deploy / deploy (push) Has been skipped
2026-03-12 16:26:36 +01:00
1ec8a43a50 Merge pull request 'dev' (#43) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 30s
Build and Deploy / test-frontend (push) Successful in 1m8s
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 20s
Reviewed-on: #43
2026-03-12 12:20:34 +01:00
96cfa91c67 Merge branch 'main' into dev
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / test-backend (push) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / test-frontend (push) Successful in 1m6s
Build and Deploy / build-and-push (push) Failing after 1m25s
Build and Deploy / deploy (push) Has been skipped
2026-03-12 12:17:42 +01:00
669ace82c0 Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / test-frontend (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
PR Checks / prettier-autofix (pull_request) Successful in 13s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m7s
2026-03-12 12:17:28 +01:00
93163ae6e8 fix(front-end): sitemap static 2026-03-12 12:17:13 +01:00
af2d506da1 Merge pull request 'dev' (#42) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Successful in 15s
Build and Deploy / deploy (push) Successful in 8s
Reviewed-on: #42
2026-03-11 17:35:12 +01:00
637541994a Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 8s
Build and Deploy / test-frontend (push) Successful in 1m1s
PR Checks / security-sast (pull_request) Successful in 31s
PR Checks / test-backend (pull_request) Successful in 26s
Build and Deploy / build-and-push (push) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 22s
2026-03-11 17:32:53 +01:00
printcalc-ci
63cd4c4f5e style: apply prettier formatting 2026-03-11 16:31:15 +00:00
fd4104da39 fix(front-end): ssr i18n fix
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 29s
Build and Deploy / deploy (push) Successful in 21s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 59s
2026-03-11 17:27:28 +01:00
5bb23fbcfa fix(front-end): ssr i18n fix
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 26s
Build and Deploy / deploy (push) Successful in 19s
2026-03-11 17:23:32 +01:00
6a22c54e9f feat(front-end): ssr i18n fix
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 30s
Build and Deploy / deploy (push) Successful in 19s
2026-03-11 17:19:26 +01:00
3ac3262e77 Merge remote-tracking branch 'origin/dev' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 27s
Build and Deploy / deploy (push) Successful in 20s
# Conflicts:
#	frontend/src/app/app.config.ts
2026-03-11 17:10:06 +01:00
18ecc07240 feat(front-end): ssr i18n fix 2026-03-11 17:09:51 +01:00
cb468492b3 Merge pull request 'feat(front-end): ssr implementation' (#41) from feat/ssr into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 52s
Build and Deploy / deploy (push) Successful in 10s
Build and Deploy / test-frontend (push) Successful in 1m0s
Reviewed-on: #41
2026-03-11 16:59:21 +01:00
379a2161ca Merge remote-tracking branch 'origin/feat/ssr' into feat/ssr
All checks were successful
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 59s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 30s
2026-03-11 16:57:07 +01:00
c47a7e28c7 feat(front-end): ssr implementation 2026-03-11 16:57:03 +01:00
printcalc-ci
502126c915 style: apply prettier formatting 2026-03-11 15:37:29 +00:00
2ace632022 feat(front-end): ssr implementation
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Failing after 57s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 27s
2026-03-11 16:37:08 +01:00
feee2b0bff Merge pull request 'dev' (#40) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 15s
Build and Deploy / deploy (push) Successful in 7s
Reviewed-on: #40
2026-03-11 15:32:14 +01:00
b7dfc53bc0 feat(front-end): update header layout
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / test-backend (push) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m4s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 48s
Build and Deploy / deploy (push) Successful in 11s
2026-03-11 15:30:21 +01:00
d77c3c7a5c feat(front-end): update header layout
Some checks failed
Build and Deploy / test-frontend (push) Successful in 1m6s
Build and Deploy / test-backend (push) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 36s
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / build-and-push (push) Has been cancelled
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m1s
2026-03-11 15:28:40 +01:00
printcalc-ci
faaa59ae01 style: apply prettier formatting 2026-03-11 14:18:47 +00:00
1bde2690b6 Merge branch 'main' into dev
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Failing after 1m41s
Build and Deploy / deploy (push) Has been skipped
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / test-backend (push) Successful in 28s
2026-03-11 15:17:52 +01:00
92fddd9e4a Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
Build and Deploy / test-frontend (push) Has been cancelled
PR Checks / prettier-autofix (pull_request) Failing after 11s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m4s
2026-03-11 15:17:32 +01:00
f9414b2db7 feat(front-end): sitemap update 2026-03-11 15:17:19 +01:00
d1e7e7eaca feat(front-end): sitemap 2026-03-11 15:07:12 +01:00
75929a15f8 Merge pull request 'fix(front-end): fix security' (#39) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 15s
Build and Deploy / deploy (push) Successful in 7s
Reviewed-on: #39
2026-03-11 11:42:50 +01:00
c0585d7107 Merge branch 'main' into dev
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 31s
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m1s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 57s
Build and Deploy / deploy (push) Successful in 12s
2026-03-11 11:39:59 +01:00
aeeed1c138 fix(front-end): fix security
Some checks failed
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / test-frontend (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 31s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m3s
2026-03-11 11:39:45 +01:00
10f05fabc9 Merge pull request 'dev' (#38) from dev into main
All checks were successful
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / deploy (push) Successful in 7s
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / build-and-push (push) Successful in 16s
Reviewed-on: #38
2026-03-11 11:05:12 +01:00
78b719d3c2 fix(front-end): fix security
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 8s
Build and Deploy / test-frontend (push) Successful in 1m5s
PR Checks / security-sast (pull_request) Successful in 36s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 23s
PR Checks / test-frontend (pull_request) Successful in 1m1s
Build and Deploy / deploy (push) Successful in 9s
2026-03-11 11:02:47 +01:00
printcalc-ci
052ade3e91 style: apply prettier formatting 2026-03-11 09:59:12 +00:00
035851133e Merge branch 'main' into dev
Some checks failed
Build and Deploy / test-backend (push) Successful in 30s
PR Checks / prettier-autofix (pull_request) Successful in 13s
Build and Deploy / test-frontend (push) Successful in 1m4s
PR Checks / security-sast (pull_request) Failing after 27s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m2s
2026-03-11 10:57:23 +01:00
1ff8883d25 fix(back-end): fix hex color
Some checks failed
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 23s
Build and Deploy / deploy (push) Successful in 8s
PR Checks / prettier-autofix (pull_request) Failing after 11s
PR Checks / security-sast (pull_request) Failing after 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m3s
2026-03-11 10:29:29 +01:00
30ada043ef fix(back-end): fix hex color
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 34s
Build and Deploy / deploy (push) Successful in 9s
2026-03-11 10:21:15 +01:00
e0aaa7c92e fix(back-end): fix hex color
All checks were successful
Build and Deploy / test-backend (push) Successful in 35s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 1m43s
Build and Deploy / deploy (push) Successful in 10s
2026-03-11 10:14:30 +01:00
edae13541f feat(back-end): rich text improvements
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 27s
Build and Deploy / deploy (push) Successful in 8s
2026-03-10 19:01:17 +01:00
d150c19f9f feat(back-end): rich text
All checks were successful
Build and Deploy / test-backend (push) Successful in 53s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 57s
Build and Deploy / deploy (push) Successful in 8s
2026-03-10 18:51:15 +01:00
71890e4cc2 feat(front-end): rich text
All checks were successful
Build and Deploy / test-backend (push) Successful in 30s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 25s
Build and Deploy / deploy (push) Successful in 9s
2026-03-10 18:48:27 +01:00
95494efcae Merge pull request 'dev' (#37) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 31s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 18s
Build and Deploy / deploy (push) Successful in 9s
Reviewed-on: #37
2026-03-10 17:43:45 +01:00
8893a80c12 feat(front-end): update media ffmpeg
All checks were successful
Build and Deploy / test-backend (push) Successful in 29s
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 33s
Build and Deploy / test-frontend (push) Successful in 1m6s
PR Checks / test-frontend (pull_request) Successful in 1m3s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / build-and-push (push) Successful in 1m40s
Build and Deploy / deploy (push) Successful in 11s
2026-03-10 17:29:38 +01:00
a4facf74d7 feat(front-end): update media ffmpeg
Some checks failed
Build and Deploy / test-backend (push) Successful in 29s
PR Checks / prettier-autofix (pull_request) Successful in 13s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-backend (pull_request) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / build-and-push (push) Failing after 1m0s
Build and Deploy / deploy (push) Has been skipped
2026-03-10 17:20:45 +01:00
ed8fd89217 Merge remote-tracking branch 'origin/dev' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / security-sast (pull_request) Successful in 31s
Build and Deploy / test-frontend (push) Successful in 1m2s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / test-backend (pull_request) Successful in 30s
Build and Deploy / build-and-push (push) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 9s
2026-03-10 15:32:59 +01:00
58869be9f7 feat(front-end): css allert fix 2026-03-10 15:32:53 +01:00
fda0174a77 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 8s
Build and Deploy / test-frontend (push) Successful in 1m5s
PR Checks / security-sast (pull_request) Successful in 36s
PR Checks / test-backend (pull_request) Successful in 29s
Build and Deploy / build-and-push (push) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 12s
2026-03-10 15:25:51 +01:00
printcalc-ci
52c6239f6d style: apply prettier formatting 2026-03-10 14:23:52 +00:00
c007cee093 feat(front-end): shop category ui
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 12s
PR Checks / security-sast (pull_request) Successful in 31s
Build and Deploy / build-and-push (push) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 13s
2026-03-10 15:23:08 +01:00
d011268d20 feat(front-end): shop category ui
Some checks failed
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Failing after 44s
Build and Deploy / deploy (push) Has been skipped
2026-03-10 15:14:10 +01:00
126d3ef4c4 feat(back-end front-end): shop route 2026-03-10 15:04:56 +01:00
3f228ef6e2 feat(back-end front-end): shop feature 2026-03-10 15:04:49 +01:00
8733184dc5 Merge branch 'feat/shop' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 31s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Successful in 38s
Build and Deploy / deploy (push) Successful in 10s
2026-03-10 12:42:42 +01:00
5f815d8a54 fix(back-end) url construction for media 2026-03-10 12:42:26 +01:00
6d491eb694 Merge pull request 'feat/shop' (#36) from feat/shop into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 50s
Build and Deploy / deploy (push) Successful in 11s
Reviewed-on: #36
2026-03-10 11:16:22 +01:00
printcalc-ci
42e0e75d70 style: apply prettier formatting 2026-03-10 10:15:56 +00:00
c24e27a9db Merge remote-tracking branch 'origin/feat/shop' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m4s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 26s
# Conflicts:
#	frontend/src/app/features/shop/shop-page.component.scss
2026-03-10 11:15:32 +01:00
3d12ae4da4 feat(front-end): ui improvements and alligment 2026-03-10 11:15:25 +01:00
printcalc-ci
b9e6916dfe style: apply prettier formatting 2026-03-10 09:54:16 +00:00
7cd9ef53b5 Merge remote-tracking branch 'origin/feat/shop' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m1s
# Conflicts:
#	frontend/src/app/features/shop/components/product-card/product-card.component.scss
#	frontend/src/app/features/shop/product-detail.component.scss
#	frontend/src/app/features/shop/shop-page.component.html
#	frontend/src/app/features/shop/shop-page.component.scss
#	frontend/src/app/features/shop/shop-page.component.ts
2026-03-10 10:53:42 +01:00
e747a9820e feat(front-end): back-office desing improvements 2026-03-10 10:53:30 +01:00
ba6940e64b Merge branch 'dev' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 58s
2026-03-10 09:21:28 +01:00
printcalc-ci
4342e9b1b1 style: apply prettier formatting 2026-03-10 07:32:17 +00:00
a212a1d8cc feat(back-end): shop ui implementation
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 19s
PR Checks / security-sast (pull_request) Successful in 34s
PR Checks / test-backend (pull_request) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 59s
2026-03-10 08:31:29 +01:00
cd0c13203f feat(back-end): category and shop implementation 2026-03-09 20:32:13 +01:00
77d7bdb265 fix(back-end): img convert
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 39s
Build and Deploy / deploy (push) Successful in 8s
2026-03-09 20:04:21 +01:00
fb5e753769 feat(back-end) shop logic 2026-03-09 19:58:53 +01:00
c953232f3c feat(back-end) entities for shop 2026-03-09 19:50:00 +01:00
cd2666d8e2 Merge pull request 'feat/shop' (#35) from feat/shop into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Successful in 28s
Build and Deploy / deploy (push) Successful in 10s
Reviewed-on: #35
2026-03-09 19:31:12 +01:00
2f34c52b6f Merge branch 'dev' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m3s
2026-03-09 19:31:06 +01:00
printcalc-ci
2606b22185 style: apply prettier formatting 2026-03-09 18:22:55 +00:00
038e1634eb Merge pull request 'fix(front-end): css file reduced' (#34) from fix/css-files into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 59s
Reviewed-on: #34
2026-03-09 19:22:33 +01:00
f03f111d5e Merge pull request 'feat/shop' (#33) from feat/shop into dev
Some checks failed
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Failing after 25s
Build and Deploy / deploy (push) Has been skipped
Reviewed-on: #33
2026-03-09 19:21:27 +01:00
aaa58346d3 fix(front-end): css file reduced 2026-03-09 19:21:18 +01:00
506762c538 Merge branch 'dev' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 57s
2026-03-09 19:19:38 +01:00
printcalc-ci
492d474c82 style: apply prettier formatting 2026-03-09 18:09:45 +00:00
225995c892 Merge pull request 'fix(front-end): css file duplicte' (#32) from fix/css-files into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 12s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m1s
Reviewed-on: #32
2026-03-09 19:09:19 +01:00
dfe109ac8d fix(front-end): css file duplicte 2026-03-09 19:08:51 +01:00
ca22c0c461 Merge pull request 'feat/shop' (#31) from feat/shop into dev
Some checks failed
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Failing after 1m32s
Build and Deploy / deploy (push) Has been skipped
Reviewed-on: #31
2026-03-09 18:52:25 +01:00
8afab3e58e Merge remote-tracking branch 'origin/feat/shop' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / test-backend (pull_request) Successful in 29s
PR Checks / test-frontend (pull_request) Successful in 1m5s
PR Checks / security-sast (pull_request) Successful in 32s
2026-03-09 18:49:34 +01:00
b4462dcd9d feat(back-end front-end):ffmpeg local 2026-03-09 18:49:25 +01:00
printcalc-ci
f598e376a6 style: apply prettier formatting 2026-03-09 17:49:19 +00:00
e8ebef926e feat(back-end front-end): traslate alt and description images
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m4s
2026-03-09 18:49:03 +01:00
85598dee3b Merge remote-tracking branch 'origin/feat/shop' into feat/shop
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 59s
2026-03-09 18:14:25 +01:00
2dbf7e9c09 fix(back-end): security problem 2026-03-09 18:14:19 +01:00
printcalc-ci
210820185b style: apply prettier formatting 2026-03-09 17:08:05 +00:00
7615b8b601 feat(front-end): admin desing improvements
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 20s
PR Checks / security-sast (pull_request) Failing after 34s
PR Checks / test-backend (pull_request) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-09 18:07:22 +01:00
17df0c6b9b feat(back-end): admin home edit image page 2026-03-09 17:44:17 +01:00
9e306ea1d1 feat(back-end): upload media 2026-03-09 16:30:00 +01:00
3ab518a9b6 Merge pull request 'dev' (#29) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 18s
Build and Deploy / deploy (push) Successful in 9s
Reviewed-on: #29
2026-03-09 09:58:45 +01:00
63804e7561 fix(back-end): nozle layer height
All checks were successful
Build and Deploy / test-backend (push) Successful in 41s
PR Checks / prettier-autofix (pull_request) Successful in 10s
Build and Deploy / test-frontend (push) Successful in 1m14s
PR Checks / security-sast (pull_request) Successful in 33s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m8s
Build and Deploy / build-and-push (push) Successful in 1m10s
Build and Deploy / deploy (push) Successful in 11s
2026-03-08 18:47:50 +01:00
0c4800443f Merge pull request 'feat/calculator-options' (#30) from feat/calculator-options into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-frontend (pull_request) Successful in 1m8s
Build and Deploy / build-and-push (push) Successful in 50s
PR Checks / prettier-autofix (pull_request) Successful in 9s
Build and Deploy / test-frontend (push) Successful in 1m2s
PR Checks / test-backend (pull_request) Successful in 25s
Build and Deploy / deploy (push) Successful in 13s
Reviewed-on: #30
2026-03-06 15:14:29 +01:00
72c0c2c098 Merge remote-tracking branch 'origin/feat/calculator-options' into feat/calculator-options
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m3s
2026-03-06 15:14:04 +01:00
b517373538 feat(back-end and front-end): calculator improvements 2026-03-06 15:13:34 +01:00
1bd6a43614 Merge pull request 'feat/calculator-options' (#28) from feat/calculator-options into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 36s
PR Checks / prettier-autofix (pull_request) Successful in 12s
Build and Deploy / test-frontend (push) Successful in 1m8s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 25s
Build and Deploy / build-and-push (push) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 13s
Reviewed-on: #28
2026-03-06 12:55:05 +01:00
printcalc-ci
575a540a70 style: apply prettier formatting 2026-03-05 20:56:22 +00:00
47c442aba9 Merge pull request 'Merge pull request 'fix(back-end): twint url' (#24) from fix/twint into main' (#27) from main into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m7s
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 26s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 13s
PR Checks / test-frontend (pull_request) Successful in 1m4s
Reviewed-on: #27
2026-03-05 21:54:27 +01:00
042c254691 Merge remote-tracking branch 'origin/feat/calculator-options' into feat/calculator-options
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 14s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m7s
# Conflicts:
#	frontend/src/app/features/checkout/checkout.component.html
2026-03-05 21:46:23 +01:00
7a699d2adf feat(back-end and front-end): calculator improvements 2026-03-05 21:46:11 +01:00
00825b1002 Merge pull request 'feat/calculator-options' (#26) from feat/calculator-options into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 28s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 50s
Build and Deploy / deploy (push) Successful in 10s
Reviewed-on: #26
2026-03-05 20:50:33 +01:00
printcalc-ci
811e0f441b style: apply prettier formatting 2026-03-05 17:31:15 +00:00
235fe7780d feat(back-end and front-end): calculator improvements
All checks were successful
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-05 18:30:37 +01:00
93b0b55f43 Merge remote-tracking branch 'origin/feat/calculator-options' into feat/calculator-options
All checks were successful
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m6s
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Successful in 32s
2026-03-05 18:30:11 +01:00
cdd0d22d9a Merge branch 'dev' into feat/calculator-options
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-05 17:43:57 +01:00
0a3510e996 Merge remote-tracking branch 'origin/feat/calculator-options' into feat/calculator-options 2026-03-05 17:29:11 +01:00
819ac01d44 Merge branch 'dev' into feat/calculator-options 2026-03-05 17:28:57 +01:00
printcalc-ci
aa6322e928 style: apply prettier formatting 2026-03-05 16:28:39 +00:00
a7491130fb chore(back-end and front-end): refractor and improvements calculator
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-05 17:28:07 +01:00
8e23bd97e6 fix(tutto rotto): dai che si fixa
Some checks failed
PR Checks / prettier-autofix (pull_request) Failing after 7s
PR Checks / test-backend (pull_request) Failing after 21s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-frontend (pull_request) Failing after 55s
2026-03-05 17:07:25 +01:00
71424f086e Merge branch 'fix/twint' into feat/calculator-options
# Conflicts:
#	backend/src/main/java/com/printcalculator/controller/admin/AdminOrderController.java
2026-03-05 17:05:45 +01:00
b2edf5ec4c fix(tutto rotto): dai che si fixa 2026-03-05 17:05:15 +01:00
8c61990827 fix(tutto rotto): 2026-03-05 16:46:24 +01:00
54b50028b1 fix(back-end): path solver
Some checks failed
PR Checks / test-frontend (pull_request) Successful in 1m1s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-backend (pull_request) Failing after 22s
PR Checks / security-sast (pull_request) Successful in 30s
2026-03-05 16:37:54 +01:00
40da5ff1b7 Merge pull request 'fix(back-end): twint url' (#24) from fix/twint into main
All checks were successful
Build and Deploy / build-and-push (push) Successful in 36s
Build and Deploy / deploy (push) Successful in 8s
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m2s
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m7s
Reviewed-on: #24
2026-03-05 15:46:48 +01:00
9facf05c10 fix(back-end): twint url
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-05 15:44:03 +01:00
fe3951b6c3 feat(front-end): calculator improvements 2026-03-05 15:43:37 +01:00
1effd4926f Merge pull request 'feat/calculator-options' (#23) from feat/calculator-options into dev
All checks were successful
Build and Deploy / build-and-push (push) Successful in 45s
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / deploy (push) Successful in 10s
Reviewed-on: #23
2026-03-05 15:14:08 +01:00
printcalc-ci
d061f21d79 style: apply prettier formatting 2026-03-05 14:07:58 +00:00
266fab5e17 feat(front-end): alt improvements
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 15s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m1s
2026-03-05 15:07:05 +01:00
a4b85b01bd feat(front-end): calculator improvements 2026-03-05 15:02:26 +01:00
30e28cb019 feat(front-end): seo 2026-03-05 15:01:56 +01:00
1a36808d9f feat(front-end and back-end): new nozle option, also fix quantity reload and reorganized service in back-end 2026-03-05 15:01:40 +01:00
8a57aa78fb Merge pull request 'dev' (#22) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Successful in 15s
Build and Deploy / deploy (push) Successful in 8s
Reviewed-on: #22
2026-03-05 09:31:09 +01:00
de9e473cca fix(front-end): button calculator
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 10s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / security-sast (pull_request) Successful in 28s
Build and Deploy / build-and-push (push) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m6s
PR Checks / test-backend (pull_request) Successful in 25s
Build and Deploy / deploy (push) Successful in 11s
2026-03-05 08:32:06 +01:00
a7f58175fa Merge remote-tracking branch 'origin/dev' into dev
Some checks failed
Build and Deploy / test-frontend (push) Has been cancelled
Build and Deploy / build-and-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
Build and Deploy / test-backend (push) Has been cancelled
2026-03-05 08:32:01 +01:00
460b878fbb fix(front-end): button calculator 2026-03-05 08:31:55 +01:00
4a8925df13 Merge pull request 'feat(back-end and front-end) 3d visualization for cad' (#21) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 18s
Build and Deploy / deploy (push) Successful in 8s
Reviewed-on: #21
2026-03-04 16:57:49 +01:00
db3619e889 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 31s
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 30s
Build and Deploy / test-frontend (push) Successful in 1m8s
PR Checks / test-backend (pull_request) Successful in 30s
Build and Deploy / build-and-push (push) Successful in 26s
Build and Deploy / deploy (push) Successful in 13s
PR Checks / test-frontend (pull_request) Successful in 1m7s
2026-03-04 16:54:25 +01:00
printcalc-ci
5e5a3949d4 style: apply prettier formatting 2026-03-04 15:53:21 +00:00
0ef97eeb9b feat(back-end and front-end) 3d visualization for cad
All checks were successful
Build and Deploy / test-backend (push) Successful in 33s
Build and Deploy / test-frontend (push) Successful in 1m5s
Build and Deploy / build-and-push (push) Successful in 43s
Build and Deploy / deploy (push) Successful in 9s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / security-sast (pull_request) Successful in 32s
PR Checks / test-frontend (pull_request) Successful in 1m6s
2026-03-04 16:49:18 +01:00
6149e4ac43 Merge pull request 'dev' (#20) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 23s
Build and Deploy / deploy (push) Successful in 10s
Reviewed-on: #20
2026-03-04 15:33:02 +01:00
printcalc-ci
57360bacd0 style: apply prettier formatting
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 59s
2026-03-04 14:21:33 +00:00
db3708aef6 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 15s
Build and Deploy / test-frontend (push) Successful in 1m7s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 28s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 12s
PR Checks / test-frontend (pull_request) Successful in 1m4s
2026-03-04 15:19:40 +01:00
2050ff35f4 feat(back-end and front-end) email
Some checks failed
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Successful in 44s
Build and Deploy / deploy (push) Successful in 9s
PR Checks / prettier-autofix (pull_request) Failing after 10s
PR Checks / security-sast (pull_request) Successful in 31s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m7s
2026-03-04 15:12:28 +01:00
038e79e52a Merge pull request 'dev' (#18) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 18s
Build and Deploy / deploy (push) Successful in 9s
Reviewed-on: #18
2026-03-04 15:03:12 +01:00
6f47d02813 feat(back-end and front-end) email
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / test-backend (pull_request) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / security-sast (pull_request) Successful in 35s
Build and Deploy / build-and-push (push) Successful in 40s
PR Checks / test-frontend (pull_request) Successful in 1m4s
Build and Deploy / deploy (push) Successful in 12s
2026-03-04 14:55:51 +01:00
3916f3ace6 feat(back-end and front-end) email for request
All checks were successful
Build and Deploy / test-backend (push) Successful in 33s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 37s
Build and Deploy / deploy (push) Successful in 9s
2026-03-04 14:45:09 +01:00
df3fecf722 Merge pull request 'dev' (#17) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 8s
Reviewed-on: #17
2026-03-04 13:54:16 +01:00
2c4fa570e1 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
PR Checks / prettier-autofix (pull_request) Successful in 11s
Build and Deploy / test-frontend (push) Successful in 1m10s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 10s
PR Checks / test-frontend (pull_request) Successful in 1m6s
2026-03-04 13:50:55 +01:00
ab2229ec8b Merge pull request 'feat/cad-bill' (#16) from feat/cad-bill into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 44s
Build and Deploy / deploy (push) Successful in 9s
PR Checks / prettier-autofix (pull_request) Successful in 11s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m7s
Reviewed-on: #16
2026-03-04 12:45:58 +01:00
cc36c0a18b Merge remote-tracking branch 'origin/feat/cad-bill' into feat/cad-bill
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 59s
2026-03-04 12:25:43 +01:00
47e22c5a61 feat(back-end and front-end) email for request 2026-03-04 12:25:23 +01:00
c2161ef1fc Merge branch 'dev' into feat/cad-bill
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-04 12:16:52 +01:00
printcalc-ci
8f6e74cf02 style: apply prettier formatting 2026-03-04 11:15:52 +00:00
767b65008b feat(back-end and front-end) email for request
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 18s
PR Checks / security-sast (pull_request) Successful in 33s
PR Checks / test-backend (pull_request) Successful in 27s
PR Checks / test-frontend (pull_request) Successful in 1m8s
2026-03-04 12:15:23 +01:00
1b3f0b16ff feat(back-end and front-end) cad bill with order 2026-03-04 12:03:09 +01:00
d9931a6fae Merge pull request 'dev' (#15) from dev into main
All checks were successful
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 9s
Reviewed-on: #15
2026-03-04 11:02:43 +01:00
179be37a36 Merge branch 'main' into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 27s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Successful in 33s
Build and Deploy / test-frontend (push) Successful in 1m4s
PR Checks / test-backend (pull_request) Successful in 26s
Build and Deploy / build-and-push (push) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m3s
Build and Deploy / deploy (push) Successful in 12s
2026-03-04 11:00:18 +01:00
printcalc-ci
412f3ae71b style: apply prettier formatting 2026-03-04 09:59:05 +00:00
0f2f2bc7a9 fix(back-end): 3mf preview
All checks were successful
Build and Deploy / test-backend (push) Successful in 24s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Successful in 23s
Build and Deploy / deploy (push) Successful in 8s
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Successful in 29s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 1m0s
2026-03-04 10:26:40 +01:00
685cd704e7 fix(back-end): 3mf preview
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m3s
Build and Deploy / build-and-push (push) Successful in 44s
Build and Deploy / deploy (push) Successful in 8s
2026-03-04 10:23:25 +01:00
09179ce825 fix(back-end): fix 3mf calculator
All checks were successful
Build and Deploy / test-backend (push) Successful in 42s
Build and Deploy / test-frontend (push) Successful in 1m13s
Build and Deploy / build-and-push (push) Successful in 32s
Build and Deploy / deploy (push) Successful in 8s
2026-03-04 09:59:25 +01:00
27d0399263 fix(back-end): fix 3mf calculator
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 1m1s
Build and Deploy / build-and-push (push) Successful in 1m32s
Build and Deploy / deploy (push) Successful in 11s
2026-03-04 09:52:09 +01:00
0f57034b52 fix(back-end): fix 3mf calculator
Some checks failed
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / test-frontend (push) Successful in 59s
Build and Deploy / build-and-push (push) Failing after 18s
Build and Deploy / deploy (push) Has been skipped
2026-03-04 09:49:03 +01:00
db748fb649 fix(back-end): fix 3mf calculator
Some checks failed
Build and Deploy / test-backend (push) Successful in 24s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Failing after 14s
Build and Deploy / deploy (push) Has been skipped
2026-03-04 09:24:21 +01:00
6eb0629136 fix(back-end): fix 3mf calculator
Some checks failed
Build and Deploy / test-backend (push) Successful in 55s
Build and Deploy / test-frontend (push) Successful in 1m10s
Build and Deploy / build-and-push (push) Failing after 14s
Build and Deploy / deploy (push) Has been skipped
2026-03-04 09:21:07 +01:00
8bd4ea54b2 fix(back-end): fix 3mf calculator
Some checks failed
Build and Deploy / test-backend (push) Successful in 24s
Build and Deploy / test-frontend (push) Successful in 1m2s
Build and Deploy / build-and-push (push) Failing after 55s
Build and Deploy / deploy (push) Has been skipped
2026-03-03 18:48:59 +01:00
d951212576 Merge pull request 'dev' (#13) from dev into main
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
Build and Deploy / test-frontend (push) Successful in 1m4s
Build and Deploy / build-and-push (push) Successful in 17s
Build and Deploy / deploy (push) Successful in 8s
Reviewed-on: #13
2026-03-03 18:28:30 +01:00
e23bca0734 fix(back-end): fix security issue
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 32s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / test-backend (pull_request) Successful in 26s
Build and Deploy / build-and-push (push) Successful in 37s
PR Checks / test-frontend (pull_request) Successful in 1m6s
Build and Deploy / deploy (push) Successful in 13s
2026-03-03 18:26:03 +01:00
f5cdaf51cb fix(back-end): fix security issue
Some checks failed
Build and Deploy / test-backend (push) Successful in 25s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / security-sast (pull_request) Failing after 29s
Build and Deploy / test-frontend (push) Successful in 1m5s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 37s
PR Checks / test-frontend (pull_request) Successful in 1m5s
Build and Deploy / deploy (push) Successful in 12s
2026-03-03 18:19:15 +01:00
476dc5b2ce fix(back-end): add extended support for 3MF conversion
Some checks failed
PR Checks / test-frontend (pull_request) Successful in 1m7s
Build and Deploy / test-backend (push) Successful in 35s
PR Checks / prettier-autofix (pull_request) Successful in 11s
Build and Deploy / test-frontend (push) Successful in 1m7s
PR Checks / security-sast (pull_request) Failing after 30s
PR Checks / test-backend (pull_request) Successful in 24s
Build and Deploy / build-and-push (push) Successful in 1m31s
Build and Deploy / deploy (push) Successful in 11s
2026-03-03 18:13:15 +01:00
548b23317f fix(chore): translation 2026-03-03 17:20:36 +01:00
9d40e74baf Merge pull request 'fix(deploy): new test' (#14) from prova into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 26s
PR Checks / security-sast (pull_request) Successful in 32s
Build and Deploy / test-frontend (push) Successful in 1m3s
PR Checks / test-backend (pull_request) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 25s
PR Checks / test-frontend (pull_request) Successful in 1m4s
Build and Deploy / deploy (push) Successful in 13s
PR Checks / prettier-autofix (pull_request) Successful in 10s
Reviewed-on: #14
2026-03-03 13:56:30 +01:00
b39fa0b693 Merge branch 'dev' into prova
All checks were successful
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 26s
PR Checks / prettier-autofix (pull_request) Successful in 8s
PR Checks / test-frontend (pull_request) Successful in 1m2s
2026-03-03 13:41:38 +01:00
printcalc-ci
f3ea2be8b0 style: apply prettier formatting 2026-03-03 12:41:15 +00:00
c66903d22e fix(deploy): new test
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 24s
PR Checks / test-frontend (pull_request) Successful in 1m6s
2026-03-03 13:39:50 +01:00
4bc815b04d Merge pull request 'fix(deploy): prova workflow' (#12) from prova into dev
All checks were successful
Build and Deploy / test-backend (push) Successful in 25s
Build and Deploy / build-and-push (push) Successful in 42s
Build and Deploy / deploy (push) Successful in 10s
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Successful in 28s
PR Checks / test-backend (pull_request) Successful in 24s
Reviewed-on: #12
2026-03-03 13:24:02 +01:00
127a321621 fix(deploy): fix security
All checks were successful
PR Checks / prettier-autofix (pull_request) Successful in 9s
PR Checks / security-sast (pull_request) Successful in 30s
PR Checks / test-backend (pull_request) Successful in 25s
2026-03-03 13:22:45 +01:00
27af5f7ebb fix(deploy): prova workflow
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Failing after 27s
PR Checks / test-backend (pull_request) Successful in 38s
2026-03-03 13:02:24 +01:00
edef17d0ad Merge remote-tracking branch 'origin/prova' into prova
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / security-sast (pull_request) Failing after 21s
PR Checks / test-backend (pull_request) Successful in 37s
# Conflicts:
#	.gitea/workflows/pr-checks.yaml
2026-03-03 13:00:28 +01:00
76f648e82a fix(deploy): prova workflow 2026-03-03 13:00:14 +01:00
printcalc-ci
b68d702a3d style: apply prettier formatting 2026-03-03 11:58:34 +00:00
173a6b70d2 fix(deploy): prova workflow
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 10s
PR Checks / security-sast (pull_request) Failing after 29s
PR Checks / test-backend (pull_request) Successful in 57s
2026-03-03 12:58:21 +01:00
c08add9b1e Merge remote-tracking branch 'origin/prova' into prova
Some checks failed
PR Checks / prettier-autofix (pull_request) Successful in 7s
PR Checks / qodana (pull_request) Failing after 8s
PR Checks / test-backend (pull_request) Successful in 39s
2026-03-03 12:55:20 +01:00
3ea8c1b2ad fix(deploy): prova workflow 2026-03-03 12:55:09 +01:00
printcalc-ci
20293cc044 style: apply prettier formatting 2026-03-03 11:46:26 +00:00
dd6f723271 fix(deploy): prova workflow
Some checks failed
PR Checks / qodana (pull_request) Failing after 32s
PR Checks / prettier-autofix (pull_request) Successful in 39s
PR Checks / test-backend (pull_request) Successful in 38s
2026-03-03 12:45:23 +01:00
c28d22ccdb fix(deploy): new worlfkow gitea
All checks were successful
Build and Deploy / test-backend (push) Successful in 38s
Build and Deploy / build-and-push (push) Successful in 15s
Build and Deploy / deploy (push) Successful in 8s
2026-03-03 12:43:20 +01:00
3abe90d8f3 fix(deploy): new worlfkow gitea 2026-03-03 12:42:23 +01:00
04cbf00a2d Merge branch 'main' into dev
All checks were successful
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 18s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / deploy (push) Successful in 10s
Build, Test, Deploy and Analysis / qodana (push) Has been skipped
# Conflicts:
#	.gitea/workflows/cicd.yaml
2026-03-03 12:35:11 +01:00
be8e523574 fix(deploy): cicd.yaml
Some checks failed
Build, Test, Deploy and Analysis / qodana (pull_request) Failing after 12s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Has been skipped
Build, Test, Deploy and Analysis / deploy (pull_request) Has been skipped
Build, Test, Deploy and Analysis / test-backend (pull_request) Successful in 39s
Build, Test, Deploy and Analysis / qodana (push) Has been skipped
Build, Test, Deploy and Analysis / test-backend (push) Has been skipped
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 17s
Build, Test, Deploy and Analysis / deploy (push) Successful in 8s
2026-03-03 12:33:18 +01:00
c680486157 fix(front-end): quantity changes and normalization
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 12s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 37s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 25s
Build, Test, Deploy and Analysis / deploy (push) Successful in 10s
2026-03-03 12:05:00 +01:00
90bdb5384d fix(front-end): color selector
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 17s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 41s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 23s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
2026-03-03 11:49:40 +01:00
04ecda99ee fix(chore):
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 13s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 34s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
2026-03-03 11:16:40 +01:00
2d1a783e9c fix(chore): prepare Qodana dirs
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 56s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 51s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 17s
Build, Test, Deploy and Analysis / deploy (push) Successful in 8s
2026-03-03 10:30:43 +01:00
ef2be1a3be Merge pull request 'dev' (#10) from dev into main
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 8s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 39s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 15s
Build, Test, Deploy and Analysis / deploy (push) Successful in 8s
Reviewed-on: #10
2026-03-03 10:26:24 +01:00
5c27d4d16b feat(back-end): pla tought
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 8s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 37s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 41s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
2026-03-03 10:24:21 +01:00
654aa775db fix(back-end): implementation of security better
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 12s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 37s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 41s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
2026-03-03 10:11:12 +01:00
9307ba6fba Merge pull request 'produzione 1' (#9) from dev into main
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 8s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 14s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / deploy (push) Successful in 7s
Reviewed-on: #9
2026-03-03 09:58:03 +01:00
c0d6b480c1 fix(back-end):
Some checks failed
Build, Test, Deploy and Analysis / qodana (push) Failing after 8s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 38s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 16s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
2026-03-03 09:57:39 +01:00
fdb1bcb282 fix(back-end): noozle diameter
Some checks failed
Build, Test, Deploy and Analysis / test-backend (push) Successful in 54s
Build, Test, Deploy and Analysis / qodana (push) Failing after 1m12s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 37s
Build, Test, Deploy and Analysis / deploy (push) Successful in 9s
Build, Test, Deploy and Analysis / qodana (pull_request) Failing after 11s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Has been cancelled
Build, Test, Deploy and Analysis / deploy (pull_request) Has been cancelled
Build, Test, Deploy and Analysis / test-backend (pull_request) Has been cancelled
2026-03-03 09:44:35 +01:00
09a4ac064f fix(back-end) security issue
Some checks failed
Build, Test, Deploy and Analysis / test-backend (pull_request) Successful in 39s
Build, Test, Deploy and Analysis / build-and-push (push) Failing after 34s
Build, Test, Deploy and Analysis / deploy (pull_request) Successful in 8s
Build, Test, Deploy and Analysis / qodana (push) Failing after 8s
Build, Test, Deploy and Analysis / qodana (pull_request) Failing after 11s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 40s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Successful in 20s
Build, Test, Deploy and Analysis / deploy (push) Has been skipped
2026-03-03 09:30:22 +01:00
c00ca5a32e feat(chore): added qodana analysis job
Some checks failed
Build, Test, Deploy and Analysis / test-backend (pull_request) Failing after 0s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Has been skipped
Build, Test, Deploy and Analysis / deploy (pull_request) Has been skipped
Build, Test, Deploy and Analysis / qodana (pull_request) Failing after 0s
Build, Test, Deploy and Analysis / qodana (push) Failing after 32s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 1m30s
Build, Test, Deploy and Analysis / build-and-push (push) Successful in 42s
Build, Test, Deploy and Analysis / deploy (push) Successful in 8s
2026-03-03 09:19:05 +01:00
9955f23f31 feat(chore): added qodana analysis job
Some checks failed
Build, Test, Deploy and Analysis / qodana (pull_request) Waiting to run
Build, Test, Deploy and Analysis / qodana (push) Failing after 5m7s
Build, Test, Deploy and Analysis / test-backend (push) Successful in 40s
Build, Test, Deploy and Analysis / test-backend (pull_request) Failing after 0s
Build, Test, Deploy and Analysis / build-and-push (pull_request) Has been skipped
Build, Test, Deploy and Analysis / deploy (pull_request) Has been skipped
Build, Test, Deploy and Analysis / build-and-push (push) Failing after 0s
Build, Test, Deploy and Analysis / deploy (push) Has been skipped
2026-03-03 09:04:12 +01:00
25afb355b4 feat(front-end): make responsive back-office 2026-03-03 08:43:13 +01:00
b7c399e3cb feat(back-end): new stock db and back-office improvements 2026-03-02 20:19:19 +01:00
02e58ea00f fix(home): reordered calculator and capabilities sections
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 56s
Build, Test and Deploy / build-and-push (push) Successful in 25s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-03-02 19:44:55 +01:00
819e00e067 feat(back-end): fix order page
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 47s
Build, Test and Deploy / build-and-push (push) Successful in 25s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-27 20:22:18 +01:00
ed76b13e4c feat(back-end and front-end): back-office pazzo
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 44s
Build, Test and Deploy / build-and-push (push) Successful in 46s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-27 15:46:41 +01:00
47553ebb82 feat(back-end and front-end): back-office test 2026-02-27 15:07:39 +01:00
949770a741 feat(back-end and front-end): back-office 2026-02-27 15:07:32 +01:00
65e1ee3be6 feat(back-end and front-end): back-office
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 37s
Build, Test and Deploy / build-and-push (push) Successful in 1m4s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-27 12:51:48 +01:00
3f938db257 feat(back-end and front-end): back-office
Some checks failed
Build, Test and Deploy / test-backend (push) Failing after 38s
Build, Test and Deploy / build-and-push (push) Has been skipped
Build, Test and Deploy / deploy (push) Has been skipped
2026-02-27 12:44:06 +01:00
1598f35c08 feat(back-end): email improvements
Some checks failed
Build, Test and Deploy / test-backend (push) Failing after 34s
Build, Test and Deploy / deploy (push) Has been skipped
Build, Test and Deploy / build-and-push (push) Has been skipped
2026-02-27 12:09:50 +01:00
8e9afbf260 feat(back-end): email improvements
Some checks failed
Build, Test and Deploy / build-and-push (push) Has been cancelled
Build, Test and Deploy / deploy (push) Has been cancelled
Build, Test and Deploy / test-backend (push) Has been cancelled
2026-02-27 12:09:37 +01:00
6e52988cdd feat(front-end): delay on update
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 36s
Build, Test and Deploy / build-and-push (push) Successful in 22s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-27 11:50:17 +01:00
2e701d5597 feat(front-end): traslation de and fr4
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 24s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-27 11:37:34 +01:00
150faf3b5a feat(front-end): traslation de and fr
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 40s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-27 11:32:14 +01:00
64cd90eabc feat(front-end): traslation en 2026-02-27 11:10:45 +01:00
877171ceb1 feat(front-end): traslation in italian 2026-02-27 11:05:20 +01:00
521009de7c feat(front-end): gestione quantità massima e price for piece 2026-02-27 10:59:02 +01:00
8028e9c7b7 feat(back-end): model dimension 2026-02-27 10:43:59 +01:00
a85c57032d fix(front-end): calculator improvements 2026-02-27 10:43:06 +01:00
219b4e127d fix(front-end): calculator improvements in base quality settings
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 37s
Build, Test and Deploy / build-and-push (push) Successful in 23s
Build, Test and Deploy / deploy (push) Successful in 11s
2026-02-27 10:22:32 +01:00
f7ddab0e93 fix(front-end): red write and last commit for today
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 22s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-26 22:30:34 +01:00
80a41b0cc2 feat(frontend e back-end): termini e condizioni
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 38s
Build, Test and Deploy / build-and-push (push) Successful in 1m3s
Build, Test and Deploy / deploy (push) Successful in 11s
2026-02-26 22:24:51 +01:00
6f3e601f21 feat(frontend): about improvments
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 24s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-26 21:42:12 +01:00
e82862821e feat(frontend): fav icon e upload multiple files
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 35s
Build, Test and Deploy / build-and-push (push) Successful in 40s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-26 19:41:40 +01:00
b6230e69e4 feat(frontend): update images, and desing home
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 42s
Build, Test and Deploy / build-and-push (push) Successful in 42s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-26 19:09:59 +01:00
c58d674a70 feat(back-end - front end): improvements in email and invoice rendering
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 37s
Build, Test and Deploy / build-and-push (push) Successful in 45s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-25 16:35:58 +01:00
fecb394272 fix(back-end) calculator improvements
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 41s
Build, Test and Deploy / build-and-push (push) Successful in 40s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-25 15:05:23 +01:00
54d12f4da0 fix(front-end): improvements
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 41s
Build, Test and Deploy / build-and-push (push) Successful in 49s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-25 10:53:08 +01:00
4ddd33662d fix(front-end): 3d view only for stl
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 33s
Build, Test and Deploy / build-and-push (push) Successful in 21s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-24 13:12:29 +01:00
a6eae757c5 feat(front-end): upload only supported file and add step warning message
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 40s
Build, Test and Deploy / build-and-push (push) Successful in 24s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-24 13:06:16 +01:00
6463fac211 fix(back-end): email fix
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 34s
Build, Test and Deploy / build-and-push (push) Successful in 40s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-24 10:47:13 +01:00
57f6301e03 feat(back-end front-end): integration with twint
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 34s
Build, Test and Deploy / build-and-push (push) Successful in 43s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-24 10:07:04 +01:00
699a968875 feat(back-end front-end): upgrade to the order componen instead of payment and order-confirmed
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 39s
Build, Test and Deploy / build-and-push (push) Successful in 1m3s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-24 08:44:42 +01:00
c1652798b4 feat(back-end front-end): new UX for
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 32s
Build, Test and Deploy / build-and-push (push) Successful in 39s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-23 18:56:24 +01:00
ec4d512136 feat(back-end front-end): uuid truncated for better UX
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 36s
Build, Test and Deploy / build-and-push (push) Successful in 41s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-23 17:30:43 +01:00
abf47e0003 feat(back-end): email service and test
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 33s
Build, Test and Deploy / build-and-push (push) Successful in 33s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-23 16:20:11 +01:00
0438ba3ae5 feat(back-end): email service and test
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 49s
Build, Test and Deploy / build-and-push (push) Successful in 55s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-23 15:23:11 +01:00
c3f9539988 feat(front-enc):
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 34s
Build, Test and Deploy / build-and-push (push) Successful in 23s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-20 17:47:34 +01:00
1d82230564 feat(front-enc): fix back-ground
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 37s
Build, Test and Deploy / build-and-push (push) Successful in 39s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-20 17:11:44 +01:00
15d5d31d06 feat(back-end, front-enc): twint payment 2026-02-20 17:09:42 +01:00
ccc53b7d4f feat(back-end): bill and qr
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 33s
Build, Test and Deploy / build-and-push (push) Successful in 1m8s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-20 14:54:28 +01:00
8e12b3bcdf feat(back-end): add ClamAV service remember to add env and compose.deploy on server 2026-02-20 10:32:07 +01:00
0d23521cac feat(back-end): add ClamAV service remember to add env and compose.deploy on server 2026-02-19 15:59:33 +01:00
2189e58cc6 fix(deploy): update env
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 26s
Build, Test and Deploy / build-and-push (push) Successful in 13s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-18 22:04:22 +01:00
87f43f2239 fix(deploy): update env
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 27s
Build, Test and Deploy / build-and-push (push) Successful in 13s
Build, Test and Deploy / deploy (push) Failing after 9s
2026-02-18 22:00:06 +01:00
0ddfed4f07 fix(back-end): try fix profile manager
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 26s
Build, Test and Deploy / build-and-push (push) Successful in 14s
Build, Test and Deploy / deploy (push) Failing after 5s
2026-02-18 21:57:23 +01:00
e7daf79394 fix(back-end): try fix profile manager
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 26s
Build, Test and Deploy / build-and-push (push) Successful in 14s
Build, Test and Deploy / deploy (push) Failing after 5s
2026-02-18 21:53:34 +01:00
7bb94da45b fix(back-end): try fix profile manager
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 28s
Build, Test and Deploy / build-and-push (push) Successful in 17s
Build, Test and Deploy / deploy (push) Failing after 4s
2026-02-18 21:48:09 +01:00
d28609ee95 fix(back-end): try fix profile manager
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 28s
Build, Test and Deploy / build-and-push (push) Successful in 20s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-18 20:07:41 +01:00
8364ad0671 fix(back-end): try fix profile manager
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 27s
Build, Test and Deploy / build-and-push (push) Successful in 18s
Build, Test and Deploy / deploy (push) Failing after 7s
2026-02-18 19:50:21 +01:00
797b10e4ad fix(back-end): try fix profile manager
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 27s
Build, Test and Deploy / build-and-push (push) Has been cancelled
Build, Test and Deploy / deploy (push) Has been cancelled
2026-02-18 19:49:56 +01:00
ec77b76abb fix(back-end): try fix profile manager
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 25s
Build, Test and Deploy / build-and-push (push) Successful in 28s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-18 19:33:57 +01:00
bb269d84a5 fix(back-end): shift model
Some checks failed
Build, Test and Deploy / build-and-push (push) Has been cancelled
Build, Test and Deploy / deploy (push) Has been cancelled
Build, Test and Deploy / test-backend (push) Has been cancelled
2026-02-17 16:34:40 +01:00
46eb980e24 fix(back-end): shift model
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m13s
Build, Test and Deploy / build-and-push (push) Successful in 28s
Build, Test and Deploy / deploy (push) Successful in 7s
2026-02-17 16:32:46 +01:00
85a4db1630 fix(back-end): shift model
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m13s
Build, Test and Deploy / build-and-push (push) Successful in 29s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-17 16:25:21 +01:00
701a10e886 fix(back-end): shift model
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 2m31s
Build, Test and Deploy / build-and-push (push) Successful in 1m47s
Build, Test and Deploy / deploy (push) Successful in 9s
2026-02-17 15:35:17 +01:00
2eea773ee2 feat(back-end): files saved in disc
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m13s
Build, Test and Deploy / build-and-push (push) Successful in 16s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-12 21:25:29 +01:00
44f9408b22 feat(back-end & front-end): add session file
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m14s
Build, Test and Deploy / build-and-push (push) Successful in 39s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-12 19:24:58 +01:00
044fba8d5a feat(back-end & front-end): checkout, update form structure, add new DTOs, refactor order logic
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m15s
Build, Test and Deploy / build-and-push (push) Successful in 39s
Build, Test and Deploy / deploy (push) Successful in 10s
2026-02-12 19:01:48 +01:00
257c60fa5e feat(back-end): refactor session creation
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m13s
Build, Test and Deploy / build-and-push (push) Successful in 56s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-12 17:07:26 +01:00
5a84fb13c0 feat(back-end): cors config update
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m12s
Build, Test and Deploy / build-and-push (push) Successful in 35s
Build, Test and Deploy / deploy (push) Successful in 8s
2026-02-12 16:10:10 +01:00
89d84ed369 feat(back-end & web): improvements
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m12s
Build, Test and Deploy / build-and-push (push) Successful in 21s
Build, Test and Deploy / deploy (push) Successful in 7s
2026-02-12 16:02:03 +01:00
9c3d5fae12 feat(back-end & ): removed Abstract repository
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 1m12s
Build, Test and Deploy / build-and-push (push) Successful in 38s
Build, Test and Deploy / deploy (push) Has been cancelled
2026-02-12 16:00:03 +01:00
96ae9bb609 feat(back-end): removed Abstract repository
All checks were successful
Build, Test and Deploy / test-backend (push) Successful in 1m12s
Build, Test and Deploy / build-and-push (push) Successful in 29s
Build, Test and Deploy / deploy (push) Successful in 6s
2026-02-12 15:32:31 +01:00
9cbd856ab6 feat(back-end): new db for custom quote requests
Some checks failed
Build, Test and Deploy / test-backend (push) Failing after 42s
Build, Test and Deploy / build-and-push (push) Has been skipped
Build, Test and Deploy / deploy (push) Has been skipped
2026-02-12 15:26:58 +01:00
449 changed files with 80205 additions and 3712 deletions

View File

@@ -1,11 +1,11 @@
name: Build, Test and Deploy
name: Build and Deploy
on:
push:
branches: [main, int, dev]
concurrency:
group: print-calculator-${{ gitea.ref }}
group: print-calculator-deploy-${{ gitea.ref }}
cancel-in-progress: true
jobs:
@@ -18,18 +18,9 @@ jobs:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('backend/gradle/wrapper/gradle-wrapper.properties', 'backend/**/*.gradle*', 'backend/gradle.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
java-version: "21"
distribution: "temurin"
cache: gradle
- name: Run Tests with Gradle
run: |
@@ -37,8 +28,55 @@ jobs:
chmod +x gradlew
./gradlew test
test-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: "frontend/package-lock.json"
- name: Resolve Chrome binary
shell: bash
run: |
set -euo pipefail
if command -v chromium >/dev/null 2>&1; then
CHROME_PATH="$(command -v chromium)"
elif command -v chromium-browser >/dev/null 2>&1; then
CHROME_PATH="$(command -v chromium-browser)"
elif command -v google-chrome >/dev/null 2>&1; then
CHROME_PATH="$(command -v google-chrome)"
else
apt-get update
apt-get install -y --no-install-recommends chromium
CHROME_PATH="$(command -v chromium)"
fi
echo "CHROME_BIN=$CHROME_PATH" >> "$GITHUB_ENV"
echo "Using CHROME_BIN=$CHROME_PATH"
- name: Install frontend dependencies
shell: bash
run: |
cd frontend
npm ci --no-audit --no-fund --prefer-offline
- name: Run frontend tests (headless)
shell: bash
env:
CI: "true"
run: |
cd frontend
echo "Karma CHROME_BIN=$CHROME_BIN"
npm run test -- --watch=false --browsers=ChromeHeadlessNoSandbox
build-and-push:
needs: test-backend
needs: [test-backend, test-frontend]
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -87,6 +125,18 @@ jobs:
docker build -t "$FRONTEND_IMAGE" ./frontend
docker push "$FRONTEND_IMAGE"
- name: Cleanup Docker on runner (prevent vdisk growth)
if: always()
shell: bash
run: |
set +e
# Keep recent artifacts, drop old local residue from CI builds.
docker container prune -f --filter "until=168h" || true
docker image prune -a -f --filter "until=168h" || true
docker builder prune -a -f --filter "until=168h" || true
docker network prune -f --filter "until=168h" || true
deploy:
needs: build-and-push
runs-on: ubuntu-latest
@@ -116,32 +166,33 @@ jobs:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# 1) Prende il secret base64 e rimuove spazi/newline/CR
printf '%s' "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | tr -d '\r\n\t ' > /tmp/key.b64
# 2) (debug sicuro) stampa solo la lunghezza della base64
echo "b64_len=$(wc -c < /tmp/key.b64)"
# 3) Decodifica in chiave privata
base64 -d /tmp/key.b64 > ~/.ssh/id_ed25519
# 4) Rimuove eventuali CRLF dentro la chiave (se proviene da Windows)
tr -d '\r' < ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.clean
mv ~/.ssh/id_ed25519.clean ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
# 5) Validazione: se fallisce qui, la chiave NON è valida/corrotta
ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null
ssh-keyscan -H "${{ secrets.SERVER_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null
- name: Write env to server
- name: Write env and compose to server
shell: bash
run: |
# 1. Start with the static env file content
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
DEPLOY_TAG="prod"
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
DEPLOY_TAG="int"
else
DEPLOY_TAG="dev"
fi
DEPLOY_OWNER=$(echo '${{ gitea.repository_owner }}' | tr '[:upper:]' '[:lower:]')
cat "deploy/envs/${{ env.ENV }}.env" > /tmp/full_env.env
# 2. Determine DB credentials
if [[ "${{ env.ENV }}" == "prod" ]]; then
DB_URL="${{ secrets.DB_URL_PROD }}"
DB_USER="${{ secrets.DB_USERNAME_PROD }}"
@@ -156,25 +207,31 @@ jobs:
DB_PASS="${{ secrets.DB_PASSWORD_DEV }}"
fi
# 3. Append DB credentials
printf '\nDB_URL=%s\nDB_USERNAME=%s\nDB_PASSWORD=%s\n' \
printf '\nDB_URL="%s"\nDB_USERNAME="%s"\nDB_PASSWORD="%s"\n' \
"$DB_URL" "$DB_USER" "$DB_PASS" >> /tmp/full_env.env
# 4. Debug: print content (for debug purposes)
echo "Preparing to send env file with variables:"
grep -v "PASSWORD" /tmp/full_env.env || true
printf 'REGISTRY_URL="%s"\nREPO_OWNER="%s"\nTAG="%s"\n' \
"${{ secrets.REGISTRY_URL }}" "$DEPLOY_OWNER" "$DEPLOY_TAG" >> /tmp/full_env.env
ADMIN_TTL="${{ secrets.ADMIN_SESSION_TTL_MINUTES }}"
ADMIN_TTL="${ADMIN_TTL:-480}"
printf 'ADMIN_PASSWORD="%s"\nADMIN_SESSION_SECRET="%s"\nADMIN_SESSION_TTL_MINUTES="%s"\n' \
"${{ secrets.ADMIN_PASSWORD }}" "${{ secrets.ADMIN_SESSION_SECRET }}" "$ADMIN_TTL" >> /tmp/full_env.env
if [[ -n "${{ secrets.OPENAI_API_KEY }}" ]]; then
printf 'OPENAI_API_KEY="%s"\n' "${{ secrets.OPENAI_API_KEY }}" >> /tmp/full_env.env
fi
echo "Preparing to send env file with variables:"
grep -Ev "PASSWORD|SECRET|KEY|TOKEN" /tmp/full_env.env || true
# 5. Send to server
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" \
"setenv ${{ env.ENV }}" < /tmp/full_env.env
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" \
"setcompose ${{ env.ENV }}" < docker-compose.deploy.yml
- name: Trigger deploy on Unraid (forced command key)
shell: bash
run: |
set -euo pipefail
# Aggiungiamo le opzioni di verbosità se dovesse fallire ancora,
# e assicuriamoci che l'input sia pulito
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" "deploy ${{ env.ENV }}"

View File

@@ -0,0 +1,185 @@
name: PR Checks
on:
pull_request:
branches: [main, int, dev]
concurrency:
group: print-calculator-pr-${{ gitea.ref }}
cancel-in-progress: true
jobs:
prettier-autofix:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Apply formatting with Prettier
shell: bash
run: |
npx --yes prettier@3.6.2 --write \
"frontend/src/**/*.{ts,html,scss,css,json}" \
".gitea/workflows/*.{yml,yaml}"
- name: Commit and push formatting changes
shell: bash
run: |
if git diff --quiet; then
echo "No formatting changes to commit."
exit 0
fi
if ! command -v jq >/dev/null 2>&1; then
apt-get update
apt-get install -y --no-install-recommends jq
fi
EVENT_FILE="${GITHUB_EVENT_PATH:-}"
if [[ -z "$EVENT_FILE" || ! -f "$EVENT_FILE" ]]; then
echo "Event payload not found, skipping auto-push."
exit 0
fi
HEAD_REPO="$(jq -r '.pull_request.head.repo.full_name // empty' "$EVENT_FILE")"
BASE_REPO="$(jq -r '.repository.full_name // empty' "$EVENT_FILE")"
PR_BRANCH="$(jq -r '.pull_request.head.ref // empty' "$EVENT_FILE")"
if [[ -z "$PR_BRANCH" ]]; then
echo "PR branch not found in event payload, skipping auto-push."
exit 0
fi
if [[ -n "$HEAD_REPO" && -n "$BASE_REPO" && "$HEAD_REPO" != "$BASE_REPO" ]]; then
echo "PR from fork ($HEAD_REPO), skipping auto-push."
exit 0
fi
git config user.name "printcalc-ci"
git config user.email "ci@printcalculator.local"
git add frontend/src .gitea/workflows
git commit -m "style: apply prettier formatting"
git push origin "HEAD:${PR_BRANCH}"
security-sast:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Python and Semgrep
shell: bash
run: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
python3 -m pip install --upgrade pip
python3 -m pip install semgrep
- name: Run Semgrep (SAST)
shell: bash
run: |
semgrep --version
semgrep --config auto --error \
--exclude frontend/node_modules \
--exclude backend/build \
backend/src frontend/src
- name: Install Gitleaks
shell: bash
run: |
set -euo pipefail
VERSION="8.24.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \
-o /tmp/gitleaks.tar.gz
tar -xzf /tmp/gitleaks.tar.gz -C /tmp
install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks
gitleaks version
- name: Run Gitleaks (secrets scan)
shell: bash
run: |
set +e
gitleaks detect --source . --no-git --redact --exit-code 1 \
--report-format json --report-path /tmp/gitleaks-report.json
rc=$?
if [[ $rc -ne 0 ]]; then
echo "Gitleaks findings:"
cat /tmp/gitleaks-report.json
fi
exit $rc
test-backend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"
cache: gradle
- name: Run Tests with Gradle
run: |
cd backend
chmod +x gradlew
./gradlew test
test-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: "frontend/package-lock.json"
- name: Resolve Chrome binary
shell: bash
run: |
set -euo pipefail
if command -v chromium >/dev/null 2>&1; then
CHROME_PATH="$(command -v chromium)"
elif command -v chromium-browser >/dev/null 2>&1; then
CHROME_PATH="$(command -v chromium-browser)"
elif command -v google-chrome >/dev/null 2>&1; then
CHROME_PATH="$(command -v google-chrome)"
else
apt-get update
apt-get install -y --no-install-recommends chromium
CHROME_PATH="$(command -v chromium)"
fi
echo "CHROME_BIN=$CHROME_PATH" >> "$GITHUB_ENV"
echo "Using CHROME_BIN=$CHROME_PATH"
- name: Install frontend dependencies
shell: bash
run: |
cd frontend
npm ci --no-audit --no-fund --prefer-offline
- name: Run frontend tests (headless)
shell: bash
env:
CI: "true"
run: |
cd frontend
echo "Karma CHROME_BIN=$CHROME_BIN"
npm run test -- --watch=false --browsers=ChromeHeadlessNoSandbox

14
.gitignore vendored
View File

@@ -41,3 +41,17 @@ target/
build/
.gradle/
.mvn/
./storage_orders
./storage_quotes
./storage_requests
./storage_media
./storage_shop
storage_orders
storage_quotes
storage_requests
storage_media
storage_shop
# Qodana local reports/artifacts
backend/.qodana/

View File

@@ -4,39 +4,42 @@ Questo file serve a dare contesto all'AI (Antigravity/Gemini) sulla struttura e
## Project Overview
**Nome**: Print Calculator
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL.
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL in modo preciso tramite slicing reale.
**Stack**:
- **Backend**: Python (FastAPI), libreria `trimesh` per analisi geometrica.
- **Frontend**: Angular 19 (TypeScript).
- **Backend**: Java 21 (Spring Boot 3.4), PostgreSQL, Flyway.
- **Frontend**: Angular 19 (TypeScript), Angular Material, Three.js per visualizzazione 3D.
## Architecture
### Backend (`/backend`)
- **`main.py`**: Entrypoint dell'applicazione FastAPI.
- Definisce l'API `POST /calculate/stl`.
- Gestisce l'upload del file, invoca lo slicer e restituisce il preventivo.
- Configura CORS per permettere chiamate dal frontend.
- **`slicer.py`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
- Gestisce i profili di stampa (Macchina, Processo, Filamento).
- Crea configurazioni on-the-fly per supportare mesh di grandi dimensioni.
- **`calculator.py`**: Analizza il G-Code generato.
- `GCodeParser`: Estrae tempo di stampa e materiale usato dai metadati del G-Code.
- `QuoteCalculator`: Applica i costi (orari, energia, materiale) per generare il prezzo finale.
- **`BackendApplication.java`**: Entrypoint dell'applicazione Spring Boot.
- **`controller/`**: Espone le API REST per l'upload e il calcolo dei preventivi.
- **`service/SlicerService.java`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
- Gestisce i profili di stampa (Macchina, Processo, Filamento) caricati da file JSON.
- Crea configurazioni on-the-fly e invoca OrcaSlicer in modalità headless.
- **`service/GCodeParser.java`**: Analizza il G-Code generato per estrarre tempo di stampa e peso del materiale dai metadati del file.
- **`service/QuoteCalculator.java`**: Calcola il prezzo finale basandosi su politiche di prezzo salvate nel database.
- Gestisce costi macchina a scaglioni (tiered pricing).
- Calcola costi energetici basati sulla potenza della stampante e costo del kWh.
- Applica markup percentuali e fee fissi per job.
### Frontend (`/frontend`)
- Applicazione Angular standard.
- Usa Angular Material.
- Service per upload STL e visualizzazione preventivo.
- Applicazione Angular 19 con architettura modulare (core, features, shared).
- **Three.js**: Utilizzato per il rendering dei file STL caricati dall'utente.
- **Angular Material**: Per l'interfaccia utente.
- **ngx-translate**: Per il supporto multilingua.
## Key Concepts
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer in modalità headless. Questo garantisce stime di tempo e materiale estremamente precise, identiche a quelle che si otterrebbero preparando il file per la stampa.
- **G-Code Parsing**: Invece di stimare geometricamente, l'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `estimated printing time`, `filament used`).
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer. Questo garantisce stime di tempo e materiale estremamente precise.
- **Database-Driven Pricing**: A differenza di versioni precedenti, il calcolo del preventivo è ora guidato da entità DB (`PricingPolicy`, `PrinterMachine`, `FilamentVariant`).
- **G-Code Metadata**: L'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `; estimated printing time`, `; filament used [g]`).
## Development Notes
- Per eseguire il backend serve `uvicorn`.
- Il frontend richiede `npm install` al primo avvio.
- Le configurazioni di stampa (layer height, wall thickness, infill) sono attualmente hardcoded o con valori di default nel backend, ma potrebbero essere esposte come parametri API in futuro.
- **Backend**: Richiede JDK 21. Si avvia con `./gradlew bootRun`.
- **Database**: Richiede PostgreSQL. Le migrazioni sono gestite da Flyway.
- **Frontend**: Richiede Node.js 22. Si avvia con `npm start`.
- **OrcaSlicer**: Deve essere installato sul sistema e il percorso configurato in `application.properties` o tramite variabile d'ambiente `SLICER_PATH`.
## AI Agent Rules
- **No Inline Code**: Tutti i componenti Angular DEVONO usare file separati per HTML (`templateUrl`) e SCSS (`styleUrl`). È vietato usare `template` o `styles` inline nel decoratore `@Component`.
- **Spring Boot Conventions**: Seguire i pattern standard di Spring Boot (Service-Repository-Controller).

140
README.md
View File

@@ -1,70 +1,116 @@
# Print Calculator (OrcaSlicer Edition)
Un'applicazione Full Stack (Angular + Python/FastAPI) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
Un'applicazione Full Stack (Angular + Spring Boot) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
## Funzionalità
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, non semplici approssimazioni geometriche.
* **Preventivazione Completa**: Calcola costo materiale, ammortamento macchina, energia e ricarico.
* **Configurabile**: Prezzi e parametri macchina modificabili via variabili d'ambiente.
* **Docker Ready**: Tutto containerizzato per un facile deployment.
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, garantendo la massima precisione.
* **Preventivazione Database-Driven**: Calcolo basato su politiche di prezzo configurabili nel database (costo materiale, ammortamento macchina a scaglioni, energia e markup).
* **Visualizzazione 3D**: Anteprima del file STL caricato tramite Three.js.
* **Multi-Profilo**: Supporto per diverse stampanti, materiali e profili di processo.
## Stack Tecnologico
- **Backend**: Java 21, Spring Boot 3.4, PostgreSQL.
- **Frontend**: Angular 19, Angular Material, Three.js.
- **Slicer**: OrcaSlicer (invocato via CLI).
## Prerequisiti
* Docker Desktop & Docker Compose installati.
* **Java 21** installato.
* **Node.js 22** e **npm** installati.
* **PostgreSQL** attivo.
* **OrcaSlicer** installato sul sistema.
* **FFmpeg** installato sul sistema o presente nell'immagine Docker del backend.
## Avvio Rapido
1. Clona il repository.
2. Esegui lo script di avvio o docker-compose:
```bash
docker-compose up --build
```
*Nota: La prima build impiegherà alcuni minuti per scaricare OrcaSlicer (~200MB) e compilare il Frontend.*
### 1. Database
Crea un database PostgreSQL chiamato `printcalc`. Lo schema viene gestito dal progetto tramite configurazione JPA/SQL del repository.
3. Accedi all'applicazione:
* **Frontend**: [http://localhost](http://localhost)
* **API Docs**: [http://localhost:8000/docs](http://localhost:8000/docs)
### 2. Backend
Configura il percorso di OrcaSlicer in `backend/src/main/resources/application.properties` o tramite la variabile d'ambiente `SLICER_PATH`. Per il media service pubblico puoi configurare anche:
## Configurazione Prezzi
- `MEDIA_STORAGE_ROOT` per la root `storage_media` usata dal backend (`original/`, `public/`, `private/`)
- `SHOP_STORAGE_ROOT` per la root `storage_shop` usata dal backend per i modelli dei prodotti shop
- `MEDIA_FFMPEG_PATH` per il binario `ffmpeg` (nel deploy Docker default: `/usr/local/bin/ffmpeg-media`)
- `MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES` per il limite per asset immagine
Puoi modificare i prezzi nel file `docker-compose.yml` (sezione `environment` del servizio backend):
* `FILAMENT_COST_PER_KG`: Costo filamento al kg (es. 25.0).
* `MACHINE_COST_PER_HOUR`: Costo orario macchina (ammortamento/manutenzione).
* `ENERGY_COST_PER_KWH`: Costo energia elettrica.
* `MARKUP_PERCENT`: Margine di profitto percentuale (es. 20 = +20%).
## Struttura del Progetto
* `/backend`: API Python FastAPI. Include Dockerfile che scarica OrcaSlicer AppImage.
* `/frontend`: Applicazione Angular 19+ con Material Design.
* `/backend/profiles`: Contiene i profili di slicing (.ini). Attualmente configurato per una stima generica simil-Bambu Lab A1.
## Troubleshooting
### Errore Download OrcaSlicer
Se la build del backend fallisce durante il download di `OrcaSlicer.AppImage`, verifica la tua connessione internet o aggiorna l'URL nel `backend/Dockerfile`.
### Slicing Fallito (Costo 0 o Errore)
Se l'API ritorna errore o valori nulli:
1. Controlla che il file STL sia valido (manifold).
2. Controlla i log del backend: `docker logs print-calculator-backend`.
## Sviluppo Locale (Senza Docker)
**Backend**:
Richiede Linux (o WSL2) per eseguire l'AppImage di OrcaSlicer.
```bash
cd backend
pip install -r requirements.txt
# Assicurati di avere OrcaSlicer installato e nel PATH o aggiorna SLICER_PATH in slicer.py
uvicorn main:app --reload
./gradlew bootRun
```
**Frontend**:
### 3. Frontend
```bash
cd frontend
npm install
npm start
```
Accedi a [http://localhost:4200](http://localhost:4200).
## Configurazione Prezzi
I prezzi non sono più gestiti tramite variabili d'ambiente fisse ma tramite tabelle nel database:
- `pricing_policy`: Definisce markup, fee fissi e costi elettrici.
- `pricing_policy_machine_hour_tier`: Definisce i costi orari delle macchine in base alla durata della stampa.
- `printer_machine`: Anagrafica stampanti e consumi energetici.
- `filament_material_type` / `filament_variant`: Listino prezzi materiali.
## Struttura del Progetto
* `/backend`: API Spring Boot.
* `/frontend`: Applicazione Angular.
* `/backend/profiles`: Contiene i file di configurazione per OrcaSlicer.
* `/storage_media`: Originali e varianti media pubbliche/private su filesystem.
* `/storage_shop`: Modelli e file prodotti dello shop.
## Media pubblici
Il backend salva sempre l'originale in `storage_media/original/` e precomputa le varianti pubbliche in `storage_media/public/`. La cartella `storage_media/private/` è predisposta per asset non pubblici.
Nel deploy Docker i volumi attesi sono `/mnt/cache/appdata/print-calculator/${ENV}/storage_media:/app/storage_media` e `/mnt/cache/appdata/print-calculator/${ENV}/storage_shop:/app/storage_shop`.
Nginx non deve passare dal backend per i file pubblici. Configurazione attesa:
```nginx
location /media/ {
alias /mnt/cache/appdata/print-calculator/${ENV}/storage_media/public/;
}
```
Usage key iniziali previste per frontend:
- `HOME_SECTION / shop-gallery`
- `HOME_SECTION / founders-gallery`
- `HOME_SECTION / capability-prototyping`
- `HOME_SECTION / capability-custom-parts`
- `HOME_SECTION / capability-small-series`
- `HOME_SECTION / capability-cad`
- `ABOUT_MEMBER / joe`
- `ABOUT_MEMBER / matteo`
- riservati per estensioni future: `SHOP_PRODUCT`, `SHOP_CATEGORY`, `SHOP_GALLERY`
Operativamente:
- carica i file dal media admin endpoint del backend
- associa ogni asset con `POST /api/admin/media/usages`
- per `ABOUT_MEMBER` imposta `isPrimary=true` sulla foto principale del membro
- home e about leggono da `GET /api/public/media/usages?usageType=...&usageKey=...`
- il frontend usa `<picture>` e preferisce AVIF/WEBP con fallback JPEG, senza usare l'originale
- nel back-office frontend la gestione operativa della home passa dalla pagina `admin/home-media`
## Troubleshooting
### Percorso OrcaSlicer
Assicurati che `slicer.path` punti al binario corretto. Su macOS è solitamente `/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer`. Su Linux è il percorso all'AppImage (estratta o meno).
### FFmpeg e media pubblici
Verifica che `MEDIA_FFMPEG_PATH` punti a un `ffmpeg` con supporto JPEG, WebP e AVIF (encoder + muxer AVIF). Nel container backend il default è `/usr/local/bin/ffmpeg-media`: usa `/usr/bin/ffmpeg` se già compatibile, altrimenti installa un fallback statico con supporto AVIF. Se gli URL media restituiti dalle API admin non sono raggiungibili, controlla che `APP_FRONTEND_BASE_URL` punti al dominio corretto, che `location /media/` sia esposto da Nginx e che il volume `storage_media` sia montato correttamente.
### Database connection
Verifica le credenziali in `application.properties`. Se usi Docker, puoi passare `DB_URL`, `DB_USERNAME` e `DB_PASSWORD` come variabili d'ambiente.
### Deploy e traduzioni OpenAI
Nel deploy Gitea la chiave OpenAI deve stare nel secret `OPENAI_API_KEY`. La pipeline la aggiunge al file `.env` dell'ambiente durante il deploy e il container backend la riceve come variabile runtime. I file `deploy/envs/*.env` restano per i valori specifici di `dev/int/prod`.

View File

@@ -10,28 +10,92 @@ RUN ./gradlew bootJar -x test --no-daemon
# Stage 2: Runtime Environment
FROM eclipse-temurin:21-jre-jammy
ARG ORCA_VERSION=2.3.1
ARG ORCA_DOWNLOAD_URL
ARG FFMPEG_STATIC_URL=https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
# Install system dependencies for OrcaSlicer (same as before)
RUN apt-get update && apt-get install -y \
# Install system dependencies for OrcaSlicer and media processing.
# Prefer system ffmpeg; if AVIF support is incomplete, install a static ffmpeg fallback.
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ffmpeg \
wget \
p7zip-full \
xz-utils \
ca-certificates \
assimp-utils \
libgl1 \
libglib2.0-0 \
libgtk-3-0 \
libdbus-1-3 \
libwebkit2gtk-4.0-37 \
&& rm -rf /var/lib/apt/lists/*
libwebkit2gtk-4.0-37; \
check_ffmpeg_support() { \
ffmpeg_bin="$1"; \
"$ffmpeg_bin" -hide_banner -encoders > /tmp/ffmpeg-encoders.txt 2>&1 || return 1; \
"$ffmpeg_bin" -hide_banner -muxers > /tmp/ffmpeg-muxers.txt 2>&1 || return 1; \
grep -Eq '[[:space:]]mjpeg[[:space:]]' /tmp/ffmpeg-encoders.txt || return 1; \
grep -Eq '[[:space:]](libwebp|webp)[[:space:]]' /tmp/ffmpeg-encoders.txt || return 1; \
grep -Eq '[[:space:]](libaom-av1|librav1e|libsvtav1)[[:space:]]' /tmp/ffmpeg-encoders.txt || return 1; \
grep -Eq '[[:space:]]avif([[:space:]]|,|$)' /tmp/ffmpeg-muxers.txt || return 1; \
return 0; \
}; \
if check_ffmpeg_support /usr/bin/ffmpeg; then \
ln -sf /usr/bin/ffmpeg /usr/local/bin/ffmpeg-media; \
else \
echo "System ffmpeg lacks AVIF support, installing static fallback from ${FFMPEG_STATIC_URL}"; \
wget -q "${FFMPEG_STATIC_URL}" -O /tmp/ffmpeg-static.tar.xz; \
tar -xJf /tmp/ffmpeg-static.tar.xz -C /tmp; \
FFMPEG_STATIC_BIN="$(find /tmp -maxdepth 2 -type f -name ffmpeg | head -n 1)"; \
test -n "${FFMPEG_STATIC_BIN}"; \
install -m 0755 "${FFMPEG_STATIC_BIN}" /usr/local/bin/ffmpeg-media; \
check_ffmpeg_support /usr/local/bin/ffmpeg-media; \
fi; \
rm -f /tmp/ffmpeg-muxers.txt; \
rm -f /tmp/ffmpeg-encoders.txt; \
rm -f /tmp/ffmpeg-static.tar.xz; \
rm -rf /tmp/ffmpeg-*-amd64-static; \
rm -rf /var/lib/apt/lists/*
# Install OrcaSlicer
WORKDIR /opt
RUN wget -q https://github.com/SoftFever/OrcaSlicer/releases/download/v2.2.0/OrcaSlicer_Linux_V2.2.0.AppImage -O OrcaSlicer.AppImage \
&& 7z x OrcaSlicer.AppImage -o/opt/orcaslicer \
RUN set -eux; \
ORCA_URL="${ORCA_DOWNLOAD_URL:-}"; \
if [ -n "${ORCA_URL}" ]; then \
wget -q "${ORCA_URL}" -O OrcaSlicer.AppImage; \
else \
CANDIDATES="\
https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v${ORCA_VERSION}/OrcaSlicer_Linux_AppImage_Ubuntu2204_V${ORCA_VERSION}.AppImage \
https://github.com/SoftFever/OrcaSlicer/releases/download/v${ORCA_VERSION}/OrcaSlicer_Linux_AppImage_Ubuntu2204_V${ORCA_VERSION}.AppImage \
https://github.com/SoftFever/OrcaSlicer/releases/download/v2.2.0/OrcaSlicer_Linux_V2.2.0.AppImage"; \
ok=0; \
for url in $CANDIDATES; do \
if wget -q --spider "$url"; then \
echo "Using OrcaSlicer URL: $url"; \
wget -q "$url" -O OrcaSlicer.AppImage; \
ok=1; \
break; \
fi; \
done; \
if [ "$ok" -ne 1 ]; then \
echo "Failed to find OrcaSlicer AppImage for version ${ORCA_VERSION}" >&2; \
echo "Tried URLs:" >&2; \
for url in $CANDIDATES; do echo " - $url" >&2; done; \
exit 1; \
fi; \
fi \
&& chmod +x OrcaSlicer.AppImage \
&& rm -rf /opt/orcaslicer /opt/squashfs-root \
&& ./OrcaSlicer.AppImage --appimage-extract >/dev/null \
&& mv /opt/squashfs-root /opt/orcaslicer \
&& chmod -R +x /opt/orcaslicer \
&& rm OrcaSlicer.AppImage
ENV PATH="/opt/orcaslicer/usr/bin:${PATH}"
# Set Slicer Path env variable for Java app
ENV SLICER_PATH="/opt/orcaslicer/AppRun"
ENV ASSIMP_PATH="assimp"
# Use ffmpeg selected at image build time (system or static fallback) for media generation.
ENV MEDIA_FFMPEG_PATH="/usr/local/bin/ffmpeg-media"
WORKDIR /app
# Copy JAR from build stage

View File

@@ -24,11 +24,37 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'xyz.capybara:clamav-client:2.1.2'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'org.postgresql:postgresql'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'io.github.openhtmltopdf:openhtmltopdf-pdfbox:1.1.37'
implementation 'io.github.openhtmltopdf:openhtmltopdf-svg-support:1.1.37'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'net.codecrete.qrbill:qrbill-generator:3.4.0'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.jsoup:jsoup:1.18.3'
implementation platform('org.lwjgl:lwjgl-bom:3.3.4')
implementation 'org.lwjgl:lwjgl'
implementation 'org.lwjgl:lwjgl-assimp'
runtimeOnly 'org.lwjgl:lwjgl::natives-linux'
runtimeOnly 'org.lwjgl:lwjgl::natives-macos'
runtimeOnly 'org.lwjgl:lwjgl::natives-macos-arm64'
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-linux'
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-macos'
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-macos-arm64'
}
tasks.named('test') {

View File

@@ -1,15 +1,76 @@
#!/bin/sh
set -e
# In container default to the ffmpeg selected during image build.
if [ -z "${MEDIA_FFMPEG_PATH:-}" ]; then
MEDIA_FFMPEG_PATH="/usr/local/bin/ffmpeg-media"
fi
export MEDIA_FFMPEG_PATH
validate_ffmpeg_support() {
ffmpeg_bin="$1"
if ! command -v "$ffmpeg_bin" >/dev/null 2>&1; then
echo "ERROR: FFmpeg executable not found: ${ffmpeg_bin}" >&2
exit 11
fi
encoders="$(mktemp)"
muxers="$(mktemp)"
trap 'rm -f "$encoders" "$muxers"' EXIT
"$ffmpeg_bin" -hide_banner -encoders > "$encoders" 2>&1 || {
echo "ERROR: Unable to inspect FFmpeg encoders from ${ffmpeg_bin}" >&2
cat "$encoders" >&2
exit 12
}
"$ffmpeg_bin" -hide_banner -muxers > "$muxers" 2>&1 || {
echo "ERROR: Unable to inspect FFmpeg muxers from ${ffmpeg_bin}" >&2
cat "$muxers" >&2
exit 13
}
grep -Eq '[[:space:]]mjpeg[[:space:]]' "$encoders" || {
echo "ERROR: FFmpeg '${ffmpeg_bin}' missing JPEG encoder (mjpeg)." >&2
exit 14
}
grep -Eq '[[:space:]](libwebp|webp)[[:space:]]' "$encoders" || {
echo "ERROR: FFmpeg '${ffmpeg_bin}' missing WebP encoder." >&2
exit 15
}
grep -Eq '[[:space:]](libaom-av1|librav1e|libsvtav1)[[:space:]]' "$encoders" || {
echo "ERROR: FFmpeg '${ffmpeg_bin}' missing AVIF-capable encoder." >&2
exit 16
}
grep -Eq '[[:space:]]avif([[:space:]]|,|$)' "$muxers" || {
echo "ERROR: FFmpeg '${ffmpeg_bin}' missing AVIF muxer." >&2
exit 17
}
}
validate_ffmpeg_support "$MEDIA_FFMPEG_PATH"
echo "----------------------------------------------------------------"
echo "Starting Backend Application"
echo "DB_URL: $DB_URL"
echo "DB_USERNAME: $DB_USERNAME"
echo "SPRING_DATASOURCE_URL: $SPRING_DATASOURCE_URL"
echo "SLICER_PATH: $SLICER_PATH"
echo "--- ALL ENV VARS ---"
env
echo "MEDIA_FFMPEG_PATH: $MEDIA_FFMPEG_PATH"
echo "----------------------------------------------------------------"
# Exec java with explicit properties from env
# Determine which environment variables to use for database connection
# This allows compatibility with different docker-compose configurations
FINAL_DB_URL="${DB_URL:-$SPRING_DATASOURCE_URL}"
FINAL_DB_USER="${DB_USERNAME:-$SPRING_DATASOURCE_USERNAME}"
FINAL_DB_PASS="${DB_PASSWORD:-$SPRING_DATASOURCE_PASSWORD}"
if [ -n "$FINAL_DB_URL" ]; then
echo "Using database URL: $FINAL_DB_URL"
exec java -jar app.jar \
--spring.datasource.url="${DB_URL}" \
--spring.datasource.username="${DB_USERNAME}" \
--spring.datasource.password="${DB_PASSWORD}"
--spring.datasource.url="${FINAL_DB_URL}" \
--spring.datasource.username="${FINAL_DB_USER}" \
--spring.datasource.password="${FINAL_DB_PASS}"
else
echo "No database URL specified in environment, relying on application.properties defaults."
exec java -jar app.jar
fi

File diff suppressed because one or more lines are too long

48
backend/qodana.yaml Normal file
View File

@@ -0,0 +1,48 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
projectJDK: "21" #(Applied in CI/CD pipeline)
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm:2025.3

View File

@@ -2,8 +2,15 @@ package com.printcalculator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
@EnableTransactionManagement
@EnableScheduling
@EnableAsync
public class BackendApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,88 @@
package com.printcalculator.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@Service
public class AllowedOriginService {
private final List<String> allowedOrigins;
public AllowedOriginService(
@Value("${app.frontend.base-url:http://localhost:4200}") String frontendBaseUrl,
@Value("${app.cors.additional-allowed-origins:}") String additionalAllowedOrigins
) {
LinkedHashSet<String> configuredOrigins = new LinkedHashSet<>();
addConfiguredOrigin(configuredOrigins, frontendBaseUrl, "app.frontend.base-url");
for (String rawOrigin : additionalAllowedOrigins.split(",")) {
addConfiguredOrigin(configuredOrigins, rawOrigin, "app.cors.additional-allowed-origins");
}
if (configuredOrigins.isEmpty()) {
throw new IllegalStateException("At least one allowed origin must be configured.");
}
this.allowedOrigins = List.copyOf(configuredOrigins);
}
public List<String> getAllowedOrigins() {
return allowedOrigins;
}
public boolean isAllowed(String rawOriginOrUrl) {
String normalizedOrigin = normalizeRequestOrigin(rawOriginOrUrl);
return normalizedOrigin != null && allowedOrigins.contains(normalizedOrigin);
}
private void addConfiguredOrigin(Set<String> configuredOrigins, String rawOriginOrUrl, String propertyName) {
if (rawOriginOrUrl == null || rawOriginOrUrl.isBlank()) {
return;
}
String normalizedOrigin = normalizeRequestOrigin(rawOriginOrUrl);
if (normalizedOrigin == null) {
throw new IllegalStateException(propertyName + " must contain absolute http(s) URLs.");
}
configuredOrigins.add(normalizedOrigin);
}
private String normalizeRequestOrigin(String rawOriginOrUrl) {
if (rawOriginOrUrl == null || rawOriginOrUrl.isBlank()) {
return null;
}
try {
URI uri = URI.create(rawOriginOrUrl.trim());
String scheme = uri.getScheme();
String host = uri.getHost();
if (scheme == null || host == null) {
return null;
}
String normalizedScheme = scheme.toLowerCase(Locale.ROOT);
if (!"http".equals(normalizedScheme) && !"https".equals(normalizedScheme)) {
return null;
}
String normalizedHost = host.toLowerCase(Locale.ROOT);
int port = uri.getPort();
if (isDefaultPort(normalizedScheme, port) || port < 0) {
return normalizedScheme + "://" + normalizedHost;
}
return normalizedScheme + "://" + normalizedHost + ":" + port;
} catch (IllegalArgumentException ignored) {
return null;
}
}
private boolean isDefaultPort(String scheme, int port) {
return ("http".equals(scheme) && port == 80)
|| ("https".equals(scheme) && port == 443);
}
}

View File

@@ -1,19 +1,27 @@
package com.printcalculator.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
public class CorsConfig {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost", "http://localhost:4200", "http://localhost:80", "http://127.0.0.1")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
@Bean
public CorsConfigurationSource corsConfigurationSource(AllowedOriginService allowedOriginService) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(allowedOriginService.getAllowedOrigins());
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@@ -0,0 +1,50 @@
package com.printcalculator.config;
import com.printcalculator.security.AdminCsrfProtectionFilter;
import com.printcalculator.security.AdminSessionAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
AdminCsrfProtectionFilter adminCsrfProtectionFilter,
AdminSessionAuthenticationFilter adminSessionAuthenticationFilter
) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.logout(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/actuator/health", "/actuator/health/**").permitAll()
.requestMatchers("/actuator/**").denyAll()
.requestMatchers("/api/admin/auth/login").permitAll()
.requestMatchers("/api/admin/**").authenticated()
.anyRequest().permitAll()
)
.exceptionHandling(ex -> ex.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(401);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"error\":\"UNAUTHORIZED\"}");
}))
.addFilterBefore(adminCsrfProtectionFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(adminSessionAuthenticationFilter, AdminCsrfProtectionFilter.class);
return http.build();
}
}

View File

@@ -0,0 +1,47 @@
package com.printcalculator.controller;
import com.printcalculator.dto.QuoteRequestDto;
import com.printcalculator.entity.CustomQuoteRequest;
import com.printcalculator.service.request.CustomQuoteRequestControllerService;
import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/custom-quote-requests")
public class CustomQuoteRequestController {
private final CustomQuoteRequestControllerService customQuoteRequestControllerService;
public CustomQuoteRequestController(CustomQuoteRequestControllerService customQuoteRequestControllerService) {
this.customQuoteRequestControllerService = customQuoteRequestControllerService;
}
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<CustomQuoteRequest> createCustomQuoteRequest(
@Valid @RequestPart("request") QuoteRequestDto requestDto,
@RequestPart(value = "files", required = false) List<MultipartFile> files
) throws IOException {
return ResponseEntity.ok(customQuoteRequestControllerService.createCustomQuoteRequest(requestDto, files));
}
@GetMapping("/{id}")
public ResponseEntity<CustomQuoteRequest> getCustomQuoteRequest(@PathVariable UUID id) {
return customQuoteRequestControllerService.getCustomQuoteRequest(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}

View File

@@ -3,18 +3,33 @@ package com.printcalculator.controller;
import com.printcalculator.dto.OptionsResponse;
import com.printcalculator.entity.FilamentMaterialType;
import com.printcalculator.entity.FilamentVariant;
import com.printcalculator.entity.*; // This line replaces specific entity imports
import com.printcalculator.entity.MaterialOrcaProfileMap;
import com.printcalculator.entity.NozzleOption;
import com.printcalculator.entity.PrinterMachine;
import com.printcalculator.entity.PrinterMachineProfile;
import com.printcalculator.repository.FilamentMaterialTypeRepository;
import com.printcalculator.repository.FilamentVariantRepository;
import com.printcalculator.repository.LayerHeightOptionRepository;
import com.printcalculator.repository.MaterialOrcaProfileMapRepository;
import com.printcalculator.repository.NozzleOptionRepository;
import com.printcalculator.repository.PrinterMachineRepository;
import com.printcalculator.repository.PrinterMachineProfileRepository;
import com.printcalculator.service.NozzleLayerHeightPolicyService;
import com.printcalculator.service.OrcaProfileResolver;
import com.printcalculator.service.ProfileManager;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
@@ -22,76 +37,121 @@ public class OptionsController {
private final FilamentMaterialTypeRepository materialRepo;
private final FilamentVariantRepository variantRepo;
private final LayerHeightOptionRepository layerHeightRepo;
private final NozzleOptionRepository nozzleRepo;
private final PrinterMachineRepository printerMachineRepo;
private final PrinterMachineProfileRepository printerMachineProfileRepo;
private final MaterialOrcaProfileMapRepository materialOrcaMapRepo;
private final OrcaProfileResolver orcaProfileResolver;
private final ProfileManager profileManager;
private final NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService;
public OptionsController(FilamentMaterialTypeRepository materialRepo,
FilamentVariantRepository variantRepo,
LayerHeightOptionRepository layerHeightRepo,
NozzleOptionRepository nozzleRepo) {
NozzleOptionRepository nozzleRepo,
PrinterMachineRepository printerMachineRepo,
PrinterMachineProfileRepository printerMachineProfileRepo,
MaterialOrcaProfileMapRepository materialOrcaMapRepo,
OrcaProfileResolver orcaProfileResolver,
ProfileManager profileManager,
NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService) {
this.materialRepo = materialRepo;
this.variantRepo = variantRepo;
this.layerHeightRepo = layerHeightRepo;
this.nozzleRepo = nozzleRepo;
this.printerMachineRepo = printerMachineRepo;
this.printerMachineProfileRepo = printerMachineProfileRepo;
this.materialOrcaMapRepo = materialOrcaMapRepo;
this.orcaProfileResolver = orcaProfileResolver;
this.profileManager = profileManager;
this.nozzleLayerHeightPolicyService = nozzleLayerHeightPolicyService;
}
@GetMapping("/api/calculator/options")
public ResponseEntity<OptionsResponse> getOptions() {
// 1. Materials & Variants
@Transactional(readOnly = true)
public ResponseEntity<OptionsResponse> getOptions(
@RequestParam(value = "printerMachineId", required = false) Long printerMachineId,
@RequestParam(value = "nozzleDiameter", required = false) Double nozzleDiameter
) {
List<FilamentMaterialType> types = materialRepo.findAll();
List<FilamentVariant> allVariants = variantRepo.findAll();
List<FilamentVariant> allVariants = variantRepo.findByIsActiveTrue().stream()
.sorted(Comparator
.comparing((FilamentVariant v) -> safeMaterialCode(v.getFilamentMaterialType()), String.CASE_INSENSITIVE_ORDER)
.thenComparing(v -> safeString(v.getVariantDisplayName()), String.CASE_INSENSITIVE_ORDER))
.toList();
Set<Long> compatibleMaterialTypeIds = resolveCompatibleMaterialTypeIds(printerMachineId, nozzleDiameter);
List<OptionsResponse.MaterialOption> materialOptions = types.stream()
.sorted(Comparator.comparing(t -> safeString(t.getMaterialCode()), String.CASE_INSENSITIVE_ORDER))
.map(type -> {
if (!compatibleMaterialTypeIds.isEmpty() && !compatibleMaterialTypeIds.contains(type.getId())) {
return null;
}
List<OptionsResponse.VariantOption> variants = allVariants.stream()
.filter(v -> v.getFilamentMaterialType().getId().equals(type.getId()) && v.getIsActive())
.filter(v -> v.getFilamentMaterialType() != null
&& v.getFilamentMaterialType().getId().equals(type.getId()))
.map(v -> new OptionsResponse.VariantOption(
v.getId(),
v.getVariantDisplayName(),
v.getColorName(),
getColorHex(v.getColorName()), // Need helper or store hex in DB
v.getStockSpools().doubleValue() <= 0
v.getColorLabelIt(),
v.getColorLabelEn(),
v.getColorLabelDe(),
v.getColorLabelFr(),
resolveHexColor(v),
v.getFinishType() != null ? v.getFinishType() : "GLOSSY",
v.getStockSpools() != null ? v.getStockSpools().doubleValue() : 0d,
toStockFilamentGrams(v),
v.getStockSpools() == null || v.getStockSpools().doubleValue() <= 0
))
.collect(Collectors.toList());
// Only include material if it has active variants
if (variants.isEmpty()) return null;
if (variants.isEmpty()) {
return null;
}
return new OptionsResponse.MaterialOption(
type.getMaterialCode(),
type.getMaterialCode() + (type.getIsFlexible() ? " (Flexible)" : " (Standard)"),
type.getMaterialCode() + (Boolean.TRUE.equals(type.getIsFlexible()) ? " (Flexible)" : " (Standard)"),
variants
);
})
.filter(m -> m != null)
.collect(Collectors.toList());
.toList();
// 2. Qualities (Static as per user request)
List<OptionsResponse.QualityOption> qualities = List.of(
new OptionsResponse.QualityOption("draft", "Draft"),
new OptionsResponse.QualityOption("standard", "Standard"),
new OptionsResponse.QualityOption("extra_fine", "High Definition")
);
// 3. Infill Patterns (Static as per user request)
List<OptionsResponse.InfillPatternOption> patterns = List.of(
new OptionsResponse.InfillPatternOption("grid", "Grid"),
new OptionsResponse.InfillPatternOption("gyroid", "Gyroid"),
new OptionsResponse.InfillPatternOption("cubic", "Cubic")
);
// 4. Layer Heights
List<OptionsResponse.LayerHeightOptionDTO> layers = layerHeightRepo.findAll().stream()
.filter(l -> l.getIsActive())
.sorted(Comparator.comparing(LayerHeightOption::getLayerHeightMm))
.map(l -> new OptionsResponse.LayerHeightOptionDTO(
l.getLayerHeightMm().doubleValue(),
String.format("%.2f mm", l.getLayerHeightMm())
))
.collect(Collectors.toList());
PrinterMachine targetMachine = resolveMachine(printerMachineId);
Set<BigDecimal> supportedMachineNozzles = targetMachine != null
? printerMachineProfileRepo.findByPrinterMachineAndIsActiveTrue(targetMachine).stream()
.map(PrinterMachineProfile::getNozzleDiameterMm)
.filter(v -> v != null)
.map(nozzleLayerHeightPolicyService::normalizeNozzle)
.collect(Collectors.toCollection(LinkedHashSet::new))
: Set.of();
boolean restrictNozzlesByMachineProfile = !supportedMachineNozzles.isEmpty();
// 5. Nozzles
List<OptionsResponse.NozzleOptionDTO> nozzles = nozzleRepo.findAll().stream()
.filter(n -> n.getIsActive())
.filter(n -> Boolean.TRUE.equals(n.getIsActive()))
.filter(n -> {
if (!restrictNozzlesByMachineProfile) {
return true;
}
BigDecimal normalized = nozzleLayerHeightPolicyService.normalizeNozzle(n.getNozzleDiameterMm());
return normalized != null && supportedMachineNozzles.contains(normalized);
})
.sorted(Comparator.comparing(NozzleOption::getNozzleDiameterMm))
.map(n -> new OptionsResponse.NozzleOptionDTO(
n.getNozzleDiameterMm().doubleValue(),
@@ -100,13 +160,207 @@ public class OptionsController {
? String.format(" (+ %.2f CHF)", n.getExtraNozzleChangeFeeChf())
: " (Standard)")
))
.collect(Collectors.toList());
.toList();
return ResponseEntity.ok(new OptionsResponse(materialOptions, qualities, patterns, layers, nozzles));
Map<BigDecimal, List<BigDecimal>> rulesByNozzle = nozzleLayerHeightPolicyService.getActiveRulesByNozzle();
Set<BigDecimal> visibleNozzlesFromOptions = nozzles.stream()
.map(OptionsResponse.NozzleOptionDTO::value)
.map(BigDecimal::valueOf)
.map(nozzleLayerHeightPolicyService::normalizeNozzle)
.filter(v -> v != null)
.collect(Collectors.toCollection(LinkedHashSet::new));
Map<BigDecimal, List<BigDecimal>> effectiveRulesByNozzle = new LinkedHashMap<>();
for (BigDecimal nozzle : visibleNozzlesFromOptions) {
List<BigDecimal> policyLayers = rulesByNozzle.getOrDefault(nozzle, List.of());
List<BigDecimal> compatibleProcessLayers = resolveCompatibleProcessLayers(targetMachine, nozzle);
List<BigDecimal> effective = mergePolicyAndProcessLayers(policyLayers, compatibleProcessLayers);
if (!effective.isEmpty()) {
effectiveRulesByNozzle.put(nozzle, effective);
}
}
if (effectiveRulesByNozzle.isEmpty()) {
for (BigDecimal nozzle : visibleNozzlesFromOptions) {
List<BigDecimal> policyLayers = rulesByNozzle.getOrDefault(nozzle, List.of());
if (!policyLayers.isEmpty()) {
effectiveRulesByNozzle.put(nozzle, policyLayers);
}
}
}
// Temporary helper until we add hex to DB
Set<BigDecimal> visibleNozzles = new LinkedHashSet<>(effectiveRulesByNozzle.keySet());
nozzles = nozzles.stream()
.filter(option -> {
BigDecimal normalized = nozzleLayerHeightPolicyService.normalizeNozzle(
BigDecimal.valueOf(option.value())
);
return normalized != null && visibleNozzles.contains(normalized);
})
.toList();
BigDecimal selectedNozzle = nozzleLayerHeightPolicyService.resolveNozzle(
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
);
if (!visibleNozzles.isEmpty() && !visibleNozzles.contains(selectedNozzle)) {
selectedNozzle = visibleNozzles.iterator().next();
}
List<OptionsResponse.LayerHeightOptionDTO> layers = toLayerDtos(
effectiveRulesByNozzle.getOrDefault(selectedNozzle, List.of())
);
if (layers.isEmpty()) {
if (!visibleNozzles.isEmpty()) {
BigDecimal fallbackNozzle = visibleNozzles.iterator().next();
layers = toLayerDtos(effectiveRulesByNozzle.getOrDefault(fallbackNozzle, List.of()));
}
if (layers.isEmpty()) {
layers = rulesByNozzle.values().stream().findFirst().map(this::toLayerDtos).orElse(List.of());
}
}
List<OptionsResponse.NozzleLayerHeightOptionsDTO> layerHeightsByNozzle = effectiveRulesByNozzle.entrySet().stream()
.map(entry -> new OptionsResponse.NozzleLayerHeightOptionsDTO(
entry.getKey().doubleValue(),
toLayerDtos(entry.getValue())
))
.toList();
return ResponseEntity.ok(new OptionsResponse(
materialOptions,
qualities,
patterns,
layers,
nozzles,
layerHeightsByNozzle
));
}
private Set<Long> resolveCompatibleMaterialTypeIds(Long printerMachineId, Double nozzleDiameter) {
PrinterMachine machine = resolveMachine(printerMachineId);
if (machine == null) {
return Set.of();
}
BigDecimal nozzle = nozzleLayerHeightPolicyService.resolveNozzle(
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
);
PrinterMachineProfile machineProfile = orcaProfileResolver
.resolveMachineProfile(machine, nozzle)
.orElse(null);
if (machineProfile == null) {
return Set.of();
}
List<MaterialOrcaProfileMap> maps = materialOrcaMapRepo.findByPrinterMachineProfileAndIsActiveTrue(machineProfile);
return maps.stream()
.map(MaterialOrcaProfileMap::getFilamentMaterialType)
.filter(m -> m != null && m.getId() != null)
.map(FilamentMaterialType::getId)
.collect(Collectors.toSet());
}
private PrinterMachine resolveMachine(Long printerMachineId) {
PrinterMachine machine = null;
if (printerMachineId != null) {
machine = printerMachineRepo.findById(printerMachineId).orElse(null);
}
if (machine == null) {
machine = printerMachineRepo.findFirstByIsActiveTrue().orElse(null);
}
return machine;
}
private List<OptionsResponse.LayerHeightOptionDTO> toLayerDtos(List<BigDecimal> layers) {
return layers.stream()
.sorted(Comparator.naturalOrder())
.map(layer -> new OptionsResponse.LayerHeightOptionDTO(
layer.doubleValue(),
String.format("%.2f mm", layer)
))
.toList();
}
private List<BigDecimal> resolveCompatibleProcessLayers(PrinterMachine machine, BigDecimal nozzle) {
if (machine == null || nozzle == null) {
return List.of();
}
PrinterMachineProfile profile = orcaProfileResolver.resolveMachineProfile(machine, nozzle).orElse(null);
if (profile == null || profile.getOrcaMachineProfileName() == null) {
return List.of();
}
return profileManager.findCompatibleProcessLayers(profile.getOrcaMachineProfileName());
}
private List<BigDecimal> mergePolicyAndProcessLayers(List<BigDecimal> policyLayers,
List<BigDecimal> processLayers) {
if ((processLayers == null || processLayers.isEmpty())
&& (policyLayers == null || policyLayers.isEmpty())) {
return List.of();
}
if (processLayers == null || processLayers.isEmpty()) {
return policyLayers != null ? policyLayers : List.of();
}
if (policyLayers == null || policyLayers.isEmpty()) {
return processLayers;
}
Set<BigDecimal> allowedByPolicy = policyLayers.stream()
.map(nozzleLayerHeightPolicyService::normalizeLayer)
.filter(v -> v != null)
.collect(Collectors.toCollection(LinkedHashSet::new));
List<BigDecimal> intersection = processLayers.stream()
.map(nozzleLayerHeightPolicyService::normalizeLayer)
.filter(v -> v != null && allowedByPolicy.contains(v))
.collect(Collectors.toCollection(ArrayList::new));
if (!intersection.isEmpty()) {
return intersection;
}
return processLayers.stream()
.map(nozzleLayerHeightPolicyService::normalizeLayer)
.filter(v -> v != null)
.collect(Collectors.toCollection(ArrayList::new));
}
private String resolveHexColor(FilamentVariant variant) {
if (variant.getColorHex() != null && !variant.getColorHex().isBlank()) {
return variant.getColorHex();
}
return getColorHex(variant.getColorName());
}
private double toStockFilamentGrams(FilamentVariant variant) {
if (variant.getStockSpools() == null || variant.getSpoolNetKg() == null) {
return 0d;
}
return variant.getStockSpools()
.multiply(variant.getSpoolNetKg())
.multiply(BigDecimal.valueOf(1000))
.doubleValue();
}
private String safeMaterialCode(FilamentMaterialType type) {
if (type == null || type.getMaterialCode() == null) {
return "";
}
return type.getMaterialCode();
}
private String safeString(String value) {
return value == null ? "" : value;
}
// Temporary helper for legacy values where color hex is not yet set in DB
private String getColorHex(String colorName) {
if (colorName == null) {
return "#9e9e9e";
}
String lower = colorName.toLowerCase();
if (lower.contains("black") || lower.contains("nero")) return "#1a1a1a";
if (lower.contains("white") || lower.contains("bianco")) return "#f5f5f5";
@@ -120,6 +374,6 @@ public class OptionsController {
}
if (lower.contains("purple") || lower.contains("viola")) return "#7b1fa2";
if (lower.contains("yellow") || lower.contains("giallo")) return "#fbc02d";
return "#9e9e9e"; // Default grey
return "#9e9e9e";
}
}

View File

@@ -0,0 +1,101 @@
package com.printcalculator.controller;
import com.printcalculator.dto.CreateOrderRequest;
import com.printcalculator.dto.OrderDto;
import com.printcalculator.service.order.OrderControllerService;
import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderControllerService orderControllerService;
public OrderController(OrderControllerService orderControllerService) {
this.orderControllerService = orderControllerService;
}
@PostMapping("/from-quote/{quoteSessionId}")
@Transactional
public ResponseEntity<OrderDto> createOrderFromQuote(
@PathVariable UUID quoteSessionId,
@Valid @RequestBody CreateOrderRequest request
) {
return ResponseEntity.ok(orderControllerService.createOrderFromQuote(quoteSessionId, request));
}
@PostMapping(value = "/{orderId}/items/{orderItemId}/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<Void> uploadOrderItemFile(
@PathVariable UUID orderId,
@PathVariable UUID orderItemId,
@RequestParam("file") MultipartFile file
) throws IOException {
boolean uploaded = orderControllerService.uploadOrderItemFile(orderId, orderItemId, file);
if (!uploaded) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderDto> getOrder(@PathVariable UUID orderId) {
return orderControllerService.getOrder(orderId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/{orderId}/payments/report")
@Transactional
public ResponseEntity<OrderDto> reportPayment(
@PathVariable UUID orderId,
@RequestBody Map<String, String> payload
) {
return orderControllerService.reportPayment(orderId, payload.get("method"))
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/{orderId}/confirmation")
public ResponseEntity<byte[]> getConfirmation(@PathVariable UUID orderId) {
return orderControllerService.getConfirmation(orderId);
}
@GetMapping("/{orderId}/invoice")
public ResponseEntity<byte[]> getInvoice(@PathVariable UUID orderId) {
return ResponseEntity.notFound().build();
}
@GetMapping("/{orderId}/twint")
public ResponseEntity<Map<String, String>> getTwintPayment(@PathVariable UUID orderId) {
return orderControllerService.getTwintPayment(orderId);
}
@GetMapping("/{orderId}/twint/open")
public ResponseEntity<Void> openTwintPayment(@PathVariable UUID orderId) {
return orderControllerService.openTwintPayment(orderId);
}
@GetMapping("/{orderId}/twint/qr")
public ResponseEntity<byte[]> getTwintQr(
@PathVariable UUID orderId,
@RequestParam(defaultValue = "320") int size
) {
return orderControllerService.getTwintQr(orderId, size);
}
}

View File

@@ -0,0 +1,31 @@
package com.printcalculator.controller;
import com.printcalculator.dto.PublicMediaUsageDto;
import com.printcalculator.service.media.PublicMediaQueryService;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/public/media")
@Transactional(readOnly = true)
public class PublicMediaController {
private final PublicMediaQueryService publicMediaQueryService;
public PublicMediaController(PublicMediaQueryService publicMediaQueryService) {
this.publicMediaQueryService = publicMediaQueryService;
}
@GetMapping("/usages")
public ResponseEntity<List<PublicMediaUsageDto>> getUsageMedia(@RequestParam String usageType,
@RequestParam String usageKey,
@RequestParam(required = false) String lang) {
return ResponseEntity.ok(publicMediaQueryService.getUsageMedia(usageType, usageKey, lang));
}
}

View File

@@ -0,0 +1,77 @@
package com.printcalculator.controller;
import com.printcalculator.dto.ShopCategoryDetailDto;
import com.printcalculator.dto.ShopCategoryTreeDto;
import com.printcalculator.dto.ShopProductCatalogResponseDto;
import com.printcalculator.dto.ShopProductDetailDto;
import com.printcalculator.service.shop.PublicShopCatalogService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/api/shop")
@Transactional(readOnly = true)
public class PublicShopController {
private final PublicShopCatalogService publicShopCatalogService;
public PublicShopController(PublicShopCatalogService publicShopCatalogService) {
this.publicShopCatalogService = publicShopCatalogService;
}
@GetMapping("/categories")
public ResponseEntity<List<ShopCategoryTreeDto>> getCategories(@RequestParam(required = false) String lang) {
return ResponseEntity.ok(publicShopCatalogService.getCategories(lang));
}
@GetMapping("/categories/{slug}")
public ResponseEntity<ShopCategoryDetailDto> getCategory(@PathVariable String slug,
@RequestParam(required = false) String lang) {
return ResponseEntity.ok(publicShopCatalogService.getCategory(slug, lang));
}
@GetMapping("/products")
public ResponseEntity<ShopProductCatalogResponseDto> getProducts(
@RequestParam(required = false) String categorySlug,
@RequestParam(required = false) Boolean featured,
@RequestParam(required = false) String lang
) {
return ResponseEntity.ok(publicShopCatalogService.getProductCatalog(categorySlug, featured, lang));
}
@GetMapping("/products/{slug}")
public ResponseEntity<ShopProductDetailDto> getProduct(@PathVariable String slug,
@RequestParam(required = false) String lang) {
return ResponseEntity.ok(publicShopCatalogService.getProduct(slug, lang));
}
@GetMapping("/products/{slug}/model")
public ResponseEntity<Resource> getProductModel(@PathVariable String slug) throws IOException {
PublicShopCatalogService.ProductModelDownload model = publicShopCatalogService.getProductModelDownload(slug);
Resource resource = new UrlResource(model.path().toUri());
MediaType contentType = MediaType.APPLICATION_OCTET_STREAM;
if (model.mimeType() != null && !model.mimeType().isBlank()) {
try {
contentType = MediaType.parseMediaType(model.mimeType());
} catch (IllegalArgumentException ignored) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
}
return ResponseEntity.ok()
.contentType(contentType)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + model.filename() + "\"")
.body(resource);
}
}

View File

@@ -4,17 +4,24 @@ import com.printcalculator.entity.PrinterMachine;
import com.printcalculator.model.PrintStats;
import com.printcalculator.model.QuoteResult;
import com.printcalculator.repository.PrinterMachineRepository;
import com.printcalculator.service.NozzleLayerHeightPolicyService;
import com.printcalculator.service.QuoteCalculator;
import com.printcalculator.service.SlicerService;
import com.printcalculator.service.storage.ClamAVService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
@RestController
public class QuoteController {
@@ -22,15 +29,23 @@ public class QuoteController {
private final SlicerService slicerService;
private final QuoteCalculator quoteCalculator;
private final PrinterMachineRepository machineRepo;
private final ClamAVService clamAVService;
private final NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService;
// Defaults (using aliases defined in ProfileManager)
private static final String DEFAULT_FILAMENT = "pla_basic";
private static final String DEFAULT_PROCESS = "standard";
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator, PrinterMachineRepository machineRepo) {
public QuoteController(SlicerService slicerService,
QuoteCalculator quoteCalculator,
PrinterMachineRepository machineRepo,
ClamAVService clamAVService,
NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService) {
this.slicerService = slicerService;
this.quoteCalculator = quoteCalculator;
this.machineRepo = machineRepo;
this.clamAVService = clamAVService;
this.nozzleLayerHeightPolicyService = nozzleLayerHeightPolicyService;
}
@PostMapping("/api/quote")
@@ -67,15 +82,27 @@ public class QuoteController {
if (infillPattern != null && !infillPattern.isEmpty()) {
processOverrides.put("sparse_infill_pattern", infillPattern);
}
BigDecimal normalizedNozzle = nozzleLayerHeightPolicyService.resolveNozzle(
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
);
if (layerHeight != null) {
processOverrides.put("layer_height", String.valueOf(layerHeight));
BigDecimal normalizedLayer = nozzleLayerHeightPolicyService.normalizeLayer(BigDecimal.valueOf(layerHeight));
if (!nozzleLayerHeightPolicyService.isAllowed(normalizedNozzle, normalizedLayer)) {
throw new ResponseStatusException(
BAD_REQUEST,
"Layer height " + normalizedLayer.stripTrailingZeros().toPlainString()
+ " is not allowed for nozzle " + normalizedNozzle.stripTrailingZeros().toPlainString()
+ ". Allowed: " + nozzleLayerHeightPolicyService.allowedLayersLabel(normalizedNozzle)
);
}
processOverrides.put("layer_height", normalizedLayer.stripTrailingZeros().toPlainString());
}
if (supportEnabled != null) {
processOverrides.put("enable_support", supportEnabled ? "1" : "0");
}
if (nozzleDiameter != null) {
machineOverrides.put("nozzle_diameter", String.valueOf(nozzleDiameter));
machineOverrides.put("nozzle_diameter", normalizedNozzle.stripTrailingZeros().toPlainString());
// Also need to ensure the printer profile is compatible or just override?
// Usually nozzle diameter changes require a different printer profile or deep overrides.
// For now, we trust the override key works on the base profile.
@@ -98,6 +125,12 @@ public class QuoteController {
if (file.isEmpty()) {
return ResponseEntity.badRequest().build();
}
if (!isSupportedInputFile(file)) {
throw new ResponseStatusException(BAD_REQUEST, "Unsupported file type. Allowed: stl, 3mf");
}
// Scan for virus
clamAVService.scan(file.getInputStream());
// Fetch Default Active Machine
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
@@ -124,4 +157,14 @@ public class QuoteController {
Files.deleteIfExists(tempInput);
}
}
private boolean isSupportedInputFile(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || originalFilename.isBlank()) {
return false;
}
String normalized = originalFilename.toLowerCase(Locale.ROOT);
return normalized.endsWith(".stl") || normalized.endsWith(".3mf");
}
}

View File

@@ -0,0 +1,244 @@
package com.printcalculator.controller;
import com.printcalculator.dto.PrintSettingsDto;
import com.printcalculator.entity.QuoteLineItem;
import com.printcalculator.entity.QuoteSession;
import com.printcalculator.repository.QuoteLineItemRepository;
import com.printcalculator.repository.QuoteSessionRepository;
import com.printcalculator.service.QuoteCalculator;
import com.printcalculator.service.QuoteSessionTotalsService;
import com.printcalculator.service.quote.QuoteSessionItemService;
import com.printcalculator.service.quote.QuoteSessionResponseAssembler;
import com.printcalculator.service.quote.QuoteStorageService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
@RestController
@RequestMapping("/api/quote-sessions")
public class QuoteSessionController {
private final QuoteSessionRepository sessionRepo;
private final QuoteLineItemRepository lineItemRepo;
private final QuoteCalculator quoteCalculator;
private final com.printcalculator.repository.PricingPolicyRepository pricingRepo;
private final QuoteSessionTotalsService quoteSessionTotalsService;
private final QuoteSessionItemService quoteSessionItemService;
private final QuoteStorageService quoteStorageService;
private final QuoteSessionResponseAssembler quoteSessionResponseAssembler;
public QuoteSessionController(QuoteSessionRepository sessionRepo,
QuoteLineItemRepository lineItemRepo,
QuoteCalculator quoteCalculator,
com.printcalculator.repository.PricingPolicyRepository pricingRepo,
QuoteSessionTotalsService quoteSessionTotalsService,
QuoteSessionItemService quoteSessionItemService,
QuoteStorageService quoteStorageService,
QuoteSessionResponseAssembler quoteSessionResponseAssembler) {
this.sessionRepo = sessionRepo;
this.lineItemRepo = lineItemRepo;
this.quoteCalculator = quoteCalculator;
this.pricingRepo = pricingRepo;
this.quoteSessionTotalsService = quoteSessionTotalsService;
this.quoteSessionItemService = quoteSessionItemService;
this.quoteStorageService = quoteStorageService;
this.quoteSessionResponseAssembler = quoteSessionResponseAssembler;
}
@PostMapping(value = "")
@Transactional
public ResponseEntity<QuoteSession> createSession() {
QuoteSession session = new QuoteSession();
session.setStatus("ACTIVE");
session.setSessionType("PRINT_QUOTE");
session.setPricingVersion("v1");
session.setMaterialCode("PLA");
session.setSupportsEnabled(false);
session.setCreatedAt(OffsetDateTime.now());
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
var policy = pricingRepo.findFirstByIsActiveTrueOrderByValidFromDesc();
session.setSetupCostChf(quoteCalculator.calculateSessionSetupFee(policy));
session = sessionRepo.save(session);
return ResponseEntity.ok(session);
}
@PostMapping(value = "/{id}/line-items", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<QuoteLineItem> addItemToExistingSession(@PathVariable UUID id,
@RequestPart("settings") PrintSettingsDto settings,
@RequestPart("file") MultipartFile file) throws IOException {
QuoteSession session = sessionRepo.findById(id)
.orElseThrow(() -> new RuntimeException("Session not found"));
QuoteLineItem item = quoteSessionItemService.addItemToSession(session, file, settings);
return ResponseEntity.ok(item);
}
@PatchMapping("/line-items/{lineItemId}")
@Transactional
public ResponseEntity<QuoteLineItem> updateLineItem(@PathVariable UUID lineItemId,
@RequestBody Map<String, Object> updates) {
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
QuoteSession session = item.getQuoteSession();
if ("CONVERTED".equals(session.getStatus())) {
throw new ResponseStatusException(BAD_REQUEST, "Cannot modify a converted session");
}
if (updates.containsKey("quantity")) {
item.setQuantity(parsePositiveQuantity(updates.get("quantity")));
}
if (updates.containsKey("color_code")) {
Object colorValue = updates.get("color_code");
if (colorValue != null) {
item.setColorCode(String.valueOf(colorValue));
}
}
item.setUpdatedAt(OffsetDateTime.now());
return ResponseEntity.ok(lineItemRepo.save(item));
}
@DeleteMapping("/{sessionId}/line-items/{lineItemId}")
@Transactional
public ResponseEntity<Void> deleteLineItem(@PathVariable UUID sessionId,
@PathVariable UUID lineItemId) {
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (!item.getQuoteSession().getId().equals(sessionId)) {
return ResponseEntity.badRequest().build();
}
lineItemRepo.delete(item);
return ResponseEntity.noContent().build();
}
@GetMapping("/{id}")
@Transactional(readOnly = true)
public ResponseEntity<Map<String, Object>> getQuoteSession(@PathVariable UUID id) {
QuoteSession session = sessionRepo.findById(id)
.orElseThrow(() -> new RuntimeException("Session not found"));
List<QuoteLineItem> items = lineItemRepo.findByQuoteSessionId(id);
QuoteSessionTotalsService.QuoteSessionTotals totals = quoteSessionTotalsService.compute(session, items);
return ResponseEntity.ok(quoteSessionResponseAssembler.assemble(session, items, totals));
}
@GetMapping(value = "/{sessionId}/line-items/{lineItemId}/content")
public ResponseEntity<Resource> downloadLineItemContent(@PathVariable UUID sessionId,
@PathVariable UUID lineItemId,
@RequestParam(name = "preview", required = false, defaultValue = "false") boolean preview)
throws IOException {
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (!item.getQuoteSession().getId().equals(sessionId)) {
return ResponseEntity.badRequest().build();
}
String targetStoredPath = item.getStoredPath();
if (preview) {
String convertedPath = quoteStorageService.extractConvertedStoredPath(item);
if (convertedPath != null && !convertedPath.isBlank()) {
targetStoredPath = convertedPath;
}
}
if (targetStoredPath == null) {
return ResponseEntity.notFound().build();
}
java.nio.file.Path path = quoteStorageService.resolveStoredQuotePath(targetStoredPath, sessionId);
if (path == null || !java.nio.file.Files.exists(path)) {
return ResponseEntity.notFound().build();
}
Resource resource = new UrlResource(path.toUri());
String downloadName = preview ? path.getFileName().toString() : item.getOriginalFilename();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + downloadName + "\"")
.body(resource);
}
@GetMapping(value = "/{sessionId}/line-items/{lineItemId}/stl-preview")
public ResponseEntity<Resource> downloadLineItemStlPreview(@PathVariable UUID sessionId,
@PathVariable UUID lineItemId)
throws IOException {
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (!item.getQuoteSession().getId().equals(sessionId)) {
return ResponseEntity.badRequest().build();
}
if (!"stl".equals(quoteStorageService.getSafeExtension(item.getOriginalFilename(), ""))) {
return ResponseEntity.notFound().build();
}
String targetStoredPath = item.getStoredPath();
if (targetStoredPath == null || targetStoredPath.isBlank()) {
return ResponseEntity.notFound().build();
}
java.nio.file.Path path = quoteStorageService.resolveStoredQuotePath(targetStoredPath, sessionId);
if (path == null || !java.nio.file.Files.exists(path)) {
return ResponseEntity.notFound().build();
}
if (!"stl".equals(quoteStorageService.getSafeExtension(path.getFileName().toString(), ""))) {
return ResponseEntity.notFound().build();
}
Resource resource = new UrlResource(path.toUri());
String downloadName = path.getFileName().toString();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("model/stl"))
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + downloadName + "\"")
.body(resource);
}
private int parsePositiveQuantity(Object raw) {
if (raw == null) {
throw new ResponseStatusException(BAD_REQUEST, "Quantity is required");
}
int quantity;
if (raw instanceof Number number) {
double numericValue = number.doubleValue();
if (!Double.isFinite(numericValue)) {
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be a finite number");
}
quantity = (int) Math.floor(numericValue);
} else {
try {
quantity = Integer.parseInt(String.valueOf(raw).trim());
} catch (NumberFormatException ex) {
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be an integer");
}
}
if (quantity < 1) {
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be >= 1");
}
return quantity;
}
}

View File

@@ -0,0 +1,85 @@
package com.printcalculator.controller;
import com.printcalculator.dto.ShopCartAddItemRequest;
import com.printcalculator.dto.ShopCartUpdateItemRequest;
import com.printcalculator.service.shop.ShopCartCookieService;
import com.printcalculator.service.shop.ShopCartService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api/shop/cart")
public class ShopCartController {
private final ShopCartService shopCartService;
private final ShopCartCookieService shopCartCookieService;
public ShopCartController(ShopCartService shopCartService, ShopCartCookieService shopCartCookieService) {
this.shopCartService = shopCartService;
this.shopCartCookieService = shopCartCookieService;
}
@GetMapping
public ResponseEntity<?> getCart(HttpServletRequest request, HttpServletResponse response) {
ShopCartService.CartResult result = shopCartService.loadCart(request);
applyCookie(response, result);
return ResponseEntity.ok(result.response());
}
@PostMapping("/items")
public ResponseEntity<?> addItem(HttpServletRequest request,
HttpServletResponse response,
@Valid @RequestBody ShopCartAddItemRequest payload) {
ShopCartService.CartResult result = shopCartService.addItem(request, payload);
applyCookie(response, result);
return ResponseEntity.ok(result.response());
}
@PatchMapping("/items/{lineItemId}")
public ResponseEntity<?> updateItem(HttpServletRequest request,
HttpServletResponse response,
@PathVariable UUID lineItemId,
@Valid @RequestBody ShopCartUpdateItemRequest payload) {
ShopCartService.CartResult result = shopCartService.updateItem(request, lineItemId, payload);
applyCookie(response, result);
return ResponseEntity.ok(result.response());
}
@DeleteMapping("/items/{lineItemId}")
public ResponseEntity<?> removeItem(HttpServletRequest request,
HttpServletResponse response,
@PathVariable UUID lineItemId) {
ShopCartService.CartResult result = shopCartService.removeItem(request, lineItemId);
applyCookie(response, result);
return ResponseEntity.ok(result.response());
}
@DeleteMapping
public ResponseEntity<?> clearCart(HttpServletRequest request, HttpServletResponse response) {
ShopCartService.CartResult result = shopCartService.clearCart(request);
applyCookie(response, result);
return ResponseEntity.ok(result.response());
}
private void applyCookie(HttpServletResponse response, ShopCartService.CartResult result) {
if (result.clearCookie()) {
response.addHeader(HttpHeaders.SET_COOKIE, shopCartCookieService.buildClearCookie().toString());
return;
}
if (result.sessionId() != null) {
response.addHeader(HttpHeaders.SET_COOKIE, shopCartCookieService.buildSessionCookie(result.sessionId()).toString());
}
}
}

View File

@@ -0,0 +1,37 @@
package com.printcalculator.controller;
import com.printcalculator.service.shop.ShopSitemapService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
@RestController
public class SitemapController {
private final ShopSitemapService shopSitemapService;
private final long cacheSeconds;
public SitemapController(
ShopSitemapService shopSitemapService,
@Value("${app.sitemap.shop.cache-seconds:3600}") long cacheSeconds
) {
this.shopSitemapService = shopSitemapService;
this.cacheSeconds = Math.max(cacheSeconds, 0L);
}
@GetMapping(value = "/api/sitemap-shop.xml", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> getShopSitemap() {
CacheControl cacheControl = cacheSeconds > 0
? CacheControl.maxAge(Duration.ofSeconds(cacheSeconds)).cachePublic()
: CacheControl.noCache();
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/xml;charset=UTF-8"))
.cacheControl(cacheControl)
.body(shopSitemapService.getShopSitemapXml());
}
}

View File

@@ -0,0 +1,83 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminLoginRequest;
import com.printcalculator.security.AdminLoginThrottleService;
import com.printcalculator.security.AdminSessionService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.OptionalLong;
@RestController
@RequestMapping("/api/admin/auth")
public class AdminAuthController {
private final AdminSessionService adminSessionService;
private final AdminLoginThrottleService adminLoginThrottleService;
public AdminAuthController(
AdminSessionService adminSessionService,
AdminLoginThrottleService adminLoginThrottleService
) {
this.adminSessionService = adminSessionService;
this.adminLoginThrottleService = adminLoginThrottleService;
}
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(
@Valid @RequestBody AdminLoginRequest request,
HttpServletRequest httpRequest,
HttpServletResponse response
) {
String clientKey = adminLoginThrottleService.resolveClientKey(httpRequest);
OptionalLong remainingLock = adminLoginThrottleService.getRemainingLockSeconds(clientKey);
if (remainingLock.isPresent()) {
long retryAfter = remainingLock.getAsLong();
return ResponseEntity.status(429)
.header(HttpHeaders.RETRY_AFTER, String.valueOf(retryAfter))
.body(Map.of(
"authenticated", false,
"retryAfterSeconds", retryAfter
));
}
if (!adminSessionService.isPasswordValid(request.getPassword())) {
long retryAfter = adminLoginThrottleService.registerFailure(clientKey);
return ResponseEntity.status(401)
.header(HttpHeaders.RETRY_AFTER, String.valueOf(retryAfter))
.body(Map.of(
"authenticated", false,
"retryAfterSeconds", retryAfter
));
}
adminLoginThrottleService.reset(clientKey);
String token = adminSessionService.createSessionToken();
response.addHeader(HttpHeaders.SET_COOKIE, adminSessionService.buildLoginCookie(token).toString());
return ResponseEntity.ok(Map.of(
"authenticated", true,
"expiresInMinutes", adminSessionService.getSessionTtlMinutes()
));
}
@PostMapping("/logout")
public ResponseEntity<Map<String, Object>> logout(HttpServletResponse response) {
response.addHeader(HttpHeaders.SET_COOKIE, adminSessionService.buildLogoutCookie().toString());
return ResponseEntity.ok(Map.of("authenticated", false));
}
@GetMapping("/me")
public ResponseEntity<Map<String, Object>> me() {
return ResponseEntity.ok(Map.of("authenticated", true));
}
}

View File

@@ -0,0 +1,82 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminFilamentMaterialTypeDto;
import com.printcalculator.dto.AdminFilamentVariantDto;
import com.printcalculator.dto.AdminUpsertFilamentMaterialTypeRequest;
import com.printcalculator.dto.AdminUpsertFilamentVariantRequest;
import com.printcalculator.service.admin.AdminFilamentControllerService;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/admin/filaments")
@Transactional(readOnly = true)
public class AdminFilamentController {
private final AdminFilamentControllerService adminFilamentControllerService;
public AdminFilamentController(AdminFilamentControllerService adminFilamentControllerService) {
this.adminFilamentControllerService = adminFilamentControllerService;
}
@GetMapping("/materials")
public ResponseEntity<List<AdminFilamentMaterialTypeDto>> getMaterials() {
return ResponseEntity.ok(adminFilamentControllerService.getMaterials());
}
@GetMapping("/variants")
public ResponseEntity<List<AdminFilamentVariantDto>> getVariants() {
return ResponseEntity.ok(adminFilamentControllerService.getVariants());
}
@PostMapping("/materials")
@Transactional
public ResponseEntity<AdminFilamentMaterialTypeDto> createMaterial(
@RequestBody AdminUpsertFilamentMaterialTypeRequest payload
) {
return ResponseEntity.ok(adminFilamentControllerService.createMaterial(payload));
}
@PutMapping("/materials/{materialTypeId}")
@Transactional
public ResponseEntity<AdminFilamentMaterialTypeDto> updateMaterial(
@PathVariable Long materialTypeId,
@RequestBody AdminUpsertFilamentMaterialTypeRequest payload
) {
return ResponseEntity.ok(adminFilamentControllerService.updateMaterial(materialTypeId, payload));
}
@PostMapping("/variants")
@Transactional
public ResponseEntity<AdminFilamentVariantDto> createVariant(
@RequestBody AdminUpsertFilamentVariantRequest payload
) {
return ResponseEntity.ok(adminFilamentControllerService.createVariant(payload));
}
@PutMapping("/variants/{variantId}")
@Transactional
public ResponseEntity<AdminFilamentVariantDto> updateVariant(
@PathVariable Long variantId,
@RequestBody AdminUpsertFilamentVariantRequest payload
) {
return ResponseEntity.ok(adminFilamentControllerService.updateVariant(variantId, payload));
}
@DeleteMapping("/variants/{variantId}")
@Transactional
public ResponseEntity<Void> deleteVariant(@PathVariable Long variantId) {
adminFilamentControllerService.deleteVariant(variantId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,89 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminCreateMediaUsageRequest;
import com.printcalculator.dto.AdminMediaAssetDto;
import com.printcalculator.dto.AdminMediaUsageDto;
import com.printcalculator.dto.AdminUpdateMediaAssetRequest;
import com.printcalculator.dto.AdminUpdateMediaUsageRequest;
import com.printcalculator.service.admin.AdminMediaControllerService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin/media")
@Transactional(readOnly = true)
public class AdminMediaController {
private final AdminMediaControllerService adminMediaControllerService;
public AdminMediaController(AdminMediaControllerService adminMediaControllerService) {
this.adminMediaControllerService = adminMediaControllerService;
}
@PostMapping(value = "/assets", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<AdminMediaAssetDto> uploadAsset(@RequestParam("file") MultipartFile file,
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "altText", required = false) String altText,
@RequestParam(value = "visibility", required = false) String visibility) {
return ResponseEntity.ok(adminMediaControllerService.uploadAsset(file, title, altText, visibility));
}
@GetMapping("/assets")
public ResponseEntity<List<AdminMediaAssetDto>> listAssets() {
return ResponseEntity.ok(adminMediaControllerService.listAssets());
}
@GetMapping("/assets/{mediaAssetId}")
public ResponseEntity<AdminMediaAssetDto> getAsset(@PathVariable UUID mediaAssetId) {
return ResponseEntity.ok(adminMediaControllerService.getAsset(mediaAssetId));
}
@GetMapping("/usages")
public ResponseEntity<List<AdminMediaUsageDto>> getUsages(@RequestParam String usageType,
@RequestParam String usageKey,
@RequestParam(required = false) UUID ownerId) {
return ResponseEntity.ok(adminMediaControllerService.getUsages(usageType, usageKey, ownerId));
}
@PatchMapping("/assets/{mediaAssetId}")
@Transactional
public ResponseEntity<AdminMediaAssetDto> updateAsset(@PathVariable UUID mediaAssetId,
@RequestBody AdminUpdateMediaAssetRequest payload) {
return ResponseEntity.ok(adminMediaControllerService.updateAsset(mediaAssetId, payload));
}
@PostMapping("/usages")
@Transactional
public ResponseEntity<AdminMediaUsageDto> createUsage(@RequestBody AdminCreateMediaUsageRequest payload) {
return ResponseEntity.ok(adminMediaControllerService.createUsage(payload));
}
@PatchMapping("/usages/{mediaUsageId}")
@Transactional
public ResponseEntity<AdminMediaUsageDto> updateUsage(@PathVariable UUID mediaUsageId,
@RequestBody AdminUpdateMediaUsageRequest payload) {
return ResponseEntity.ok(adminMediaControllerService.updateUsage(mediaUsageId, payload));
}
@DeleteMapping("/usages/{mediaUsageId}")
@Transactional
public ResponseEntity<Void> deleteUsage(@PathVariable UUID mediaUsageId) {
adminMediaControllerService.deleteUsage(mediaUsageId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,93 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminCadInvoiceCreateRequest;
import com.printcalculator.dto.AdminCadInvoiceDto;
import com.printcalculator.dto.AdminContactRequestDetailDto;
import com.printcalculator.dto.AdminContactRequestDto;
import com.printcalculator.dto.AdminFilamentStockDto;
import com.printcalculator.dto.AdminQuoteSessionDto;
import com.printcalculator.dto.AdminUpdateContactRequestStatusRequest;
import com.printcalculator.service.admin.AdminOperationsControllerService;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin")
@Transactional(readOnly = true)
public class AdminOperationsController {
private final AdminOperationsControllerService adminOperationsControllerService;
public AdminOperationsController(AdminOperationsControllerService adminOperationsControllerService) {
this.adminOperationsControllerService = adminOperationsControllerService;
}
@GetMapping("/filament-stock")
public ResponseEntity<List<AdminFilamentStockDto>> getFilamentStock() {
return ResponseEntity.ok(adminOperationsControllerService.getFilamentStock());
}
@GetMapping("/contact-requests")
public ResponseEntity<List<AdminContactRequestDto>> getContactRequests() {
return ResponseEntity.ok(adminOperationsControllerService.getContactRequests());
}
@GetMapping("/contact-requests/{requestId}")
public ResponseEntity<AdminContactRequestDetailDto> getContactRequestDetail(@PathVariable UUID requestId) {
return ResponseEntity.ok(adminOperationsControllerService.getContactRequestDetail(requestId));
}
@PatchMapping("/contact-requests/{requestId}/status")
@Transactional
public ResponseEntity<AdminContactRequestDetailDto> updateContactRequestStatus(
@PathVariable UUID requestId,
@RequestBody AdminUpdateContactRequestStatusRequest payload
) {
return ResponseEntity.ok(adminOperationsControllerService.updateContactRequestStatus(requestId, payload));
}
@GetMapping("/contact-requests/{requestId}/attachments/{attachmentId}/file")
public ResponseEntity<Resource> downloadContactRequestAttachment(
@PathVariable UUID requestId,
@PathVariable UUID attachmentId
) {
return adminOperationsControllerService.downloadContactRequestAttachment(requestId, attachmentId);
}
@GetMapping("/sessions")
public ResponseEntity<List<AdminQuoteSessionDto>> getQuoteSessions() {
return ResponseEntity.ok(adminOperationsControllerService.getQuoteSessions());
}
@GetMapping("/cad-invoices")
public ResponseEntity<List<AdminCadInvoiceDto>> getCadInvoices() {
return ResponseEntity.ok(adminOperationsControllerService.getCadInvoices());
}
@PostMapping("/cad-invoices")
@Transactional
public ResponseEntity<AdminCadInvoiceDto> createOrUpdateCadInvoice(
@RequestBody AdminCadInvoiceCreateRequest payload
) {
return ResponseEntity.ok(adminOperationsControllerService.createOrUpdateCadInvoice(payload));
}
@DeleteMapping("/sessions/{sessionId}")
@Transactional
public ResponseEntity<Void> deleteQuoteSession(@PathVariable UUID sessionId) {
adminOperationsControllerService.deleteQuoteSession(sessionId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,76 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminOrderStatusUpdateRequest;
import com.printcalculator.dto.OrderDto;
import com.printcalculator.service.order.AdminOrderControllerService;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin/orders")
@Transactional(readOnly = true)
public class AdminOrderController {
private final AdminOrderControllerService adminOrderControllerService;
public AdminOrderController(AdminOrderControllerService adminOrderControllerService) {
this.adminOrderControllerService = adminOrderControllerService;
}
@GetMapping
public ResponseEntity<List<OrderDto>> listOrders() {
return ResponseEntity.ok(adminOrderControllerService.listOrders());
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderDto> getOrder(@PathVariable UUID orderId) {
return ResponseEntity.ok(adminOrderControllerService.getOrder(orderId));
}
@PostMapping("/{orderId}/payments/confirm")
@Transactional
public ResponseEntity<OrderDto> updatePaymentMethod(
@PathVariable UUID orderId,
@RequestBody(required = false) Map<String, String> payload
) {
return ResponseEntity.ok(adminOrderControllerService.updatePaymentMethod(orderId, payload));
}
@PostMapping("/{orderId}/status")
@Transactional
public ResponseEntity<OrderDto> updateOrderStatus(
@PathVariable UUID orderId,
@RequestBody AdminOrderStatusUpdateRequest payload
) {
return ResponseEntity.ok(adminOrderControllerService.updateOrderStatus(orderId, payload));
}
@GetMapping("/{orderId}/items/{orderItemId}/file")
public ResponseEntity<Resource> downloadOrderItemFile(
@PathVariable UUID orderId,
@PathVariable UUID orderItemId
) {
return adminOrderControllerService.downloadOrderItemFile(orderId, orderItemId);
}
@GetMapping("/{orderId}/documents/confirmation")
public ResponseEntity<byte[]> downloadOrderConfirmation(@PathVariable UUID orderId) {
return adminOrderControllerService.downloadOrderConfirmation(orderId);
}
@GetMapping("/{orderId}/documents/invoice")
public ResponseEntity<byte[]> downloadOrderInvoice(@PathVariable UUID orderId) {
return adminOrderControllerService.downloadOrderInvoice(orderId);
}
}

View File

@@ -0,0 +1,64 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminShopCategoryDto;
import com.printcalculator.dto.AdminUpsertShopCategoryRequest;
import com.printcalculator.service.admin.AdminShopCategoryControllerService;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin/shop/categories")
@Transactional(readOnly = true)
public class AdminShopCategoryController {
private final AdminShopCategoryControllerService adminShopCategoryControllerService;
public AdminShopCategoryController(AdminShopCategoryControllerService adminShopCategoryControllerService) {
this.adminShopCategoryControllerService = adminShopCategoryControllerService;
}
@GetMapping
public ResponseEntity<List<AdminShopCategoryDto>> getCategories() {
return ResponseEntity.ok(adminShopCategoryControllerService.getCategories());
}
@GetMapping("/tree")
public ResponseEntity<List<AdminShopCategoryDto>> getCategoryTree() {
return ResponseEntity.ok(adminShopCategoryControllerService.getCategoryTree());
}
@GetMapping("/{categoryId}")
public ResponseEntity<AdminShopCategoryDto> getCategory(@PathVariable UUID categoryId) {
return ResponseEntity.ok(adminShopCategoryControllerService.getCategory(categoryId));
}
@PostMapping
@Transactional
public ResponseEntity<AdminShopCategoryDto> createCategory(@RequestBody AdminUpsertShopCategoryRequest payload) {
return ResponseEntity.ok(adminShopCategoryControllerService.createCategory(payload));
}
@PutMapping("/{categoryId}")
@Transactional
public ResponseEntity<AdminShopCategoryDto> updateCategory(@PathVariable UUID categoryId,
@RequestBody AdminUpsertShopCategoryRequest payload) {
return ResponseEntity.ok(adminShopCategoryControllerService.updateCategory(categoryId, payload));
}
@DeleteMapping("/{categoryId}")
@Transactional
public ResponseEntity<Void> deleteCategory(@PathVariable UUID categoryId) {
adminShopCategoryControllerService.deleteCategory(categoryId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,110 @@
package com.printcalculator.controller.admin;
import com.printcalculator.dto.AdminShopProductDto;
import com.printcalculator.dto.AdminTranslateShopProductRequest;
import com.printcalculator.dto.AdminTranslateShopProductResponse;
import com.printcalculator.dto.AdminUpsertShopProductRequest;
import com.printcalculator.service.admin.AdminShopProductControllerService;
import com.printcalculator.service.admin.AdminShopProductTranslationService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/admin/shop/products")
@Transactional(readOnly = true)
public class AdminShopProductController {
private final AdminShopProductControllerService adminShopProductControllerService;
private final AdminShopProductTranslationService adminShopProductTranslationService;
public AdminShopProductController(AdminShopProductControllerService adminShopProductControllerService,
AdminShopProductTranslationService adminShopProductTranslationService) {
this.adminShopProductControllerService = adminShopProductControllerService;
this.adminShopProductTranslationService = adminShopProductTranslationService;
}
@GetMapping
public ResponseEntity<List<AdminShopProductDto>> getProducts() {
return ResponseEntity.ok(adminShopProductControllerService.getProducts());
}
@GetMapping("/{productId}")
public ResponseEntity<AdminShopProductDto> getProduct(@PathVariable UUID productId) {
return ResponseEntity.ok(adminShopProductControllerService.getProduct(productId));
}
@PostMapping
@Transactional
public ResponseEntity<AdminShopProductDto> createProduct(@RequestBody AdminUpsertShopProductRequest payload) {
return ResponseEntity.ok(adminShopProductControllerService.createProduct(payload));
}
@PostMapping("/translate")
public ResponseEntity<AdminTranslateShopProductResponse> translateProduct(@RequestBody AdminTranslateShopProductRequest payload) {
return ResponseEntity.ok(adminShopProductTranslationService.translateProduct(payload));
}
@PutMapping("/{productId}")
@Transactional
public ResponseEntity<AdminShopProductDto> updateProduct(@PathVariable UUID productId,
@RequestBody AdminUpsertShopProductRequest payload) {
return ResponseEntity.ok(adminShopProductControllerService.updateProduct(productId, payload));
}
@DeleteMapping("/{productId}")
@Transactional
public ResponseEntity<Void> deleteProduct(@PathVariable UUID productId) {
adminShopProductControllerService.deleteProduct(productId);
return ResponseEntity.noContent().build();
}
@PostMapping("/{productId}/model")
@Transactional
public ResponseEntity<AdminShopProductDto> uploadProductModel(@PathVariable UUID productId,
@RequestParam("file") MultipartFile file) throws IOException {
return ResponseEntity.ok(adminShopProductControllerService.uploadProductModel(productId, file));
}
@DeleteMapping("/{productId}/model")
@Transactional
public ResponseEntity<Void> deleteProductModel(@PathVariable UUID productId) {
adminShopProductControllerService.deleteProductModel(productId);
return ResponseEntity.noContent().build();
}
@GetMapping("/{productId}/model")
public ResponseEntity<Resource> getProductModel(@PathVariable UUID productId) throws IOException {
AdminShopProductControllerService.ProductModelDownload model = adminShopProductControllerService.getProductModel(productId);
Resource resource = new UrlResource(model.path().toUri());
MediaType contentType = MediaType.APPLICATION_OCTET_STREAM;
if (model.mimeType() != null && !model.mimeType().isBlank()) {
try {
contentType = MediaType.parseMediaType(model.mimeType());
} catch (IllegalArgumentException ignored) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
}
return ResponseEntity.ok()
.contentType(contentType)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + model.filename() + "\"")
.body(resource);
}
}

View File

@@ -0,0 +1,16 @@
package com.printcalculator.dto;
import lombok.Data;
@Data
public class AddressDto {
private String firstName;
private String lastName;
private String companyName;
private String contactPerson;
private String addressLine1;
private String addressLine2;
private String zip;
private String city;
private String countryCode;
}

View File

@@ -0,0 +1,52 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.UUID;
public class AdminCadInvoiceCreateRequest {
private UUID sessionId;
private UUID sourceRequestId;
private BigDecimal cadHours;
private BigDecimal cadHourlyRateChf;
private String notes;
public UUID getSessionId() {
return sessionId;
}
public void setSessionId(UUID sessionId) {
this.sessionId = sessionId;
}
public UUID getSourceRequestId() {
return sourceRequestId;
}
public void setSourceRequestId(UUID sourceRequestId) {
this.sourceRequestId = sourceRequestId;
}
public BigDecimal getCadHours() {
return cadHours;
}
public void setCadHours(BigDecimal cadHours) {
this.cadHours = cadHours;
}
public BigDecimal getCadHourlyRateChf() {
return cadHourlyRateChf;
}
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
this.cadHourlyRateChf = cadHourlyRateChf;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}

View File

@@ -0,0 +1,143 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminCadInvoiceDto {
private UUID sessionId;
private String sessionStatus;
private UUID sourceRequestId;
private BigDecimal cadHours;
private BigDecimal cadHourlyRateChf;
private BigDecimal cadTotalChf;
private BigDecimal printItemsTotalChf;
private BigDecimal setupCostChf;
private BigDecimal shippingCostChf;
private BigDecimal grandTotalChf;
private UUID convertedOrderId;
private String convertedOrderStatus;
private String checkoutPath;
private String notes;
private OffsetDateTime createdAt;
public UUID getSessionId() {
return sessionId;
}
public void setSessionId(UUID sessionId) {
this.sessionId = sessionId;
}
public String getSessionStatus() {
return sessionStatus;
}
public void setSessionStatus(String sessionStatus) {
this.sessionStatus = sessionStatus;
}
public UUID getSourceRequestId() {
return sourceRequestId;
}
public void setSourceRequestId(UUID sourceRequestId) {
this.sourceRequestId = sourceRequestId;
}
public BigDecimal getCadHours() {
return cadHours;
}
public void setCadHours(BigDecimal cadHours) {
this.cadHours = cadHours;
}
public BigDecimal getCadHourlyRateChf() {
return cadHourlyRateChf;
}
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
this.cadHourlyRateChf = cadHourlyRateChf;
}
public BigDecimal getCadTotalChf() {
return cadTotalChf;
}
public void setCadTotalChf(BigDecimal cadTotalChf) {
this.cadTotalChf = cadTotalChf;
}
public BigDecimal getPrintItemsTotalChf() {
return printItemsTotalChf;
}
public void setPrintItemsTotalChf(BigDecimal printItemsTotalChf) {
this.printItemsTotalChf = printItemsTotalChf;
}
public BigDecimal getSetupCostChf() {
return setupCostChf;
}
public void setSetupCostChf(BigDecimal setupCostChf) {
this.setupCostChf = setupCostChf;
}
public BigDecimal getShippingCostChf() {
return shippingCostChf;
}
public void setShippingCostChf(BigDecimal shippingCostChf) {
this.shippingCostChf = shippingCostChf;
}
public BigDecimal getGrandTotalChf() {
return grandTotalChf;
}
public void setGrandTotalChf(BigDecimal grandTotalChf) {
this.grandTotalChf = grandTotalChf;
}
public UUID getConvertedOrderId() {
return convertedOrderId;
}
public void setConvertedOrderId(UUID convertedOrderId) {
this.convertedOrderId = convertedOrderId;
}
public String getConvertedOrderStatus() {
return convertedOrderStatus;
}
public void setConvertedOrderStatus(String convertedOrderStatus) {
this.convertedOrderStatus = convertedOrderStatus;
}
public String getCheckoutPath() {
return checkoutPath;
}
public void setCheckoutPath(String checkoutPath) {
this.checkoutPath = checkoutPath;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,52 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminContactRequestAttachmentDto {
private UUID id;
private String originalFilename;
private String mimeType;
private Long fileSizeBytes;
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,125 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public class AdminContactRequestDetailDto {
private UUID id;
private String requestType;
private String customerType;
private String email;
private String phone;
private String name;
private String companyName;
private String contactPerson;
private String message;
private String status;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
private List<AdminContactRequestAttachmentDto> attachments;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public String getCustomerType() {
return customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getContactPerson() {
return contactPerson;
}
public void setContactPerson(String contactPerson) {
this.contactPerson = contactPerson;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public List<AdminContactRequestAttachmentDto> getAttachments() {
return attachments;
}
public void setAttachments(List<AdminContactRequestAttachmentDto> attachments) {
this.attachments = attachments;
}
}

View File

@@ -0,0 +1,88 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminContactRequestDto {
private UUID id;
private String requestType;
private String customerType;
private String email;
private String phone;
private String name;
private String companyName;
private String status;
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public String getCustomerType() {
return customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,79 @@
package com.printcalculator.dto;
import java.util.UUID;
import java.util.Map;
public class AdminCreateMediaUsageRequest {
private String usageType;
private String usageKey;
private UUID ownerId;
private UUID mediaAssetId;
private Integer sortOrder;
private Boolean isPrimary;
private Boolean isActive;
private Map<String, MediaTextTranslationDto> translations;
public String getUsageType() {
return usageType;
}
public void setUsageType(String usageType) {
this.usageType = usageType;
}
public String getUsageKey() {
return usageKey;
}
public void setUsageKey(String usageKey) {
this.usageKey = usageKey;
}
public UUID getOwnerId() {
return ownerId;
}
public void setOwnerId(UUID ownerId) {
this.ownerId = ownerId;
}
public UUID getMediaAssetId() {
return mediaAssetId;
}
public void setMediaAssetId(UUID mediaAssetId) {
this.mediaAssetId = mediaAssetId;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean primary) {
isPrimary = primary;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Map<String, MediaTextTranslationDto> getTranslations() {
return translations;
}
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
this.translations = translations;
}
}

View File

@@ -0,0 +1,49 @@
package com.printcalculator.dto;
public class AdminFilamentMaterialTypeDto {
private Long id;
private String materialCode;
private Boolean isFlexible;
private Boolean isTechnical;
private String technicalTypeLabel;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public Boolean getIsFlexible() {
return isFlexible;
}
public void setIsFlexible(Boolean isFlexible) {
this.isFlexible = isFlexible;
}
public Boolean getIsTechnical() {
return isTechnical;
}
public void setIsTechnical(Boolean isTechnical) {
this.isTechnical = isTechnical;
}
public String getTechnicalTypeLabel() {
return technicalTypeLabel;
}
public void setTechnicalTypeLabel(String technicalTypeLabel) {
this.technicalTypeLabel = technicalTypeLabel;
}
}

View File

@@ -0,0 +1,87 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
public class AdminFilamentStockDto {
private Long filamentVariantId;
private String materialCode;
private String variantDisplayName;
private String colorName;
private BigDecimal stockSpools;
private BigDecimal spoolNetKg;
private BigDecimal stockKg;
private BigDecimal stockFilamentGrams;
private Boolean active;
public Long getFilamentVariantId() {
return filamentVariantId;
}
public void setFilamentVariantId(Long filamentVariantId) {
this.filamentVariantId = filamentVariantId;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public String getVariantDisplayName() {
return variantDisplayName;
}
public void setVariantDisplayName(String variantDisplayName) {
this.variantDisplayName = variantDisplayName;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public BigDecimal getStockSpools() {
return stockSpools;
}
public void setStockSpools(BigDecimal stockSpools) {
this.stockSpools = stockSpools;
}
public BigDecimal getSpoolNetKg() {
return spoolNetKg;
}
public void setSpoolNetKg(BigDecimal spoolNetKg) {
this.spoolNetKg = spoolNetKg;
}
public BigDecimal getStockKg() {
return stockKg;
}
public void setStockKg(BigDecimal stockKg) {
this.stockKg = stockKg;
}
public BigDecimal getStockFilamentGrams() {
return stockFilamentGrams;
}
public void setStockFilamentGrams(BigDecimal stockFilamentGrams) {
this.stockFilamentGrams = stockFilamentGrams;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}

View File

@@ -0,0 +1,223 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
public class AdminFilamentVariantDto {
private Long id;
private Long materialTypeId;
private String materialCode;
private Boolean materialIsFlexible;
private Boolean materialIsTechnical;
private String materialTechnicalTypeLabel;
private String variantDisplayName;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String finishType;
private String brand;
private Boolean isMatte;
private Boolean isSpecial;
private BigDecimal costChfPerKg;
private BigDecimal stockSpools;
private BigDecimal spoolNetKg;
private BigDecimal stockKg;
private BigDecimal stockFilamentGrams;
private Boolean isActive;
private OffsetDateTime createdAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getMaterialTypeId() {
return materialTypeId;
}
public void setMaterialTypeId(Long materialTypeId) {
this.materialTypeId = materialTypeId;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public Boolean getMaterialIsFlexible() {
return materialIsFlexible;
}
public void setMaterialIsFlexible(Boolean materialIsFlexible) {
this.materialIsFlexible = materialIsFlexible;
}
public Boolean getMaterialIsTechnical() {
return materialIsTechnical;
}
public void setMaterialIsTechnical(Boolean materialIsTechnical) {
this.materialIsTechnical = materialIsTechnical;
}
public String getMaterialTechnicalTypeLabel() {
return materialTechnicalTypeLabel;
}
public void setMaterialTechnicalTypeLabel(String materialTechnicalTypeLabel) {
this.materialTechnicalTypeLabel = materialTechnicalTypeLabel;
}
public String getVariantDisplayName() {
return variantDisplayName;
}
public void setVariantDisplayName(String variantDisplayName) {
this.variantDisplayName = variantDisplayName;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getFinishType() {
return finishType;
}
public void setFinishType(String finishType) {
this.finishType = finishType;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Boolean getIsMatte() {
return isMatte;
}
public void setIsMatte(Boolean isMatte) {
this.isMatte = isMatte;
}
public Boolean getIsSpecial() {
return isSpecial;
}
public void setIsSpecial(Boolean isSpecial) {
this.isSpecial = isSpecial;
}
public BigDecimal getCostChfPerKg() {
return costChfPerKg;
}
public void setCostChfPerKg(BigDecimal costChfPerKg) {
this.costChfPerKg = costChfPerKg;
}
public BigDecimal getStockSpools() {
return stockSpools;
}
public void setStockSpools(BigDecimal stockSpools) {
this.stockSpools = stockSpools;
}
public BigDecimal getSpoolNetKg() {
return spoolNetKg;
}
public void setSpoolNetKg(BigDecimal spoolNetKg) {
this.spoolNetKg = spoolNetKg;
}
public BigDecimal getStockKg() {
return stockKg;
}
public void setStockKg(BigDecimal stockKg) {
this.stockKg = stockKg;
}
public BigDecimal getStockFilamentGrams() {
return stockFilamentGrams;
}
public void setStockFilamentGrams(BigDecimal stockFilamentGrams) {
this.stockFilamentGrams = stockFilamentGrams;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,17 @@
package com.printcalculator.dto;
import jakarta.validation.constraints.NotBlank;
public class AdminLoginRequest {
@NotBlank
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,153 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class AdminMediaAssetDto {
private UUID id;
private String originalFilename;
private String storageKey;
private String mimeType;
private Long fileSizeBytes;
private String sha256Hex;
private Integer widthPx;
private Integer heightPx;
private String status;
private String visibility;
private String title;
private String altText;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
private List<AdminMediaVariantDto> variants = new ArrayList<>();
private List<AdminMediaUsageDto> usages = new ArrayList<>();
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getStorageKey() {
return storageKey;
}
public void setStorageKey(String storageKey) {
this.storageKey = storageKey;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public Integer getWidthPx() {
return widthPx;
}
public void setWidthPx(Integer widthPx) {
this.widthPx = widthPx;
}
public Integer getHeightPx() {
return heightPx;
}
public void setHeightPx(Integer heightPx) {
this.heightPx = heightPx;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getVisibility() {
return visibility;
}
public void setVisibility(String visibility) {
this.visibility = visibility;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAltText() {
return altText;
}
public void setAltText(String altText) {
this.altText = altText;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public List<AdminMediaVariantDto> getVariants() {
return variants;
}
public void setVariants(List<AdminMediaVariantDto> variants) {
this.variants = variants;
}
public List<AdminMediaUsageDto> getUsages() {
return usages;
}
public void setUsages(List<AdminMediaUsageDto> usages) {
this.usages = usages;
}
}

View File

@@ -0,0 +1,98 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.UUID;
public class AdminMediaUsageDto {
private UUID id;
private String usageType;
private String usageKey;
private UUID ownerId;
private UUID mediaAssetId;
private Integer sortOrder;
private Boolean isPrimary;
private Boolean isActive;
private Map<String, MediaTextTranslationDto> translations;
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getUsageType() {
return usageType;
}
public void setUsageType(String usageType) {
this.usageType = usageType;
}
public String getUsageKey() {
return usageKey;
}
public void setUsageKey(String usageKey) {
this.usageKey = usageKey;
}
public UUID getOwnerId() {
return ownerId;
}
public void setOwnerId(UUID ownerId) {
this.ownerId = ownerId;
}
public UUID getMediaAssetId() {
return mediaAssetId;
}
public void setMediaAssetId(UUID mediaAssetId) {
this.mediaAssetId = mediaAssetId;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean primary) {
isPrimary = primary;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Map<String, MediaTextTranslationDto> getTranslations() {
return translations;
}
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
this.translations = translations;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,106 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminMediaVariantDto {
private UUID id;
private String variantName;
private String format;
private String storageKey;
private String mimeType;
private Integer widthPx;
private Integer heightPx;
private Long fileSizeBytes;
private Boolean isGenerated;
private String publicUrl;
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getVariantName() {
return variantName;
}
public void setVariantName(String variantName) {
this.variantName = variantName;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getStorageKey() {
return storageKey;
}
public void setStorageKey(String storageKey) {
this.storageKey = storageKey;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Integer getWidthPx() {
return widthPx;
}
public void setWidthPx(Integer widthPx) {
this.widthPx = widthPx;
}
public Integer getHeightPx() {
return heightPx;
}
public void setHeightPx(Integer heightPx) {
this.heightPx = heightPx;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public Boolean getIsGenerated() {
return isGenerated;
}
public void setIsGenerated(Boolean generated) {
isGenerated = generated;
}
public String getPublicUrl() {
return publicUrl;
}
public void setPublicUrl(String publicUrl) {
this.publicUrl = publicUrl;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,13 @@
package com.printcalculator.dto;
public class AdminOrderStatusUpdateRequest {
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -0,0 +1,107 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.math.BigDecimal;
import java.util.UUID;
public class AdminQuoteSessionDto {
private UUID id;
private String status;
private String sessionType;
private String materialCode;
private OffsetDateTime createdAt;
private OffsetDateTime expiresAt;
private UUID convertedOrderId;
private UUID sourceRequestId;
private BigDecimal cadHours;
private BigDecimal cadHourlyRateChf;
private BigDecimal cadTotalChf;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getSessionType() {
return sessionType;
}
public void setSessionType(String sessionType) {
this.sessionType = sessionType;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(OffsetDateTime expiresAt) {
this.expiresAt = expiresAt;
}
public UUID getConvertedOrderId() {
return convertedOrderId;
}
public void setConvertedOrderId(UUID convertedOrderId) {
this.convertedOrderId = convertedOrderId;
}
public UUID getSourceRequestId() {
return sourceRequestId;
}
public void setSourceRequestId(UUID sourceRequestId) {
this.sourceRequestId = sourceRequestId;
}
public BigDecimal getCadHours() {
return cadHours;
}
public void setCadHours(BigDecimal cadHours) {
this.cadHours = cadHours;
}
public BigDecimal getCadHourlyRateChf() {
return cadHourlyRateChf;
}
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
this.cadHourlyRateChf = cadHourlyRateChf;
}
public BigDecimal getCadTotalChf() {
return cadTotalChf;
}
public void setCadTotalChf(BigDecimal cadTotalChf) {
this.cadTotalChf = cadTotalChf;
}
}

View File

@@ -0,0 +1,359 @@
package com.printcalculator.dto;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public class AdminShopCategoryDto {
private UUID id;
private UUID parentCategoryId;
private String parentCategoryName;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
private Boolean isActive;
private Integer sortOrder;
private Integer depth;
private Integer childCount;
private Integer directProductCount;
private Integer descendantProductCount;
private String mediaUsageType;
private String mediaUsageKey;
private List<AdminShopCategoryRefDto> breadcrumbs;
private List<AdminShopCategoryDto> children;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getParentCategoryId() {
return parentCategoryId;
}
public void setParentCategoryId(UUID parentCategoryId) {
this.parentCategoryId = parentCategoryId;
}
public String getParentCategoryName() {
return parentCategoryName;
}
public void setParentCategoryName(String parentCategoryName) {
this.parentCategoryName = parentCategoryName;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getDepth() {
return depth;
}
public void setDepth(Integer depth) {
this.depth = depth;
}
public Integer getChildCount() {
return childCount;
}
public void setChildCount(Integer childCount) {
this.childCount = childCount;
}
public Integer getDirectProductCount() {
return directProductCount;
}
public void setDirectProductCount(Integer directProductCount) {
this.directProductCount = directProductCount;
}
public Integer getDescendantProductCount() {
return descendantProductCount;
}
public void setDescendantProductCount(Integer descendantProductCount) {
this.descendantProductCount = descendantProductCount;
}
public String getMediaUsageType() {
return mediaUsageType;
}
public void setMediaUsageType(String mediaUsageType) {
this.mediaUsageType = mediaUsageType;
}
public String getMediaUsageKey() {
return mediaUsageKey;
}
public void setMediaUsageKey(String mediaUsageKey) {
this.mediaUsageKey = mediaUsageKey;
}
public List<AdminShopCategoryRefDto> getBreadcrumbs() {
return breadcrumbs;
}
public void setBreadcrumbs(List<AdminShopCategoryRefDto> breadcrumbs) {
this.breadcrumbs = breadcrumbs;
}
public List<AdminShopCategoryDto> getChildren() {
return children;
}
public void setChildren(List<AdminShopCategoryDto> children) {
this.children = children;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,33 @@
package com.printcalculator.dto;
import java.util.UUID;
public class AdminShopCategoryRefDto {
private UUID id;
private String slug;
private String name;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,441 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public class AdminShopProductDto {
private UUID id;
private UUID categoryId;
private String categoryName;
private String categorySlug;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String excerpt;
private String excerptIt;
private String excerptEn;
private String excerptDe;
private String excerptFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
private Boolean isFeatured;
private Boolean isActive;
private Integer sortOrder;
private Integer variantCount;
private Integer activeVariantCount;
private BigDecimal priceFromChf;
private BigDecimal priceToChf;
private String mediaUsageType;
private String mediaUsageKey;
private List<AdminMediaUsageDto> mediaUsages;
private List<PublicMediaUsageDto> images;
private ShopProductModelDto model3d;
private List<AdminShopProductVariantDto> variants;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getCategoryId() {
return categoryId;
}
public void setCategoryId(UUID categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public String getCategorySlug() {
return categorySlug;
}
public void setCategorySlug(String categorySlug) {
this.categorySlug = categorySlug;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsFeatured() {
return isFeatured;
}
public void setIsFeatured(Boolean featured) {
isFeatured = featured;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getVariantCount() {
return variantCount;
}
public void setVariantCount(Integer variantCount) {
this.variantCount = variantCount;
}
public Integer getActiveVariantCount() {
return activeVariantCount;
}
public void setActiveVariantCount(Integer activeVariantCount) {
this.activeVariantCount = activeVariantCount;
}
public BigDecimal getPriceFromChf() {
return priceFromChf;
}
public void setPriceFromChf(BigDecimal priceFromChf) {
this.priceFromChf = priceFromChf;
}
public BigDecimal getPriceToChf() {
return priceToChf;
}
public void setPriceToChf(BigDecimal priceToChf) {
this.priceToChf = priceToChf;
}
public String getMediaUsageType() {
return mediaUsageType;
}
public void setMediaUsageType(String mediaUsageType) {
this.mediaUsageType = mediaUsageType;
}
public String getMediaUsageKey() {
return mediaUsageKey;
}
public void setMediaUsageKey(String mediaUsageKey) {
this.mediaUsageKey = mediaUsageKey;
}
public List<AdminMediaUsageDto> getMediaUsages() {
return mediaUsages;
}
public void setMediaUsages(List<AdminMediaUsageDto> mediaUsages) {
this.mediaUsages = mediaUsages;
}
public List<PublicMediaUsageDto> getImages() {
return images;
}
public void setImages(List<PublicMediaUsageDto> images) {
this.images = images;
}
public ShopProductModelDto getModel3d() {
return model3d;
}
public void setModel3d(ShopProductModelDto model3d) {
this.model3d = model3d;
}
public List<AdminShopProductVariantDto> getVariants() {
return variants;
}
public void setVariants(List<AdminShopProductVariantDto> variants) {
this.variants = variants;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,152 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
public class AdminShopProductVariantDto {
private UUID id;
private String sku;
private String variantLabel;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String internalMaterialCode;
private BigDecimal priceChf;
private Boolean isDefault;
private Boolean isActive;
private Integer sortOrder;
private OffsetDateTime createdAt;
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getVariantLabel() {
return variantLabel;
}
public void setVariantLabel(String variantLabel) {
this.variantLabel = variantLabel;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getInternalMaterialCode() {
return internalMaterialCode;
}
public void setInternalMaterialCode(String internalMaterialCode) {
this.internalMaterialCode = internalMaterialCode;
}
public BigDecimal getPriceChf() {
return priceChf;
}
public void setPriceChf(BigDecimal priceChf) {
this.priceChf = priceChf;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean aDefault) {
isDefault = aDefault;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,89 @@
package com.printcalculator.dto;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class AdminTranslateShopProductRequest {
private UUID categoryId;
private String sourceLanguage;
private Boolean overwriteExisting;
private List<String> materialCodes;
private Map<String, String> names;
private Map<String, String> excerpts;
private Map<String, String> descriptions;
private Map<String, String> seoTitles;
private Map<String, String> seoDescriptions;
public UUID getCategoryId() {
return categoryId;
}
public void setCategoryId(UUID categoryId) {
this.categoryId = categoryId;
}
public String getSourceLanguage() {
return sourceLanguage;
}
public void setSourceLanguage(String sourceLanguage) {
this.sourceLanguage = sourceLanguage;
}
public Boolean getOverwriteExisting() {
return overwriteExisting;
}
public void setOverwriteExisting(Boolean overwriteExisting) {
this.overwriteExisting = overwriteExisting;
}
public List<String> getMaterialCodes() {
return materialCodes;
}
public void setMaterialCodes(List<String> materialCodes) {
this.materialCodes = materialCodes;
}
public Map<String, String> getNames() {
return names;
}
public void setNames(Map<String, String> names) {
this.names = names;
}
public Map<String, String> getExcerpts() {
return excerpts;
}
public void setExcerpts(Map<String, String> excerpts) {
this.excerpts = excerpts;
}
public Map<String, String> getDescriptions() {
return descriptions;
}
public void setDescriptions(Map<String, String> descriptions) {
this.descriptions = descriptions;
}
public Map<String, String> getSeoTitles() {
return seoTitles;
}
public void setSeoTitles(Map<String, String> seoTitles) {
this.seoTitles = seoTitles;
}
public Map<String, String> getSeoDescriptions() {
return seoDescriptions;
}
public void setSeoDescriptions(Map<String, String> seoDescriptions) {
this.seoDescriptions = seoDescriptions;
}
}

View File

@@ -0,0 +1,70 @@
package com.printcalculator.dto;
import java.util.List;
import java.util.Map;
public class AdminTranslateShopProductResponse {
private String sourceLanguage;
private List<String> targetLanguages;
private Map<String, String> names;
private Map<String, String> excerpts;
private Map<String, String> descriptions;
private Map<String, String> seoTitles;
private Map<String, String> seoDescriptions;
public String getSourceLanguage() {
return sourceLanguage;
}
public void setSourceLanguage(String sourceLanguage) {
this.sourceLanguage = sourceLanguage;
}
public List<String> getTargetLanguages() {
return targetLanguages;
}
public void setTargetLanguages(List<String> targetLanguages) {
this.targetLanguages = targetLanguages;
}
public Map<String, String> getNames() {
return names;
}
public void setNames(Map<String, String> names) {
this.names = names;
}
public Map<String, String> getExcerpts() {
return excerpts;
}
public void setExcerpts(Map<String, String> excerpts) {
this.excerpts = excerpts;
}
public Map<String, String> getDescriptions() {
return descriptions;
}
public void setDescriptions(Map<String, String> descriptions) {
this.descriptions = descriptions;
}
public Map<String, String> getSeoTitles() {
return seoTitles;
}
public void setSeoTitles(Map<String, String> seoTitles) {
this.seoTitles = seoTitles;
}
public Map<String, String> getSeoDescriptions() {
return seoDescriptions;
}
public void setSeoDescriptions(Map<String, String> seoDescriptions) {
this.seoDescriptions = seoDescriptions;
}
}

View File

@@ -0,0 +1,13 @@
package com.printcalculator.dto;
public class AdminUpdateContactRequestStatusRequest {
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -0,0 +1,40 @@
package com.printcalculator.dto;
public class AdminUpdateMediaAssetRequest {
private String title;
private String altText;
private String visibility;
private String status;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAltText() {
return altText;
}
public void setAltText(String altText) {
this.altText = altText;
}
public String getVisibility() {
return visibility;
}
public void setVisibility(String visibility) {
this.visibility = visibility;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -0,0 +1,79 @@
package com.printcalculator.dto;
import java.util.UUID;
import java.util.Map;
public class AdminUpdateMediaUsageRequest {
private String usageType;
private String usageKey;
private UUID ownerId;
private UUID mediaAssetId;
private Integer sortOrder;
private Boolean isPrimary;
private Boolean isActive;
private Map<String, MediaTextTranslationDto> translations;
public String getUsageType() {
return usageType;
}
public void setUsageType(String usageType) {
this.usageType = usageType;
}
public String getUsageKey() {
return usageKey;
}
public void setUsageKey(String usageKey) {
this.usageKey = usageKey;
}
public UUID getOwnerId() {
return ownerId;
}
public void setOwnerId(UUID ownerId) {
this.ownerId = ownerId;
}
public UUID getMediaAssetId() {
return mediaAssetId;
}
public void setMediaAssetId(UUID mediaAssetId) {
this.mediaAssetId = mediaAssetId;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean primary) {
isPrimary = primary;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Map<String, MediaTextTranslationDto> getTranslations() {
return translations;
}
public void setTranslations(Map<String, MediaTextTranslationDto> translations) {
this.translations = translations;
}
}

View File

@@ -0,0 +1,40 @@
package com.printcalculator.dto;
public class AdminUpsertFilamentMaterialTypeRequest {
private String materialCode;
private Boolean isFlexible;
private Boolean isTechnical;
private String technicalTypeLabel;
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public Boolean getIsFlexible() {
return isFlexible;
}
public void setIsFlexible(Boolean isFlexible) {
this.isFlexible = isFlexible;
}
public Boolean getIsTechnical() {
return isTechnical;
}
public void setIsTechnical(Boolean isTechnical) {
this.isTechnical = isTechnical;
}
public String getTechnicalTypeLabel() {
return technicalTypeLabel;
}
public void setTechnicalTypeLabel(String technicalTypeLabel) {
this.technicalTypeLabel = technicalTypeLabel;
}
}

View File

@@ -0,0 +1,150 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
public class AdminUpsertFilamentVariantRequest {
private Long materialTypeId;
private String variantDisplayName;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String finishType;
private String brand;
private Boolean isMatte;
private Boolean isSpecial;
private BigDecimal costChfPerKg;
private BigDecimal stockSpools;
private BigDecimal spoolNetKg;
private Boolean isActive;
public Long getMaterialTypeId() {
return materialTypeId;
}
public void setMaterialTypeId(Long materialTypeId) {
this.materialTypeId = materialTypeId;
}
public String getVariantDisplayName() {
return variantDisplayName;
}
public void setVariantDisplayName(String variantDisplayName) {
this.variantDisplayName = variantDisplayName;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getFinishType() {
return finishType;
}
public void setFinishType(String finishType) {
this.finishType = finishType;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Boolean getIsMatte() {
return isMatte;
}
public void setIsMatte(Boolean isMatte) {
this.isMatte = isMatte;
}
public Boolean getIsSpecial() {
return isSpecial;
}
public void setIsSpecial(Boolean isSpecial) {
this.isSpecial = isSpecial;
}
public BigDecimal getCostChfPerKg() {
return costChfPerKg;
}
public void setCostChfPerKg(BigDecimal costChfPerKg) {
this.costChfPerKg = costChfPerKg;
}
public BigDecimal getStockSpools() {
return stockSpools;
}
public void setStockSpools(BigDecimal stockSpools) {
this.stockSpools = stockSpools;
}
public BigDecimal getSpoolNetKg() {
return spoolNetKg;
}
public void setSpoolNetKg(BigDecimal spoolNetKg) {
this.spoolNetKg = spoolNetKg;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@@ -0,0 +1,249 @@
package com.printcalculator.dto;
import java.util.UUID;
public class AdminUpsertShopCategoryRequest {
private UUID parentCategoryId;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
private Boolean isActive;
private Integer sortOrder;
public UUID getParentCategoryId() {
return parentCategoryId;
}
public void setParentCategoryId(UUID parentCategoryId) {
this.parentCategoryId = parentCategoryId;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
}

View File

@@ -0,0 +1,313 @@
package com.printcalculator.dto;
import java.util.List;
import java.util.UUID;
public class AdminUpsertShopProductRequest {
private UUID categoryId;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String excerpt;
private String excerptIt;
private String excerptEn;
private String excerptDe;
private String excerptFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoTitleIt;
private String seoTitleEn;
private String seoTitleDe;
private String seoTitleFr;
private String seoDescription;
private String seoDescriptionIt;
private String seoDescriptionEn;
private String seoDescriptionDe;
private String seoDescriptionFr;
private String ogTitle;
private String ogDescription;
private Boolean indexable;
private Boolean isFeatured;
private Boolean isActive;
private Integer sortOrder;
private List<AdminUpsertShopProductVariantRequest> variants;
public UUID getCategoryId() {
return categoryId;
}
public void setCategoryId(UUID categoryId) {
this.categoryId = categoryId;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsFeatured() {
return isFeatured;
}
public void setIsFeatured(Boolean featured) {
isFeatured = featured;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public List<AdminUpsertShopProductVariantRequest> getVariants() {
return variants;
}
public void setVariants(List<AdminUpsertShopProductVariantRequest> variants) {
this.variants = variants;
}
}

View File

@@ -0,0 +1,133 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.UUID;
public class AdminUpsertShopProductVariantRequest {
private UUID id;
private String sku;
private String variantLabel;
private String colorName;
private String colorLabelIt;
private String colorLabelEn;
private String colorLabelDe;
private String colorLabelFr;
private String colorHex;
private String internalMaterialCode;
private BigDecimal priceChf;
private Boolean isDefault;
private Boolean isActive;
private Integer sortOrder;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getVariantLabel() {
return variantLabel;
}
public void setVariantLabel(String variantLabel) {
this.variantLabel = variantLabel;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getInternalMaterialCode() {
return internalMaterialCode;
}
public void setInternalMaterialCode(String internalMaterialCode) {
this.internalMaterialCode = internalMaterialCode;
}
public BigDecimal getPriceChf() {
return priceChf;
}
public void setPriceChf(BigDecimal priceChf) {
this.priceChf = priceChf;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean aDefault) {
isDefault = aDefault;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
}

View File

@@ -0,0 +1,19 @@
package com.printcalculator.dto;
import lombok.Data;
import jakarta.validation.constraints.AssertTrue;
@Data
public class CreateOrderRequest {
private CustomerDto customer;
private AddressDto billingAddress;
private AddressDto shippingAddress;
private String language;
private boolean shippingSameAsBilling;
@AssertTrue(message = "L'accettazione dei Termini e Condizioni e obbligatoria.")
private boolean acceptTerms;
@AssertTrue(message = "L'accettazione dell'Informativa Privacy e obbligatoria.")
private boolean acceptPrivacy;
}

View File

@@ -0,0 +1,10 @@
package com.printcalculator.dto;
import lombok.Data;
@Data
public class CustomerDto {
private String email;
private String phone;
private String customerType; // "PRIVATE", "BUSINESS"
}

View File

@@ -0,0 +1,22 @@
package com.printcalculator.dto;
public class MediaTextTranslationDto {
private String title;
private String altText;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAltText() {
return altText;
}
public void setAltText(String altText) {
this.altText = altText;
}
}

View File

@@ -7,12 +7,27 @@ public record OptionsResponse(
List<QualityOption> qualities,
List<InfillPatternOption> infillPatterns,
List<LayerHeightOptionDTO> layerHeights,
List<NozzleOptionDTO> nozzleDiameters
List<NozzleOptionDTO> nozzleDiameters,
List<NozzleLayerHeightOptionsDTO> layerHeightsByNozzle
) {
public record MaterialOption(String code, String label, List<VariantOption> variants) {}
public record VariantOption(String name, String colorName, String hexColor, boolean isOutOfStock) {}
public record VariantOption(
Long id,
String name,
String colorName,
String colorLabelIt,
String colorLabelEn,
String colorLabelDe,
String colorLabelFr,
String hexColor,
String finishType,
Double stockSpools,
Double stockFilamentGrams,
boolean isOutOfStock
) {}
public record QualityOption(String id, String label) {}
public record InfillPatternOption(String id, String label) {}
public record LayerHeightOptionDTO(double value, String label) {}
public record NozzleOptionDTO(double value, String label) {}
public record NozzleLayerHeightOptionsDTO(double nozzleDiameter, List<LayerHeightOptionDTO> layerHeights) {}
}

View File

@@ -0,0 +1,138 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
public class OrderDto {
private UUID id;
private String orderNumber;
private String sourceType;
private String status;
private String paymentStatus;
private String paymentMethod;
private String customerEmail;
private String customerPhone;
private String preferredLanguage;
private String billingCustomerType;
private AddressDto billingAddress;
private AddressDto shippingAddress;
private Boolean shippingSameAsBilling;
private String currency;
private BigDecimal setupCostChf;
private BigDecimal shippingCostChf;
private BigDecimal discountChf;
private BigDecimal subtotalChf;
private Boolean isCadOrder;
private UUID sourceRequestId;
private BigDecimal cadHours;
private BigDecimal cadHourlyRateChf;
private BigDecimal cadTotalChf;
private BigDecimal totalChf;
private OffsetDateTime createdAt;
private String printMaterialCode;
private BigDecimal printNozzleDiameterMm;
private BigDecimal printLayerHeightMm;
private String printInfillPattern;
private Integer printInfillPercent;
private Boolean printSupportsEnabled;
private List<OrderItemDto> items;
// Getters and Setters
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
public String getOrderNumber() { return orderNumber; }
public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
public String getSourceType() { return sourceType; }
public void setSourceType(String sourceType) { this.sourceType = sourceType; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getPaymentStatus() { return paymentStatus; }
public void setPaymentStatus(String paymentStatus) { this.paymentStatus = paymentStatus; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public String getCustomerEmail() { return customerEmail; }
public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }
public String getCustomerPhone() { return customerPhone; }
public void setCustomerPhone(String customerPhone) { this.customerPhone = customerPhone; }
public String getPreferredLanguage() { return preferredLanguage; }
public void setPreferredLanguage(String preferredLanguage) { this.preferredLanguage = preferredLanguage; }
public String getBillingCustomerType() { return billingCustomerType; }
public void setBillingCustomerType(String billingCustomerType) { this.billingCustomerType = billingCustomerType; }
public AddressDto getBillingAddress() { return billingAddress; }
public void setBillingAddress(AddressDto billingAddress) { this.billingAddress = billingAddress; }
public AddressDto getShippingAddress() { return shippingAddress; }
public void setShippingAddress(AddressDto shippingAddress) { this.shippingAddress = shippingAddress; }
public Boolean getShippingSameAsBilling() { return shippingSameAsBilling; }
public void setShippingSameAsBilling(Boolean shippingSameAsBilling) { this.shippingSameAsBilling = shippingSameAsBilling; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public BigDecimal getSetupCostChf() { return setupCostChf; }
public void setSetupCostChf(BigDecimal setupCostChf) { this.setupCostChf = setupCostChf; }
public BigDecimal getShippingCostChf() { return shippingCostChf; }
public void setShippingCostChf(BigDecimal shippingCostChf) { this.shippingCostChf = shippingCostChf; }
public BigDecimal getDiscountChf() { return discountChf; }
public void setDiscountChf(BigDecimal discountChf) { this.discountChf = discountChf; }
public BigDecimal getSubtotalChf() { return subtotalChf; }
public void setSubtotalChf(BigDecimal subtotalChf) { this.subtotalChf = subtotalChf; }
public Boolean getIsCadOrder() { return isCadOrder; }
public void setIsCadOrder(Boolean isCadOrder) { this.isCadOrder = isCadOrder; }
public UUID getSourceRequestId() { return sourceRequestId; }
public void setSourceRequestId(UUID sourceRequestId) { this.sourceRequestId = sourceRequestId; }
public BigDecimal getCadHours() { return cadHours; }
public void setCadHours(BigDecimal cadHours) { this.cadHours = cadHours; }
public BigDecimal getCadHourlyRateChf() { return cadHourlyRateChf; }
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) { this.cadHourlyRateChf = cadHourlyRateChf; }
public BigDecimal getCadTotalChf() { return cadTotalChf; }
public void setCadTotalChf(BigDecimal cadTotalChf) { this.cadTotalChf = cadTotalChf; }
public BigDecimal getTotalChf() { return totalChf; }
public void setTotalChf(BigDecimal totalChf) { this.totalChf = totalChf; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
public String getPrintMaterialCode() { return printMaterialCode; }
public void setPrintMaterialCode(String printMaterialCode) { this.printMaterialCode = printMaterialCode; }
public BigDecimal getPrintNozzleDiameterMm() { return printNozzleDiameterMm; }
public void setPrintNozzleDiameterMm(BigDecimal printNozzleDiameterMm) { this.printNozzleDiameterMm = printNozzleDiameterMm; }
public BigDecimal getPrintLayerHeightMm() { return printLayerHeightMm; }
public void setPrintLayerHeightMm(BigDecimal printLayerHeightMm) { this.printLayerHeightMm = printLayerHeightMm; }
public String getPrintInfillPattern() { return printInfillPattern; }
public void setPrintInfillPattern(String printInfillPattern) { this.printInfillPattern = printInfillPattern; }
public Integer getPrintInfillPercent() { return printInfillPercent; }
public void setPrintInfillPercent(Integer printInfillPercent) { this.printInfillPercent = printInfillPercent; }
public Boolean getPrintSupportsEnabled() { return printSupportsEnabled; }
public void setPrintSupportsEnabled(Boolean printSupportsEnabled) { this.printSupportsEnabled = printSupportsEnabled; }
public List<OrderItemDto> getItems() { return items; }
public void setItems(List<OrderItemDto> items) { this.items = items; }
}

View File

@@ -0,0 +1,152 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.UUID;
public class OrderItemDto {
private UUID id;
private String itemType;
private String originalFilename;
private String displayName;
private String materialCode;
private String colorCode;
private Long filamentVariantId;
private UUID shopProductId;
private UUID shopProductVariantId;
private String shopProductSlug;
private String shopProductName;
private String shopVariantLabel;
private String shopVariantColorName;
private String shopVariantColorLabelIt;
private String shopVariantColorLabelEn;
private String shopVariantColorLabelDe;
private String shopVariantColorLabelFr;
private String shopVariantColorHex;
private String filamentVariantDisplayName;
private String filamentColorName;
private String filamentColorLabelIt;
private String filamentColorLabelEn;
private String filamentColorLabelDe;
private String filamentColorLabelFr;
private String filamentColorHex;
private String quality;
private BigDecimal nozzleDiameterMm;
private BigDecimal layerHeightMm;
private Integer infillPercent;
private String infillPattern;
private Boolean supportsEnabled;
private Integer quantity;
private Integer printTimeSeconds;
private BigDecimal materialGrams;
private BigDecimal unitPriceChf;
private BigDecimal lineTotalChf;
// Getters and Setters
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
public String getItemType() { return itemType; }
public void setItemType(String itemType) { this.itemType = itemType; }
public String getOriginalFilename() { return originalFilename; }
public void setOriginalFilename(String originalFilename) { this.originalFilename = originalFilename; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getMaterialCode() { return materialCode; }
public void setMaterialCode(String materialCode) { this.materialCode = materialCode; }
public String getColorCode() { return colorCode; }
public void setColorCode(String colorCode) { this.colorCode = colorCode; }
public Long getFilamentVariantId() { return filamentVariantId; }
public void setFilamentVariantId(Long filamentVariantId) { this.filamentVariantId = filamentVariantId; }
public UUID getShopProductId() { return shopProductId; }
public void setShopProductId(UUID shopProductId) { this.shopProductId = shopProductId; }
public UUID getShopProductVariantId() { return shopProductVariantId; }
public void setShopProductVariantId(UUID shopProductVariantId) { this.shopProductVariantId = shopProductVariantId; }
public String getShopProductSlug() { return shopProductSlug; }
public void setShopProductSlug(String shopProductSlug) { this.shopProductSlug = shopProductSlug; }
public String getShopProductName() { return shopProductName; }
public void setShopProductName(String shopProductName) { this.shopProductName = shopProductName; }
public String getShopVariantLabel() { return shopVariantLabel; }
public void setShopVariantLabel(String shopVariantLabel) { this.shopVariantLabel = shopVariantLabel; }
public String getShopVariantColorName() { return shopVariantColorName; }
public void setShopVariantColorName(String shopVariantColorName) { this.shopVariantColorName = shopVariantColorName; }
public String getShopVariantColorLabelIt() { return shopVariantColorLabelIt; }
public void setShopVariantColorLabelIt(String shopVariantColorLabelIt) { this.shopVariantColorLabelIt = shopVariantColorLabelIt; }
public String getShopVariantColorLabelEn() { return shopVariantColorLabelEn; }
public void setShopVariantColorLabelEn(String shopVariantColorLabelEn) { this.shopVariantColorLabelEn = shopVariantColorLabelEn; }
public String getShopVariantColorLabelDe() { return shopVariantColorLabelDe; }
public void setShopVariantColorLabelDe(String shopVariantColorLabelDe) { this.shopVariantColorLabelDe = shopVariantColorLabelDe; }
public String getShopVariantColorLabelFr() { return shopVariantColorLabelFr; }
public void setShopVariantColorLabelFr(String shopVariantColorLabelFr) { this.shopVariantColorLabelFr = shopVariantColorLabelFr; }
public String getShopVariantColorHex() { return shopVariantColorHex; }
public void setShopVariantColorHex(String shopVariantColorHex) { this.shopVariantColorHex = shopVariantColorHex; }
public String getFilamentVariantDisplayName() { return filamentVariantDisplayName; }
public void setFilamentVariantDisplayName(String filamentVariantDisplayName) { this.filamentVariantDisplayName = filamentVariantDisplayName; }
public String getFilamentColorName() { return filamentColorName; }
public void setFilamentColorName(String filamentColorName) { this.filamentColorName = filamentColorName; }
public String getFilamentColorLabelIt() { return filamentColorLabelIt; }
public void setFilamentColorLabelIt(String filamentColorLabelIt) { this.filamentColorLabelIt = filamentColorLabelIt; }
public String getFilamentColorLabelEn() { return filamentColorLabelEn; }
public void setFilamentColorLabelEn(String filamentColorLabelEn) { this.filamentColorLabelEn = filamentColorLabelEn; }
public String getFilamentColorLabelDe() { return filamentColorLabelDe; }
public void setFilamentColorLabelDe(String filamentColorLabelDe) { this.filamentColorLabelDe = filamentColorLabelDe; }
public String getFilamentColorLabelFr() { return filamentColorLabelFr; }
public void setFilamentColorLabelFr(String filamentColorLabelFr) { this.filamentColorLabelFr = filamentColorLabelFr; }
public String getFilamentColorHex() { return filamentColorHex; }
public void setFilamentColorHex(String filamentColorHex) { this.filamentColorHex = filamentColorHex; }
public String getQuality() { return quality; }
public void setQuality(String quality) { this.quality = quality; }
public BigDecimal getNozzleDiameterMm() { return nozzleDiameterMm; }
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) { this.nozzleDiameterMm = nozzleDiameterMm; }
public BigDecimal getLayerHeightMm() { return layerHeightMm; }
public void setLayerHeightMm(BigDecimal layerHeightMm) { this.layerHeightMm = layerHeightMm; }
public Integer getInfillPercent() { return infillPercent; }
public void setInfillPercent(Integer infillPercent) { this.infillPercent = infillPercent; }
public String getInfillPattern() { return infillPattern; }
public void setInfillPattern(String infillPattern) { this.infillPattern = infillPattern; }
public Boolean getSupportsEnabled() { return supportsEnabled; }
public void setSupportsEnabled(Boolean supportsEnabled) { this.supportsEnabled = supportsEnabled; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public Integer getPrintTimeSeconds() { return printTimeSeconds; }
public void setPrintTimeSeconds(Integer printTimeSeconds) { this.printTimeSeconds = printTimeSeconds; }
public BigDecimal getMaterialGrams() { return materialGrams; }
public void setMaterialGrams(BigDecimal materialGrams) { this.materialGrams = materialGrams; }
public BigDecimal getUnitPriceChf() { return unitPriceChf; }
public void setUnitPriceChf(BigDecimal unitPriceChf) { this.unitPriceChf = unitPriceChf; }
public BigDecimal getLineTotalChf() { return lineTotalChf; }
public void setLineTotalChf(BigDecimal lineTotalChf) { this.lineTotalChf = lineTotalChf; }
}

View File

@@ -0,0 +1,157 @@
package com.printcalculator.dto;
public class PrintSettingsDto {
// Mode: "BASIC" or "ADVANCED"
private String complexityMode;
// Common
private String material; // e.g. "PLA", "PLA TOUGH", "PETG"
private String color; // e.g. "White", "#FFFFFF"
private Integer quantity;
private Long filamentVariantId;
private Long printerMachineId;
// Basic Mode
private String quality; // "draft", "standard", "high"
// Advanced Mode (Optional in Basic)
private Double nozzleDiameter;
private Double layerHeight;
private Double infillDensity;
private String infillPattern;
private Boolean supportsEnabled;
private String notes;
// Dimensions
private Double boundingBoxX;
private Double boundingBoxY;
private Double boundingBoxZ;
public String getComplexityMode() {
return complexityMode;
}
public void setComplexityMode(String complexityMode) {
this.complexityMode = complexityMode;
}
public String getMaterial() {
return material;
}
public void setMaterial(String material) {
this.material = material;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Long getFilamentVariantId() {
return filamentVariantId;
}
public void setFilamentVariantId(Long filamentVariantId) {
this.filamentVariantId = filamentVariantId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public Long getPrinterMachineId() {
return printerMachineId;
}
public void setPrinterMachineId(Long printerMachineId) {
this.printerMachineId = printerMachineId;
}
public String getQuality() {
return quality;
}
public void setQuality(String quality) {
this.quality = quality;
}
public Double getNozzleDiameter() {
return nozzleDiameter;
}
public void setNozzleDiameter(Double nozzleDiameter) {
this.nozzleDiameter = nozzleDiameter;
}
public Double getLayerHeight() {
return layerHeight;
}
public void setLayerHeight(Double layerHeight) {
this.layerHeight = layerHeight;
}
public Double getInfillDensity() {
return infillDensity;
}
public void setInfillDensity(Double infillDensity) {
this.infillDensity = infillDensity;
}
public String getInfillPattern() {
return infillPattern;
}
public void setInfillPattern(String infillPattern) {
this.infillPattern = infillPattern;
}
public Boolean getSupportsEnabled() {
return supportsEnabled;
}
public void setSupportsEnabled(Boolean supportsEnabled) {
this.supportsEnabled = supportsEnabled;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public Double getBoundingBoxX() {
return boundingBoxX;
}
public void setBoundingBoxX(Double boundingBoxX) {
this.boundingBoxX = boundingBoxX;
}
public Double getBoundingBoxY() {
return boundingBoxY;
}
public void setBoundingBoxY(Double boundingBoxY) {
this.boundingBoxY = boundingBoxY;
}
public Double getBoundingBoxZ() {
return boundingBoxZ;
}
public void setBoundingBoxZ(Double boundingBoxZ) {
this.boundingBoxZ = boundingBoxZ;
}
}

View File

@@ -0,0 +1,96 @@
package com.printcalculator.dto;
import java.util.UUID;
public class PublicMediaUsageDto {
private UUID mediaAssetId;
private String title;
private String altText;
private String usageType;
private String usageKey;
private Integer sortOrder;
private Boolean isPrimary;
private PublicMediaVariantDto thumb;
private PublicMediaVariantDto card;
private PublicMediaVariantDto hero;
public UUID getMediaAssetId() {
return mediaAssetId;
}
public void setMediaAssetId(UUID mediaAssetId) {
this.mediaAssetId = mediaAssetId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAltText() {
return altText;
}
public void setAltText(String altText) {
this.altText = altText;
}
public String getUsageType() {
return usageType;
}
public void setUsageType(String usageType) {
this.usageType = usageType;
}
public String getUsageKey() {
return usageKey;
}
public void setUsageKey(String usageKey) {
this.usageKey = usageKey;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean primary) {
isPrimary = primary;
}
public PublicMediaVariantDto getThumb() {
return thumb;
}
public void setThumb(PublicMediaVariantDto thumb) {
this.thumb = thumb;
}
public PublicMediaVariantDto getCard() {
return card;
}
public void setCard(PublicMediaVariantDto card) {
this.card = card;
}
public PublicMediaVariantDto getHero() {
return hero;
}
public void setHero(PublicMediaVariantDto hero) {
this.hero = hero;
}
}

View File

@@ -0,0 +1,31 @@
package com.printcalculator.dto;
public class PublicMediaVariantDto {
private String avifUrl;
private String webpUrl;
private String jpegUrl;
public String getAvifUrl() {
return avifUrl;
}
public void setAvifUrl(String avifUrl) {
this.avifUrl = avifUrl;
}
public String getWebpUrl() {
return webpUrl;
}
public void setWebpUrl(String webpUrl) {
this.webpUrl = webpUrl;
}
public String getJpegUrl() {
return jpegUrl;
}
public void setJpegUrl(String jpegUrl) {
this.jpegUrl = jpegUrl;
}
}

View File

@@ -0,0 +1,23 @@
package com.printcalculator.dto;
import lombok.Data;
import jakarta.validation.constraints.AssertTrue;
@Data
public class QuoteRequestDto {
private String requestType; // "PRINT_SERVICE" or "DESIGN_SERVICE"
private String customerType; // "PRIVATE" or "BUSINESS"
private String language; // "it" | "en" | "de" | "fr"
private String email;
private String phone;
private String name;
private String companyName;
private String contactPerson;
private String message;
@AssertTrue(message = "L'accettazione dei Termini e Condizioni e obbligatoria.")
private boolean acceptTerms;
@AssertTrue(message = "L'accettazione dell'Informativa Privacy e obbligatoria.")
private boolean acceptPrivacy;
}

View File

@@ -0,0 +1,30 @@
package com.printcalculator.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
public class ShopCartAddItemRequest {
@NotNull
private UUID shopProductVariantId;
@Min(1)
private Integer quantity = 1;
public UUID getShopProductVariantId() {
return shopProductVariantId;
}
public void setShopProductVariantId(UUID shopProductVariantId) {
this.shopProductVariantId = shopProductVariantId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}

View File

@@ -0,0 +1,18 @@
package com.printcalculator.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
public class ShopCartUpdateItemRequest {
@NotNull
@Min(1)
private Integer quantity;
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}

View File

@@ -0,0 +1,23 @@
package com.printcalculator.dto;
import java.util.List;
import java.util.UUID;
public record ShopCategoryDetailDto(
UUID id,
String slug,
String name,
String description,
String seoTitle,
String seoDescription,
String ogTitle,
String ogDescription,
Boolean indexable,
Integer sortOrder,
Integer productCount,
List<ShopCategoryRefDto> breadcrumbs,
PublicMediaUsageDto primaryImage,
List<PublicMediaUsageDto> images,
List<ShopCategoryTreeDto> children
) {
}

View File

@@ -0,0 +1,10 @@
package com.printcalculator.dto;
import java.util.UUID;
public record ShopCategoryRefDto(
UUID id,
String slug,
String name
) {
}

View File

@@ -0,0 +1,22 @@
package com.printcalculator.dto;
import java.util.List;
import java.util.UUID;
public record ShopCategoryTreeDto(
UUID id,
UUID parentCategoryId,
String slug,
String name,
String description,
String seoTitle,
String seoDescription,
String ogTitle,
String ogDescription,
Boolean indexable,
Integer sortOrder,
Integer productCount,
PublicMediaUsageDto primaryImage,
List<ShopCategoryTreeDto> children
) {
}

View File

@@ -0,0 +1,11 @@
package com.printcalculator.dto;
import java.util.List;
public record ShopProductCatalogResponseDto(
String categorySlug,
Boolean featuredOnly,
ShopCategoryDetailDto category,
List<ShopProductSummaryDto> products
) {
}

View File

@@ -0,0 +1,33 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public record ShopProductDetailDto(
UUID id,
String slug,
String name,
String excerpt,
String description,
String seoTitle,
String seoDescription,
String ogTitle,
String ogDescription,
Boolean indexable,
Boolean isFeatured,
Integer sortOrder,
ShopCategoryRefDto category,
List<ShopCategoryRefDto> breadcrumbs,
BigDecimal priceFromChf,
BigDecimal priceToChf,
ShopProductVariantOptionDto defaultVariant,
List<ShopProductVariantOptionDto> variants,
PublicMediaUsageDto primaryImage,
List<PublicMediaUsageDto> images,
ShopProductModelDto model3d,
String publicPath,
Map<String, String> localizedPaths
) {
}

View File

@@ -0,0 +1,14 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
public record ShopProductModelDto(
String url,
String originalFilename,
String mimeType,
Long fileSizeBytes,
BigDecimal boundingBoxXMm,
BigDecimal boundingBoxYMm,
BigDecimal boundingBoxZMm
) {
}

View File

@@ -0,0 +1,23 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.Map;
import java.util.UUID;
public record ShopProductSummaryDto(
UUID id,
String slug,
String name,
String excerpt,
Boolean isFeatured,
Integer sortOrder,
ShopCategoryRefDto category,
BigDecimal priceFromChf,
BigDecimal priceToChf,
ShopProductVariantOptionDto defaultVariant,
PublicMediaUsageDto primaryImage,
ShopProductModelDto model3d,
String publicPath,
Map<String, String> localizedPaths
) {
}

View File

@@ -0,0 +1,16 @@
package com.printcalculator.dto;
import java.math.BigDecimal;
import java.util.UUID;
public record ShopProductVariantOptionDto(
UUID id,
String sku,
String variantLabel,
String colorName,
String colorLabel,
String colorHex,
BigDecimal priceChf,
Boolean isDefault
) {
}

View File

@@ -0,0 +1,149 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "custom_quote_requests", indexes = {@Index(name = "ix_custom_quote_requests_status",
columnList = "status")})
public class CustomQuoteRequest {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "request_id", nullable = false)
private UUID id;
@Column(name = "request_type", nullable = false, length = Integer.MAX_VALUE)
private String requestType;
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
private String customerType;
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
private String email;
@Column(name = "phone", length = Integer.MAX_VALUE)
private String phone;
@Column(name = "name", length = Integer.MAX_VALUE)
private String name;
@Column(name = "company_name", length = Integer.MAX_VALUE)
private String companyName;
@Column(name = "contact_person", length = Integer.MAX_VALUE)
private String contactPerson;
@Column(name = "message", nullable = false, length = Integer.MAX_VALUE)
private String message;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public String getCustomerType() {
return customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getContactPerson() {
return contactPerson;
}
public void setContactPerson(String contactPerson) {
this.contactPerson = contactPerson;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,121 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "custom_quote_request_attachments", indexes = {@Index(name = "ix_custom_quote_attachments_request",
columnList = "request_id")})
public class CustomQuoteRequestAttachment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "attachment_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "request_id", nullable = false)
private CustomQuoteRequest request;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
private String storedRelativePath;
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
private String storedFilename;
@Column(name = "file_size_bytes")
private Long fileSizeBytes;
@Column(name = "mime_type", length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
private String sha256Hex;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public CustomQuoteRequest getRequest() {
return request;
}
public void setRequest(CustomQuoteRequest request) {
this.request = request;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getStoredRelativePath() {
return storedRelativePath;
}
public void setStoredRelativePath(String storedRelativePath) {
this.storedRelativePath = storedRelativePath;
}
public String getStoredFilename() {
return storedFilename;
}
public void setStoredFilename(String storedFilename) {
this.storedFilename = storedFilename;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public void setCustomQuoteRequest(CustomQuoteRequest request) {
}
}

View File

@@ -0,0 +1,126 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "customer_id", nullable = false)
private UUID id;
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
private String customerType;
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
private String email;
@Column(name = "phone", length = Integer.MAX_VALUE)
private String phone;
@Column(name = "first_name", length = Integer.MAX_VALUE)
private String firstName;
@Column(name = "last_name", length = Integer.MAX_VALUE)
private String lastName;
@Column(name = "company_name", length = Integer.MAX_VALUE)
private String companyName;
@Column(name = "contact_person", length = Integer.MAX_VALUE)
private String contactPerson;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getCustomerType() {
return customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getContactPerson() {
return contactPerson;
}
public void setContactPerson(String contactPerson) {
this.contactPerson = contactPerson;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -24,6 +24,28 @@ public class FilamentVariant {
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
private String colorName;
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
private String colorLabelIt;
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
private String colorLabelEn;
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
private String colorLabelDe;
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
private String colorLabelFr;
@Column(name = "color_hex", length = Integer.MAX_VALUE)
private String colorHex;
@ColumnDefault("'GLOSSY'")
@Column(name = "finish_type", length = Integer.MAX_VALUE)
private String finishType;
@Column(name = "brand", length = Integer.MAX_VALUE)
private String brand;
@ColumnDefault("false")
@Column(name = "is_matte", nullable = false)
private Boolean isMatte;
@@ -83,6 +105,62 @@ public class FilamentVariant {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getFinishType() {
return finishType;
}
public void setFinishType(String finishType) {
this.finishType = finishType;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Boolean getIsMatte() {
return isMatte;
}
@@ -139,4 +217,60 @@ public class FilamentVariant {
this.createdAt = createdAt;
}
public String getColorLabelForLanguage(String language) {
return resolveLocalizedValue(
language,
colorName,
colorLabelIt,
colorLabelEn,
colorLabelDe,
colorLabelFr
);
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,72 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
@Entity
@Table(name = "filament_variant_orca_override", uniqueConstraints = {
@UniqueConstraint(name = "ux_filament_variant_orca_override_variant_machine", columnNames = {
"filament_variant_id", "printer_machine_profile_id"
})
})
public class FilamentVariantOrcaOverride {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "filament_variant_orca_override_id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "filament_variant_id", nullable = false)
private FilamentVariant filamentVariant;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "printer_machine_profile_id", nullable = false)
private PrinterMachineProfile printerMachineProfile;
@Column(name = "orca_filament_profile_name", nullable = false, length = Integer.MAX_VALUE)
private String orcaFilamentProfileName;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public FilamentVariant getFilamentVariant() {
return filamentVariant;
}
public void setFilamentVariant(FilamentVariant filamentVariant) {
this.filamentVariant = filamentVariant;
}
public PrinterMachineProfile getPrinterMachineProfile() {
return printerMachineProfile;
}
public void setPrinterMachineProfile(PrinterMachineProfile printerMachineProfile) {
this.printerMachineProfile = printerMachineProfile;
}
public String getOrcaFilamentProfileName() {
return orcaFilamentProfileName;
}
public void setOrcaFilamentProfileName(String orcaFilamentProfileName) {
this.orcaFilamentProfileName = orcaFilamentProfileName;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@@ -0,0 +1,72 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
@Entity
@Table(name = "material_orca_profile_map", uniqueConstraints = {
@UniqueConstraint(name = "ux_material_orca_profile_map_machine_material", columnNames = {
"printer_machine_profile_id", "filament_material_type_id"
})
})
public class MaterialOrcaProfileMap {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "material_orca_profile_map_id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "printer_machine_profile_id", nullable = false)
private PrinterMachineProfile printerMachineProfile;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "filament_material_type_id", nullable = false)
private FilamentMaterialType filamentMaterialType;
@Column(name = "orca_filament_profile_name", nullable = false, length = Integer.MAX_VALUE)
private String orcaFilamentProfileName;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public PrinterMachineProfile getPrinterMachineProfile() {
return printerMachineProfile;
}
public void setPrinterMachineProfile(PrinterMachineProfile printerMachineProfile) {
this.printerMachineProfile = printerMachineProfile;
}
public FilamentMaterialType getFilamentMaterialType() {
return filamentMaterialType;
}
public void setFilamentMaterialType(FilamentMaterialType filamentMaterialType) {
this.filamentMaterialType = filamentMaterialType;
}
public String getOrcaFilamentProfileName() {
return orcaFilamentProfileName;
}
public void setOrcaFilamentProfileName(String orcaFilamentProfileName) {
this.orcaFilamentProfileName = orcaFilamentProfileName;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@@ -0,0 +1,177 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "media_asset", indexes = {
@Index(name = "ix_media_asset_status_visibility_created_at", columnList = "status, visibility, created_at")
})
public class MediaAsset {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "media_asset_id", nullable = false)
private UUID id;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "storage_key", nullable = false, length = Integer.MAX_VALUE, unique = true)
private String storageKey;
@Column(name = "mime_type", nullable = false, length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "file_size_bytes", nullable = false)
private Long fileSizeBytes;
@Column(name = "sha256_hex", nullable = false, length = Integer.MAX_VALUE)
private String sha256Hex;
@Column(name = "width_px")
private Integer widthPx;
@Column(name = "height_px")
private Integer heightPx;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@Column(name = "visibility", nullable = false, length = Integer.MAX_VALUE)
private String visibility;
@Column(name = "title", length = Integer.MAX_VALUE)
private String title;
@Column(name = "alt_text", length = Integer.MAX_VALUE)
private String altText;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getStorageKey() {
return storageKey;
}
public void setStorageKey(String storageKey) {
this.storageKey = storageKey;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public Integer getWidthPx() {
return widthPx;
}
public void setWidthPx(Integer widthPx) {
this.widthPx = widthPx;
}
public Integer getHeightPx() {
return heightPx;
}
public void setHeightPx(Integer heightPx) {
this.heightPx = heightPx;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getVisibility() {
return visibility;
}
public void setVisibility(String visibility) {
this.visibility = visibility;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAltText() {
return altText;
}
public void setAltText(String altText) {
this.altText = altText;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,273 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "media_usage", indexes = {
@Index(name = "ix_media_usage_scope", columnList = "usage_type, usage_key, is_active, sort_order")
})
public class MediaUsage {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "media_usage_id", nullable = false)
private UUID id;
@Column(name = "usage_type", nullable = false, length = Integer.MAX_VALUE)
private String usageType;
@Column(name = "usage_key", nullable = false, length = Integer.MAX_VALUE)
private String usageKey;
@Column(name = "owner_id")
private UUID ownerId;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "media_asset_id", nullable = false)
private MediaAsset mediaAsset;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("false")
@Column(name = "is_primary", nullable = false)
private Boolean isPrimary;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@Column(name = "title_it", length = Integer.MAX_VALUE)
private String titleIt;
@Column(name = "title_en", length = Integer.MAX_VALUE)
private String titleEn;
@Column(name = "title_de", length = Integer.MAX_VALUE)
private String titleDe;
@Column(name = "title_fr", length = Integer.MAX_VALUE)
private String titleFr;
@Column(name = "alt_text_it", length = Integer.MAX_VALUE)
private String altTextIt;
@Column(name = "alt_text_en", length = Integer.MAX_VALUE)
private String altTextEn;
@Column(name = "alt_text_de", length = Integer.MAX_VALUE)
private String altTextDe;
@Column(name = "alt_text_fr", length = Integer.MAX_VALUE)
private String altTextFr;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getUsageType() {
return usageType;
}
public void setUsageType(String usageType) {
this.usageType = usageType;
}
public String getUsageKey() {
return usageKey;
}
public void setUsageKey(String usageKey) {
this.usageKey = usageKey;
}
public UUID getOwnerId() {
return ownerId;
}
public void setOwnerId(UUID ownerId) {
this.ownerId = ownerId;
}
public MediaAsset getMediaAsset() {
return mediaAsset;
}
public void setMediaAsset(MediaAsset mediaAsset) {
this.mediaAsset = mediaAsset;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean primary) {
isPrimary = primary;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public String getTitleIt() {
return titleIt;
}
public void setTitleIt(String titleIt) {
this.titleIt = titleIt;
}
public String getTitleEn() {
return titleEn;
}
public void setTitleEn(String titleEn) {
this.titleEn = titleEn;
}
public String getTitleDe() {
return titleDe;
}
public void setTitleDe(String titleDe) {
this.titleDe = titleDe;
}
public String getTitleFr() {
return titleFr;
}
public void setTitleFr(String titleFr) {
this.titleFr = titleFr;
}
public String getAltTextIt() {
return altTextIt;
}
public void setAltTextIt(String altTextIt) {
this.altTextIt = altTextIt;
}
public String getAltTextEn() {
return altTextEn;
}
public void setAltTextEn(String altTextEn) {
this.altTextEn = altTextEn;
}
public String getAltTextDe() {
return altTextDe;
}
public void setAltTextDe(String altTextDe) {
this.altTextDe = altTextDe;
}
public String getAltTextFr() {
return altTextFr;
}
public void setAltTextFr(String altTextFr) {
this.altTextFr = altTextFr;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public String getTitleForLanguage(String language) {
if (language == null) {
return null;
}
return switch (language.trim().toLowerCase()) {
case "it" -> titleIt;
case "en" -> titleEn;
case "de" -> titleDe;
case "fr" -> titleFr;
default -> null;
};
}
public void setTitleForLanguage(String language, String value) {
if (language == null) {
return;
}
switch (language.trim().toLowerCase()) {
case "it" -> titleIt = value;
case "en" -> titleEn = value;
case "de" -> titleDe = value;
case "fr" -> titleFr = value;
default -> {
}
}
}
public String getAltTextForLanguage(String language) {
if (language == null) {
return null;
}
return switch (language.trim().toLowerCase()) {
case "it" -> altTextIt;
case "en" -> altTextEn;
case "de" -> altTextDe;
case "fr" -> altTextFr;
default -> null;
};
}
public void setAltTextForLanguage(String language, String value) {
if (language == null) {
return;
}
switch (language.trim().toLowerCase()) {
case "it" -> altTextIt = value;
case "en" -> altTextEn = value;
case "de" -> altTextDe = value;
case "fr" -> altTextFr = value;
default -> {
}
}
}
}

View File

@@ -0,0 +1,154 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "media_variant", indexes = {
@Index(name = "ix_media_variant_asset", columnList = "media_asset_id")
}, uniqueConstraints = {
@UniqueConstraint(name = "uq_media_variant_asset_name_format", columnNames = {"media_asset_id", "variant_name", "format"})
})
public class MediaVariant {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "media_variant_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "media_asset_id", nullable = false)
private MediaAsset mediaAsset;
@Column(name = "variant_name", nullable = false, length = Integer.MAX_VALUE)
private String variantName;
@Column(name = "format", nullable = false, length = Integer.MAX_VALUE)
private String format;
@Column(name = "storage_key", nullable = false, length = Integer.MAX_VALUE, unique = true)
private String storageKey;
@Column(name = "mime_type", nullable = false, length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "width_px", nullable = false)
private Integer widthPx;
@Column(name = "height_px", nullable = false)
private Integer heightPx;
@Column(name = "file_size_bytes", nullable = false)
private Long fileSizeBytes;
@ColumnDefault("true")
@Column(name = "is_generated", nullable = false)
private Boolean isGenerated;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public MediaAsset getMediaAsset() {
return mediaAsset;
}
public void setMediaAsset(MediaAsset mediaAsset) {
this.mediaAsset = mediaAsset;
}
public String getVariantName() {
return variantName;
}
public void setVariantName(String variantName) {
this.variantName = variantName;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getStorageKey() {
return storageKey;
}
public void setStorageKey(String storageKey) {
this.storageKey = storageKey;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Integer getWidthPx() {
return widthPx;
}
public void setWidthPx(Integer widthPx) {
this.widthPx = widthPx;
}
public Integer getHeightPx() {
return heightPx;
}
public void setHeightPx(Integer heightPx) {
this.heightPx = heightPx;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public Boolean getIsGenerated() {
return isGenerated;
}
public void setIsGenerated(Boolean generated) {
isGenerated = generated;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,63 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
@Entity
@Table(
name = "nozzle_layer_height_option",
uniqueConstraints = @UniqueConstraint(
name = "ux_nozzle_layer_height_option_nozzle_layer",
columnNames = {"nozzle_diameter_mm", "layer_height_mm"}
)
)
public class NozzleLayerHeightOption {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "nozzle_layer_height_option_id", nullable = false)
private Long id;
@Column(name = "nozzle_diameter_mm", nullable = false, precision = 4, scale = 2)
private BigDecimal nozzleDiameterMm;
@Column(name = "layer_height_mm", nullable = false, precision = 5, scale = 3)
private BigDecimal layerHeightMm;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BigDecimal getNozzleDiameterMm() {
return nozzleDiameterMm;
}
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
this.nozzleDiameterMm = nozzleDiameterMm;
}
public BigDecimal getLayerHeightMm() {
return layerHeightMm;
}
public void setLayerHeightMm(BigDecimal layerHeightMm) {
this.layerHeightMm = layerHeightMm;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@@ -0,0 +1,532 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "orders", indexes = {@Index(name = "ix_orders_status",
columnList = "status")})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "order_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "source_quote_session_id")
private QuoteSession sourceQuoteSession;
@ColumnDefault("'CALCULATOR'")
@Column(name = "source_type", nullable = false, length = Integer.MAX_VALUE)
private String sourceType;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
@Column(name = "customer_email", nullable = false, length = Integer.MAX_VALUE)
private String customerEmail;
@Column(name = "customer_phone", length = Integer.MAX_VALUE)
private String customerPhone;
@Column(name = "billing_customer_type", nullable = false, length = Integer.MAX_VALUE)
private String billingCustomerType;
@Column(name = "billing_first_name", length = Integer.MAX_VALUE)
private String billingFirstName;
@Column(name = "billing_last_name", length = Integer.MAX_VALUE)
private String billingLastName;
@Column(name = "billing_company_name", length = Integer.MAX_VALUE)
private String billingCompanyName;
@Column(name = "billing_contact_person", length = Integer.MAX_VALUE)
private String billingContactPerson;
@Column(name = "billing_address_line1", nullable = false, length = Integer.MAX_VALUE)
private String billingAddressLine1;
@Column(name = "billing_address_line2", length = Integer.MAX_VALUE)
private String billingAddressLine2;
@Column(name = "billing_zip", nullable = false, length = Integer.MAX_VALUE)
private String billingZip;
@Column(name = "billing_city", nullable = false, length = Integer.MAX_VALUE)
private String billingCity;
@ColumnDefault("'CH'")
@Column(name = "billing_country_code", nullable = false, length = 2)
private String billingCountryCode;
@ColumnDefault("true")
@Column(name = "shipping_same_as_billing", nullable = false)
private Boolean shippingSameAsBilling;
@Column(name = "shipping_first_name", length = Integer.MAX_VALUE)
private String shippingFirstName;
@Column(name = "shipping_last_name", length = Integer.MAX_VALUE)
private String shippingLastName;
@Column(name = "shipping_company_name", length = Integer.MAX_VALUE)
private String shippingCompanyName;
@Column(name = "shipping_contact_person", length = Integer.MAX_VALUE)
private String shippingContactPerson;
@Column(name = "shipping_address_line1", length = Integer.MAX_VALUE)
private String shippingAddressLine1;
@Column(name = "shipping_address_line2", length = Integer.MAX_VALUE)
private String shippingAddressLine2;
@Column(name = "shipping_zip", length = Integer.MAX_VALUE)
private String shippingZip;
@Column(name = "shipping_city", length = Integer.MAX_VALUE)
private String shippingCity;
@Column(name = "shipping_country_code", length = 2)
private String shippingCountryCode;
@ColumnDefault("'it'")
@Column(name = "preferred_language", length = 2)
private String preferredLanguage;
@ColumnDefault("'CHF'")
@Column(name = "currency", nullable = false, length = 3)
private String currency;
@ColumnDefault("0.00")
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal setupCostChf;
@ColumnDefault("0.00")
@Column(name = "shipping_cost_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal shippingCostChf;
@ColumnDefault("0.00")
@Column(name = "discount_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal discountChf;
@ColumnDefault("0.00")
@Column(name = "subtotal_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal subtotalChf;
@ColumnDefault("false")
@Column(name = "is_cad_order", nullable = false)
private Boolean isCadOrder;
@Column(name = "source_request_id")
private UUID sourceRequestId;
@Column(name = "cad_hours", precision = 10, scale = 2)
private BigDecimal cadHours;
@Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2)
private BigDecimal cadHourlyRateChf;
@ColumnDefault("0.00")
@Column(name = "cad_total_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal cadTotalChf;
@ColumnDefault("0.00")
@Column(name = "total_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal totalChf;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@Column(name = "paid_at")
private OffsetDateTime paidAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (shippingSameAsBilling == null) {
shippingSameAsBilling = true;
}
if (sourceType == null || sourceType.isBlank()) {
sourceType = "CALCULATOR";
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (shippingSameAsBilling == null) {
shippingSameAsBilling = true;
}
if (sourceType == null || sourceType.isBlank()) {
sourceType = "CALCULATOR";
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
@Transient
public String getOrderNumber() {
if (id == null) {
return null;
}
String rawId = id.toString();
int dashIndex = rawId.indexOf('-');
return dashIndex > 0 ? rawId.substring(0, dashIndex) : rawId;
}
public QuoteSession getSourceQuoteSession() {
return sourceQuoteSession;
}
public void setSourceQuoteSession(QuoteSession sourceQuoteSession) {
this.sourceQuoteSession = sourceQuoteSession;
}
public String getSourceType() {
return sourceType;
}
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public String getCustomerEmail() {
return customerEmail;
}
public void setCustomerEmail(String customerEmail) {
this.customerEmail = customerEmail;
}
public String getCustomerPhone() {
return customerPhone;
}
public void setCustomerPhone(String customerPhone) {
this.customerPhone = customerPhone;
}
public String getBillingCustomerType() {
return billingCustomerType;
}
public void setBillingCustomerType(String billingCustomerType) {
this.billingCustomerType = billingCustomerType;
}
public String getBillingFirstName() {
return billingFirstName;
}
public void setBillingFirstName(String billingFirstName) {
this.billingFirstName = billingFirstName;
}
public String getBillingLastName() {
return billingLastName;
}
public void setBillingLastName(String billingLastName) {
this.billingLastName = billingLastName;
}
public String getBillingCompanyName() {
return billingCompanyName;
}
public void setBillingCompanyName(String billingCompanyName) {
this.billingCompanyName = billingCompanyName;
}
public String getBillingContactPerson() {
return billingContactPerson;
}
public void setBillingContactPerson(String billingContactPerson) {
this.billingContactPerson = billingContactPerson;
}
public String getBillingAddressLine1() {
return billingAddressLine1;
}
public void setBillingAddressLine1(String billingAddressLine1) {
this.billingAddressLine1 = billingAddressLine1;
}
public String getBillingAddressLine2() {
return billingAddressLine2;
}
public void setBillingAddressLine2(String billingAddressLine2) {
this.billingAddressLine2 = billingAddressLine2;
}
public String getBillingZip() {
return billingZip;
}
public void setBillingZip(String billingZip) {
this.billingZip = billingZip;
}
public String getBillingCity() {
return billingCity;
}
public void setBillingCity(String billingCity) {
this.billingCity = billingCity;
}
public String getBillingCountryCode() {
return billingCountryCode;
}
public void setBillingCountryCode(String billingCountryCode) {
this.billingCountryCode = billingCountryCode;
}
public Boolean getShippingSameAsBilling() {
return shippingSameAsBilling;
}
public void setShippingSameAsBilling(Boolean shippingSameAsBilling) {
this.shippingSameAsBilling = shippingSameAsBilling;
}
public String getShippingFirstName() {
return shippingFirstName;
}
public void setShippingFirstName(String shippingFirstName) {
this.shippingFirstName = shippingFirstName;
}
public String getShippingLastName() {
return shippingLastName;
}
public void setShippingLastName(String shippingLastName) {
this.shippingLastName = shippingLastName;
}
public String getShippingCompanyName() {
return shippingCompanyName;
}
public void setShippingCompanyName(String shippingCompanyName) {
this.shippingCompanyName = shippingCompanyName;
}
public String getShippingContactPerson() {
return shippingContactPerson;
}
public void setShippingContactPerson(String shippingContactPerson) {
this.shippingContactPerson = shippingContactPerson;
}
public String getShippingAddressLine1() {
return shippingAddressLine1;
}
public void setShippingAddressLine1(String shippingAddressLine1) {
this.shippingAddressLine1 = shippingAddressLine1;
}
public String getShippingAddressLine2() {
return shippingAddressLine2;
}
public void setShippingAddressLine2(String shippingAddressLine2) {
this.shippingAddressLine2 = shippingAddressLine2;
}
public String getShippingZip() {
return shippingZip;
}
public void setShippingZip(String shippingZip) {
this.shippingZip = shippingZip;
}
public String getShippingCity() {
return shippingCity;
}
public void setShippingCity(String shippingCity) {
this.shippingCity = shippingCity;
}
public String getShippingCountryCode() {
return shippingCountryCode;
}
public void setShippingCountryCode(String shippingCountryCode) {
this.shippingCountryCode = shippingCountryCode;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public String getPreferredLanguage() {
return preferredLanguage;
}
public void setPreferredLanguage(String preferredLanguage) {
this.preferredLanguage = preferredLanguage;
}
public BigDecimal getSetupCostChf() {
return setupCostChf;
}
public void setSetupCostChf(BigDecimal setupCostChf) {
this.setupCostChf = setupCostChf;
}
public BigDecimal getShippingCostChf() {
return shippingCostChf;
}
public void setShippingCostChf(BigDecimal shippingCostChf) {
this.shippingCostChf = shippingCostChf;
}
public BigDecimal getDiscountChf() {
return discountChf;
}
public void setDiscountChf(BigDecimal discountChf) {
this.discountChf = discountChf;
}
public BigDecimal getSubtotalChf() {
return subtotalChf;
}
public void setSubtotalChf(BigDecimal subtotalChf) {
this.subtotalChf = subtotalChf;
}
public Boolean getIsCadOrder() {
return isCadOrder;
}
public void setIsCadOrder(Boolean isCadOrder) {
this.isCadOrder = isCadOrder;
}
public UUID getSourceRequestId() {
return sourceRequestId;
}
public void setSourceRequestId(UUID sourceRequestId) {
this.sourceRequestId = sourceRequestId;
}
public BigDecimal getCadHours() {
return cadHours;
}
public void setCadHours(BigDecimal cadHours) {
this.cadHours = cadHours;
}
public BigDecimal getCadHourlyRateChf() {
return cadHourlyRateChf;
}
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
this.cadHourlyRateChf = cadHourlyRateChf;
}
public BigDecimal getCadTotalChf() {
return cadTotalChf;
}
public void setCadTotalChf(BigDecimal cadTotalChf) {
this.cadTotalChf = cadTotalChf;
}
public BigDecimal getTotalChf() {
return totalChf;
}
public void setTotalChf(BigDecimal totalChf) {
this.totalChf = totalChf;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public OffsetDateTime getPaidAt() {
return paidAt;
}
public void setPaidAt(OffsetDateTime paidAt) {
this.paidAt = paidAt;
}
}

View File

@@ -0,0 +1,429 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "order_items", indexes = {@Index(name = "ix_order_items_order",
columnList = "order_id")})
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "order_item_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
@ColumnDefault("'PRINT_FILE'")
@Column(name = "item_type", nullable = false, length = Integer.MAX_VALUE)
private String itemType;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "display_name", length = Integer.MAX_VALUE)
private String displayName;
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
private String storedRelativePath;
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
private String storedFilename;
@Column(name = "file_size_bytes")
private Long fileSizeBytes;
@Column(name = "mime_type", length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
private String sha256Hex;
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
private String materialCode;
@Column(name = "quality", length = Integer.MAX_VALUE)
private String quality;
@Column(name = "nozzle_diameter_mm", precision = 4, scale = 2)
private BigDecimal nozzleDiameterMm;
@Column(name = "layer_height_mm", precision = 5, scale = 3)
private BigDecimal layerHeightMm;
@Column(name = "infill_percent")
private Integer infillPercent;
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
private String infillPattern;
@Column(name = "supports_enabled")
private Boolean supportsEnabled;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "filament_variant_id")
private FilamentVariant filamentVariant;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_id")
private ShopProduct shopProduct;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_variant_id")
private ShopProductVariant shopProductVariant;
@Column(name = "shop_product_slug", length = Integer.MAX_VALUE)
private String shopProductSlug;
@Column(name = "shop_product_name", length = Integer.MAX_VALUE)
private String shopProductName;
@Column(name = "shop_variant_label", length = Integer.MAX_VALUE)
private String shopVariantLabel;
@Column(name = "shop_variant_color_name", length = Integer.MAX_VALUE)
private String shopVariantColorName;
@Column(name = "shop_variant_color_hex", length = Integer.MAX_VALUE)
private String shopVariantColorHex;
@Column(name = "color_code", length = Integer.MAX_VALUE)
private String colorCode;
@ColumnDefault("1")
@Column(name = "quantity", nullable = false)
private Integer quantity;
@Column(name = "print_time_seconds")
private Integer printTimeSeconds;
@Column(name = "material_grams", precision = 12, scale = 2)
private BigDecimal materialGrams;
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxXMm;
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxYMm;
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxZMm;
@Column(name = "unit_price_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal unitPriceChf;
@Column(name = "line_total_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal lineTotalChf;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@PrePersist
private void onCreate() {
if (createdAt == null) {
createdAt = OffsetDateTime.now();
}
if (quantity == null) {
quantity = 1;
}
if (itemType == null || itemType.isBlank()) {
itemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public String getItemType() {
return itemType;
}
public void setItemType(String itemType) {
this.itemType = itemType;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getStoredRelativePath() {
return storedRelativePath;
}
public void setStoredRelativePath(String storedRelativePath) {
this.storedRelativePath = storedRelativePath;
}
public String getStoredFilename() {
return storedFilename;
}
public void setStoredFilename(String storedFilename) {
this.storedFilename = storedFilename;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public String getQuality() {
return quality;
}
public void setQuality(String quality) {
this.quality = quality;
}
public BigDecimal getNozzleDiameterMm() {
return nozzleDiameterMm;
}
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
this.nozzleDiameterMm = nozzleDiameterMm;
}
public BigDecimal getLayerHeightMm() {
return layerHeightMm;
}
public void setLayerHeightMm(BigDecimal layerHeightMm) {
this.layerHeightMm = layerHeightMm;
}
public Integer getInfillPercent() {
return infillPercent;
}
public void setInfillPercent(Integer infillPercent) {
this.infillPercent = infillPercent;
}
public String getInfillPattern() {
return infillPattern;
}
public void setInfillPattern(String infillPattern) {
this.infillPattern = infillPattern;
}
public Boolean getSupportsEnabled() {
return supportsEnabled;
}
public void setSupportsEnabled(Boolean supportsEnabled) {
this.supportsEnabled = supportsEnabled;
}
public FilamentVariant getFilamentVariant() {
return filamentVariant;
}
public void setFilamentVariant(FilamentVariant filamentVariant) {
this.filamentVariant = filamentVariant;
}
public ShopProduct getShopProduct() {
return shopProduct;
}
public void setShopProduct(ShopProduct shopProduct) {
this.shopProduct = shopProduct;
}
public ShopProductVariant getShopProductVariant() {
return shopProductVariant;
}
public void setShopProductVariant(ShopProductVariant shopProductVariant) {
this.shopProductVariant = shopProductVariant;
}
public String getShopProductSlug() {
return shopProductSlug;
}
public void setShopProductSlug(String shopProductSlug) {
this.shopProductSlug = shopProductSlug;
}
public String getShopProductName() {
return shopProductName;
}
public void setShopProductName(String shopProductName) {
this.shopProductName = shopProductName;
}
public String getShopVariantLabel() {
return shopVariantLabel;
}
public void setShopVariantLabel(String shopVariantLabel) {
this.shopVariantLabel = shopVariantLabel;
}
public String getShopVariantColorName() {
return shopVariantColorName;
}
public void setShopVariantColorName(String shopVariantColorName) {
this.shopVariantColorName = shopVariantColorName;
}
public String getShopVariantColorHex() {
return shopVariantColorHex;
}
public void setShopVariantColorHex(String shopVariantColorHex) {
this.shopVariantColorHex = shopVariantColorHex;
}
public String getColorCode() {
return colorCode;
}
public void setColorCode(String colorCode) {
this.colorCode = colorCode;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public Integer getPrintTimeSeconds() {
return printTimeSeconds;
}
public void setPrintTimeSeconds(Integer printTimeSeconds) {
this.printTimeSeconds = printTimeSeconds;
}
public BigDecimal getMaterialGrams() {
return materialGrams;
}
public void setMaterialGrams(BigDecimal materialGrams) {
this.materialGrams = materialGrams;
}
public BigDecimal getBoundingBoxXMm() {
return boundingBoxXMm;
}
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
this.boundingBoxXMm = boundingBoxXMm;
}
public BigDecimal getBoundingBoxYMm() {
return boundingBoxYMm;
}
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
this.boundingBoxYMm = boundingBoxYMm;
}
public BigDecimal getBoundingBoxZMm() {
return boundingBoxZMm;
}
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
this.boundingBoxZMm = boundingBoxZMm;
}
public BigDecimal getUnitPriceChf() {
return unitPriceChf;
}
public void setUnitPriceChf(BigDecimal unitPriceChf) {
this.unitPriceChf = unitPriceChf;
}
public BigDecimal getLineTotalChf() {
return lineTotalChf;
}
public void setLineTotalChf(BigDecimal lineTotalChf) {
this.lineTotalChf = lineTotalChf;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,157 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "payments", indexes = {
@Index(name = "ix_payments_order",
columnList = "order_id"),
@Index(name = "ix_payments_reference",
columnList = "payment_reference")})
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "payment_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
@Column(name = "method", nullable = false, length = Integer.MAX_VALUE)
private String method;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@ColumnDefault("'CHF'")
@Column(name = "currency", nullable = false, length = 3)
private String currency;
@Column(name = "amount_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal amountChf;
@Column(name = "payment_reference", length = Integer.MAX_VALUE)
private String paymentReference;
@Column(name = "provider_transaction_id", length = Integer.MAX_VALUE)
private String providerTransactionId;
@Column(name = "qr_payload", length = Integer.MAX_VALUE)
private String qrPayload;
@ColumnDefault("now()")
@Column(name = "initiated_at", nullable = false)
private OffsetDateTime initiatedAt;
@Column(name = "reported_at")
private OffsetDateTime reportedAt;
@Column(name = "received_at")
private OffsetDateTime receivedAt;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public BigDecimal getAmountChf() {
return amountChf;
}
public void setAmountChf(BigDecimal amountChf) {
this.amountChf = amountChf;
}
public String getPaymentReference() {
return paymentReference;
}
public void setPaymentReference(String paymentReference) {
this.paymentReference = paymentReference;
}
public String getProviderTransactionId() {
return providerTransactionId;
}
public void setProviderTransactionId(String providerTransactionId) {
this.providerTransactionId = providerTransactionId;
}
public String getQrPayload() {
return qrPayload;
}
public void setQrPayload(String qrPayload) {
this.qrPayload = qrPayload;
}
public OffsetDateTime getInitiatedAt() {
return initiatedAt;
}
public void setInitiatedAt(OffsetDateTime initiatedAt) {
this.initiatedAt = initiatedAt;
}
public OffsetDateTime getReportedAt() {
return reportedAt;
}
public void setReportedAt(OffsetDateTime reportedAt) {
this.reportedAt = reportedAt;
}
public OffsetDateTime getReceivedAt() {
return receivedAt;
}
public void setReceivedAt(OffsetDateTime receivedAt) {
this.receivedAt = receivedAt;
}
}

View File

@@ -0,0 +1,85 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
@Entity
@Table(name = "printer_machine_profile", uniqueConstraints = {
@UniqueConstraint(name = "ux_printer_machine_profile_machine_nozzle", columnNames = {
"printer_machine_id", "nozzle_diameter_mm"
})
})
public class PrinterMachineProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "printer_machine_profile_id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "printer_machine_id", nullable = false)
private PrinterMachine printerMachine;
@Column(name = "nozzle_diameter_mm", nullable = false, precision = 4, scale = 2)
private BigDecimal nozzleDiameterMm;
@Column(name = "orca_machine_profile_name", nullable = false, length = Integer.MAX_VALUE)
private String orcaMachineProfileName;
@ColumnDefault("false")
@Column(name = "is_default", nullable = false)
private Boolean isDefault;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public PrinterMachine getPrinterMachine() {
return printerMachine;
}
public void setPrinterMachine(PrinterMachine printerMachine) {
this.printerMachine = printerMachine;
}
public BigDecimal getNozzleDiameterMm() {
return nozzleDiameterMm;
}
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
this.nozzleDiameterMm = nozzleDiameterMm;
}
public String getOrcaMachineProfileName() {
return orcaMachineProfileName;
}
public void setOrcaMachineProfileName(String orcaMachineProfileName) {
this.orcaMachineProfileName = orcaMachineProfileName;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean isDefault) {
this.isDefault = isDefault;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
}

View File

@@ -0,0 +1,444 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.type.SqlTypes;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.UUID;
@Entity
@Table(name = "quote_line_items", indexes = {@Index(name = "ix_quote_line_items_session",
columnList = "quote_session_id")})
public class QuoteLineItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "quote_line_item_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "quote_session_id", nullable = false)
@com.fasterxml.jackson.annotation.JsonIgnore
private QuoteSession quoteSession;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@ColumnDefault("'PRINT_FILE'")
@Column(name = "line_item_type", nullable = false, length = Integer.MAX_VALUE)
private String lineItemType;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "display_name", length = Integer.MAX_VALUE)
private String displayName;
@ColumnDefault("1")
@Column(name = "quantity", nullable = false)
private Integer quantity;
@Column(name = "color_code", length = Integer.MAX_VALUE)
private String colorCode;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "filament_variant_id")
@com.fasterxml.jackson.annotation.JsonIgnore
private FilamentVariant filamentVariant;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_id")
@com.fasterxml.jackson.annotation.JsonIgnore
private ShopProduct shopProduct;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_product_variant_id")
@com.fasterxml.jackson.annotation.JsonIgnore
private ShopProductVariant shopProductVariant;
@Column(name = "shop_product_slug", length = Integer.MAX_VALUE)
private String shopProductSlug;
@Column(name = "shop_product_name", length = Integer.MAX_VALUE)
private String shopProductName;
@Column(name = "shop_variant_label", length = Integer.MAX_VALUE)
private String shopVariantLabel;
@Column(name = "shop_variant_color_name", length = Integer.MAX_VALUE)
private String shopVariantColorName;
@Column(name = "shop_variant_color_hex", length = Integer.MAX_VALUE)
private String shopVariantColorHex;
@Column(name = "material_code", length = Integer.MAX_VALUE)
private String materialCode;
@Column(name = "quality", length = Integer.MAX_VALUE)
private String quality;
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
private BigDecimal nozzleDiameterMm;
@Column(name = "layer_height_mm", precision = 6, scale = 3)
private BigDecimal layerHeightMm;
@Column(name = "infill_percent")
private Integer infillPercent;
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
private String infillPattern;
@Column(name = "supports_enabled")
private Boolean supportsEnabled;
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxXMm;
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxYMm;
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxZMm;
@Column(name = "print_time_seconds")
private Integer printTimeSeconds;
@Column(name = "material_grams", precision = 12, scale = 2)
private BigDecimal materialGrams;
@Column(name = "unit_price_chf", precision = 12, scale = 2)
private BigDecimal unitPriceChf;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "pricing_breakdown")
private Map<String, Object> pricingBreakdown;
@Column(name = "error_message", length = Integer.MAX_VALUE)
private String errorMessage;
@Column(name = "stored_path", length = Integer.MAX_VALUE)
private String storedPath;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (quantity == null) {
quantity = 1;
}
if (lineItemType == null || lineItemType.isBlank()) {
lineItemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (lineItemType == null || lineItemType.isBlank()) {
lineItemType = "PRINT_FILE";
}
if ((displayName == null || displayName.isBlank()) && originalFilename != null && !originalFilename.isBlank()) {
displayName = originalFilename;
} else if ((displayName == null || displayName.isBlank()) && shopProductName != null && !shopProductName.isBlank()) {
displayName = shopProductName;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public QuoteSession getQuoteSession() {
return quoteSession;
}
public void setQuoteSession(QuoteSession quoteSession) {
this.quoteSession = quoteSession;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getLineItemType() {
return lineItemType;
}
public void setLineItemType(String lineItemType) {
this.lineItemType = lineItemType;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
public String getColorCode() {
return colorCode;
}
public void setColorCode(String colorCode) {
this.colorCode = colorCode;
}
public FilamentVariant getFilamentVariant() {
return filamentVariant;
}
public void setFilamentVariant(FilamentVariant filamentVariant) {
this.filamentVariant = filamentVariant;
}
public ShopProduct getShopProduct() {
return shopProduct;
}
public void setShopProduct(ShopProduct shopProduct) {
this.shopProduct = shopProduct;
}
public ShopProductVariant getShopProductVariant() {
return shopProductVariant;
}
public void setShopProductVariant(ShopProductVariant shopProductVariant) {
this.shopProductVariant = shopProductVariant;
}
public String getShopProductSlug() {
return shopProductSlug;
}
public void setShopProductSlug(String shopProductSlug) {
this.shopProductSlug = shopProductSlug;
}
public String getShopProductName() {
return shopProductName;
}
public void setShopProductName(String shopProductName) {
this.shopProductName = shopProductName;
}
public String getShopVariantLabel() {
return shopVariantLabel;
}
public void setShopVariantLabel(String shopVariantLabel) {
this.shopVariantLabel = shopVariantLabel;
}
public String getShopVariantColorName() {
return shopVariantColorName;
}
public void setShopVariantColorName(String shopVariantColorName) {
this.shopVariantColorName = shopVariantColorName;
}
public String getShopVariantColorHex() {
return shopVariantColorHex;
}
public void setShopVariantColorHex(String shopVariantColorHex) {
this.shopVariantColorHex = shopVariantColorHex;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public String getQuality() {
return quality;
}
public void setQuality(String quality) {
this.quality = quality;
}
public BigDecimal getNozzleDiameterMm() {
return nozzleDiameterMm;
}
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
this.nozzleDiameterMm = nozzleDiameterMm;
}
public BigDecimal getLayerHeightMm() {
return layerHeightMm;
}
public void setLayerHeightMm(BigDecimal layerHeightMm) {
this.layerHeightMm = layerHeightMm;
}
public Integer getInfillPercent() {
return infillPercent;
}
public void setInfillPercent(Integer infillPercent) {
this.infillPercent = infillPercent;
}
public String getInfillPattern() {
return infillPattern;
}
public void setInfillPattern(String infillPattern) {
this.infillPattern = infillPattern;
}
public Boolean getSupportsEnabled() {
return supportsEnabled;
}
public void setSupportsEnabled(Boolean supportsEnabled) {
this.supportsEnabled = supportsEnabled;
}
public BigDecimal getBoundingBoxXMm() {
return boundingBoxXMm;
}
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
this.boundingBoxXMm = boundingBoxXMm;
}
public BigDecimal getBoundingBoxYMm() {
return boundingBoxYMm;
}
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
this.boundingBoxYMm = boundingBoxYMm;
}
public BigDecimal getBoundingBoxZMm() {
return boundingBoxZMm;
}
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
this.boundingBoxZMm = boundingBoxZMm;
}
public Integer getPrintTimeSeconds() {
return printTimeSeconds;
}
public void setPrintTimeSeconds(Integer printTimeSeconds) {
this.printTimeSeconds = printTimeSeconds;
}
public BigDecimal getMaterialGrams() {
return materialGrams;
}
public void setMaterialGrams(BigDecimal materialGrams) {
this.materialGrams = materialGrams;
}
public BigDecimal getUnitPriceChf() {
return unitPriceChf;
}
public void setUnitPriceChf(BigDecimal unitPriceChf) {
this.unitPriceChf = unitPriceChf;
}
public Map<String, Object> getPricingBreakdown() {
return pricingBreakdown;
}
public void setPricingBreakdown(Map<String, Object> pricingBreakdown) {
this.pricingBreakdown = pricingBreakdown;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getStoredPath() {
return storedPath;
}
public void setStoredPath(String storedPath) {
this.storedPath = storedPath;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,234 @@
package com.printcalculator.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "quote_sessions", indexes = {
@Index(name = "ix_quote_sessions_status",
columnList = "status"),
@Index(name = "ix_quote_sessions_expires_at",
columnList = "expires_at")})
public class QuoteSession {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "quote_session_id", nullable = false)
private UUID id;
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@ColumnDefault("'PRINT_QUOTE'")
@Column(name = "session_type", nullable = false, length = Integer.MAX_VALUE)
private String sessionType;
@Column(name = "pricing_version", nullable = false, length = Integer.MAX_VALUE)
private String pricingVersion;
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
private String materialCode;
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
private BigDecimal nozzleDiameterMm;
@Column(name = "layer_height_mm", precision = 6, scale = 3)
private BigDecimal layerHeightMm;
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
private String infillPattern;
@Column(name = "infill_percent")
private Integer infillPercent;
@ColumnDefault("false")
@Column(name = "supports_enabled", nullable = false)
private Boolean supportsEnabled;
@Column(name = "notes", length = Integer.MAX_VALUE)
private String notes;
@ColumnDefault("0.00")
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal setupCostChf;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@Column(name = "expires_at", nullable = false)
private OffsetDateTime expiresAt;
@Column(name = "converted_order_id")
private UUID convertedOrderId;
@Column(name = "source_request_id")
private UUID sourceRequestId;
@Column(name = "cad_hours", precision = 10, scale = 2)
private BigDecimal cadHours;
@Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2)
private BigDecimal cadHourlyRateChf;
@PrePersist
private void onCreate() {
if (sessionType == null || sessionType.isBlank()) {
sessionType = "PRINT_QUOTE";
}
if (supportsEnabled == null) {
supportsEnabled = false;
}
if (createdAt == null) {
createdAt = OffsetDateTime.now();
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getSessionType() {
return sessionType;
}
public void setSessionType(String sessionType) {
this.sessionType = sessionType;
}
public String getPricingVersion() {
return pricingVersion;
}
public void setPricingVersion(String pricingVersion) {
this.pricingVersion = pricingVersion;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public BigDecimal getNozzleDiameterMm() {
return nozzleDiameterMm;
}
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
this.nozzleDiameterMm = nozzleDiameterMm;
}
public BigDecimal getLayerHeightMm() {
return layerHeightMm;
}
public void setLayerHeightMm(BigDecimal layerHeightMm) {
this.layerHeightMm = layerHeightMm;
}
public String getInfillPattern() {
return infillPattern;
}
public void setInfillPattern(String infillPattern) {
this.infillPattern = infillPattern;
}
public Integer getInfillPercent() {
return infillPercent;
}
public void setInfillPercent(Integer infillPercent) {
this.infillPercent = infillPercent;
}
public Boolean getSupportsEnabled() {
return supportsEnabled;
}
public void setSupportsEnabled(Boolean supportsEnabled) {
this.supportsEnabled = supportsEnabled;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public BigDecimal getSetupCostChf() {
return setupCostChf;
}
public void setSetupCostChf(BigDecimal setupCostChf) {
this.setupCostChf = setupCostChf;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(OffsetDateTime expiresAt) {
this.expiresAt = expiresAt;
}
public UUID getConvertedOrderId() {
return convertedOrderId;
}
public void setConvertedOrderId(UUID convertedOrderId) {
this.convertedOrderId = convertedOrderId;
}
public UUID getSourceRequestId() {
return sourceRequestId;
}
public void setSourceRequestId(UUID sourceRequestId) {
this.sourceRequestId = sourceRequestId;
}
public BigDecimal getCadHours() {
return cadHours;
}
public void setCadHours(BigDecimal cadHours) {
this.cadHours = cadHours;
}
public BigDecimal getCadHourlyRateChf() {
return cadHourlyRateChf;
}
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
this.cadHourlyRateChf = cadHourlyRateChf;
}
}

View File

@@ -0,0 +1,505 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "shop_category", indexes = {
@Index(name = "ix_shop_category_parent_sort", columnList = "parent_category_id, sort_order"),
@Index(name = "ix_shop_category_active_sort", columnList = "is_active, sort_order")
})
public class ShopCategory {
public static final List<String> SUPPORTED_LANGUAGES = List.of("it", "en", "de", "fr");
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_category_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_category_id")
private ShopCategory parentCategory;
@Column(name = "slug", nullable = false, unique = true, length = Integer.MAX_VALUE)
private String slug;
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "name_it", length = Integer.MAX_VALUE)
private String nameIt;
@Column(name = "name_en", length = Integer.MAX_VALUE)
private String nameEn;
@Column(name = "name_de", length = Integer.MAX_VALUE)
private String nameDe;
@Column(name = "name_fr", length = Integer.MAX_VALUE)
private String nameFr;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "description_it", length = Integer.MAX_VALUE)
private String descriptionIt;
@Column(name = "description_en", length = Integer.MAX_VALUE)
private String descriptionEn;
@Column(name = "description_de", length = Integer.MAX_VALUE)
private String descriptionDe;
@Column(name = "description_fr", length = Integer.MAX_VALUE)
private String descriptionFr;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@Column(name = "seo_title_it", length = Integer.MAX_VALUE)
private String seoTitleIt;
@Column(name = "seo_title_en", length = Integer.MAX_VALUE)
private String seoTitleEn;
@Column(name = "seo_title_de", length = Integer.MAX_VALUE)
private String seoTitleDe;
@Column(name = "seo_title_fr", length = Integer.MAX_VALUE)
private String seoTitleFr;
@Column(name = "seo_description", length = Integer.MAX_VALUE)
private String seoDescription;
@Column(name = "seo_description_it", length = Integer.MAX_VALUE)
private String seoDescriptionIt;
@Column(name = "seo_description_en", length = Integer.MAX_VALUE)
private String seoDescriptionEn;
@Column(name = "seo_description_de", length = Integer.MAX_VALUE)
private String seoDescriptionDe;
@Column(name = "seo_description_fr", length = Integer.MAX_VALUE)
private String seoDescriptionFr;
@Column(name = "og_title", length = Integer.MAX_VALUE)
private String ogTitle;
@Column(name = "og_description", length = Integer.MAX_VALUE)
private String ogDescription;
@ColumnDefault("true")
@Column(name = "indexable", nullable = false)
private Boolean indexable;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (indexable == null) {
indexable = true;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (indexable == null) {
indexable = true;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopCategory getParentCategory() {
return parentCategory;
}
public void setParentCategory(ShopCategory parentCategory) {
this.parentCategory = parentCategory;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getNameForLanguage(String language) {
return resolveLocalizedValue(language, name, nameIt, nameEn, nameDe, nameFr);
}
public void setNameForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> nameIt = value;
case "en" -> nameEn = value;
case "de" -> nameDe = value;
case "fr" -> nameFr = value;
default -> {
}
}
}
public String getDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, description, descriptionIt, descriptionEn, descriptionDe, descriptionFr);
}
public void setDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> descriptionIt = value;
case "en" -> descriptionEn = value;
case "de" -> descriptionDe = value;
case "fr" -> descriptionFr = value;
default -> {
}
}
}
public String getSeoTitleForLanguage(String language) {
return resolveLocalizedValue(language, seoTitle, seoTitleIt, seoTitleEn, seoTitleDe, seoTitleFr);
}
public void setSeoTitleForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoTitleIt = value;
case "en" -> seoTitleEn = value;
case "de" -> seoTitleDe = value;
case "fr" -> seoTitleFr = value;
default -> {
}
}
}
public String getSeoDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, seoDescription, seoDescriptionIt, seoDescriptionEn, seoDescriptionDe, seoDescriptionFr);
}
public void setSeoDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoDescriptionIt = value;
case "en" -> seoDescriptionEn = value;
case "de" -> seoDescriptionDe = value;
case "fr" -> seoDescriptionFr = value;
default -> {
}
}
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,593 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "shop_product", indexes = {
@Index(name = "ix_shop_product_category_active_sort", columnList = "shop_category_id, is_active, sort_order"),
@Index(name = "ix_shop_product_featured_sort", columnList = "is_featured, is_active, sort_order")
})
public class ShopProduct {
public static final List<String> SUPPORTED_LANGUAGES = List.of("it", "en", "de", "fr");
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_category_id", nullable = false)
private ShopCategory category;
@Column(name = "slug", nullable = false, unique = true, length = Integer.MAX_VALUE)
private String slug;
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "name_it", length = Integer.MAX_VALUE)
private String nameIt;
@Column(name = "name_en", length = Integer.MAX_VALUE)
private String nameEn;
@Column(name = "name_de", length = Integer.MAX_VALUE)
private String nameDe;
@Column(name = "name_fr", length = Integer.MAX_VALUE)
private String nameFr;
@Column(name = "excerpt", length = Integer.MAX_VALUE)
private String excerpt;
@Column(name = "excerpt_it", length = Integer.MAX_VALUE)
private String excerptIt;
@Column(name = "excerpt_en", length = Integer.MAX_VALUE)
private String excerptEn;
@Column(name = "excerpt_de", length = Integer.MAX_VALUE)
private String excerptDe;
@Column(name = "excerpt_fr", length = Integer.MAX_VALUE)
private String excerptFr;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "description_it", length = Integer.MAX_VALUE)
private String descriptionIt;
@Column(name = "description_en", length = Integer.MAX_VALUE)
private String descriptionEn;
@Column(name = "description_de", length = Integer.MAX_VALUE)
private String descriptionDe;
@Column(name = "description_fr", length = Integer.MAX_VALUE)
private String descriptionFr;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@Column(name = "seo_title_it", length = Integer.MAX_VALUE)
private String seoTitleIt;
@Column(name = "seo_title_en", length = Integer.MAX_VALUE)
private String seoTitleEn;
@Column(name = "seo_title_de", length = Integer.MAX_VALUE)
private String seoTitleDe;
@Column(name = "seo_title_fr", length = Integer.MAX_VALUE)
private String seoTitleFr;
@Column(name = "seo_description", length = Integer.MAX_VALUE)
private String seoDescription;
@Column(name = "seo_description_it", length = Integer.MAX_VALUE)
private String seoDescriptionIt;
@Column(name = "seo_description_en", length = Integer.MAX_VALUE)
private String seoDescriptionEn;
@Column(name = "seo_description_de", length = Integer.MAX_VALUE)
private String seoDescriptionDe;
@Column(name = "seo_description_fr", length = Integer.MAX_VALUE)
private String seoDescriptionFr;
@Column(name = "og_title", length = Integer.MAX_VALUE)
private String ogTitle;
@Column(name = "og_description", length = Integer.MAX_VALUE)
private String ogDescription;
@ColumnDefault("true")
@Column(name = "indexable", nullable = false)
private Boolean indexable;
@ColumnDefault("false")
@Column(name = "is_featured", nullable = false)
private Boolean isFeatured;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (indexable == null) {
indexable = true;
}
if (isFeatured == null) {
isFeatured = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (indexable == null) {
indexable = true;
}
if (isFeatured == null) {
isFeatured = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopCategory getCategory() {
return category;
}
public void setCategory(ShopCategory category) {
this.category = category;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
public void setSeoTitle(String seoTitle) {
this.seoTitle = seoTitle;
}
public String getSeoDescription() {
return seoDescription;
}
public void setSeoDescription(String seoDescription) {
this.seoDescription = seoDescription;
}
public String getSeoTitleIt() {
return seoTitleIt;
}
public void setSeoTitleIt(String seoTitleIt) {
this.seoTitleIt = seoTitleIt;
}
public String getSeoTitleEn() {
return seoTitleEn;
}
public void setSeoTitleEn(String seoTitleEn) {
this.seoTitleEn = seoTitleEn;
}
public String getSeoTitleDe() {
return seoTitleDe;
}
public void setSeoTitleDe(String seoTitleDe) {
this.seoTitleDe = seoTitleDe;
}
public String getSeoTitleFr() {
return seoTitleFr;
}
public void setSeoTitleFr(String seoTitleFr) {
this.seoTitleFr = seoTitleFr;
}
public String getSeoDescriptionIt() {
return seoDescriptionIt;
}
public void setSeoDescriptionIt(String seoDescriptionIt) {
this.seoDescriptionIt = seoDescriptionIt;
}
public String getSeoDescriptionEn() {
return seoDescriptionEn;
}
public void setSeoDescriptionEn(String seoDescriptionEn) {
this.seoDescriptionEn = seoDescriptionEn;
}
public String getSeoDescriptionDe() {
return seoDescriptionDe;
}
public void setSeoDescriptionDe(String seoDescriptionDe) {
this.seoDescriptionDe = seoDescriptionDe;
}
public String getSeoDescriptionFr() {
return seoDescriptionFr;
}
public void setSeoDescriptionFr(String seoDescriptionFr) {
this.seoDescriptionFr = seoDescriptionFr;
}
public String getOgTitle() {
return ogTitle;
}
public void setOgTitle(String ogTitle) {
this.ogTitle = ogTitle;
}
public String getOgDescription() {
return ogDescription;
}
public void setOgDescription(String ogDescription) {
this.ogDescription = ogDescription;
}
public Boolean getIndexable() {
return indexable;
}
public void setIndexable(Boolean indexable) {
this.indexable = indexable;
}
public Boolean getIsFeatured() {
return isFeatured;
}
public void setIsFeatured(Boolean featured) {
isFeatured = featured;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getNameForLanguage(String language) {
return resolveLocalizedValue(language, name, nameIt, nameEn, nameDe, nameFr);
}
public void setNameForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> nameIt = value;
case "en" -> nameEn = value;
case "de" -> nameDe = value;
case "fr" -> nameFr = value;
default -> {
}
}
}
public String getExcerptForLanguage(String language) {
return resolveLocalizedValue(language, excerpt, excerptIt, excerptEn, excerptDe, excerptFr);
}
public void setExcerptForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> excerptIt = value;
case "en" -> excerptEn = value;
case "de" -> excerptDe = value;
case "fr" -> excerptFr = value;
default -> {
}
}
}
public String getDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, description, descriptionIt, descriptionEn, descriptionDe, descriptionFr);
}
public void setDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> descriptionIt = value;
case "en" -> descriptionEn = value;
case "de" -> descriptionDe = value;
case "fr" -> descriptionFr = value;
default -> {
}
}
}
public String getSeoTitleForLanguage(String language) {
return resolveLocalizedValue(language, seoTitle, seoTitleIt, seoTitleEn, seoTitleDe, seoTitleFr);
}
public void setSeoTitleForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoTitleIt = value;
case "en" -> seoTitleEn = value;
case "de" -> seoTitleDe = value;
case "fr" -> seoTitleFr = value;
default -> {
}
}
}
public String getSeoDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, seoDescription, seoDescriptionIt, seoDescriptionEn, seoDescriptionDe, seoDescriptionFr);
}
public void setSeoDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> seoDescriptionIt = value;
case "en" -> seoDescriptionEn = value;
case "de" -> seoDescriptionDe = value;
case "fr" -> seoDescriptionFr = value;
default -> {
}
}
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,189 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_product_model_asset", indexes = {
@Index(name = "ix_shop_product_model_asset_product", columnList = "shop_product_id")
})
public class ShopProductModelAsset {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_model_asset_id", nullable = false)
private UUID id;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_product_id", nullable = false, unique = true)
private ShopProduct product;
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
private String originalFilename;
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
private String storedRelativePath;
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
private String storedFilename;
@Column(name = "file_size_bytes")
private Long fileSizeBytes;
@Column(name = "mime_type", length = Integer.MAX_VALUE)
private String mimeType;
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
private String sha256Hex;
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxXMm;
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxYMm;
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
private BigDecimal boundingBoxZMm;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopProduct getProduct() {
return product;
}
public void setProduct(ShopProduct product) {
this.product = product;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getStoredRelativePath() {
return storedRelativePath;
}
public void setStoredRelativePath(String storedRelativePath) {
this.storedRelativePath = storedRelativePath;
}
public String getStoredFilename() {
return storedFilename;
}
public void setStoredFilename(String storedFilename) {
this.storedFilename = storedFilename;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public void setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public String getSha256Hex() {
return sha256Hex;
}
public void setSha256Hex(String sha256Hex) {
this.sha256Hex = sha256Hex;
}
public BigDecimal getBoundingBoxXMm() {
return boundingBoxXMm;
}
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
this.boundingBoxXMm = boundingBoxXMm;
}
public BigDecimal getBoundingBoxYMm() {
return boundingBoxYMm;
}
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
this.boundingBoxYMm = boundingBoxYMm;
}
public BigDecimal getBoundingBoxZMm() {
return boundingBoxZMm;
}
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
this.boundingBoxZMm = boundingBoxZMm;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -0,0 +1,318 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.UUID;
@Entity
@Table(name = "shop_product_variant", indexes = {
@Index(name = "ix_shop_product_variant_product_active_sort", columnList = "shop_product_id, is_active, sort_order"),
@Index(name = "ix_shop_product_variant_sku", columnList = "sku")
})
public class ShopProductVariant {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_variant_id", nullable = false)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "shop_product_id", nullable = false)
private ShopProduct product;
@Column(name = "sku", unique = true, length = Integer.MAX_VALUE)
private String sku;
@Column(name = "variant_label", nullable = false, length = Integer.MAX_VALUE)
private String variantLabel;
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
private String colorName;
@Column(name = "color_label_it", length = Integer.MAX_VALUE)
private String colorLabelIt;
@Column(name = "color_label_en", length = Integer.MAX_VALUE)
private String colorLabelEn;
@Column(name = "color_label_de", length = Integer.MAX_VALUE)
private String colorLabelDe;
@Column(name = "color_label_fr", length = Integer.MAX_VALUE)
private String colorLabelFr;
@Column(name = "color_hex", length = Integer.MAX_VALUE)
private String colorHex;
@Column(name = "internal_material_code", nullable = false, length = Integer.MAX_VALUE)
private String internalMaterialCode;
@ColumnDefault("0.00")
@Column(name = "price_chf", nullable = false, precision = 12, scale = 2)
private BigDecimal priceChf;
@ColumnDefault("false")
@Column(name = "is_default", nullable = false)
private Boolean isDefault;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive;
@ColumnDefault("0")
@Column(name = "sort_order", nullable = false)
private Integer sortOrder;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
@PrePersist
private void onCreate() {
OffsetDateTime now = OffsetDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
if (priceChf == null) {
priceChf = BigDecimal.ZERO;
}
if (isDefault == null) {
isDefault = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
@PreUpdate
private void onUpdate() {
updatedAt = OffsetDateTime.now();
if (priceChf == null) {
priceChf = BigDecimal.ZERO;
}
if (isDefault == null) {
isDefault = false;
}
if (isActive == null) {
isActive = true;
}
if (sortOrder == null) {
sortOrder = 0;
}
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public ShopProduct getProduct() {
return product;
}
public void setProduct(ShopProduct product) {
this.product = product;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public String getVariantLabel() {
return variantLabel;
}
public void setVariantLabel(String variantLabel) {
this.variantLabel = variantLabel;
}
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
public String getColorLabelIt() {
return colorLabelIt;
}
public void setColorLabelIt(String colorLabelIt) {
this.colorLabelIt = colorLabelIt;
}
public String getColorLabelEn() {
return colorLabelEn;
}
public void setColorLabelEn(String colorLabelEn) {
this.colorLabelEn = colorLabelEn;
}
public String getColorLabelDe() {
return colorLabelDe;
}
public void setColorLabelDe(String colorLabelDe) {
this.colorLabelDe = colorLabelDe;
}
public String getColorLabelFr() {
return colorLabelFr;
}
public void setColorLabelFr(String colorLabelFr) {
this.colorLabelFr = colorLabelFr;
}
public String getColorHex() {
return colorHex;
}
public void setColorHex(String colorHex) {
this.colorHex = colorHex;
}
public String getInternalMaterialCode() {
return internalMaterialCode;
}
public void setInternalMaterialCode(String internalMaterialCode) {
this.internalMaterialCode = internalMaterialCode;
}
public BigDecimal getPriceChf() {
return priceChf;
}
public void setPriceChf(BigDecimal priceChf) {
this.priceChf = priceChf;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean aDefault) {
isDefault = aDefault;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean active) {
isActive = active;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getColorLabelForLanguage(String language) {
return resolveLocalizedValue(
language,
colorName,
colorLabelIt,
colorLabelEn,
colorLabelDe,
colorLabelFr
);
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

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