cabloy 5.1.111 → 5.1.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-backend-scaffold/SKILL.md +31 -1
  3. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +21 -0
  4. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +3 -0
  5. package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +4 -0
  6. package/.claude/skills/cabloy-master-detail/SKILL.md +13 -2
  7. package/.github/workflows/playwright-e2e.yml +77 -0
  8. package/.github/workflows/vona-test-pg.yml +8 -0
  9. package/.gitignore +1 -0
  10. package/CHANGELOG.md +30 -0
  11. package/CLAUDE.md +6 -1
  12. package/README.md +8 -8
  13. package/cabloy-docs/backend/crud-workflow.md +10 -0
  14. package/cabloy-docs/backend/foundation.md +29 -4
  15. package/cabloy-docs/backend/introduction.md +1 -1
  16. package/cabloy-docs/backend/migration-and-changes.md +13 -9
  17. package/cabloy-docs/backend/model-guide.md +9 -11
  18. package/cabloy-docs/backend/multi-instance-and-instance-resolution.md +18 -0
  19. package/cabloy-docs/backend/service-guide.md +1 -1
  20. package/cabloy-docs/backend/unit-testing.md +25 -0
  21. package/cabloy-docs/frontend/a-router-guide.md +1 -1
  22. package/cabloy-docs/frontend/introduction.md +1 -1
  23. package/cabloy-docs/frontend/navigation-guards-guide.md +20 -11
  24. package/cabloy-docs/frontend/route-alias-guide.md +24 -0
  25. package/cabloy-docs/frontend/scripts.md +71 -24
  26. package/cabloy-docs/frontend/ssr-init-data.md +25 -0
  27. package/cabloy-docs/frontend/ssr-review-checklist.md +17 -7
  28. package/cabloy-docs/frontend/use-state-data-best-practices.md +4 -0
  29. package/cabloy-docs/frontend/zova-router-under-the-hood.md +4 -1
  30. package/cabloy-docs/fullstack/comparison-with-other-frameworks.md +19 -13
  31. package/cabloy-docs/fullstack/introduction.md +10 -10
  32. package/cabloy-docs/fullstack/quickstart.md +23 -1
  33. package/cabloy-docs/index.md +14 -14
  34. package/cabloy-docs/reference/package-map.md +11 -7
  35. package/cabloy-docs/reference/repo-scripts.md +108 -23
  36. package/e2e/scripts/e2e.ts +1 -1
  37. package/e2e/scripts/startE2eVona.ts +51 -13
  38. package/e2e/specs/a-commerce/commerce.spec.ts +125 -1
  39. package/e2e/specs/{a-basic → cabloy-basic}/basic.spec.ts +12 -7
  40. package/package.json +2 -1
  41. package/scripts/init.ts +6 -12
  42. package/scripts/upgrade.ts +3 -1
  43. package/vona/packages-cli/cli/package.json +1 -1
  44. package/vona/packages-cli/cli-set-api/package.json +1 -1
  45. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +6 -9
  46. package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +50 -21
  47. package/vona/packages-vona/vona/package.json +1 -1
  48. package/vona/pnpm-lock.yaml +94 -163
  49. package/vona/src/backend/config/config/config.test.ts +1 -1
  50. package/vona/src/backend/config/config/config.ts +11 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +4 -2
  52. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +732 -2
  53. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/locales.ts +18 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.index.ts +14 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +52 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/ssrMenu.category.ts +30 -0
  57. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/ssrMenu.product.ts +30 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/ssrMenu.sku.ts +30 -0
  59. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +19 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +19 -0
  61. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/category.ts +65 -0
  62. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/product.ts +86 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/controller/sku.ts +65 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryCreate.tsx +28 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectReq.tsx +25 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectRes.tsx +11 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +39 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryUpdate.tsx +28 -0
  69. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +25 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +28 -0
  71. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublic.tsx +38 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicSelectReq.tsx +23 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicSelectRes.tsx +11 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productPublicSku.tsx +25 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productSelectReq.tsx +25 -0
  76. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productSelectRes.tsx +11 -0
  77. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productSelectResItem.tsx +39 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +28 -0
  79. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +25 -0
  80. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuCreate.tsx +28 -0
  81. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectReq.tsx +25 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectRes.tsx +11 -0
  83. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +39 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuUpdate.tsx +28 -0
  85. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuView.tsx +25 -0
  86. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +53 -0
  87. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/product.tsx +48 -0
  88. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/sku.tsx +66 -0
  89. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  90. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +15 -0
  91. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/product.ts +46 -0
  92. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/sku.ts +18 -0
  93. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +47 -0
  94. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/product.ts +162 -0
  95. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +76 -0
  96. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/catalog.test.ts +592 -0
  97. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/category.test.ts +91 -0
  98. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/product.test.ts +154 -0
  99. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/sku.test.ts +190 -0
  100. package/vona/src/suite/a-commerce/modules/commerce-member/package.json +2 -1
  101. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +342 -5
  102. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/locales.ts +18 -0
  103. package/vona/src/suite/a-commerce/modules/commerce-member/src/bean/meta.index.ts +12 -0
  104. package/vona/src/suite/a-commerce/modules/commerce-member/src/bean/meta.version.ts +31 -0
  105. package/vona/src/suite/a-commerce/modules/commerce-member/src/config/locale/en-us.ts +12 -0
  106. package/vona/src/suite/a-commerce/modules/commerce-member/src/config/locale/zh-cn.ts +12 -0
  107. package/vona/src/suite/a-commerce/modules/commerce-member/src/controller/address.ts +59 -0
  108. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressCreate.tsx +39 -0
  109. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectReq.tsx +30 -0
  110. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectRes.tsx +11 -0
  111. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectResItem.tsx +53 -0
  112. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressUpdate.tsx +39 -0
  113. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressView.tsx +39 -0
  114. package/vona/src/suite/a-commerce/modules/commerce-member/src/entity/address.tsx +76 -0
  115. package/vona/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  116. package/vona/src/suite/a-commerce/modules/commerce-member/src/model/address.ts +10 -0
  117. package/vona/src/suite/a-commerce/modules/commerce-member/src/service/address.ts +84 -0
  118. package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +203 -0
  119. package/vona/src/suite/a-commerce/modules/commerce-seed/package.json +52 -0
  120. package/vona/src/suite/a-commerce/modules/commerce-seed/src/.metadata/index.ts +55 -0
  121. package/vona/src/suite/a-commerce/modules/commerce-seed/src/.metadata/this.ts +2 -0
  122. package/vona/src/suite/a-commerce/modules/commerce-seed/src/bean/meta.version.ts +80 -0
  123. package/vona/src/suite/a-commerce/modules/commerce-seed/src/index.ts +1 -0
  124. package/vona/src/suite/a-commerce/modules/commerce-seed/tsconfig.build.json +11 -0
  125. package/vona/src/suite/a-commerce/modules/commerce-seed/tsconfig.json +7 -0
  126. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +4 -2
  127. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +192 -2
  128. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/locales.ts +18 -0
  129. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/meta.version.ts +32 -0
  130. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenu.home.ts +3 -1
  131. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenuGroup.catalog.ts +21 -0
  132. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenuGroup.inventory.ts +21 -0
  133. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/config/locale/en-us.ts +5 -0
  134. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/config/locale/zh-cn.ts +5 -0
  135. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/controller/operator.ts +21 -0
  136. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/dto/operatorContext.ts +21 -0
  137. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  138. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/service/operator.ts +18 -0
  139. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/meta.version.test.ts +44 -0
  140. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/operatorAccess.test.ts +55 -0
  141. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/ssrMenu.test.ts +77 -0
  142. package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +2 -1
  143. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +773 -2
  144. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/locales.ts +18 -0
  145. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.index.ts +23 -0
  146. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.version.ts +83 -0
  147. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/ssrMenu.stockAudit.ts +30 -0
  148. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/ssrMenu.stockBalance.ts +30 -0
  149. package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +29 -0
  150. package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +29 -0
  151. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/cart.ts +48 -0
  152. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/stockAudit.ts +38 -0
  153. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/stockBalance.ts +49 -0
  154. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/cartAddItem.tsx +19 -0
  155. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/cartItem.tsx +34 -0
  156. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/cartUpdateItem.tsx +15 -0
  157. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/cartView.tsx +18 -0
  158. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +32 -0
  159. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditCreate.tsx +28 -0
  160. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditSelectReq.tsx +28 -0
  161. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditSelectRes.tsx +11 -0
  162. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditSelectResItem.tsx +33 -0
  163. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditUpdate.tsx +28 -0
  164. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAuditView.tsx +25 -0
  165. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceCreate.tsx +28 -0
  166. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceSelectReq.tsx +28 -0
  167. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceSelectRes.tsx +11 -0
  168. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceSelectResItem.tsx +33 -0
  169. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceUpdate.tsx +28 -0
  170. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockBalanceView.tsx +25 -0
  171. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/cart.tsx +24 -0
  172. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/cartItem.tsx +30 -0
  173. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockAudit.tsx +118 -0
  174. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockBalance.tsx +53 -0
  175. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockReservation.tsx +63 -0
  176. package/vona/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  177. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +17 -0
  178. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/cartItem.ts +10 -0
  179. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/stockAudit.ts +10 -0
  180. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +15 -0
  181. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/stockReservation.ts +10 -0
  182. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/cart.ts +152 -0
  183. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockAudit.ts +20 -0
  184. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +342 -0
  185. package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +174 -0
  186. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockAudit.test.ts +37 -0
  187. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +389 -0
  188. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +457 -0
  189. package/vona/src/suite/a-commerce/package.json +1 -0
  190. package/vona/src/suite/a-commerce/tsconfig.json +3 -0
  191. package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +7 -1
  192. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +14 -23
  193. package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +10 -8
  194. package/vona/src/suite/a-training/modules/training-recordsubject/src/.metadata/index.ts +2 -0
  195. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +3 -1
  196. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  197. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/.metadata/index.ts +7 -3
  198. package/vona/src/suite-vendor/a-auth/modules/auth-simple/package.json +1 -1
  199. package/vona/src/suite-vendor/a-auth/modules/auth-simple/src/.metadata/index.ts +2 -0
  200. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  201. package/vona/src/suite-vendor/a-cabloy/modules/a-datasharding/package.json +1 -1
  202. package/vona/src/suite-vendor/a-cabloy/modules/a-datasharding/src/.metadata/index.ts +1 -1
  203. package/vona/src/suite-vendor/a-cabloy/modules/a-datasource/package.json +1 -1
  204. package/vona/src/suite-vendor/a-cabloy/modules/a-datasource/src/.metadata/index.ts +3 -1
  205. package/vona/src/suite-vendor/a-cabloy/modules/a-socket/package.json +1 -1
  206. package/vona/src/suite-vendor/a-cabloy/modules/a-socket/src/.metadata/index.ts +1 -1
  207. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  208. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/.metadata/index.ts +47 -47
  209. package/vona/src/suite-vendor/a-cabloy/modules/a-ssrhmr/package.json +1 -1
  210. package/vona/src/suite-vendor/a-cabloy/modules/a-ssrhmr/src/.metadata/index.ts +1 -1
  211. package/vona/src/suite-vendor/a-cabloy/modules/a-status/package.json +1 -1
  212. package/vona/src/suite-vendor/a-cabloy/modules/a-status/src/.metadata/index.ts +2 -0
  213. package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
  214. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/package.json +1 -1
  215. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/.metadata/index.ts +1 -1
  216. package/vona/src/suite-vendor/a-captcha/package.json +1 -1
  217. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +1 -1
  218. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +42 -38
  219. package/vona/src/suite-vendor/a-file/package.json +2 -2
  220. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  221. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +42 -38
  222. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  223. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/.metadata/index.ts +42 -27
  224. package/vona/src/suite-vendor/a-image/package.json +1 -1
  225. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/package.json +1 -1
  226. package/vona/src/suite-vendor/a-paypal/modules/a-paypal/src/.metadata/index.ts +3 -1
  227. package/vona/src/suite-vendor/a-paypal/package.json +1 -1
  228. package/vona/src/suite-vendor/a-vona/modules/a-beanmutate/package.json +1 -1
  229. package/vona/src/suite-vendor/a-vona/modules/a-beanmutate/src/.metadata/index.ts +1 -1
  230. package/vona/src/suite-vendor/a-vona/modules/a-body/package.json +1 -1
  231. package/vona/src/suite-vendor/a-vona/modules/a-body/src/.metadata/index.ts +2 -2
  232. package/vona/src/suite-vendor/a-vona/modules/a-cache/package.json +1 -1
  233. package/vona/src/suite-vendor/a-vona/modules/a-cache/src/.metadata/index.ts +1 -1
  234. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  235. package/vona/src/suite-vendor/a-vona/modules/a-core/src/.metadata/index.ts +1 -1
  236. package/vona/src/suite-vendor/a-vona/modules/a-error/package.json +1 -1
  237. package/vona/src/suite-vendor/a-vona/modules/a-error/src/.metadata/index.ts +1 -1
  238. package/vona/src/suite-vendor/a-vona/modules/a-event/package.json +1 -1
  239. package/vona/src/suite-vendor/a-vona/modules/a-event/src/.metadata/index.ts +1 -1
  240. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  241. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/.metadata/index.ts +1 -1
  242. package/vona/src/suite-vendor/a-vona/modules/a-hmr/package.json +1 -1
  243. package/vona/src/suite-vendor/a-vona/modules/a-hmr/src/.metadata/index.ts +1 -1
  244. package/vona/src/suite-vendor/a-vona/modules/a-instance/package.json +1 -1
  245. package/vona/src/suite-vendor/a-vona/modules/a-instance/src/.metadata/index.ts +3 -1
  246. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  247. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/.metadata/index.ts +1 -1
  248. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  249. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/.metadata/index.ts +1 -1
  250. package/vona/src/suite-vendor/a-vona/modules/a-mail/package.json +1 -1
  251. package/vona/src/suite-vendor/a-vona/modules/a-mail/src/.metadata/index.ts +3 -1
  252. package/vona/src/suite-vendor/a-vona/modules/a-mailconfirm/package.json +1 -1
  253. package/vona/src/suite-vendor/a-vona/modules/a-mailconfirm/src/.metadata/index.ts +1 -1
  254. package/vona/src/suite-vendor/a-vona/modules/a-onion/package.json +1 -1
  255. package/vona/src/suite-vendor/a-vona/modules/a-onion/src/.metadata/index.ts +1 -1
  256. package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
  257. package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/.metadata/index.ts +1 -1
  258. package/vona/src/suite-vendor/a-vona/modules/a-orm/cli/model/metadata/generate.ts +5 -11
  259. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  260. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_cache.ts +52 -0
  261. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGet.ts +4 -10
  262. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  263. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/.metadata/index.ts +1 -1
  264. package/vona/src/suite-vendor/a-vona/modules/a-security/package.json +1 -1
  265. package/vona/src/suite-vendor/a-vona/modules/a-security/src/.metadata/index.ts +1 -1
  266. package/vona/src/suite-vendor/a-vona/modules/a-serialization/package.json +1 -1
  267. package/vona/src/suite-vendor/a-vona/modules/a-serialization/src/.metadata/index.ts +1 -1
  268. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  269. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +2 -2
  270. package/vona/src/suite-vendor/a-vona/modules/a-summer/package.json +1 -1
  271. package/vona/src/suite-vendor/a-vona/modules/a-summer/src/.metadata/index.ts +1 -1
  272. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  273. package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +4 -5
  274. package/vona/src/suite-vendor/a-vona/modules/a-validation/package.json +1 -1
  275. package/vona/src/suite-vendor/a-vona/modules/a-validation/src/.metadata/index.ts +1 -1
  276. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  277. package/vona/src/suite-vendor/a-vona/modules/a-version/src/.metadata/index.ts +6 -0
  278. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/version.ts +12 -12
  279. package/vona/src/suite-vendor/a-vona/modules/a-version/src/types/version.ts +4 -4
  280. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  281. package/vona/src/suite-vendor/a-vona/modules/a-web/src/.metadata/index.ts +1 -1
  282. package/vona/src/suite-vendor/a-vona/modules/a-worker/package.json +1 -1
  283. package/vona/src/suite-vendor/a-vona/modules/a-worker/src/.metadata/index.ts +1 -1
  284. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  285. package/zova/packages-cli/cli/package.json +2 -2
  286. package/zova/packages-cli/cli-set-front/package.json +1 -1
  287. package/zova/packages-cli/cli-set-front/src/lib/bean/toolsMetadata/generateScope.ts +1 -1
  288. package/zova/packages-zova/zova/package.json +2 -2
  289. package/zova/pnpm-lock.yaml +9 -9
  290. package/zova/src/front/config/config/config.cabloyCommerce.ts +5 -1
  291. package/zova/src/front/config/config/config.cabloyCommerceAdmin.ts +2 -1
  292. package/zova/src/suite/a-commerce/modules/commerce-catalog/cli/openapi.config.ts +9 -0
  293. package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +2 -1
  294. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +202 -3
  295. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/locales.ts +7 -0
  296. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/page/catalogue.ts +23 -0
  297. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/page/product.ts +19 -0
  298. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/commerceCatalogProduct.ts +52 -0
  299. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/baseURL.ts +5 -0
  300. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/index.ts +3 -0
  301. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/schemas.ts +412 -0
  302. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/api/openapi/types.ts +7159 -0
  303. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/apiSchema/commerceCatalogProduct.ts +20 -0
  304. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +8 -0
  305. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +8 -0
  306. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  307. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/model/catalogue.ts +38 -0
  308. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/catalogue/controller.tsx +71 -0
  309. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +107 -0
  310. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/routes.ts +19 -0
  311. package/zova/src/suite/a-commerce/modules/commerce-member/cli/openapi.config.ts +9 -0
  312. package/zova/src/suite/a-commerce/modules/commerce-member/package.json +2 -1
  313. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +182 -3
  314. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/locales.ts +7 -0
  315. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/page/address.ts +19 -0
  316. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/commerceMemberAddress.ts +111 -0
  317. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/baseURL.ts +5 -0
  318. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/index.ts +3 -0
  319. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +432 -0
  320. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +7445 -0
  321. package/zova/src/suite/a-commerce/modules/commerce-member/src/apiSchema/commerceMemberAddress.ts +35 -0
  322. package/zova/src/suite/a-commerce/modules/commerce-member/src/config/locale/en-us.ts +17 -0
  323. package/zova/src/suite/a-commerce/modules/commerce-member/src/config/locale/zh-cn.ts +17 -0
  324. package/zova/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  325. package/zova/src/suite/a-commerce/modules/commerce-member/src/model/address.ts +38 -0
  326. package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +194 -0
  327. package/zova/src/suite/a-commerce/modules/commerce-member/src/routes.ts +12 -0
  328. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/cli/openapi.config.ts +9 -0
  329. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/package.json +2 -1
  330. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +175 -3
  331. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/locales.ts +7 -0
  332. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/page/dashboard.ts +9 -0
  333. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/commerceSiteadminOperator.ts +25 -0
  334. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/baseURL.ts +5 -0
  335. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/index.ts +3 -0
  336. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/schemas.ts +304 -0
  337. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/types.ts +5710 -0
  338. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/apiSchema/commerceSiteadminOperator.ts +13 -0
  339. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/config/locale/en-us.ts +5 -0
  340. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/config/locale/zh-cn.ts +5 -0
  341. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  342. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/model/operator.ts +17 -0
  343. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/page/dashboard/controller.tsx +34 -0
  344. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/routes.ts +5 -0
  345. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +15 -0
  346. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +2 -1
  347. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +182 -3
  348. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/locales.ts +7 -0
  349. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/cart.ts +19 -0
  350. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCart.ts +99 -0
  351. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/baseURL.ts +5 -0
  352. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/index.ts +3 -0
  353. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +446 -0
  354. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +7651 -0
  355. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCart.ts +35 -0
  356. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +8 -0
  357. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +8 -0
  358. package/zova/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  359. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +78 -0
  360. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +108 -0
  361. package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +12 -0
  362. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/index.ts +5 -5
  363. package/zova/src/suite/a-demo/modules/demo-todo/src/.metadata/index.ts +2 -2
  364. package/zova/src/suite/a-home/modules/home-base/src/service/routerGuards.ts +18 -10
  365. package/zova/src/suite/a-home/modules/home-indexweb/src/.metadata/index.ts +2 -2
  366. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +17 -7
  367. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx +1 -1
  368. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +17 -7
  369. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  370. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/index.ts +4 -4
  371. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
  372. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  373. package/zova/src/suite-vendor/a-zova/modules/a-bean/cli/controller/metadata/generateMetaPage.ts +3 -1
  374. package/zova/src/suite-vendor/a-zova/modules/a-bean/package.json +1 -1
  375. package/zova/src/suite-vendor/a-zova/modules/a-fetch/package.json +2 -2
  376. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  377. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +3 -1
  378. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  379. package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/sys.router.ts +35 -8
  380. package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkeySys.ts +28 -12
  381. package/zova/src/suite-vendor/a-zova/modules/a-router/src/service/routerGuards.ts +6 -8
  382. package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/router.ts +14 -2
  383. package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/utils.ts +4 -1
  384. package/zova/src/suite-vendor/a-zova/package.json +5 -5
  385. package/CLAUDE.local.md +0 -4
