@youzan-cloud/zan-saas-cli 0.0.0-stage → 0.1.0-beta.3

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 (329) hide show
  1. package/DEPENDENCIES.json +50 -0
  2. package/INSTALL.md +79 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE +1 -0
  5. package/README.md +151 -2
  6. package/THIRD_PARTY_NOTICES.txt +162 -0
  7. package/bin/zan-saas-cli.js +28 -0
  8. package/internal/registry/index.js +123 -0
  9. package/internal/registry/manifest.json +424 -0
  10. package/npm-shrinkwrap.json +86 -0
  11. package/package.json +33 -4
  12. package/skills/README.md +30 -0
  13. package/skills/zan-saas/SKILL.md +54 -0
  14. package/skills/zan-saas/references/host-entry.md +25 -0
  15. package/skills/zan-saas-access/SKILL.md +37 -0
  16. package/skills/zan-saas-access/references/role-list.md +11 -0
  17. package/skills/zan-saas-access/references/staff-perms-get.md +11 -0
  18. package/skills/zan-saas-aftersale/SKILL.md +31 -0
  19. package/skills/zan-saas-aftersale/references/case-list.md +13 -0
  20. package/skills/zan-saas-aftersale/references/case-scan.md +31 -0
  21. package/skills/zan-saas-benefit/SKILL.md +46 -0
  22. package/skills/zan-saas-benefit/references/card-get.md +7 -0
  23. package/skills/zan-saas-benefit/references/card-grant-list.md +11 -0
  24. package/skills/zan-saas-benefit/references/card-list.md +7 -0
  25. package/skills/zan-saas-benefit/references/customer-card-get.md +7 -0
  26. package/skills/zan-saas-benefit/references/customer-card-list.md +7 -0
  27. package/skills/zan-saas-coupon/SKILL.md +41 -0
  28. package/skills/zan-saas-coupon/references/activity-get.md +11 -0
  29. package/skills/zan-saas-coupon/references/activity-list.md +15 -0
  30. package/skills/zan-saas-coupon/references/voucher-get.md +13 -0
  31. package/skills/zan-saas-coupon/references/voucher-list.md +25 -0
  32. package/skills/zan-saas-customer/SKILL.md +73 -0
  33. package/skills/zan-saas-customer/references/customer-scan.md +11 -0
  34. package/skills/zan-saas-customer/references/segment-list.md +22 -0
  35. package/skills/zan-saas-fulfillment/SKILL.md +36 -0
  36. package/skills/zan-saas-fulfillment/references/delivery-get.md +11 -0
  37. package/skills/zan-saas-fulfillment/references/shipment-list.md +11 -0
  38. package/skills/zan-saas-fulfillment/references/waybill-get.md +9 -0
  39. package/skills/zan-saas-interaction/SKILL.md +37 -0
  40. package/skills/zan-saas-interaction/references/lottery-get.md +11 -0
  41. package/skills/zan-saas-interaction/references/lottery-list.md +19 -0
  42. package/skills/zan-saas-item/SKILL.md +61 -0
  43. package/skills/zan-saas-item/references/attribute-list.md +11 -0
  44. package/skills/zan-saas-item/references/brand-list.md +7 -0
  45. package/skills/zan-saas-item/references/classification-get.md +7 -0
  46. package/skills/zan-saas-item/references/classification-list.md +7 -0
  47. package/skills/zan-saas-item/references/group-list.md +9 -0
  48. package/skills/zan-saas-item/references/item-content.md +9 -0
  49. package/skills/zan-saas-item/references/item-get.md +9 -0
  50. package/skills/zan-saas-item/references/item-search.md +23 -0
  51. package/skills/zan-saas-item/references/product-identity.md +11 -0
  52. package/skills/zan-saas-member/SKILL.md +41 -0
  53. package/skills/zan-saas-member/references/customer-level-get.md +7 -0
  54. package/skills/zan-saas-member/references/growth-log-list.md +17 -0
  55. package/skills/zan-saas-member/references/membership-get.md +7 -0
  56. package/skills/zan-saas-order/SKILL.md +56 -0
  57. package/skills/zan-saas-order/references/order-get.md +26 -0
  58. package/skills/zan-saas-order/references/order-list.md +25 -0
  59. package/skills/zan-saas-order/references/order-scan.md +35 -0
  60. package/skills/zan-saas-org/SKILL.md +37 -0
  61. package/skills/zan-saas-org/references/staff-get.md +15 -0
  62. package/skills/zan-saas-org/references/staff-role-get.md +11 -0
  63. package/skills/zan-saas-org/references/unit-get.md +11 -0
  64. package/skills/zan-saas-org/references/unit-list.md +15 -0
  65. package/skills/zan-saas-points/SKILL.md +42 -0
  66. package/skills/zan-saas-points/references/account-get.md +7 -0
  67. package/skills/zan-saas-points/references/log-list.md +17 -0
  68. package/skills/zan-saas-points/references/rule-list.md +2 -0
  69. package/skills/zan-saas-privacy/SKILL.md +30 -0
  70. package/skills/zan-saas-privacy/references/field-decrypt.md +19 -0
  71. package/skills/zan-saas-shared/SKILL.md +94 -0
  72. package/skills/zan-saas-shared/references/authentication.md +52 -0
  73. package/skills/zan-saas-shared/references/value-processing.md +27 -0
  74. package/skills/zan-saas-stock/SKILL.md +28 -0
  75. package/skills/zan-saas-stock/references/log-list.md +11 -0
  76. package/skills/zan-saas-valuecard/SKILL.md +37 -0
  77. package/skills/zan-saas-valuecard/references/card-get.md +13 -0
  78. package/skills/zan-saas-valuecard/references/card-list.md +11 -0
  79. package/src/auth/browser.js +28 -0
  80. package/src/auth/callback-server.js +99 -0
  81. package/src/auth/config.js +53 -0
  82. package/src/auth/credential-store.js +109 -0
  83. package/src/auth/oauth-client.js +105 -0
  84. package/src/auth/pkce.js +28 -0
  85. package/src/auth/session.js +178 -0
  86. package/src/auth/tool.js +25 -0
  87. package/src/cli/command-paths.js +126 -0
  88. package/src/cli/context-help.js +102 -0
  89. package/src/cli/diagnostic-command.js +57 -0
  90. package/src/cli/domain-catalog.js +42 -0
  91. package/src/cli/help-text.js +32 -0
  92. package/src/cli/native-loader.js +120 -0
  93. package/src/cli/native-path.js +22 -0
  94. package/src/cli/parser-policy.js +141 -0
  95. package/src/cli/program.js +134 -0
  96. package/src/cli/skill-catalog.js +241 -0
  97. package/src/commands/native/access/commands.index.json +24 -0
  98. package/src/commands/native/access/domain.json +3 -0
  99. package/src/commands/native/access/role/list/command.js +141 -0
  100. package/src/commands/native/access/role/list/schema.json +393 -0
  101. package/src/commands/native/access/staff-perms/get/command.js +119 -0
  102. package/src/commands/native/access/staff-perms/get/schema.json +703 -0
  103. package/src/commands/native/aftersale/case/get/command.js +104 -0
  104. package/src/commands/native/aftersale/case/get/exact-json.js +52 -0
  105. package/src/commands/native/aftersale/case/get/money.js +28 -0
  106. package/src/commands/native/aftersale/case/get/projection.js +120 -0
  107. package/src/commands/native/aftersale/case/get/query.js +72 -0
  108. package/src/commands/native/aftersale/case/get/schema.json +134 -0
  109. package/src/commands/native/aftersale/case/list/command.js +135 -0
  110. package/src/commands/native/aftersale/case/list/schema.json +1095 -0
  111. package/src/commands/native/aftersale/case/scan/command.js +110 -0
  112. package/src/commands/native/aftersale/case/scan/query.js +62 -0
  113. package/src/commands/native/aftersale/case/scan/scan.js +78 -0
  114. package/src/commands/native/aftersale/case/scan/schema.json +964 -0
  115. package/src/commands/native/aftersale/case/scan/search.js +108 -0
  116. package/src/commands/native/aftersale/commands.index.json +42 -0
  117. package/src/commands/native/aftersale/domain.json +3 -0
  118. package/src/commands/native/aftersale/refund/list/command.js +97 -0
  119. package/src/commands/native/aftersale/refund/list/money.js +25 -0
  120. package/src/commands/native/aftersale/refund/list/projection.js +50 -0
  121. package/src/commands/native/aftersale/refund/list/query.js +73 -0
  122. package/src/commands/native/aftersale/refund/list/schema.json +130 -0
  123. package/src/commands/native/aftersale/shared/query.js +58 -0
  124. package/src/commands/native/aftersale/shared/search.js +128 -0
  125. package/src/commands/native/benefit/card-definition/get/command.js +110 -0
  126. package/src/commands/native/benefit/card-definition/get/schema.json +545 -0
  127. package/src/commands/native/benefit/card-definition/list/command.js +105 -0
  128. package/src/commands/native/benefit/card-definition/list/schema.json +690 -0
  129. package/src/commands/native/benefit/card-grant/list/command.js +135 -0
  130. package/src/commands/native/benefit/card-grant/list/schema.json +758 -0
  131. package/src/commands/native/benefit/commands.index.json +51 -0
  132. package/src/commands/native/benefit/customer-card/get/command.js +110 -0
  133. package/src/commands/native/benefit/customer-card/get/schema.json +340 -0
  134. package/src/commands/native/benefit/customer-card/list/command.js +102 -0
  135. package/src/commands/native/benefit/customer-card/list/schema.json +731 -0
  136. package/src/commands/native/benefit/domain.json +3 -0
  137. package/src/commands/native/coupon/activity/get/command.js +183 -0
  138. package/src/commands/native/coupon/activity/get/schema.json +624 -0
  139. package/src/commands/native/coupon/activity/list/command.js +169 -0
  140. package/src/commands/native/coupon/activity/list/schema.json +845 -0
  141. package/src/commands/native/coupon/commands.index.json +42 -0
  142. package/src/commands/native/coupon/domain.json +3 -0
  143. package/src/commands/native/coupon/shared/query.js +15 -0
  144. package/src/commands/native/coupon/voucher/get/command.js +113 -0
  145. package/src/commands/native/coupon/voucher/get/schema.json +147 -0
  146. package/src/commands/native/coupon/voucher/list/command.js +155 -0
  147. package/src/commands/native/coupon/voucher/list/schema.json +772 -0
  148. package/src/commands/native/customer/commands.index.json +96 -0
  149. package/src/commands/native/customer/customer/get/adapter.js +82 -0
  150. package/src/commands/native/customer/customer/get/command.js +106 -0
  151. package/src/commands/native/customer/customer/get/schema.json +958 -0
  152. package/src/commands/native/customer/customer/list/command.js +78 -0
  153. package/src/commands/native/customer/customer/list/input.js +145 -0
  154. package/src/commands/native/customer/customer/list/query.js +103 -0
  155. package/src/commands/native/customer/customer/list/schema.json +969 -0
  156. package/src/commands/native/customer/customer/scan/command.js +80 -0
  157. package/src/commands/native/customer/customer/scan/scan.js +92 -0
  158. package/src/commands/native/customer/customer/scan/schema.json +211 -0
  159. package/src/commands/native/customer/customer-tag/get/command.js +166 -0
  160. package/src/commands/native/customer/customer-tag/get/schema.json +317 -0
  161. package/src/commands/native/customer/customer-tag/list/command.js +143 -0
  162. package/src/commands/native/customer/customer-tag/list/schema.json +259 -0
  163. package/src/commands/native/customer/domain.json +3 -0
  164. package/src/commands/native/customer/segment/list/command.js +96 -0
  165. package/src/commands/native/customer/segment/list/query.js +98 -0
  166. package/src/commands/native/customer/segment/list/schema.json +135 -0
  167. package/src/commands/native/customer/segment-customer/list/command.js +171 -0
  168. package/src/commands/native/customer/segment-customer/list/schema.json +123 -0
  169. package/src/commands/native/customer/shared/search-input.js +179 -0
  170. package/src/commands/native/customer/shared/search-page.js +54 -0
  171. package/src/commands/native/customer/tag/get/command.js +73 -0
  172. package/src/commands/native/customer/tag/get/schema.json +339 -0
  173. package/src/commands/native/customer/tagcategory/get/command.js +80 -0
  174. package/src/commands/native/customer/tagcategory/get/query.js +98 -0
  175. package/src/commands/native/customer/tagcategory/get/schema.json +339 -0
  176. package/src/commands/native/customer/tagcategory/list/command.js +127 -0
  177. package/src/commands/native/customer/tagcategory/list/schema.json +394 -0
  178. package/src/commands/native/fulfillment/commands.index.json +33 -0
  179. package/src/commands/native/fulfillment/delivery/get/command.js +107 -0
  180. package/src/commands/native/fulfillment/delivery/get/projection.js +142 -0
  181. package/src/commands/native/fulfillment/delivery/get/schema.json +173 -0
  182. package/src/commands/native/fulfillment/domain.json +3 -0
  183. package/src/commands/native/fulfillment/shipment/list/command.js +83 -0
  184. package/src/commands/native/fulfillment/shipment/list/projection.js +130 -0
  185. package/src/commands/native/fulfillment/shipment/list/schema.json +752 -0
  186. package/src/commands/native/fulfillment/waybill/get/command.js +193 -0
  187. package/src/commands/native/fulfillment/waybill/get/response.js +68 -0
  188. package/src/commands/native/fulfillment/waybill/get/schema.json +190 -0
  189. package/src/commands/native/interaction/commands.index.json +24 -0
  190. package/src/commands/native/interaction/domain.json +3 -0
  191. package/src/commands/native/interaction/lottery/get/command.js +61 -0
  192. package/src/commands/native/interaction/lottery/get/query.js +152 -0
  193. package/src/commands/native/interaction/lottery/get/schema.json +1004 -0
  194. package/src/commands/native/interaction/lottery/list/command.js +176 -0
  195. package/src/commands/native/interaction/lottery/list/schema.json +898 -0
  196. package/src/commands/native/item/attribute/list/command.js +122 -0
  197. package/src/commands/native/item/attribute/list/schema.json +727 -0
  198. package/src/commands/native/item/brand/list/command.js +114 -0
  199. package/src/commands/native/item/brand/list/schema.json +725 -0
  200. package/src/commands/native/item/classification/get/command.js +87 -0
  201. package/src/commands/native/item/classification/get/schema.json +596 -0
  202. package/src/commands/native/item/classification/list/command.js +105 -0
  203. package/src/commands/native/item/classification/list/schema.json +676 -0
  204. package/src/commands/native/item/commands.index.json +87 -0
  205. package/src/commands/native/item/domain.json +1 -0
  206. package/src/commands/native/item/group/list/command.js +122 -0
  207. package/src/commands/native/item/group/list/schema.json +809 -0
  208. package/src/commands/native/item/item/get/command.js +122 -0
  209. package/src/commands/native/item/item/get/schema.json +3799 -0
  210. package/src/commands/native/item/item/list/command.js +7 -0
  211. package/src/commands/native/item/item/list/schema.json +1118 -0
  212. package/src/commands/native/item/item/scan/command.js +7 -0
  213. package/src/commands/native/item/item/scan/schema.json +1046 -0
  214. package/src/commands/native/item/item-content/get/command.js +54 -0
  215. package/src/commands/native/item/item-content/get/schema.json +677 -0
  216. package/src/commands/native/item/shared/api.js +31 -0
  217. package/src/commands/native/item/shared/content.js +20 -0
  218. package/src/commands/native/item/shared/diagnostics.js +61 -0
  219. package/src/commands/native/item/shared/h5.js +63 -0
  220. package/src/commands/native/item/shared/identity.js +62 -0
  221. package/src/commands/native/item/shared/normalize.js +49 -0
  222. package/src/commands/native/item/shared/scope.js +22 -0
  223. package/src/commands/native/item/shared/search-command.js +61 -0
  224. package/src/commands/native/item/shared/search-options.js +150 -0
  225. package/src/commands/native/item/shared/search.js +158 -0
  226. package/src/commands/native/member/commands.index.json +60 -0
  227. package/src/commands/native/member/customer-level/get/command.js +122 -0
  228. package/src/commands/native/member/customer-level/get/schema.json +300 -0
  229. package/src/commands/native/member/domain.json +3 -0
  230. package/src/commands/native/member/growth/get/command.js +64 -0
  231. package/src/commands/native/member/growth/get/schema.json +283 -0
  232. package/src/commands/native/member/growth-log/list/command.js +203 -0
  233. package/src/commands/native/member/growth-log/list/schema.json +695 -0
  234. package/src/commands/native/member/level/get/command.js +70 -0
  235. package/src/commands/native/member/level/get/query.js +125 -0
  236. package/src/commands/native/member/level/get/schema.json +339 -0
  237. package/src/commands/native/member/level/list/command.js +50 -0
  238. package/src/commands/native/member/level/list/query.js +109 -0
  239. package/src/commands/native/member/level/list/schema.json +104 -0
  240. package/src/commands/native/member/membership/get/command.js +78 -0
  241. package/src/commands/native/member/membership/get/schema.json +283 -0
  242. package/src/commands/native/order/commands.index.json +33 -0
  243. package/src/commands/native/order/domain.json +3 -0
  244. package/src/commands/native/order/order/get/command.js +155 -0
  245. package/src/commands/native/order/order/get/schema.json +1167 -0
  246. package/src/commands/native/order/order/list/command.js +230 -0
  247. package/src/commands/native/order/order/list/schema.json +1363 -0
  248. package/src/commands/native/order/order/scan/command.js +148 -0
  249. package/src/commands/native/order/order/scan/scan.js +89 -0
  250. package/src/commands/native/order/order/scan/schema.json +1445 -0
  251. package/src/commands/native/order/shared/projection.js +183 -0
  252. package/src/commands/native/order/shared/query.js +84 -0
  253. package/src/commands/native/order/shared/search.js +112 -0
  254. package/src/commands/native/org/commands.index.json +42 -0
  255. package/src/commands/native/org/domain.json +3 -0
  256. package/src/commands/native/org/shared/single-staff.js +115 -0
  257. package/src/commands/native/org/staff/get/command.js +148 -0
  258. package/src/commands/native/org/staff/get/schema.json +114 -0
  259. package/src/commands/native/org/staff-role/get/command.js +45 -0
  260. package/src/commands/native/org/staff-role/get/schema.json +93 -0
  261. package/src/commands/native/org/unit/get/command.js +131 -0
  262. package/src/commands/native/org/unit/get/schema.json +478 -0
  263. package/src/commands/native/org/unit/list/command.js +187 -0
  264. package/src/commands/native/org/unit/list/schema.json +828 -0
  265. package/src/commands/native/points/account/get/command.js +65 -0
  266. package/src/commands/native/points/account/get/schema.json +303 -0
  267. package/src/commands/native/points/commands.index.json +33 -0
  268. package/src/commands/native/points/domain.json +3 -0
  269. package/src/commands/native/points/log/list/command.js +141 -0
  270. package/src/commands/native/points/log/list/schema.json +761 -0
  271. package/src/commands/native/points/rule/list/command.js +82 -0
  272. package/src/commands/native/points/rule/list/schema.json +294 -0
  273. package/src/commands/native/privacy/commands.index.json +15 -0
  274. package/src/commands/native/privacy/domain.json +3 -0
  275. package/src/commands/native/privacy/field/decrypt/command.js +185 -0
  276. package/src/commands/native/privacy/field/decrypt/schema.json +656 -0
  277. package/src/commands/native/privacy/shared/order-source.js +33 -0
  278. package/src/commands/native/stock/commands.index.json +15 -0
  279. package/src/commands/native/stock/domain.json +3 -0
  280. package/src/commands/native/stock/log/list/command.js +207 -0
  281. package/src/commands/native/stock/log/list/schema.json +922 -0
  282. package/src/commands/native/valuecard/card/get/command.js +107 -0
  283. package/src/commands/native/valuecard/card/get/schema.json +683 -0
  284. package/src/commands/native/valuecard/card/list/command.js +136 -0
  285. package/src/commands/native/valuecard/card/list/pagination.js +23 -0
  286. package/src/commands/native/valuecard/card/list/schema.json +780 -0
  287. package/src/commands/native/valuecard/commands.index.json +24 -0
  288. package/src/commands/native/valuecard/domain.json +3 -0
  289. package/src/commands/native/valuecard/shared/projection.js +57 -0
  290. package/src/commands/system/api.js +47 -0
  291. package/src/commands/system/auth.js +118 -0
  292. package/src/commands/system/contracts.js +373 -0
  293. package/src/commands/system/doctor.js +24 -0
  294. package/src/commands/system/install.js +41 -0
  295. package/src/commands/system/printer.js +16 -0
  296. package/src/commands/system/schema.js +162 -0
  297. package/src/commands/system/shop-guidance.js +9 -0
  298. package/src/commands/system/shop.js +237 -0
  299. package/src/commands/system/skills.js +49 -0
  300. package/src/execution/beijing-date-time.js +29 -0
  301. package/src/execution/beijing-time.js +21 -0
  302. package/src/execution/bounded-input.js +42 -0
  303. package/src/execution/command-io.js +137 -0
  304. package/src/execution/command-policy.js +41 -0
  305. package/src/execution/command-schema.js +131 -0
  306. package/src/execution/datetime.js +83 -0
  307. package/src/execution/error-details.js +399 -0
  308. package/src/execution/error-model.js +82 -0
  309. package/src/execution/exact-json.js +80 -0
  310. package/src/execution/list-total.js +10 -0
  311. package/src/execution/money.js +59 -0
  312. package/src/execution/parser-error.js +66 -0
  313. package/src/execution/privacy.js +197 -0
  314. package/src/execution/public-schema.js +52 -0
  315. package/src/execution/query-api.js +101 -0
  316. package/src/execution/range.js +84 -0
  317. package/src/execution/request-state.js +55 -0
  318. package/src/execution/schema-completeness.js +116 -0
  319. package/src/execution/shop-query.js +54 -0
  320. package/src/execution/target-shop-default.js +23 -0
  321. package/src/execution/target-shop.js +49 -0
  322. package/src/identity/context-store.js +54 -0
  323. package/src/identity/context.js +38 -0
  324. package/src/installation/install.js +158 -0
  325. package/src/installation/npm-error.js +93 -0
  326. package/src/installation/skill.js +113 -0
  327. package/src/transport/account-api.js +152 -0
  328. package/src/transport/api.js +199 -0
  329. package/src/transport/shop-app.js +63 -0
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ const { readTotal } = require('../../../../../execution/list-total');
4
+
5
+ const { withTargetShopDefault } = require('../../../../../execution/target-shop-default');
6
+
7
+ const { Command, Option } = require('commander');
8
+ const { resolveTargetShop } = require('../../../../../execution/target-shop');
9
+ const { createBoundShopQuery, bindingPath } = require('../../../../../execution/shop-query');
10
+ const { QueryError, object } = require('../../../../../execution/query-api');
11
+ const { validateCommandValue } = require('../../../../../execution/command-schema');
12
+ const { normalizeMoney } = require('../../../../../execution/money');
13
+ const { normalizeTimes } = require('../../../../../execution/datetime');
14
+ const schema = require('./schema.json');
15
+
16
+ const command = 'customer.segment-customer.list';
17
+ const nextStep = '核对原店铺、分群 ID、页码和服务端授权;需要时稍后主动重查,不自动切换身份、店铺或重放。';
18
+ const responseLimit = 2 * 1024 * 1024;
19
+ const outputLimit = 5 * 1024 * 1024;
20
+ const fields = {
21
+ yzOpenId: 'yz_open_id', mobileCountryCode: 'mobile_country_code', mobile: 'mobile',
22
+ nickName: 'nick_name', gender: 'gender', freeLevelName: 'free_level_name',
23
+ payLevelName: 'pay_level_name', cardNames: 'card_names', points: 'points',
24
+ storedBalance: 'stored_balance', allChannelTotalTradeCount: 'all_channel_total_trade_count',
25
+ allChannelTotalTradeAmount: 'all_channel_total_trade_amount',
26
+ allChannelLastTradeAt: 'all_channel_last_trade_time'
27
+ };
28
+
29
+ function failure(category, stage, scope = {}, state = 'not_sent', facts = {}) {
30
+ const safeFacts = {};
31
+ for (const name of ['api', 'httpStatus', 'businessCode', 'trace_id', 'request_id']) {
32
+ if (Object.hasOwn(facts, name)) safeFacts[name] = facts[name];
33
+ }
34
+ return new QueryError(category, stage, scope, safeFacts, state, command, nextStep);
35
+ }
36
+
37
+ function integer(text) {
38
+ if (typeof text !== 'string' || !/^[1-9]\d*$/.test(text) ||
39
+ !Number.isSafeInteger(Number(text))) throw new Error('invalid integer');
40
+ return Number(text);
41
+ }
42
+
43
+ function buildInput(options, context) {
44
+ let scope;
45
+ try { scope = resolveTargetShop(options, context, command); }
46
+ catch (error) {
47
+ const details = error.cliDetails;
48
+ throw failure(details.category, details.stage === 'validation' ? 'input' : details.stage,
49
+ details.targetScope);
50
+ }
51
+ try {
52
+ if (typeof options.segmentId !== 'string' || !/^[1-9]\d{0,18}$/.test(options.segmentId) ||
53
+ BigInt(options.segmentId) > 9223372036854775807n) throw new Error('invalid segment');
54
+ if (!['json', 'pretty'].includes(options.format)) throw new Error('invalid format');
55
+ const input = { ...scope, segmentId: options.segmentId,
56
+ pageNo: integer(options.pageNo), pageSize: integer(options.pageSize) };
57
+ validateCommandValue(schema, 'input', input);
58
+ return input;
59
+ } catch { throw failure('validation', 'input', scope); }
60
+ }
61
+
62
+ function project(data, input, facts) {
63
+ const scope = { kdtId: input.kdtId };
64
+ const invalid = () => failure('protocol', 'segment-customers', scope, 'sent', facts);
65
+ try {
66
+ if (!object(data) || !Array.isArray(data.crowd_customer_info_list) ||
67
+ data.page_no !== input.pageNo || data.page_size !== input.pageSize) throw invalid();
68
+ const rows = data.crowd_customer_info_list;
69
+ if (rows.length > input.pageSize) throw invalid();
70
+ const total = readTotal(data, 'total_count');
71
+ let hasMore = null;
72
+ if (total !== null) {
73
+ const offset = BigInt(input.pageNo - 1) * BigInt(input.pageSize);
74
+ const end = offset + BigInt(rows.length);
75
+ const count = BigInt(total);
76
+ if ((rows.length > 0 && end > count) || (rows.length === 0 && offset < count) ||
77
+ (rows.length > 0 && rows.length < input.pageSize && end !== count)) throw invalid();
78
+ hasMore = BigInt(input.pageNo) * BigInt(input.pageSize) < count
79
+ ? (input.pageNo < 10000 ? true : null) : false;
80
+ }
81
+ const seen = new Set();
82
+ const customerModel = schema.definitions.customer;
83
+ const customers = rows.map(row => {
84
+ if (!object(row) || typeof row.yz_open_id !== 'string' || !row.yz_open_id ||
85
+ row.yz_open_id.trim() !== row.yz_open_id || seen.has(row.yz_open_id)) throw invalid();
86
+ seen.add(row.yz_open_id);
87
+ const selected = {};
88
+ for (const [name, source] of Object.entries(fields)) {
89
+ if (Object.hasOwn(row, source)) selected[name] = row[source];
90
+ }
91
+ return normalizeTimes(normalizeMoney(selected, customerModel), customerModel);
92
+ });
93
+ const output = { data: { customers,
94
+ pagination: { pageNo: data.page_no, pageSize: data.page_size,
95
+ ...(total === null ? {} : { total }), hasMore } } };
96
+ validateCommandValue(schema, 'output', output);
97
+ return output;
98
+ } catch { throw invalid(); }
99
+ }
100
+
101
+ function serialize(output, format, scope) {
102
+ const text = `${JSON.stringify(output, null, format === 'pretty' ? 2 : 0)}\n`;
103
+ if (Buffer.byteLength(text) > outputLimit) {
104
+ throw failure('limit', 'output', scope, 'sent');
105
+ }
106
+ return text;
107
+ }
108
+
109
+ function createCommand({ context } = {}) {
110
+ const leaf = new Command('list')
111
+ .description('查询已知自定义分群内一页客户摘要,不扫描、不导出')
112
+ .allowExcessArguments(false)
113
+ .exitOverride(error => {
114
+ if (['commander.helpDisplayed', 'commander.version'].includes(error.code)) throw error;
115
+ throw failure('validation', 'input');
116
+ })
117
+ .requiredOption('--segment-id <id>', '分群 ID,规范正十进制文本,最大 9223372036854775807')
118
+ .option('--kdt-id <id>', '目标店铺正安全整数;默认当前有效上下文店铺')
119
+ .option('--page-no <n>', '页码 1..10000,不自动翻页', '1')
120
+ .option('--page-size <n>', '每页 1、2、4、5、8、10、20、25、40、50、100 条', '20')
121
+ .addOption(new Option('--format <format>', 'json=单行 JSON,pretty=缩进 JSON')
122
+ .choices(['json', 'pretty']).default('json'))
123
+ .option('--dry-run', '仅本地校验并预览两步逻辑请求,不读取凭证、不联网')
124
+ .addHelpText('after', `
125
+ 返回 data.customers 和 pagination.pageNo/pageSize/total/hasMore,只观察当前页,不是历史快照。
126
+ 使用店铺与分群 ID 定位;客户以同店 yzOpenId 引用。等级、卡名不证明会员身份或卡当前有效。
127
+ 未取得总数时省略 total,hasMore 为 null;仍有下一页但超页码预算时 hasMore 为 null。
128
+ hasMore=true 时保留店铺、分群和 pageSize,将 pageNo 加 1 手动继续。短页与人数冲突整体失败。
129
+ 金额为人民币元字符串,固定两位;时间为北京时间 YYYY-MM-DD HH:mm:ss.SSS。
130
+ 先核验同店,再取一页;1106 公共应用准备可能增加 HTTP 请求。普通失败不自动重放。
131
+ 每次业务响应最多 2 MiB,最终输出含换行最多 5 MiB,超限整体失败。
132
+ 支持官方列明的微商城/零售单店、连锁 D/L 总部,不承诺连锁分店或系统推荐分群。
133
+ 不支持分群目录、规则、筛选、排序、include 或触达;缺少分群 ID 时不自动选群。
134
+ 示例:zan-saas customer segment-customer list --segment-id 4938280 --kdt-id 63077 --dry-run
135
+ 契约:zan-saas schema customer.segment-customer.list`)
136
+ .action(async options => {
137
+ const input = buildInput(options, context);
138
+ const scope = { kdtId: input.kdtId };
139
+ const body = { request: `{"crowd_id":${input.segmentId},"page_no":${input.pageNo},"page_size":${input.pageSize}}` };
140
+ const preview = { scope, requests: [
141
+ { method: 'POST', path: bindingPath, body: {} },
142
+ { method: 'POST', path: schema.api.path, body }
143
+ ] };
144
+ validateCommandValue(schema, 'preview', preview);
145
+ let output = preview;
146
+ if (!options.dryRun) {
147
+ try {
148
+ const { read } = await createBoundShopQuery(scope, command, 'shop-binding', {
149
+ transportOptions: { maxResponseBytes: () => responseLimit, shopApp: { notify: () => {} } }
150
+ });
151
+ const { data, facts } = await read(schema.api.path, body, 'segment-customers');
152
+ output = project(data, input, facts);
153
+ } catch (error) {
154
+ const details = error.cliDetails;
155
+ if (!details) throw failure('protocol', 'segment-customers', scope, 'unknown');
156
+ throw failure(details.category, details.stage, scope, details.requestState, details);
157
+ }
158
+ }
159
+ process.stdout.write(serialize(output, options.format, scope));
160
+ });
161
+ const seen = new Set();
162
+ for (const option of leaf.options) {
163
+ leaf.on(`option:${option.name()}`, () => {
164
+ if (seen.has(option.name())) throw failure('validation', 'input');
165
+ seen.add(option.name());
166
+ });
167
+ }
168
+ return leaf;
169
+ }
170
+
171
+ module.exports = { kind: 'atomic', risk: 'read', at: 'all', createCommand: withTargetShopDefault(createCommand), project, serialize };
@@ -0,0 +1,123 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "api": { "method": "POST", "path": "youzan.crm.crowd.query/1.0.0" },
4
+ "input": {
5
+ "type": "object", "additionalProperties": false,
6
+ "required": ["kdtId", "segmentId", "pageNo", "pageSize"],
7
+ "properties": {
8
+ "kdtId": { "$ref": "#/definitions/kdtId" },
9
+ "segmentId": { "type": "string", "pattern": "^[1-9][0-9]{0,18}$", "description": "目标店铺内的分群 ID,范围 1..9223372036854775807,保留完整十进制文本,不接受符号、空白、前导零或指数" },
10
+ "pageNo": { "$ref": "#/definitions/pageNo" },
11
+ "pageSize": { "$ref": "#/definitions/pageSize" }
12
+ }
13
+ },
14
+ "output": {
15
+ "type": "object", "additionalProperties": false, "required": ["data"],
16
+ "properties": {
17
+ "data": {
18
+ "type": "object", "additionalProperties": false, "required": ["customers", "pagination"],
19
+ "properties": {
20
+ "customers": { "type": "array", "maxItems": 100, "items": { "$ref": "#/definitions/customer" }, "description": "当前页客户摘要,保留返回顺序;实际成功空页为 [],不保证跨页无重复或遗漏" },
21
+ "pagination": {
22
+ "type": "object", "additionalProperties": false, "required": ["pageNo", "pageSize", "hasMore"],
23
+ "properties": {
24
+ "pageNo": { "$ref": "#/definitions/pageNo" },
25
+ "pageSize": { "$ref": "#/definitions/pageSize" },
26
+ "total": { "type": "integer", "minimum": 0, "maximum": 9007199254740991, "description": "匹配本次查询条件的总记录数,不是当前页返回条数;上游未提供可确认总数时省略。" },
27
+ "hasMore": { "type": ["boolean", "null"], "description": "true 表示还有后续页,false 表示已确认没有后续页;无法确定时为 null,不以空页或短页推算。" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "preview": {
35
+ "type": "object", "additionalProperties": false, "required": ["scope", "requests"],
36
+ "properties": {
37
+ "scope": { "$ref": "#/definitions/scope" },
38
+ "requests": {
39
+ "type": "array", "minItems": 2, "maxItems": 2, "additionalItems": false,
40
+ "description": "两步逻辑计划;不展开公共认证或条件性的应用准备,不证明真实授权",
41
+ "items": [
42
+ {
43
+ "type": "object", "additionalProperties": false, "required": ["method", "path", "body"],
44
+ "properties": {
45
+ "method": { "const": "POST" }, "path": { "const": "youzan.shop.basic.get/3.0.0" },
46
+ "body": { "type": "object", "additionalProperties": false, "properties": {} }
47
+ }
48
+ },
49
+ {
50
+ "type": "object", "additionalProperties": false, "required": ["method", "path", "body"],
51
+ "properties": {
52
+ "method": { "const": "POST" }, "path": { "const": "youzan.crm.crowd.query/1.0.0" },
53
+ "body": {
54
+ "type": "object", "additionalProperties": false, "required": ["request"],
55
+ "properties": { "request": { "type": "string", "description": "JSON 对象文本,仅 crowd_id、page_no、page_size;crowd_id 是完整分群 ID 的无损数字 token,另两项为实际页码和条数" } }
56
+ }
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ },
63
+ "error": {
64
+ "type": "object", "additionalProperties": false, "required": ["success", "code", "message", "details"],
65
+ "properties": {
66
+ "success": { "const": false }, "code": { "const": -10001 }, "message": { "type": "string" },
67
+ "details": {
68
+ "type": "object", "additionalProperties": false,
69
+ "required": ["command", "category", "stage", "targetScope", "requestState", "nextStep"],
70
+ "properties": {
71
+ "command": { "const": "customer.segment-customer.list" },
72
+ "category": { "type": "string", "enum": ["validation", "configuration", "authentication", "permission", "scope_unresolved", "http", "business_rejected", "transport", "protocol", "limit"], "description": "validation=输入无效,configuration=上下文或环境不可用,authentication=身份拒绝,permission=权限或位置拒绝,scope_unresolved=店铺未核实,http=HTTP 拒绝,business_rejected=业务拒绝,transport=传输失败,protocol=响应违约,limit=预算超限" },
73
+ "stage": { "type": "string", "enum": ["input", "context", "scope", "configuration", "shop-binding", "segment-customers", "output"], "description": "input=选项校验,context=上下文,scope=目标解析,configuration=环境准备,shop-binding=同店核验,segment-customers=客户页查询及适配,output=序列化和输出预算" },
74
+ "targetScope": { "type": "object", "additionalProperties": false, "properties": { "kdtId": { "$ref": "#/definitions/kdtId" } }, "description": "目标已确定时只有 kdtId,否则为空对象" },
75
+ "requestState": { "type": "string", "enum": ["not_sent", "sent", "unknown"], "description": "针对失败阶段,not_sent=对应业务请求未发,sent=已取得响应,unknown=发送或结果无法确认;不否认先前绑定及公共准备" },
76
+ "nextStep": { "type": "string" },
77
+ "api": { "type": "string", "enum": ["youzan.shop.basic.get/3.0.0", "youzan.crm.crowd.query/1.0.0"], "description": "失败接口,分别为店铺核验、分群客户页查询" },
78
+ "httpStatus": { "type": "integer", "minimum": 100, "maximum": 599 },
79
+ "businessCode": { "anyOf": [{ "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, { "type": "string", "pattern": "^-?[0-9]{1,12}$" }], "description": "安全诊断码,不包含上游自由文本" },
80
+ "trace_id": { "$ref": "#/definitions/trace" }, "request_id": { "$ref": "#/definitions/trace" }
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "definitions": {
86
+ "kdtId": { "type": "integer", "minimum": 1, "maximum": 9007199254740991, "description": "目标店铺 ID,默认当前有效上下文店铺;shop 位置须同店,不代表服务端授权" },
87
+ "scope": { "type": "object", "additionalProperties": false, "required": ["kdtId"], "properties": { "kdtId": { "$ref": "#/definitions/kdtId" } } },
88
+ "pageNo": { "type": "integer", "minimum": 1, "maximum": 10000, "description": "实际页码,从 1 开始,最多 10000,不自动翻页" },
89
+ "pageSize": { "type": "integer", "enum": [1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100], "description": "每页条数,只支持 1、2、4、5、8、10、20、25、40、50、100;不表示总读取量" },
90
+ "trace": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,128}$", "description": "安全追踪值" },
91
+ "customer": {
92
+ "type": "object", "additionalProperties": false, "required": ["yzOpenId"],
93
+ "description": "分群内客户摘要,不代表会员身份或完整档案;可选字段缺失省略,返回 null 或错型整体失败;保留合法空串、零和空数组",
94
+ "properties": {
95
+ "yzOpenId": { "type": "string", "minLength": 1, "pattern": "^\\S(?:[\\s\\S]*\\S)?$", "description": "同店客户稳定标识,不接受首尾空白且同页不能重复,不以手机号或昵称合并" },
96
+ "mobileCountryCode": { "type": "string", "description": "手机号国家或地区区号,原样保留 86 或 +86 等文本,不自动改写" },
97
+ "mobile": { "type": "string", "description": "本次返回的手机号,保留空串或掩码,不补全,也不证明可触达" },
98
+ "nickName": { "type": "string", "description": "微信昵称,可为空,不作为姓名或稳定标识" },
99
+ "gender": { "type": "integer", "minimum": -2147483648, "maximum": 2147483647, "description": "整数 0=未知,1=男,2=女;未来 Java Integer 范围整数码原样保留,不猜含义,不接受字符串或 null" },
100
+ "freeLevelName": { "type": "string", "description": "免费等级名称摘要,可为空,不据此推算会员身份或完整等级关系" },
101
+ "payLevelName": { "type": "string", "description": "付费等级名称摘要,可为空,不证明付费资格当前有效" },
102
+ "cardNames": { "type": "array", "items": { "type": "string" }, "description": "权益卡名称摘要,保留空数组、空名称和重复名称,不推断持卡号、卡数量或当前有效性" },
103
+ "points": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "本次积分摘要,保留零和负值,不代表累计积分" },
104
+ "storedBalance": { "type": "string", "pattern": "^-?(0|[1-9][0-9]*)\\.[0-9]{2}$", "$comment": "upstream-money:fen", "description": "储值余额摘要,人民币 CNY 元,固定两位,范围 -90071992547409.91..90071992547409.91;不是逐卡可支付余额或客户全部资产" },
105
+ "allChannelTotalTradeCount": { "type": "integer", "minimum": 0, "maximum": 2147483647, "description": "全渠道累计有效消费次数,不是任意时间窗口的订单笔数" },
106
+ "allChannelTotalTradeAmount": { "type": "string", "pattern": "^-?(0|[1-9][0-9]*)\\.[0-9]{2}$", "$comment": "upstream-money:fen", "description": "全渠道累计有效消费金额,人民币 CNY 元,固定两位,范围 -90071992547409.91..90071992547409.91;不是净收入或近 90 天金额" },
107
+ "allChannelLastTradeAt": { "type": "string", "format": "beijing-date-time-ms", "$comment": "upstream-time:milliseconds", "description": "全渠道上次消费时间,北京时间 YYYY-MM-DD HH:mm:ss.SSS;纪元零值为 1970-01-01 08:00:00.000,不解释为从未消费" }
108
+ }
109
+ }
110
+ },
111
+ "success": { "description": "同店核验后取得一页,客户标识、摘要、分页和字节预算全部通过才一次性输出 data;无部分成功" },
112
+ "errors": { "description": "stdout 为空,stderr 一份脱敏 JSON,退出 1。绑定不一致、人数冲突、所选字段 null 或错型不降级为空页;普通失败、超时和断连不自动重放" },
113
+ "notes": [
114
+ "仅查询已知自定义分群的一页客户。支持官方列明的微商城单店、零售单店及连锁 D/L 总部;不承诺连锁分店或系统推荐分群。",
115
+ "执行需要有效四字段上下文;省略店铺用当前店铺。无上下文时仅可显式指定合法店铺本地预览;无效上下文不能绕过。",
116
+ "结果是动态观察,不是历史快照。跨页可能新增、删除、重复或遗漏,空页不能证明分群不存在。",
117
+ "hasMore=true 时保留店铺、分群 ID 和 pageSize,将 pageNo 加 1;达到 10000 页不改变条数冒充无缝恢复。未知总数时不能从短页证明读完。",
118
+ "非空短页必须恰好到报告尾部;人数冲突整体失败。超过尾页的成功空页 hasMore=false,不表示分群不存在。",
119
+ "正常两个逻辑业务查询;公共 1106 应用准备可能增加 HTTP 请求,最多 22 次业务发送及 22 次账号准备调用。普通失败不重试。",
120
+ "每次业务响应最多 2 MiB,最终序列化输出含换行最多 5 MiB;超限整体失败,不截断客户或卡名。",
121
+ "不读取目录或规则、不遍历、不导出、不触达、不逐客户补档案。等级、卡名和手机号均不能代替同店 yzOpenId。"
122
+ ]
123
+ }
@@ -0,0 +1,179 @@
1
+ 'use strict';
2
+
3
+ const { parseDateTime, formatDateTime } = require('../../../../execution/datetime');
4
+ const { resolveTargetShop } = require('../../../../execution/target-shop');
5
+ const { failure } = require('../../../../execution/shop-query');
6
+
7
+ const command = 'customer.customer.scan';
8
+ const searchPath = 'youzan.scrm.customer.list/1.0.0';
9
+ const maxSafe = Number.MAX_SAFE_INTEGER;
10
+
11
+ function reject(category, stage, scope = {}, state = 'not_sent', facts = {}) {
12
+ const error = failure(command, category, stage, scope, state, facts);
13
+ error.cliDetails.nextStep = '核对原店铺、参数、字段来源和服务端授权;前批结果不构成成功,不自动重试、切店、换身份或改变查询模式。';
14
+ return error;
15
+ }
16
+
17
+ function adaptError(error, scope) {
18
+ const details = error.cliDetails;
19
+ if (!details) return reject('protocol', 'output', scope, 'sent');
20
+ const stage = ['input', 'context', 'configuration', 'shop-binding', 'search', 'scan', 'output']
21
+ .includes(details.stage) ? details.stage : 'input';
22
+ const adapted = reject(details.category, stage, scope, details.requestState);
23
+ for (const key of ['api', 'httpStatus', 'businessCode', 'gatewayCode', 'trace_id', 'request_id']) {
24
+ if (Object.hasOwn(details, key)) adapted.cliDetails[key] = details[key];
25
+ }
26
+ return adapted;
27
+ }
28
+
29
+ function integer(value, maximum = maxSafe, minimum = 0) {
30
+ if (typeof value !== 'string' || !/^(0|[1-9]\d*)$/.test(value) ||
31
+ !Number.isSafeInteger(Number(value)) || Number(value) < minimum || Number(value) > maximum) {
32
+ throw new Error('invalid integer');
33
+ }
34
+ return Number(value);
35
+ }
36
+
37
+ function id(value) {
38
+ integer(value, maxSafe, 1);
39
+ return value;
40
+ }
41
+
42
+ function ids(value) {
43
+ if (typeof value !== 'string') throw new Error('invalid IDs');
44
+ const values = value.startsWith('[') ? JSON.parse(value) : value.split(',');
45
+ if (!Array.isArray(values) || values.length < 1 || values.length > 50) throw new Error('invalid IDs');
46
+ return [...new Set(values.map(id))];
47
+ }
48
+
49
+ function boolean(value) {
50
+ if (!['true', 'false'].includes(value)) throw new Error('invalid boolean');
51
+ return value === 'true';
52
+ }
53
+
54
+ function cents(value) {
55
+ if (typeof value !== 'string' || !/^(0|[1-9]\d*)(?:\.\d{1,2})?$/.test(value)) {
56
+ throw new Error('invalid money');
57
+ }
58
+ const [whole, fraction = ''] = value.split('.');
59
+ const amount = BigInt(whole) * 100n + BigInt(fraction.padEnd(2, '0'));
60
+ if (amount > BigInt(maxSafe)) throw new Error('unsafe money');
61
+ return amount;
62
+ }
63
+
64
+ function money(value) {
65
+ const amount = cents(value);
66
+ return `${amount / 100n}.${String(amount % 100n).padStart(2, '0')}`;
67
+ }
68
+
69
+ function time(value) {
70
+ if (typeof value !== 'string' || value.trim() !== value) throw new Error('invalid time');
71
+ const milliseconds = parseDateTime(value);
72
+ if (milliseconds % 1000 !== 0 || milliseconds < 0 || milliseconds > 4102444800000) {
73
+ throw new Error('invalid seconds');
74
+ }
75
+ return formatDateTime(milliseconds);
76
+ }
77
+
78
+ function range(value, parse, temporal = false) {
79
+ if (typeof value !== 'string') throw new Error('invalid range');
80
+ const parts = value.split('..');
81
+ if (parts.length > 2 || temporal && parts.length !== 2 || parts.every(part => part === '')) {
82
+ throw new Error('invalid range');
83
+ }
84
+ if (parts.length === 1) parts.push(parts[0]);
85
+ const [lower, upper] = temporal ? ['start', 'end'] : ['min', 'max'];
86
+ const result = {};
87
+ if (parts[0] !== '') result[lower] = parse(parts[0]);
88
+ if (parts[1] !== '') result[upper] = parse(parts[1]);
89
+ const compare = parse === money ? cents : value => value;
90
+ if (result[lower] !== undefined && result[upper] !== undefined &&
91
+ compare(result[lower]) > compare(result[upper])) throw new Error('reversed range');
92
+ return result;
93
+ }
94
+
95
+ function buildInput(options, context) {
96
+ let scope = {};
97
+ try {
98
+ scope = { kdtId: id(options.kdtId) };
99
+ const filters = {};
100
+ for (const key of ['keyword', 'mobile']) {
101
+ if (options[key] === undefined) continue;
102
+ const value = options[key];
103
+ if (typeof value !== 'string' || value.trim() !== value || !value ||
104
+ (key === 'keyword' ? value.length > 200 : !/^\+?\d{5,20}$/.test(value))) {
105
+ throw new Error('invalid text');
106
+ }
107
+ filters[key] = value;
108
+ }
109
+ if (filters.keyword !== undefined && filters.mobile !== undefined) throw new Error('conflict');
110
+ for (const key of ['isMember', 'hasLevel', 'hasBenefitCard']) {
111
+ if (options[key] !== undefined) filters[key] = boolean(options[key]);
112
+ }
113
+ for (const key of ['tagIds', 'levelIds', 'benefitCardIds']) {
114
+ if (options[key] !== undefined) filters[key] = ids(options[key]);
115
+ }
116
+ for (const key of ['created', 'memberCreated', 'lastTrade']) {
117
+ if (options[key] !== undefined) filters[key] = range(options[key], time, true);
118
+ }
119
+ for (const [key, parse] of [['points', integer], ['tradeCount', value => integer(value, 2147483647)],
120
+ ['tradeAmount', money], ['storedBalance', money]]) {
121
+ if (options[key] !== undefined) filters[key] = range(options[key], parse);
122
+ }
123
+ if (filters.hasLevel === false && filters.levelIds ||
124
+ filters.hasBenefitCard === false && filters.benefitCardIds) throw new Error('conflict');
125
+ const created = filters.created;
126
+ if (!created?.start || !created.end || created.start >= created.end ||
127
+ parseDateTime(created.end) - parseDateTime(created.start) > 366 * 86400000) {
128
+ throw new Error('invalid scan window');
129
+ }
130
+ const input = { ...scope, filters, limits: {
131
+ maxItems: integer(options.maxItems ?? '200', 2000, 1),
132
+ maxRequests: integer(options.maxRequests ?? '20', 200, 2)
133
+ } };
134
+ try { resolveTargetShop(options, context, command); }
135
+ catch (error) { throw adaptError(error, scope); }
136
+ for (const value of Object.values(filters)) {
137
+ if (value && typeof value === 'object') Object.freeze(value);
138
+ }
139
+ Object.freeze(filters);
140
+ Object.freeze(input.limits);
141
+ return Object.freeze(input);
142
+ } catch (error) {
143
+ if (error.cliDetails) throw error;
144
+ throw reject('validation', 'input', scope);
145
+ }
146
+ }
147
+
148
+ function bodyFor(filters, window, pageNo = 1) {
149
+ const body = { page_no: pageNo, page_size: 50 };
150
+ for (const key of ['keyword', 'mobile']) {
151
+ if (filters[key] !== undefined) body[key] = filters[key];
152
+ }
153
+ if (filters.isMember !== undefined) body.is_member = filters.isMember ? 1 : 0;
154
+ for (const [key, field] of [['hasLevel', 'has_level'], ['hasBenefitCard', 'has_card']]) {
155
+ if (filters[key] !== undefined) body[field] = filters[key];
156
+ }
157
+ for (const [key, field] of [['tagIds', 'all_match_tag_ids'], ['levelIds', 'level_ids'], ['benefitCardIds', 'card_ids']]) {
158
+ if (filters[key] !== undefined) body[field] = filters[key].map(Number);
159
+ }
160
+ for (const [key, prefix] of [['created', 'created_at'], ['memberCreated', 'created_member_at'], ['lastTrade', 'last_trade_time']]) {
161
+ const bounds = key === 'created' && window ? window : filters[key];
162
+ if (!bounds) continue;
163
+ for (const side of ['start', 'end']) {
164
+ if (bounds[side] !== undefined) body[`${prefix}_${side}`] = typeof bounds[side] === 'number' ?
165
+ bounds[side] : parseDateTime(bounds[side]) / 1000;
166
+ }
167
+ }
168
+ for (const [key, prefix] of [['tradeCount', 'total_trade_count'], ['points', 'points'],
169
+ ['tradeAmount', 'total_trade_amount'], ['storedBalance', 'stored_balance']]) {
170
+ if (!filters[key]) continue;
171
+ for (const side of ['min', 'max']) {
172
+ if (filters[key][side] !== undefined) body[`${prefix}_${side}`] =
173
+ ['tradeAmount', 'storedBalance'].includes(key) ? Number(cents(filters[key][side])) : filters[key][side];
174
+ }
175
+ }
176
+ return body;
177
+ }
178
+
179
+ module.exports = { buildInput, bodyFor, reject, adaptError, searchPath, command, range, integer, money, time };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const { object } = require('../../../../execution/query-api');
4
+ const { formatDateTime, parseDateTime } = require('../../../../execution/datetime');
5
+ const { reject } = require('./search-input');
6
+
7
+ function projectPage(data, pageNo, originalWindow, scope, facts) {
8
+ const invalid = () => { throw reject('protocol', 'search', scope, 'sent', facts); };
9
+ if (!object(data) || !Array.isArray(data.record_list) || data.record_list.length > 50) invalid();
10
+ for (const [field, expected] of [['page_no', pageNo], ['page_size', 50]]) {
11
+ if (Object.hasOwn(data, field) && data[field] !== expected) invalid();
12
+ }
13
+ const total = data.total;
14
+ if (Object.hasOwn(data, 'total') && (!Number.isSafeInteger(total) || total < 0)) invalid();
15
+ if (total !== undefined && data.record_list.length &&
16
+ (pageNo - 1) * 50 + data.record_list.length > total) invalid();
17
+ const customers = data.record_list.map(source => {
18
+ if (!object(source) || typeof source.yz_open_id !== 'string' || !source.yz_open_id ||
19
+ source.yz_open_id.trim() !== source.yz_open_id) invalid();
20
+ const customer = { yzOpenId: source.yz_open_id };
21
+ for (const [field, key] of [['name', 'name'], ['show_name', 'showName'], ['mobile', 'mobile']]) {
22
+ if (!Object.hasOwn(source, field)) continue;
23
+ if (typeof source[field] !== 'string') invalid();
24
+ customer[key] = source[field];
25
+ }
26
+ for (const [field, key] of [['gender', 'gender'], ['trade_count', 'tradeCount'], ['points', 'points']]) {
27
+ if (!Object.hasOwn(source, field)) continue;
28
+ if (!Number.isSafeInteger(source[field]) ||
29
+ field === 'trade_count' && (source[field] < 0 || source[field] > 2147483647)) invalid();
30
+ customer[key] = source[field];
31
+ }
32
+ if (Object.hasOwn(source, 'is_member')) {
33
+ if (![0, 1].includes(source.is_member)) invalid();
34
+ customer.isMember = source.is_member === 1;
35
+ }
36
+ if (Object.hasOwn(source, 'is_mobile_auth')) {
37
+ if (typeof source.is_mobile_auth !== 'boolean') invalid();
38
+ customer.isMobileAuthorized = source.is_mobile_auth;
39
+ }
40
+ for (const [field, key] of [['created_at', 'createdAt'], ['created_member_at', 'memberCreatedAt']]) {
41
+ if (!Object.hasOwn(source, field)) continue;
42
+ const value = source[field];
43
+ if (field === 'created_member_at' && value === 0) { customer[key] = 0; continue; }
44
+ if (!Number.isSafeInteger(value) || value <= 0 || !Number.isSafeInteger(value * 1000)) invalid();
45
+ try { customer[key] = formatDateTime(value * 1000); } catch { invalid(); }
46
+ if (field === 'created_at' && (parseDateTime(customer[key]) / 1000 < originalWindow.start ||
47
+ parseDateTime(customer[key]) / 1000 > originalWindow.end)) invalid();
48
+ }
49
+ return customer;
50
+ });
51
+ return { customers, total };
52
+ }
53
+
54
+ module.exports = { projectPage };
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const { withTargetShopDefault } = require('../../../../../execution/target-shop-default');
4
+
5
+ const { Command, Option } = require('commander');
6
+ const { resolveTargetShop } = require('../../../../../execution/target-shop');
7
+ const { validateCommandValue } = require('../../../../../execution/command-schema');
8
+ const { createShopQuery, failure, bindingPath } = require('../../../../../execution/shop-query');
9
+ const { object } = require('../../../../../execution/query-api');
10
+ const schema = require('./schema.json');
11
+ const { normalizeTimes } = require('../../../../../execution/datetime');
12
+
13
+ const command = 'customer.tag.get';
14
+ const reject = (category, stage, scope, state, facts) =>
15
+ failure(command, category, stage, scope, state, facts);
16
+
17
+ function createCommand({ context } = {}) {
18
+ return new Command('get')
19
+ .description('按标签 ID 查询一个标签定义,不查询客户已打标关系')
20
+ .allowExcessArguments(false)
21
+ .exitOverride(error => {
22
+ if (['commander.helpDisplayed', 'commander.version'].includes(error.code)) throw error;
23
+ throw reject('parameter', 'input');
24
+ })
25
+ .option('--kdt-id <id>', '目标店铺正安全整数;须与凭证店铺一致;省略时使用当前上下文店铺')
26
+ .requiredOption('--tag-id <id>', '必填:标签定义正安全整数 ID;不是标签组 ID')
27
+ .addOption(new Option('--format <format>', 'json 或 pretty,仅改变缩进')
28
+ .choices(['json', 'pretty']).default('json'))
29
+ .option('--dry-run', '本地预览目标、店铺核验和标签请求,不读凭证、不联网')
30
+ .addHelpText('after', `
31
+ 时间点统一输出北京时间 YYYY-MM-DD HH:mm:ss.SSS,字段声明的业务哨兵保留。
32
+ 返回 data.tag;名称与主键必需,其余选定明细缺失时保持省略,不推断无值。
33
+ 标签状态 0 不可用、1 可用;不套用标签组的状态方向。
34
+ 只支持官方声明的微商城单店;at=shop 不承诺零售、总部或连锁分店。
35
+ 先核验凭证店铺,再直读标签;不搜索名称、不遍历列表、不重试或切换接口。
36
+ 真实执行需四字段上下文和同店授权凭证,店铺回读不证明员工 OAuth。
37
+ 契约:zan-saas schema customer.tag.get
38
+ 示例:zan-saas customer tag get --kdt-id 63077 --tag-id 123 --dry-run
39
+ `)
40
+ .action(async options => {
41
+ const scope = resolveTargetShop(options, context, command);
42
+ let input;
43
+ try {
44
+ if (!/^[1-9]\d*$/.test(options.tagId)) throw new Error('invalid tag ID');
45
+ input = { ...scope, tagId: Number(options.tagId) };
46
+ validateCommandValue(schema, 'input', input);
47
+ } catch { throw reject('parameter', 'input', scope); }
48
+ const preview = {
49
+ method: 'POST', path: schema.api.path, body: { tag_id: input.tagId },
50
+ scope, prerequisites: [{ method: 'POST', path: bindingPath, body: {} }]
51
+ };
52
+ validateCommandValue(schema, 'preview', preview);
53
+ let output = preview;
54
+ if (!options.dryRun) {
55
+ const read = await createShopQuery(scope, command);
56
+ const { data, facts } = await read(preview.path, preview.body, 'tag');
57
+ if (!object(data) || data.tag_id !== input.tagId) {
58
+ throw reject('target_mismatch', 'tag-model', scope, 'sent', facts);
59
+ }
60
+ const tag = Object.fromEntries(Object.keys(schema.definitions.tag.properties)
61
+ .filter(field => Object.hasOwn(data, field)).map(field => [field, data[field]]));
62
+ output = { data: { tag } };
63
+ try {
64
+ output.data.tag = normalizeTimes(tag, schema.definitions.tag, schema.definitions);
65
+ validateCommandValue(schema, 'output', output);
66
+ }
67
+ catch { throw reject('protocol', 'tag-model', scope, 'sent', facts); }
68
+ }
69
+ process.stdout.write(`${JSON.stringify(output, null, options.format === 'pretty' ? 2 : 0)}\n`);
70
+ });
71
+ }
72
+
73
+ module.exports = { kind: 'atomic', risk: 'read', at: 'shop', createCommand: withTargetShopDefault(createCommand) };