@@ -0,0 +1,592 @@
1
+ import { catchError } from '@cabloy/utils';
2
+ import assert from 'node:assert';
3
+ import { describe, it } from 'node:test';
4
+ import { app } from 'vona-mock';
5
+
6
+ async function createCatalog(
7
+ prefix: string,
8
+ lifecycle: 'draft' | 'active' | 'inactive' | 'archived',
9
+ ) {
10
+ await app.bean.passport.signinMock();
11
+ try {
12
+ const categoryId = await app.bean.executor.performAction('post', '/commerce/catalog/category', {
13
+ body: { name: `${prefix} category`, published: true },
14
+ });
15
+ const productId = await app.bean.executor.performAction('post', '/commerce/catalog/product', {
16
+ body: { categoryId, title: `${prefix} product`, published: true },
17
+ });
18
+ const skuId = await app.bean.executor.performAction('post', '/commerce/catalog/sku', {
19
+ body: { code: `${prefix}-sku`, productId, priceCents: 1299, lifecycle },
20
+ });
21
+ return { categoryId, productId, skuId };
22
+ } finally {
23
+ await app.bean.passport.signout();
24
+ }
25
+ }
26
+
27
+ describe('catalog.test.ts', () => {
28
+ it('enforces operator access and category/product relations', async () => {
29
+ await app.bean.executor.mockCtx(async () => {
30
+ await app.bean.passport.signinMock();
31
+ try {
32
+ app.bean.passport.current!.roles = [];
33
+ const permissions = await Promise.all([
34
+ app.bean.permission.retrievePermissionAction('commerce-catalog:category', 'create'),
35
+ app.bean.permission.retrievePermissionAction('commerce-catalog:product', 'create'),
36
+ app.bean.permission.retrievePermissionAction('commerce-catalog:sku', 'create'),
37
+ ]);
38
+ assert.deepEqual(permissions, [false, false, false]);
39
+ } finally {
40
+ await app.bean.passport.signout();
41
+ }
42
+
43
+ await app.bean.passport.signinMock();
44
+ try {
45
+ await assert.rejects(() =>
46
+ app.bean.executor.performAction('post', '/commerce/catalog/product', {
47
+ body: { categoryId: 999999, title: 'Missing category', published: false },
48
+ }),
49
+ );
50
+ } finally {
51
+ await app.bean.passport.signout();
52
+ }
53
+ });
54
+ });
55
+
56
+ it('stores integer cents and exposes only sellable products to customers', async () => {
57
+ await app.bean.executor.mockCtx(async () => {
58
+ const active = await createCatalog('__catalog-active__', 'active');
59
+ const draft = await createCatalog('__catalog-draft__', 'draft');
60
+ const inactive = await createCatalog('__catalog-inactive__', 'inactive');
61
+ const archived = await createCatalog('__catalog-archived__', 'archived');
62
+ const unpublishedProduct = await createCatalog('__catalog-unpublished-product__', 'active');
63
+ const unpublishedCategory = await createCatalog('__catalog-unpublished-category__', 'active');
64
+ const zeroStock = await createCatalog('__catalog-zero-stock__', 'active');
65
+
66
+ for (const catalog of [
67
+ active,
68
+ draft,
69
+ inactive,
70
+ archived,
71
+ unpublishedProduct,
72
+ unpublishedCategory,
73
+ ]) {
74
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
75
+ skuId: catalog.skuId,
76
+ delta: 2,
77
+ reason: 'catalog public fixture',
78
+ correlationId: `catalog-public-${catalog.skuId}`,
79
+ });
80
+ }
81
+ await app.scope('commerce-catalog').model.product.updateById(unpublishedProduct.productId, {
82
+ published: false,
83
+ });
84
+ await app
85
+ .scope('commerce-catalog')
86
+ .model.category.updateById(unpublishedCategory.categoryId, {
87
+ published: false,
88
+ });
89
+
90
+ const publicProducts = await app.bean.executor.performAction(
91
+ 'get',
92
+ '/commerce/catalog/product/public',
93
+ { innerAccess: false },
94
+ );
95
+ assert.equal(
96
+ publicProducts.list.some((product: any) => String(product.id) === String(active.productId)),
97
+ true,
98
+ );
99
+ for (const catalog of [
100
+ draft,
101
+ inactive,
102
+ archived,
103
+ unpublishedProduct,
104
+ unpublishedCategory,
105
+ zeroStock,
106
+ ]) {
107
+ assert.equal(
108
+ publicProducts.list.some(
109
+ (product: any) => String(product.id) === String(catalog.productId),
110
+ ),
111
+ false,
112
+ );
113
+ }
114
+ const catalogPublicProduct = publicProducts.list.find(
115
+ (product: any) => String(product.id) === String(active.productId),
116
+ );
117
+ assert.deepEqual(Object.keys(catalogPublicProduct).sort(), [
118
+ 'available',
119
+ 'categoryId',
120
+ 'categoryName',
121
+ 'description',
122
+ 'id',
123
+ 'priceCents',
124
+ 'skuAvailables',
125
+ 'title',
126
+ ]);
127
+ assert.equal(catalogPublicProduct.priceCents, 1299);
128
+ assert.equal(catalogPublicProduct.available, 2);
129
+ const publicProduct = await app.bean.executor.performAction(
130
+ 'get',
131
+ '/commerce/catalog/product/public/:id',
132
+ { params: { id: active.productId }, innerAccess: false },
133
+ );
134
+ assert.equal(String(publicProduct.id), String(active.productId));
135
+ for (const catalog of [
136
+ draft,
137
+ inactive,
138
+ archived,
139
+ unpublishedProduct,
140
+ unpublishedCategory,
141
+ zeroStock,
142
+ ]) {
143
+ const publicProduct = await app.bean.executor.performAction(
144
+ 'get',
145
+ '/commerce/catalog/product/public/:id',
146
+ { params: { id: catalog.productId }, innerAccess: false },
147
+ );
148
+ assert.equal(publicProduct, undefined);
149
+ }
150
+ });
151
+ });
152
+
153
+ it('paginates and aggregates public Products without duplicate roots', async () => {
154
+ await app.bean.executor.mockCtx(async () => {
155
+ const first = await createCatalog('__catalog-page-first__', 'active');
156
+ const second = await createCatalog('__catalog-page-second__', 'active');
157
+ const scopeCatalog = app.scope('commerce-catalog');
158
+ const additionalSku = await scopeCatalog.model.sku.insert({
159
+ code: '__catalog-page-first-additional-sku__',
160
+ productId: first.productId,
161
+ priceCents: 999,
162
+ lifecycle: 'active',
163
+ });
164
+ const inactiveSku = await scopeCatalog.model.sku.insert({
165
+ code: '__catalog-page-first-inactive-sku__',
166
+ productId: first.productId,
167
+ priceCents: 100,
168
+ lifecycle: 'inactive',
169
+ });
170
+ for (const [skuId, delta] of [
171
+ [first.skuId, 2],
172
+ [additionalSku.id, 3],
173
+ [inactiveSku.id, 4],
174
+ [second.skuId, 1],
175
+ ] as const) {
176
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
177
+ skuId,
178
+ delta,
179
+ reason: 'catalog pagination fixture',
180
+ correlationId: `catalog-pagination-${skuId}`,
181
+ });
182
+ }
183
+ const query = { title: '__catalog-page-', pageSize: 1 };
184
+ const pageOne = await app.bean.executor.performAction(
185
+ 'get',
186
+ '/commerce/catalog/product/public',
187
+ {
188
+ query: { ...query, pageNo: 1 },
189
+ innerAccess: false,
190
+ },
191
+ );
192
+ const pageTwo = await app.bean.executor.performAction(
193
+ 'get',
194
+ '/commerce/catalog/product/public',
195
+ {
196
+ query: { ...query, pageNo: 2 },
197
+ innerAccess: false,
198
+ },
199
+ );
200
+ assert.equal(pageOne.total, '2');
201
+ assert.equal(pageOne.pageCount, 2);
202
+ assert.equal(pageOne.pageSize, 1);
203
+ assert.equal(pageOne.pageNo, 1);
204
+ assert.equal(pageTwo.total, '2');
205
+ assert.equal(pageTwo.pageNo, 2);
206
+ assert.notEqual(String(pageOne.list[0].id), String(pageTwo.list[0].id));
207
+ const firstPublicProduct = [pageOne, pageTwo]
208
+ .flatMap(page => page.list)
209
+ .find(product => String(product.id) === String(first.productId));
210
+ assert.equal(firstPublicProduct.priceCents, 999);
211
+ assert.equal(firstPublicProduct.available, 5);
212
+ assert.deepEqual(
213
+ firstPublicProduct.skuAvailables.map(sku => String(sku.id)).sort(),
214
+ [first.skuId, additionalSku.id].map(String).sort(),
215
+ );
216
+ const categoryProducts = await app.bean.executor.performAction(
217
+ 'get',
218
+ '/commerce/catalog/product/public',
219
+ {
220
+ query: { categoryId: first.categoryId },
221
+ innerAccess: false,
222
+ },
223
+ );
224
+ assert.equal(categoryProducts.total, '1');
225
+ assert.equal(String(categoryProducts.list[0].id), String(first.productId));
226
+ });
227
+ });
228
+
229
+ it('refreshes public eligibility after Category, SKU, and stock changes', async () => {
230
+ await app.bean.executor.mockCtx(async () => {
231
+ const catalog = await createCatalog('__catalog-cache__', 'active');
232
+ const query = { title: '__catalog-cache__' };
233
+ const selectPublic = async () => {
234
+ return await app.bean.executor.performAction('get', '/commerce/catalog/product/public', {
235
+ query,
236
+ innerAccess: false,
237
+ });
238
+ };
239
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
240
+ skuId: catalog.skuId,
241
+ delta: 1,
242
+ reason: 'catalog cache fixture',
243
+ correlationId: `catalog-cache-add-${catalog.skuId}`,
244
+ });
245
+ assert.equal((await selectPublic()).total, '1');
246
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
247
+ skuId: catalog.skuId,
248
+ delta: -1,
249
+ reason: 'catalog cache fixture',
250
+ correlationId: `catalog-cache-remove-${catalog.skuId}`,
251
+ });
252
+ assert.equal((await selectPublic()).total, '0');
253
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
254
+ skuId: catalog.skuId,
255
+ delta: 1,
256
+ reason: 'catalog cache fixture',
257
+ correlationId: `catalog-cache-restore-${catalog.skuId}`,
258
+ });
259
+ assert.equal((await selectPublic()).total, '1');
260
+ await app.scope('commerce-catalog').model.sku.updateById(catalog.skuId, {
261
+ lifecycle: 'inactive',
262
+ });
263
+ assert.equal((await selectPublic()).total, '0');
264
+ await app.scope('commerce-catalog').model.sku.updateById(catalog.skuId, {
265
+ lifecycle: 'active',
266
+ });
267
+ assert.equal((await selectPublic()).total, '1');
268
+ await app.scope('commerce-catalog').model.category.updateById(catalog.categoryId, {
269
+ published: false,
270
+ });
271
+ assert.equal((await selectPublic()).total, '0');
272
+ });
273
+ });
274
+
275
+ it('isolates catalog data and SKU uniqueness by tenant', async () => {
276
+ let defaultProductId: unknown;
277
+ await app.bean.executor.mockCtx(async () => {
278
+ const catalog = await createCatalog('__catalog-default__', 'active');
279
+ defaultProductId = catalog.productId;
280
+
281
+ await app.bean.passport.signinMock();
282
+ try {
283
+ await assert.rejects(() =>
284
+ app.bean.executor.performAction('post', '/commerce/catalog/sku', {
285
+ body: {
286
+ code: '__catalog-default__-sku',
287
+ productId: catalog.productId,
288
+ priceCents: 2000,
289
+ lifecycle: 'draft',
290
+ },
291
+ }),
292
+ );
293
+ } finally {
294
+ await app.bean.passport.signout();
295
+ }
296
+ });
297
+
298
+ await app.bean.executor.mockCtx(
299
+ async () => {
300
+ const catalog = await createCatalog('__catalog-share__', 'active');
301
+ await app.bean.passport.signinMock();
302
+ try {
303
+ const duplicateCodeSkuId = await app.bean.executor.performAction(
304
+ 'post',
305
+ '/commerce/catalog/sku',
306
+ {
307
+ body: {
308
+ code: '__catalog-default__-sku',
309
+ productId: catalog.productId,
310
+ priceCents: 2500,
311
+ lifecycle: 'active',
312
+ },
313
+ },
314
+ );
315
+ assert.equal(!!duplicateCodeSkuId, true);
316
+ } finally {
317
+ await app.bean.passport.signout();
318
+ }
319
+
320
+ await app.scope('commerce-trade').service.stockBalance.adjustStock({
321
+ skuId: catalog.skuId,
322
+ delta: 1,
323
+ reason: 'catalog tenant fixture',
324
+ correlationId: `catalog-tenant-${catalog.skuId}`,
325
+ });
326
+ const publicProducts = await app.bean.executor.performAction(
327
+ 'get',
328
+ '/commerce/catalog/product/public',
329
+ {
330
+ innerAccess: false,
331
+ },
332
+ );
333
+ assert.equal(
334
+ publicProducts.list.some(
335
+ (product: any) => String(product.id) === String(defaultProductId),
336
+ ),
337
+ false,
338
+ );
339
+ assert.equal(
340
+ publicProducts.list.some((product: any) => product.title === '__catalog-share__ product'),
341
+ true,
342
+ );
343
+ const foreignProduct = await app.bean.executor.performAction(
344
+ 'get',
345
+ '/commerce/catalog/product/public/:id',
346
+ { params: { id: defaultProductId }, innerAccess: false },
347
+ );
348
+ assert.equal(foreignProduct, undefined);
349
+ const publicProduct = await app.bean.executor.performAction(
350
+ 'get',
351
+ '/commerce/catalog/product/public/:id',
352
+ { params: { id: catalog.productId }, innerAccess: false },
353
+ );
354
+ assert.equal(String(publicProduct.id), String(catalog.productId));
355
+ },
356
+ { instanceName: 'shareTest' as any },
357
+ );
358
+ });
359
+
360
+ it('treats foreign operator catalogue resources as absent', async () => {
361
+ const prefix = `__catalog-foreign-${Date.now()}__`;
362
+ let defaultCatalog!: Awaited<ReturnType<typeof createCatalog>>;
363
+ await app.bean.executor.mockCtx(async () => {
364
+ defaultCatalog = await createCatalog(prefix, 'active');
365
+ });
366
+
367
+ await app.bean.executor.mockCtx(
368
+ async () => {
369
+ await app.bean.passport.signinMock();
370
+ try {
371
+ const resources = [
372
+ ['category', defaultCatalog.categoryId, { name: 'foreign category', published: false }],
373
+ [
374
+ 'product',
375
+ defaultCatalog.productId,
376
+ {
377
+ categoryId: defaultCatalog.categoryId,
378
+ title: 'foreign product',
379
+ published: false,
380
+ },
381
+ ],
382
+ [
383
+ 'sku',
384
+ defaultCatalog.skuId,
385
+ {
386
+ productId: defaultCatalog.productId,
387
+ code: 'foreign-sku',
388
+ priceCents: 100,
389
+ lifecycle: 'inactive',
390
+ },
391
+ ],
392
+ ] as const;
393
+ for (const [resource, id, body] of resources) {
394
+ assert.equal(
395
+ await app.bean.executor.performAction('get', `/commerce/catalog/${resource}/:id`, {
396
+ params: { id },
397
+ }),
398
+ undefined,
399
+ );
400
+ const selected = await app.bean.executor.performAction(
401
+ 'get',
402
+ `/commerce/catalog/${resource}`,
403
+ );
404
+ assert.equal(
405
+ selected.list.some((item: { id: unknown }) => String(item.id) === String(id)),
406
+ false,
407
+ );
408
+ const update = () =>
409
+ app.bean.executor.performAction('patch', `/commerce/catalog/${resource}/:id`, {
410
+ params: { id },
411
+ body,
412
+ });
413
+ if (resource === 'category') {
414
+ assert.equal(await update(), null);
415
+ } else {
416
+ await assert.rejects(update);
417
+ }
418
+ assert.equal(
419
+ await app.bean.executor.performAction('delete', `/commerce/catalog/${resource}/:id`, {
420
+ params: { id },
421
+ }),
422
+ null,
423
+ );
424
+ }
425
+ } finally {
426
+ await app.bean.passport.signout();
427
+ }
428
+ },
429
+ { instanceName: 'shareTest' as any },
430
+ );
431
+
432
+ await app.bean.executor.mockCtx(async () => {
433
+ const category = await app
434
+ .scope('commerce-catalog')
435
+ .model.category.getById(defaultCatalog.categoryId);
436
+ const product = await app
437
+ .scope('commerce-catalog')
438
+ .model.product.getById(defaultCatalog.productId);
439
+ const sku = await app.scope('commerce-catalog').model.sku.getById(defaultCatalog.skuId);
440
+ assert.equal(category?.name, `${prefix} category`);
441
+ assert.equal(product?.title, `${prefix} product`);
442
+ assert.equal(sku?.code, `${prefix}-sku`);
443
+ });
444
+ });
445
+
446
+ it('rejects foreign catalogue relation identifiers without local writes', async () => {
447
+ const suffix = `${Date.now()}`;
448
+ let foreignCatalog!: Awaited<ReturnType<typeof createCatalog>>;
449
+ await app.bean.executor.mockCtx(
450
+ async () => {
451
+ foreignCatalog = await createCatalog(`__catalog-relation-foreign-${suffix}__`, 'active');
452
+ },
453
+ { instanceName: 'shareTest' as any },
454
+ );
455
+
456
+ await app.bean.executor.mockCtx(async () => {
457
+ await app.bean.passport.signinMock();
458
+ try {
459
+ const parentCategoryId = await app.bean.executor.performAction(
460
+ 'post',
461
+ '/commerce/catalog/category',
462
+ { body: { name: `relation-parent-${suffix}`, published: true } },
463
+ );
464
+ const categoryId = await app.bean.executor.performAction(
465
+ 'post',
466
+ '/commerce/catalog/category',
467
+ {
468
+ body: {
469
+ name: `relation-category-${suffix}`,
470
+ parentId: parentCategoryId,
471
+ published: true,
472
+ },
473
+ },
474
+ );
475
+ const productId = await app.bean.executor.performAction(
476
+ 'post',
477
+ '/commerce/catalog/product',
478
+ {
479
+ body: {
480
+ categoryId,
481
+ title: `relation-product-${suffix}`,
482
+ published: true,
483
+ },
484
+ },
485
+ );
486
+ const skuId = await app.bean.executor.performAction('post', '/commerce/catalog/sku', {
487
+ body: {
488
+ productId,
489
+ code: `relation-sku-${suffix}`,
490
+ priceCents: 100,
491
+ lifecycle: 'draft',
492
+ },
493
+ });
494
+ const operations = [
495
+ () =>
496
+ app.bean.executor.performAction('post', '/commerce/catalog/category', {
497
+ body: {
498
+ name: `foreign-parent-category-${suffix}`,
499
+ parentId: foreignCatalog.categoryId,
500
+ published: true,
501
+ },
502
+ }),
503
+ () =>
504
+ app.bean.executor.performAction('patch', '/commerce/catalog/category/:id', {
505
+ params: { id: categoryId },
506
+ body: {
507
+ name: `relation-category-updated-${suffix}`,
508
+ parentId: foreignCatalog.categoryId,
509
+ published: false,
510
+ },
511
+ }),
512
+ () =>
513
+ app.bean.executor.performAction('post', '/commerce/catalog/product', {
514
+ body: {
515
+ categoryId: foreignCatalog.categoryId,
516
+ title: `foreign-category-product-${suffix}`,
517
+ published: true,
518
+ },
519
+ }),
520
+ () =>
521
+ app.bean.executor.performAction('patch', '/commerce/catalog/product/:id', {
522
+ params: { id: productId },
523
+ body: {
524
+ categoryId: foreignCatalog.categoryId,
525
+ title: `relation-product-updated-${suffix}`,
526
+ published: false,
527
+ },
528
+ }),
529
+ () =>
530
+ app.bean.executor.performAction('post', '/commerce/catalog/sku', {
531
+ body: {
532
+ productId: foreignCatalog.productId,
533
+ code: `foreign-product-sku-${suffix}`,
534
+ priceCents: 100,
535
+ lifecycle: 'draft',
536
+ },
537
+ }),
538
+ () =>
539
+ app.bean.executor.performAction('patch', '/commerce/catalog/sku/:id', {
540
+ params: { id: skuId },
541
+ body: {
542
+ productId: foreignCatalog.productId,
543
+ code: `relation-sku-updated-${suffix}`,
544
+ priceCents: 200,
545
+ lifecycle: 'active',
546
+ },
547
+ }),
548
+ ];
549
+ for (const operation of operations) {
550
+ const [_, err] = await catchError(operation);
551
+ assert.equal(err?.code, 404);
552
+ }
553
+
554
+ const category = await app.scope('commerce-catalog').model.category.getById(categoryId);
555
+ const product = await app.scope('commerce-catalog').model.product.getById(productId);
556
+ const sku = await app.scope('commerce-catalog').model.sku.getById(skuId);
557
+ assert.deepEqual(
558
+ [category?.name, category?.parentId, category?.published],
559
+ [`relation-category-${suffix}`, parentCategoryId, true],
560
+ );
561
+ assert.deepEqual(
562
+ [product?.categoryId, product?.title, product?.published],
563
+ [categoryId, `relation-product-${suffix}`, true],
564
+ );
565
+ assert.deepEqual(
566
+ [sku?.productId, sku?.code, sku?.priceCents, sku?.lifecycle],
567
+ [productId, `relation-sku-${suffix}`, 100, 'draft'],
568
+ );
569
+ assert.equal(
570
+ await app.scope('commerce-catalog').model.category.get({
571
+ name: `foreign-parent-category-${suffix}`,
572
+ }),
573
+ undefined,
574
+ );
575
+ assert.equal(
576
+ await app.scope('commerce-catalog').model.product.get({
577
+ title: `foreign-category-product-${suffix}`,
578
+ }),
579
+ undefined,
580
+ );
581
+ assert.equal(
582
+ await app.scope('commerce-catalog').model.sku.get({
583
+ code: `foreign-product-sku-${suffix}`,
584
+ }),
585
+ undefined,
586
+ );
587
+ } finally {
588
+ await app.bean.passport.signout();
589
+ }
590
+ });
591
+ });
592
+ });
@@ -0,0 +1,91 @@
1
+ import type {
2
+ DtoCategoryCreate,
3
+ DtoCategorySelectRes,
4
+ DtoCategoryUpdate,
5
+ EntityCategory,
6
+ } from 'vona-module-commerce-catalog';
7
+
8
+ import assert from 'node:assert';
9
+ import { describe, it } from 'node:test';
10
+ import { app } from 'vona-mock';
11
+
12
+ describe('category.test.ts', () => {
13
+ it('action:category', async () => {
14
+ await app.bean.executor.mockCtx(async () => {
15
+ // data
16
+ const data: DtoCategoryCreate = {
17
+ name: '__Tom__',
18
+ published: false,
19
+ description: 'This is a test',
20
+ };
21
+ const dataUpdate: DtoCategoryUpdate = {
22
+ name: '__TomNew__',
23
+ published: true,
24
+ description: 'This is a test',
25
+ };
26
+ // role-less authenticated users cannot access generated admin actions
27
+ await app.bean.passport.signinMock();
28
+ try {
29
+ app.bean.passport.current!.roles = [];
30
+ const actions = ['create', 'select', 'view', 'update', 'delete'];
31
+ const permissions = await Promise.all(
32
+ actions.map(action =>
33
+ app.bean.permission.retrievePermissionAction('commerce-catalog:category', action),
34
+ ),
35
+ );
36
+ assert.deepEqual(
37
+ permissions,
38
+ actions.map(() => false),
39
+ );
40
+ } finally {
41
+ await app.bean.passport.signout();
42
+ }
43
+ // login as system admin
44
+ await app.bean.passport.signinMock();
45
+ // create
46
+ const categoryId = await app.bean.executor.performAction(
47
+ 'post',
48
+ '/commerce/catalog/category',
49
+ { body: data },
50
+ );
51
+ assert.equal(!!categoryId, true);
52
+ // findMany
53
+ const selectRes: DtoCategorySelectRes = await app.bean.executor.performAction(
54
+ 'get',
55
+ '/commerce/catalog/category',
56
+ );
57
+ assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
58
+ // update
59
+ const updateRes = await app.bean.executor.performAction(
60
+ 'patch',
61
+ '/commerce/catalog/category/:id',
62
+ {
63
+ params: { id: categoryId },
64
+ body: dataUpdate,
65
+ },
66
+ );
67
+ assert.equal(updateRes, null);
68
+ // findOne
69
+ let category: EntityCategory = await app.bean.executor.performAction(
70
+ 'get',
71
+ '/commerce/catalog/category/:id',
72
+ { params: { id: categoryId } },
73
+ );
74
+ assert.equal(category.name, dataUpdate.name);
75
+ // delete
76
+ const deleteRes = await app.bean.executor.performAction(
77
+ 'delete',
78
+ '/commerce/catalog/category/:id',
79
+ { params: { id: category.id } },
80
+ );
81
+ assert.equal(deleteRes, null);
82
+ // findOne
83
+ category = await app.bean.executor.performAction('get', '/commerce/catalog/category/:id', {
84
+ params: { id: category.id },
85
+ });
86
+ assert.equal(category, undefined);
87
+ // logout
88
+ await app.bean.passport.signout();
89
+ });
90
+ });
91
+ });