@chenchaolong/plugin-trade-compliance-workbench 1.0.5 → 1.0.7

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 (196) hide show
  1. package/dist/index.d.ts +18 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +20 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/adapters/hsbianma.provider.d.ts +6 -8
  6. package/dist/lib/adapters/hsbianma.provider.d.ts.map +1 -1
  7. package/dist/lib/adapters/hsbianma.provider.js +159 -53
  8. package/dist/lib/adapters/hsbianma.provider.js.map +1 -1
  9. package/dist/lib/adapters/translation.provider.d.ts +7 -1
  10. package/dist/lib/adapters/translation.provider.d.ts.map +1 -1
  11. package/dist/lib/adapters/translation.provider.js +15 -5
  12. package/dist/lib/adapters/translation.provider.js.map +1 -1
  13. package/dist/lib/domain/catalog-localization.d.ts +14 -0
  14. package/dist/lib/domain/catalog-localization.d.ts.map +1 -0
  15. package/dist/lib/domain/catalog-localization.js +19 -0
  16. package/dist/lib/domain/catalog-localization.js.map +1 -0
  17. package/dist/lib/domain/file-name.d.ts +8 -0
  18. package/dist/lib/domain/file-name.d.ts.map +1 -0
  19. package/dist/lib/domain/file-name.js +111 -0
  20. package/dist/lib/domain/file-name.js.map +1 -0
  21. package/dist/lib/domain/import-issues.d.ts +4 -0
  22. package/dist/lib/domain/import-issues.d.ts.map +1 -1
  23. package/dist/lib/domain/import-issues.js +14 -3
  24. package/dist/lib/domain/import-issues.js.map +1 -1
  25. package/dist/lib/entities/catalog.entity.d.ts +5 -1
  26. package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
  27. package/dist/lib/entities/catalog.entity.js +16 -0
  28. package/dist/lib/entities/catalog.entity.js.map +1 -1
  29. package/dist/lib/entities/customer.entity.d.ts +3 -0
  30. package/dist/lib/entities/customer.entity.d.ts.map +1 -1
  31. package/dist/lib/entities/customer.entity.js +12 -0
  32. package/dist/lib/entities/customer.entity.js.map +1 -1
  33. package/dist/lib/entities/shared.entity.d.ts +9 -0
  34. package/dist/lib/entities/shared.entity.d.ts.map +1 -1
  35. package/dist/lib/entities/shared.entity.js +33 -0
  36. package/dist/lib/entities/shared.entity.js.map +1 -1
  37. package/dist/lib/import-jobs/agent-handoff.dispatcher.d.ts +15 -0
  38. package/dist/lib/import-jobs/agent-handoff.dispatcher.d.ts.map +1 -0
  39. package/dist/lib/import-jobs/agent-handoff.dispatcher.js +114 -0
  40. package/dist/lib/import-jobs/agent-handoff.dispatcher.js.map +1 -0
  41. package/dist/lib/import-jobs/contracts.d.ts +76 -0
  42. package/dist/lib/import-jobs/contracts.d.ts.map +1 -0
  43. package/dist/lib/import-jobs/contracts.js +31 -0
  44. package/dist/lib/import-jobs/contracts.js.map +1 -0
  45. package/dist/lib/import-jobs/import-command.builder.d.ts +30 -0
  46. package/dist/lib/import-jobs/import-command.builder.d.ts.map +1 -0
  47. package/dist/lib/import-jobs/import-command.builder.js +97 -0
  48. package/dist/lib/import-jobs/import-command.builder.js.map +1 -0
  49. package/dist/lib/import-jobs/import-job-callback.processor.d.ts +15 -0
  50. package/dist/lib/import-jobs/import-job-callback.processor.d.ts.map +1 -0
  51. package/dist/lib/import-jobs/import-job-callback.processor.js +107 -0
  52. package/dist/lib/import-jobs/import-job-callback.processor.js.map +1 -0
  53. package/dist/lib/import-jobs/import-job.coordinator.d.ts +17 -0
  54. package/dist/lib/import-jobs/import-job.coordinator.d.ts.map +1 -0
  55. package/dist/lib/import-jobs/import-job.coordinator.js +256 -0
  56. package/dist/lib/import-jobs/import-job.coordinator.js.map +1 -0
  57. package/dist/lib/import-jobs/import-job.processor.d.ts +24 -0
  58. package/dist/lib/import-jobs/import-job.processor.d.ts.map +1 -0
  59. package/dist/lib/import-jobs/import-job.processor.js +213 -0
  60. package/dist/lib/import-jobs/import-job.processor.js.map +1 -0
  61. package/dist/lib/import-jobs/import-timeout.processor.d.ts +12 -0
  62. package/dist/lib/import-jobs/import-timeout.processor.d.ts.map +1 -0
  63. package/dist/lib/import-jobs/import-timeout.processor.js +37 -0
  64. package/dist/lib/import-jobs/import-timeout.processor.js.map +1 -0
  65. package/dist/lib/import-jobs/organization-lease.d.ts +39 -0
  66. package/dist/lib/import-jobs/organization-lease.d.ts.map +1 -0
  67. package/dist/lib/import-jobs/organization-lease.js +136 -0
  68. package/dist/lib/import-jobs/organization-lease.js.map +1 -0
  69. package/dist/lib/plugin-context.token.d.ts +4 -0
  70. package/dist/lib/plugin-context.token.d.ts.map +1 -0
  71. package/dist/lib/plugin-context.token.js +2 -0
  72. package/dist/lib/plugin-context.token.js.map +1 -0
  73. package/dist/lib/public-dto.d.ts +6 -0
  74. package/dist/lib/public-dto.d.ts.map +1 -0
  75. package/dist/lib/public-dto.js +33 -0
  76. package/dist/lib/public-dto.js.map +1 -0
  77. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  78. package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
  79. package/dist/lib/remote-ui/action-execution.d.ts +26 -0
  80. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -0
  81. package/dist/lib/remote-ui/action-execution.js +85 -0
  82. package/dist/lib/remote-ui/action-execution.js.map +1 -0
  83. package/dist/lib/remote-ui/analytics-contracts.d.ts +3 -2
  84. package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -1
  85. package/dist/lib/remote-ui/analytics-contracts.js.map +1 -1
  86. package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
  87. package/dist/lib/remote-ui/app-source.js +426 -169
  88. package/dist/lib/remote-ui/app-source.js.map +1 -1
  89. package/dist/lib/remote-ui/company-logo-lifecycle.d.ts +14 -0
  90. package/dist/lib/remote-ui/company-logo-lifecycle.d.ts.map +1 -0
  91. package/dist/lib/remote-ui/company-logo-lifecycle.js +52 -0
  92. package/dist/lib/remote-ui/company-logo-lifecycle.js.map +1 -0
  93. package/dist/lib/remote-ui/components/data-grid.d.ts +9 -4
  94. package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
  95. package/dist/lib/remote-ui/components/data-grid.js +41 -9
  96. package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
  97. package/dist/lib/remote-ui/components/forms.d.ts +6 -1
  98. package/dist/lib/remote-ui/components/forms.d.ts.map +1 -1
  99. package/dist/lib/remote-ui/components/forms.js +6 -1
  100. package/dist/lib/remote-ui/components/forms.js.map +1 -1
  101. package/dist/lib/remote-ui/components/layout.d.ts.map +1 -1
  102. package/dist/lib/remote-ui/components/layout.js +3 -3
  103. package/dist/lib/remote-ui/components/layout.js.map +1 -1
  104. package/dist/lib/remote-ui/contract-import.d.ts +37 -32
  105. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
  106. package/dist/lib/remote-ui/contract-import.js +51 -59
  107. package/dist/lib/remote-ui/contract-import.js.map +1 -1
  108. package/dist/lib/remote-ui/contracts.d.ts +20 -27
  109. package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
  110. package/dist/lib/remote-ui/contracts.js +8 -0
  111. package/dist/lib/remote-ui/contracts.js.map +1 -1
  112. package/dist/lib/remote-ui/customer-contract-contracts.d.ts +5 -11
  113. package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
  114. package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -1
  115. package/dist/lib/remote-ui/date-time.d.ts +3 -0
  116. package/dist/lib/remote-ui/date-time.d.ts.map +1 -0
  117. package/dist/lib/remote-ui/date-time.js +58 -0
  118. package/dist/lib/remote-ui/date-time.js.map +1 -0
  119. package/dist/lib/remote-ui/model.d.ts +26 -1
  120. package/dist/lib/remote-ui/model.d.ts.map +1 -1
  121. package/dist/lib/remote-ui/model.js +151 -15
  122. package/dist/lib/remote-ui/model.js.map +1 -1
  123. package/dist/lib/remote-ui/pages/analytics.d.ts +2 -0
  124. package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
  125. package/dist/lib/remote-ui/pages/analytics.js +11 -6
  126. package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
  127. package/dist/lib/remote-ui/pages/catalogs.d.ts +37 -14
  128. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  129. package/dist/lib/remote-ui/pages/catalogs.js +149 -91
  130. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  131. package/dist/lib/remote-ui/pages/contracts.d.ts +11 -6
  132. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
  133. package/dist/lib/remote-ui/pages/contracts.js +45 -33
  134. package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
  135. package/dist/lib/remote-ui/pages/files.d.ts +29 -3
  136. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  137. package/dist/lib/remote-ui/pages/files.js +60 -16
  138. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  139. package/dist/lib/remote-ui/pages/home.d.ts.map +1 -1
  140. package/dist/lib/remote-ui/pages/home.js +18 -18
  141. package/dist/lib/remote-ui/pages/home.js.map +1 -1
  142. package/dist/lib/remote-ui/pages/procurement.d.ts +29 -4
  143. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  144. package/dist/lib/remote-ui/pages/procurement.js +58 -63
  145. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  146. package/dist/lib/remote-ui/pages/sales.d.ts +47 -1
  147. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  148. package/dist/lib/remote-ui/pages/sales.js +60 -20
  149. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  150. package/dist/lib/remote-ui/pages/settings.d.ts +34 -1
  151. package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
  152. package/dist/lib/remote-ui/pages/settings.js +120 -94
  153. package/dist/lib/remote-ui/pages/settings.js.map +1 -1
  154. package/dist/lib/remote-ui/polling.d.ts +41 -0
  155. package/dist/lib/remote-ui/polling.d.ts.map +1 -0
  156. package/dist/lib/remote-ui/polling.js +148 -0
  157. package/dist/lib/remote-ui/polling.js.map +1 -0
  158. package/dist/lib/remote-ui/procurement-contracts.d.ts +4 -9
  159. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  160. package/dist/lib/remote-ui/sales-contracts.d.ts +2 -2
  161. package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -1
  162. package/dist/lib/remote-ui/settings-contracts.d.ts +9 -1
  163. package/dist/lib/remote-ui/settings-contracts.d.ts.map +1 -1
  164. package/dist/lib/remote-ui/settings-contracts.js.map +1 -1
  165. package/dist/lib/remote-ui/toast-queue.d.ts +29 -0
  166. package/dist/lib/remote-ui/toast-queue.d.ts.map +1 -0
  167. package/dist/lib/remote-ui/toast-queue.js +71 -0
  168. package/dist/lib/remote-ui/toast-queue.js.map +1 -0
  169. package/dist/lib/sales-file.service.d.ts.map +1 -1
  170. package/dist/lib/sales-file.service.js +5 -5
  171. package/dist/lib/sales-file.service.js.map +1 -1
  172. package/dist/lib/types.d.ts +5 -0
  173. package/dist/lib/types.d.ts.map +1 -1
  174. package/dist/lib/types.js +3 -0
  175. package/dist/lib/types.js.map +1 -1
  176. package/dist/lib/workbench-action-contract.d.ts +4 -0
  177. package/dist/lib/workbench-action-contract.d.ts.map +1 -0
  178. package/dist/lib/workbench-action-contract.js +43 -0
  179. package/dist/lib/workbench-action-contract.js.map +1 -0
  180. package/dist/lib/workbench-view.provider.d.ts +57 -78
  181. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  182. package/dist/lib/workbench-view.provider.js +89 -91
  183. package/dist/lib/workbench-view.provider.js.map +1 -1
  184. package/dist/lib/workbench.middleware.d.ts +129 -72
  185. package/dist/lib/workbench.middleware.d.ts.map +1 -1
  186. package/dist/lib/workbench.middleware.js +16 -3
  187. package/dist/lib/workbench.middleware.js.map +1 -1
  188. package/dist/lib/workbench.plugin.d.ts +3 -1
  189. package/dist/lib/workbench.plugin.d.ts.map +1 -1
  190. package/dist/lib/workbench.plugin.js +13 -4
  191. package/dist/lib/workbench.plugin.js.map +1 -1
  192. package/dist/lib/workbench.service.d.ts +136 -40
  193. package/dist/lib/workbench.service.d.ts.map +1 -1
  194. package/dist/lib/workbench.service.js +985 -71
  195. package/dist/lib/workbench.service.js.map +1 -1
  196. package/package.json +6 -5
@@ -1,14 +1,17 @@
1
- import type { FilterDefinition, FilterMapping, FilterState, WorkbenchRoute } from '../contracts.js';
1
+ import type { FilterDefinition, FilterMapping, FilterState, PaginationPageSize, WorkbenchRoute } from '../contracts.js';
2
2
  import type { ProcurementActionResult, ProcurementDeleteEntityType, ProcurementMetrics, ProcurementPageProps, PurchaseContractReviewDraft, PurchaseOrderLineDraft, PurchaseOrderLineRow, PurchaseOrderRow, SupplierRow } from '../procurement-contracts.js';
3
+ import './procurement-contracts.css';
3
4
  export type { ProcurementPageProps } from '../procurement-contracts.js';
4
5
  export declare const procurementFilterMappings: {
5
6
  suppliers: Record<string, FilterMapping<SupplierRow>>;
6
7
  purchaseOrders: Record<string, FilterMapping<PurchaseOrderRow>>;
7
8
  purchaseLines: Record<string, FilterMapping<PurchaseOrderLineRow>>;
8
9
  };
9
- export declare function createProcurementQueryScheduler(query: (filters: FilterState) => void, debounceMs?: number): {
10
- schedule(type: FilterDefinition["type"], filters: FilterState): void;
11
- flush(filters: FilterState): void;
10
+ export declare function createProcurementQueryScheduler(query: (page: number, filters: FilterState, pageSize: PaginationPageSize) => void, debounceMs?: number): {
11
+ schedule(type: FilterDefinition["type"], filters: FilterState, pageSize?: PaginationPageSize): void;
12
+ flush(filters: FilterState, pageSize?: PaginationPageSize): void;
13
+ page(page: number, filters: FilterState, pageSize?: PaginationPageSize): void;
14
+ pageSize(pageSize: PaginationPageSize, filters: FilterState): void;
12
15
  cancel: () => void;
13
16
  };
14
17
  export declare function createProcurementActionApi({ onAction, onFileAction }: Pick<ProcurementPageProps, 'onAction' | 'onFileAction'>): {
@@ -33,4 +36,26 @@ export declare function ProcurementContextBar(props: {
33
36
  order: PurchaseOrderRow;
34
37
  onRouteChange: (route: WorkbenchRoute) => void;
35
38
  }): import("react/jsx-runtime").JSX.Element;
39
+ export declare function LineDialog({ state, order, busy, onAction, onClose, onSave }: {
40
+ state: {
41
+ mode: 'create' | 'review';
42
+ item: PurchaseOrderLineDraft;
43
+ } | null;
44
+ order: PurchaseOrderRow | null;
45
+ busy: boolean;
46
+ onAction: ProcurementPageProps['onAction'];
47
+ onClose: () => void;
48
+ onSave: (item: PurchaseOrderLineDraft) => Promise<void> | void;
49
+ }): import("react/jsx-runtime").JSX.Element | null;
50
+ type DeleteTarget = {
51
+ entityType: ProcurementDeleteEntityType;
52
+ item: SupplierRow | PurchaseOrderRow | PurchaseOrderLineRow;
53
+ };
54
+ export declare function DeleteDialog({ target, supplier, busy, onCancel, onConfirm }: {
55
+ target: DeleteTarget | null;
56
+ supplier: SupplierRow | null;
57
+ busy: boolean;
58
+ onCancel: () => void;
59
+ onConfirm: () => void;
60
+ }): import("react/jsx-runtime").JSX.Element;
36
61
  //# sourceMappingURL=procurement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"procurement.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-ui/pages/procurement.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAgC,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACnJ,OAAO,KAAK,EAGV,uBAAuB,EAEvB,2BAA2B,EAC3B,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAGhB,WAAW,EACZ,MAAM,6BAA6B,CAAA;AAKpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAkCvE,eAAO,MAAM,yBAAyB,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAA;IACrD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC/D,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAA;CAwBnE,CAAA;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,EAAE,UAAU,SAAM;mBAIpF,gBAAgB,CAAC,MAAM,CAAC,WAAW,WAAW;mBAK9C,WAAW;;EAG7B;AAED,wBAAgB,0BAA0B,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,cAAc,CAAC;mCAOrF,IAAI;iCAKZ,MAAM,SAAS,2BAA2B;4BAC/C,WAAW;iCACN,gBAAgB;uCACV,MAAM,SAAS,sBAAsB;gCAC5C,oBAAoB;+BACrB,2BAA2B,MAAM,MAAM;EAErE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,WAAW,SAA2B,GAAG,kBAAkB,CAqB7J;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,2BAA2B,GAAG,MAAM,EAAE,CA2B3F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAwD1D;AA2FD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CAAE,2CAY7P"}
1
+ {"version":3,"file":"procurement.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-ui/pages/procurement.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAe,kBAAkB,EAAmB,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACvK,OAAO,KAAK,EAGV,uBAAuB,EAEvB,2BAA2B,EAC3B,kBAAkB,EAClB,oBAAoB,EACpB,2BAA2B,EAC3B,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EAGhB,WAAW,EACZ,MAAM,6BAA6B,CAAA;AAMpC,OAAO,6BAA6B,CAAA;AAEpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAkCvE,eAAO,MAAM,yBAAyB,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAA;IACrD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC/D,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAA;CAwBnE,CAAA;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,kBAAkB,KAAK,IAAI,EAAE,UAAU,SAAM;mBAIhI,gBAAgB,CAAC,MAAM,CAAC,WAAW,WAAW,aAAY,kBAAkB;mBAK5E,WAAW,aAAY,kBAAkB;eAC7C,MAAM,WAAW,WAAW,aAAY,kBAAkB;uBAClD,kBAAkB,WAAW,WAAW;;EAG9D;AAED,wBAAgB,0BAA0B,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,cAAc,CAAC;mCAOrF,IAAI;iCAKZ,MAAM,SAAS,2BAA2B;4BAC/C,WAAW;iCACN,gBAAgB;uCACV,MAAM,SAAS,sBAAsB;gCAC5C,oBAAoB;+BACrB,2BAA2B,MAAM,MAAM;EAErE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,WAAW,SAAwD,GAAG,kBAAkB,CAqB1L;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,2BAA2B,GAAG,MAAM,EAAE,CA2B3F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAsD1D;AA2FD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAC;IAAC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CAAE,2CAO7P;AAwDD,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;QAAC,IAAI,EAAE,sBAAsB,CAAA;KAAE,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,IAAI,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAAE,kDAqC1U;AAiDD,KAAK,YAAY,GAAG;IAAE,UAAU,EAAE,2BAA2B,CAAC;IAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,GAAG,oBAAoB,CAAA;CAAE,CAAA;AAE5H,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,IAAI,CAAA;CAAE,2CAKtM"}
@@ -3,6 +3,9 @@ import React from 'react';
3
3
  import { DataGrid, FilterPanel, InfoNotice, RiskNotice } from '../components/data-grid.js';
4
4
  import { ConfirmDialog, FieldGrid, FormField, Modal, ReviewSplit } from '../components/forms.js';
5
5
  import { HsCodeLookupDialog } from './catalogs.js';
6
+ import { formatBeijingDateTime, formatBusinessDate } from '../date-time.js';
7
+ import { runNotifiedOperation } from '../toast-queue.js';
8
+ import './procurement-contracts.css';
6
9
  const PAGE_SIZE = 20;
7
10
  const sanctionLabels = { UNCHECKED: '未检查', CLEAR: '无命中', HIT: '命中提醒' };
8
11
  const hsStatusLabels = { UNCONFIRMED: '待审核', AI_CONFIRMED: 'AI确认', MANUALLY_CONFIRMED: '人工确认' };
@@ -21,7 +24,7 @@ const orderFilters = [
21
24
  { type: 'amount-range', key: 'contractAmountCny', label: '采购金额', currency: 'CNY' },
22
25
  { type: 'select', key: 'hasUnpaid', label: '未付款', options: booleanOptions() },
23
26
  { type: 'select', key: 'hasUnrefunded', label: '未退税', options: booleanOptions() },
24
- { type: 'select', key: 'linkStatus', label: '关联状态', options: [{ value: 'LINKED', label: '已关联' }, { value: 'UNLINKED', label: '未关联' }] }
27
+ { type: 'select', key: 'linkStatus', label: '已匹配客户合同', options: [{ value: 'LINKED', label: '已关联' }, { value: 'UNLINKED', label: '未关联' }] }
25
28
  ];
26
29
  const lineFilters = [
27
30
  { type: 'text', key: 'productName', label: '商品名称' },
@@ -59,15 +62,17 @@ export function createProcurementQueryScheduler(query, debounceMs = 300) {
59
62
  const cancel = () => { if (timer)
60
63
  clearTimeout(timer); timer = null; };
61
64
  return {
62
- schedule(type, filters) {
65
+ schedule(type, filters, pageSize = PAGE_SIZE) {
63
66
  cancel();
64
67
  if (type !== 'text') {
65
- query(filters);
68
+ query(1, filters, pageSize);
66
69
  return;
67
70
  }
68
- timer = setTimeout(() => { timer = null; query(filters); }, debounceMs);
71
+ timer = setTimeout(() => { timer = null; query(1, filters, pageSize); }, debounceMs);
69
72
  },
70
- flush(filters) { cancel(); query(filters); },
73
+ flush(filters, pageSize = PAGE_SIZE) { cancel(); query(1, filters, pageSize); },
74
+ page(page, filters, pageSize = PAGE_SIZE) { cancel(); query(page, filters, pageSize); },
75
+ pageSize(pageSize, filters) { cancel(); query(1, filters, pageSize); },
71
76
  cancel
72
77
  };
73
78
  }
@@ -91,7 +96,7 @@ export function createProcurementActionApi({ onAction, onFileAction }) {
91
96
  deleteEntity: (entityType, id) => mutate('delete_entity', { entityType, id })
92
97
  };
93
98
  }
94
- export function buildProcurementMetrics(orders, lines, currentYear = new Date().getFullYear()) {
99
+ export function buildProcurementMetrics(orders, lines, currentYear = Number(formatBeijingDateTime(new Date()).slice(0, 4))) {
95
100
  const currentYearPrefix = `${currentYear}-`;
96
101
  const productAmounts = new Map();
97
102
  const orderBalances = {};
@@ -170,37 +175,28 @@ export function ProcurementPage(props) {
170
175
  const [lineDialog, setLineDialog] = React.useState(null);
171
176
  const [deleteTarget, setDeleteTarget] = React.useState(null);
172
177
  const [draft, setDraft] = React.useState(props.reviewDraft);
173
- const [message, setMessage] = React.useState('');
174
178
  const closeDraft = React.useCallback(() => { setDraft(null); props.onReviewClose?.(); }, [props.onReviewClose]);
175
179
  const api = React.useMemo(() => createProcurementActionApi(props), [props.onAction, props.onFileAction]);
176
180
  const collectionKey = routeCollection(props.route);
177
181
  const definitions = collectionKey === 'suppliers' ? supplierFilters : collectionKey === 'purchaseOrders' ? orderFilters : lineFilters;
178
- const query = React.useCallback((page, nextFilters) => props.onQuery(collectionKey, {
182
+ const currentPageSize = collectionKey === 'suppliers' ? pagination(props.suppliers).pageSize : collectionKey === 'purchaseOrders' ? pagination(props.purchaseOrders).pageSize : pagination(props.purchaseLines).pageSize;
183
+ const query = React.useCallback((page, nextFilters, pageSize) => props.onQuery(collectionKey, {
179
184
  page,
180
- pageSize: PAGE_SIZE,
185
+ pageSize,
181
186
  filters: nextFilters,
182
187
  parameters: props.route.level === 'orders' ? { supplierId: props.route.supplierId } : props.route.level === 'lines' ? { supplierId: props.route.supplierId, purchaseOrderId: props.route.orderId } : undefined
183
188
  }), [collectionKey, props.onQuery, props.route]);
184
- const scheduler = React.useMemo(() => createProcurementQueryScheduler((nextFilters) => void query(1, nextFilters)), [query]);
189
+ const scheduler = React.useMemo(() => createProcurementQueryScheduler((page, nextFilters, pageSize) => void query(page, nextFilters, pageSize)), [query]);
185
190
  React.useEffect(() => () => scheduler.cancel(), [scheduler]);
186
191
  React.useEffect(() => { setDraft(props.reviewDraft); }, [props.reviewDraft]);
187
192
  const changeFilter = (key, value) => {
188
193
  const next = { ...filters, [key]: value };
189
194
  setFilters(next);
190
- scheduler.schedule(definitions.find((item) => item.key === key)?.type ?? 'text', next);
195
+ scheduler.schedule(definitions.find((item) => item.key === key)?.type ?? 'text', next, currentPageSize);
191
196
  };
192
- const reset = () => { setFilters({}); scheduler.flush({}); };
197
+ const reset = () => { setFilters({}); scheduler.flush({}, currentPageSize); };
193
198
  const execute = async (operation, success) => {
194
- setMessage('');
195
- try {
196
- await operation();
197
- setMessage(success);
198
- return true;
199
- }
200
- catch (cause) {
201
- setMessage(errorMessage(cause));
202
- return false;
203
- }
199
+ return runNotifiedOperation(async () => { await operation(); }, props.onNotify, { success, error: '采购操作失败' });
204
200
  };
205
201
  const saveLine = (item) => {
206
202
  if (stateIsCreate(lineDialog))
@@ -210,7 +206,7 @@ export function ProcurementPage(props) {
210
206
  return api.updatePurchaseLine({ ...item, id: item.id });
211
207
  };
212
208
  const reviewVisible = Boolean(props.importTask && (draft || props.importTask.duplicateContractError));
213
- return (_jsxs("section", { className: "tcw-procurement-page", "data-procurement-level": props.route.level, children: [message && _jsx("div", { className: "tcw-procurement-feedback", role: "status", children: message }), props.importTask && (props.importTask.status === 'FAILED' || props.importTask.status === 'TIMED_OUT') && _jsxs("div", { className: "tcw-procurement-feedback", role: "alert", children: [props.importTask.errorMessage || '采购合同解析失败', props.onRetryImport && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void props.onRetryImport?.(props.importTask.id), children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u65B0\u89E3\u6790"] })] }), props.route.level === 'suppliers' && _jsx(SupplierLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters), onReset: reset, onPage: (page) => void query(page, filters), onSupplierDialog: setSupplierDialog, onDelete: (item) => setDeleteTarget({ entityType: 'supplier', item }) }), props.route.level === 'orders' && _jsx(OrderLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters), onReset: reset, onPage: (page) => void query(page, filters), onOrderDialog: setOrderDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseOrder', item }) }), props.route.level === 'lines' && _jsx(LineLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters), onReset: reset, onPage: (page) => void query(page, filters), onLineDialog: setLineDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseLine', item }) }), _jsx(SupplierDialog, { state: supplierDialog, busy: props.busy, onClose: () => setSupplierDialog(null), onSave: (item) => execute(() => api.updateSupplier(item), '供应商主档已保存').then((saved) => { if (saved)
209
+ return (_jsxs("section", { className: "tcw-procurement-page tcw-procurement-prototype-surface", "data-procurement-level": props.route.level, children: [props.importTask && (props.importTask.status === 'FAILED' || props.importTask.status === 'TIMED_OUT') && _jsxs("div", { className: "tcw-procurement-import-failure", role: "alert", children: [props.importTask.errorMessage || '采购合同解析失败', props.onRetryImport && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void Promise.resolve(props.onRetryImport?.(props.importTask.id)).catch(() => undefined), children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u65B0\u89E3\u6790"] })] }), props.route.level === 'suppliers' && _jsx(SupplierLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onSupplierDialog: setSupplierDialog, onDelete: (item) => setDeleteTarget({ entityType: 'supplier', item }) }), props.route.level === 'orders' && _jsx(OrderLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onOrderDialog: setOrderDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseOrder', item }) }), props.route.level === 'lines' && _jsx(LineLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onLineDialog: setLineDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseLine', item }) }), _jsx(SupplierDialog, { state: supplierDialog, busy: props.busy, onClose: () => setSupplierDialog(null), onSave: (item) => execute(() => api.updateSupplier(item), '供应商主档已保存').then((saved) => { if (saved)
214
210
  setSupplierDialog(null); }) }), _jsx(OrderDialog, { state: orderDialog, lines: props.purchaseLines.items, busy: props.busy, onClose: () => setOrderDialog(null), onSave: (item) => execute(() => api.updatePurchaseOrder(item), '采购订单已保存').then((saved) => { if (saved)
215
211
  setOrderDialog(null); }) }), _jsx(LineDialog, { state: lineDialog, order: props.order ?? null, busy: props.busy, onAction: props.onAction, onClose: () => setLineDialog(null), onSave: (item) => execute(() => saveLine(item), '采购商品已保存').then((saved) => { if (saved)
216
212
  setLineDialog(null); }) }), _jsx(PurchaseContractReviewDialog, { open: reviewVisible, task: props.importTask, draft: draft, busy: props.busy, onChange: setDraft, onClose: closeDraft, onSave: (nextDraft) => execute(() => api.savePurchaseReview(props.importTask.id, nextDraft), '采购合同审核已保存').then((saved) => { if (saved)
@@ -221,66 +217,66 @@ export function ProcurementPage(props) {
221
217
  setDeleteTarget(null); });
222
218
  } })] }));
223
219
  }
224
- function SupplierLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onSupplierDialog, onDelete }) {
220
+ function SupplierLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onSupplierDialog, onDelete }) {
225
221
  const columns = [
226
- { type: 'custom', key: 'code', header: '供应商代码', width: 130, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-3-line", onClick: () => row.id && props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: row.id }), children: row.code || '—' }) },
222
+ { type: 'custom', key: 'code', header: '供应商代码', width: 130, title: (_, row) => row.code || undefined, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-3-line", title: row.code || undefined, onClick: () => row.id && props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: row.id }), children: row.code || '—' }) },
227
223
  { type: 'ellipsis', key: 'name', header: '供应商名称', width: 240, maxWidth: 240 },
228
224
  { type: 'text', key: 'shortName', header: '简称', width: 140 },
229
225
  { type: 'text', key: 'contactName', header: '联系人', width: 130 },
230
226
  { type: 'text', key: 'phone', header: '联系电话', width: 160 },
231
- { type: 'custom', key: 'sanctionStatus', header: '制裁风险', width: 110, render: (_, row) => _jsx(StatusTag, { tone: row.sanctionStatus === 'HIT' ? 'danger' : row.sanctionStatus === 'CLEAR' ? 'success' : 'muted', children: sanctionLabels[row.sanctionStatus ?? 'UNCHECKED'] }) },
232
- { type: 'text', key: 'createdAt', header: '创建时间', width: 170 }
227
+ { type: 'text', key: 'countryOrRegion', header: '国别/地区', width: 120, format: (value) => String(value || '') },
228
+ { type: 'custom', key: 'sanctionStatus', header: '制裁风险', width: 110, truncate: false, render: (_, row) => _jsx(StatusTag, { tone: row.sanctionStatus === 'HIT' ? 'danger' : row.sanctionStatus === 'CLEAR' ? 'success' : 'muted', children: sanctionLabels[row.sanctionStatus ?? 'UNCHECKED'] }) },
229
+ { type: 'text', key: 'createdAt', header: '创建时间', width: 170, truncate: false, format: formatBeijingDateTime }
233
230
  ];
234
- return _jsxs(_Fragment, { children: [_jsx(ProcurementLevelHeading, { title: "\u4F9B\u5E94\u5546\u5217\u8868", description: "\u4E00\u7EA7\u9875\u9762\uFF1A\u7BA1\u7406\u4F9B\u5E94\u5546\u4E3B\u6863\uFF0C\u5E76\u901A\u8FC7\u4F9B\u5E94\u5546\u4EE3\u7801\u8FDB\u5165\u8BE5\u4F9B\u5E94\u5546\u7684\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u91C7\u8D2D\u5408\u540C\u4E0A\u4F20\u540E\u81EA\u52A8\u5F52\u5165\u4F9B\u5E94\u5546\uFF1B\u540C\u540D\u4F9B\u5E94\u5546\u5F52\u5E76\u5230\u540C\u4E00\u4E3B\u6863\u3002\u70B9\u51FB\u4F9B\u5E94\u5546\u4EE3\u7801\u8FDB\u5165\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.suppliers.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.suppliers), onPageChange: onPage, emptyText: "\u6682\u65E0\u4F9B\u5E94\u5546\u6570\u636E", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onSupplierDialog({ mode: 'detail', item: row }), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onSupplierDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
231
+ return _jsxs(_Fragment, { children: [_jsx(ProcurementLevelHeading, { title: "\u4F9B\u5E94\u5546\u5217\u8868", description: "\u4E00\u7EA7\u9875\u9762\uFF1A\u7BA1\u7406\u4F9B\u5E94\u5546\u4E3B\u6863\uFF0C\u5E76\u901A\u8FC7\u4F9B\u5E94\u5546\u4EE3\u7801\u8FDB\u5165\u8BE5\u4F9B\u5E94\u5546\u7684\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u91C7\u8D2D\u5408\u540C\u4E0A\u4F20\u540E\u81EA\u52A8\u5F52\u5165\u4F9B\u5E94\u5546\uFF1B\u540C\u540D\u4F9B\u5E94\u5546\u5F52\u5E76\u5230\u540C\u4E00\u4E3B\u6863\u3002\u70B9\u51FB\u4F9B\u5E94\u5546\u4EE3\u7801\u8FDB\u5165\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.suppliers.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.suppliers), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u4F9B\u5E94\u5546\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onSupplierDialog({ mode: 'detail', item: row }), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onSupplierDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
235
232
  }
236
- function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onOrderDialog, onDelete }) {
233
+ function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onOrderDialog, onDelete }) {
237
234
  const metrics = props.metrics ?? buildProcurementMetrics(props.purchaseOrders.items, props.purchaseLines.items);
238
235
  const columns = [
239
- { type: 'custom', key: 'contractNo', header: '采购合同编号', width: 160, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-2-line", onClick: () => row.id && props.onRouteChange({ type: 'procurement', level: 'lines', supplierId: props.route.level === 'orders' ? props.route.supplierId : row.supplierId, orderId: row.id }), children: row.contractNo }) },
240
- { type: 'text', key: 'orderDate', header: '下单日期', width: 120 },
241
- { type: 'text', key: 'contractAmountCny', header: '采购金额(人民币)', width: 160, align: 'right', format: currency },
242
- { type: 'text', key: 'paidAmountCny', header: '已付款', width: 130, align: 'right', format: currency },
243
- { type: 'text', key: 'unpaidAmount', header: '未付款', width: 130, align: 'right', accessor: (row) => balance(metrics, row).unpaidAmountCny, format: currency },
244
- { type: 'text', key: 'refundedAmountCny', header: '已退税', width: 130, align: 'right', format: currency },
245
- { type: 'text', key: 'estimatedRefundAmountCny', header: '预计退税总额', width: 150, align: 'right', format: currency },
246
- { type: 'text', key: 'unrefundedAmount', header: '未退税', width: 130, align: 'right', accessor: (row) => balance(metrics, row).unrefundedAmountCny, format: currency },
236
+ { type: 'custom', key: 'contractNo', header: '采购合同编号', width: 160, title: (_, row) => row.contractNo, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-2-line", title: row.contractNo, onClick: () => row.id && props.onRouteChange({ type: 'procurement', level: 'lines', supplierId: props.route.level === 'orders' ? props.route.supplierId : row.supplierId, orderId: row.id }), children: row.contractNo }) },
237
+ { type: 'text', key: 'orderDate', header: '下单日期', width: 120, truncate: false, format: formatBusinessDate },
238
+ { type: 'text', key: 'contractAmountCny', header: '采购金额(人民币)', width: 160, align: 'right', truncate: false, format: currency },
239
+ { type: 'text', key: 'paidAmountCny', header: '已付款', width: 130, align: 'right', truncate: false, format: currency },
240
+ { type: 'text', key: 'unpaidAmount', header: '未付款', width: 130, align: 'right', truncate: false, accessor: (row) => balance(metrics, row).unpaidAmountCny, format: currency },
241
+ { type: 'text', key: 'refundedAmountCny', header: '已退税', width: 130, align: 'right', truncate: false, format: currency },
242
+ { type: 'text', key: 'estimatedRefundAmountCny', header: '预计退税总额', width: 150, align: 'right', truncate: false, format: currency },
243
+ { type: 'text', key: 'unrefundedAmount', header: '未退税', width: 130, align: 'right', truncate: false, accessor: (row) => balance(metrics, row).unrefundedAmountCny, format: currency },
247
244
  { type: 'text', key: 'customerContractNo', header: '匹配客户合同', width: 150, format: (value) => String(value || '未关联') }
248
245
  ];
249
- return _jsxs(_Fragment, { children: [props.supplier && _jsx(ProcurementContextBar, { level: "supplier", supplier: props.supplier, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u8BA2\u5355\u5217\u8868", description: "\u4E8C\u7EA7\u9875\u9762\uFF1A\u67E5\u770B\u5F53\u524D\u4F9B\u5E94\u5546\u7684\u91C7\u8D2D\u8BA2\u5355\uFF0C\u5E76\u901A\u8FC7\u91C7\u8D2D\u5408\u540C\u7F16\u53F7\u8FDB\u5165\u8BA2\u5355\u5546\u54C1\u5217\u8868\u3002" }), _jsx(MetricStrip, { metrics: metrics }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(DataGrid, { columns: columns, rows: props.purchaseOrders.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseOrders), onPageChange: onPage, emptyText: "\u6682\u65E0\u91C7\u8D2D\u8BA2\u5355\u6570\u636E", actionWidth: 160, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onOrderDialog({ mode: 'detail', item: row }), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onOrderDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
246
+ return _jsxs(_Fragment, { children: [props.supplier && _jsx(ProcurementContextBar, { level: "supplier", supplier: props.supplier, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u8BA2\u5355\u5217\u8868", description: "\u4E8C\u7EA7\u9875\u9762\uFF1A\u67E5\u770B\u5F53\u524D\u4F9B\u5E94\u5546\u7684\u91C7\u8D2D\u8BA2\u5355\uFF0C\u5E76\u901A\u8FC7\u91C7\u8D2D\u5408\u540C\u7F16\u53F7\u8FDB\u5165\u8BA2\u5355\u5546\u54C1\u5217\u8868\u3002" }), _jsx(MetricStrip, { metrics: metrics }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(DataGrid, { columns: columns, rows: props.purchaseOrders.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseOrders), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u8BA2\u5355\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onOrderDialog({ mode: 'detail', item: row }), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onOrderDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
250
247
  }
251
- function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onLineDialog, onDelete }) {
248
+ function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onLineDialog, onDelete }) {
252
249
  const columns = [
253
250
  { type: 'ellipsis', key: 'productName', header: '商品名称', width: 170, maxWidth: 170 },
254
251
  { type: 'ellipsis', key: 'productDescription', header: '商品描述', width: 190, maxWidth: 190 },
255
252
  { type: 'text', key: 'modelSpecification', header: '规格型号', width: 150 },
256
- { type: 'text', key: 'unit', header: '单位', width: 80 },
257
- { type: 'text', key: 'quantity', header: '数量', width: 110, align: 'right', format: number },
258
- { type: 'text', key: 'unitPriceTaxIncluded', header: '含税单价', width: 120, align: 'right', format: currency },
259
- { type: 'text', key: 'lineAmountTaxIncluded', header: '含税金额', width: 130, align: 'right', format: currency },
260
- { type: 'text', key: 'vatRate', header: '增值税率', width: 100, align: 'right', format: percent },
253
+ { type: 'text', key: 'unit', header: '单位', width: 80, truncate: false },
254
+ { type: 'text', key: 'quantity', header: '数量', width: 110, align: 'right', truncate: false, format: number },
255
+ { type: 'text', key: 'unitPriceTaxIncluded', header: '含税单价', width: 120, align: 'right', truncate: false, format: currency },
256
+ { type: 'text', key: 'lineAmountTaxIncluded', header: '含税金额', width: 130, align: 'right', truncate: false, format: currency },
257
+ { type: 'text', key: 'vatRate', header: '增值税率', width: 100, align: 'right', truncate: false, format: percent },
261
258
  { type: 'text', key: 'hsCode', header: '海关编码', width: 130 },
262
- { type: 'text', key: 'refundRate', header: '退税率', width: 90, align: 'right', format: percent },
263
- { type: 'custom', key: 'hsCodeStatus', header: '编码确认状态', width: 120, render: (_, row) => _jsx(StatusTag, { tone: row.hsCodeStatus === 'UNCONFIRMED' || !row.hsCodeStatus ? 'warning' : 'success', children: hsStatusLabels[row.hsCodeStatus ?? 'UNCONFIRMED'] }) },
264
- { type: 'text', key: 'complianceCategories', header: '命中合规分类', width: 150, format: (value) => Array.isArray(value) && value.length ? value.map((item) => complianceLabels[item]).join('、') : '无命中' },
265
- { type: 'text', key: 'sourceType', header: '来源类型', width: 110, format: (value) => sourceTypeLabels[value] }
259
+ { type: 'text', key: 'refundRate', header: '退税率', width: 90, align: 'right', truncate: false, format: percent },
260
+ { type: 'custom', key: 'hsCodeStatus', header: '编码确认状态', width: 120, truncate: false, render: (_, row) => _jsx(StatusTag, { tone: row.hsCodeStatus === 'UNCONFIRMED' || !row.hsCodeStatus ? 'warning' : 'success', children: hsStatusLabels[row.hsCodeStatus ?? 'UNCONFIRMED'] }) },
261
+ { type: 'text', key: 'complianceCategories', header: '命中合规分类', width: 150, format: (value) => Array.isArray(value) && value.length ? value.map((item) => complianceLabels[item]).join('、') : '无命中' }
266
262
  ];
267
263
  const newLine = { purchaseOrderId: props.order?.id ?? '', sourceType: 'MANUAL_ENTRY', productName: '', quantity: '', unitPriceTaxIncluded: '', lineAmountTaxIncluded: '', hsCodeStatus: 'UNCONFIRMED' };
268
- return _jsxs(_Fragment, { children: [_jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u5546\u54C1\u5217\u8868", description: "\u4E09\u7EA7\u9875\u9762\uFF1A\u67E5\u770B\u5F53\u524D\u91C7\u8D2D\u8BA2\u5355\u7684\u5546\u54C1\u660E\u7EC6\uFF0C\u53EF\u65B0\u589E\u3001\u5BA1\u6838\u548C\u903B\u8F91\u5220\u9664\u5546\u54C1\u3002", commands: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: props.busy || !props.order?.id, onClick: () => onLineDialog({ mode: 'create', item: newLine }), children: [_jsx("i", { className: "ri-add-line", "aria-hidden": "true" }), "\u65B0\u589E\u5546\u54C1"] }) }), props.supplier && props.order && _jsx(ProcurementContextBar, { level: "order", supplier: props.supplier, order: props.order, onRouteChange: props.onRouteChange }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u53EF\u901A\u8FC7\u5BA1\u6838\u5B8C\u5584\u82F1\u6587\u540D\u79F0\u3001\u6D77\u5173\u7F16\u7801\u3001\u9000\u7A0E\u7387\u548C\u98CE\u9669\u5339\u914D\u4FE1\u606F\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.purchaseLines.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseLines), onPageChange: onPage, emptyText: "\u6682\u65E0\u91C7\u8D2D\u5546\u54C1\u6570\u636E", actionWidth: 120, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onLineDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
264
+ return _jsxs(_Fragment, { children: [_jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u5546\u54C1\u5217\u8868", description: "\u4E09\u7EA7\u9875\u9762\uFF1A\u67E5\u770B\u5F53\u524D\u91C7\u8D2D\u8BA2\u5355\u7684\u5546\u54C1\u660E\u7EC6\uFF0C\u53EF\u65B0\u589E\u3001\u5BA1\u6838\u548C\u903B\u8F91\u5220\u9664\u5546\u54C1\u3002", commands: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: props.busy || !props.order?.id, onClick: () => onLineDialog({ mode: 'create', item: newLine }), children: [_jsx("i", { className: "ri-add-line", "aria-hidden": "true" }), "\u65B0\u589E\u5546\u54C1"] }) }), props.supplier && props.order && _jsx(ProcurementContextBar, { level: "order", supplier: props.supplier, order: props.order, onRouteChange: props.onRouteChange }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u53EF\u901A\u8FC7\u5BA1\u6838\u5B8C\u5584\u82F1\u6587\u540D\u79F0\u3001\u6D77\u5173\u7F16\u7801\u3001\u9000\u7A0E\u7387\u548C\u98CE\u9669\u5339\u914D\u4FE1\u606F\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.purchaseLines.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseLines), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u5546\u54C1\u6570\u636E", actionWidth: 120, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onLineDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
269
265
  }
270
266
  function ProcurementLevelHeading({ title, description, commands }) {
271
267
  return _jsxs("header", { className: "tcw-procurement-level-heading", children: [_jsxs("div", { children: [_jsx("h2", { children: title }), _jsx("p", { children: description })] }), commands && _jsx("div", { className: "tcw-procurement-toolbar", children: commands })] });
272
268
  }
273
269
  export function ProcurementContextBar(props) {
274
- return _jsxs("section", { className: "tcw-procurement-context", "aria-label": props.level === 'supplier' ? '供应商上下文' : '采购订单上下文', children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u4EE3\u7801", value: props.supplier.code }), _jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u540D\u79F0", value: props.supplier.name }), _jsx(ContextValue, { label: "\u8054\u7CFB\u4EBA", value: props.supplier.contactName }), _jsx(ContextValue, { label: "\u8054\u7CFB\u7535\u8BDD", value: props.supplier.phone }), props.level === 'order' && _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", value: props.order.contractNo }), _jsx(ContextValue, { label: "\u4E0B\u5355\u65E5\u671F", value: props.order.orderDate }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u91D1\u989D", value: currency(props.order.contractAmountCny) })] }), _jsxs("div", { className: "tcw-procurement-context-actions", children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'suppliers' }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u4F9B\u5E94\u5546\u5217\u8868"] }), props.level === 'order' && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: props.supplier.id ?? props.order.supplierId }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u8BA2\u5355\u5217\u8868"] })] })] });
270
+ return _jsxs("section", { className: `tcw-procurement-context tcw-procurement-context--${props.level}`, "aria-label": props.level === 'supplier' ? '供应商上下文' : '采购订单上下文', children: [props.level === 'supplier' ? _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u4EE3\u7801", value: props.supplier.code }), _jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u540D\u79F0", value: props.supplier.name }), _jsx(ContextValue, { label: "\u8054\u7CFB\u4EBA", value: props.supplier.contactName }), _jsx(ContextValue, { label: "\u8054\u7CFB\u7535\u8BDD", value: props.supplier.phone })] }) : _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546", value: props.supplier.name }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", value: props.order.contractNo }), _jsx(ContextValue, { label: "\u4E0B\u5355\u65E5\u671F", value: formatBusinessDate(props.order.orderDate) }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u91D1\u989D", value: currency(props.order.contractAmountCny) })] }), _jsx("div", { className: "tcw-procurement-context-actions", children: props.level === 'supplier' ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'suppliers' }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u4F9B\u5E94\u5546\u5217\u8868"] }) : _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: props.supplier.id ?? props.order.supplierId }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u8BA2\u5355\u5217\u8868"] }) })] });
275
271
  }
276
272
  function MetricStrip({ metrics }) {
277
273
  const items = [
278
- ['订单总数', number(metrics.totalOrderCount)],
279
- ['采购订单总额', currency(metrics.totalPurchaseAmountCny)],
280
- ['本年订单数', number(metrics.currentYearOrderCount)],
281
- ['本年采购金额', currency(metrics.currentYearPurchaseAmountCny)]
274
+ ['订单总数', number(metrics.totalOrderCount), 'ri-clipboard-line', 'blue'],
275
+ ['采购总金额', currency(metrics.totalPurchaseAmountCny), 'ri-wallet-3-line', 'green'],
276
+ ['本年订单数', number(metrics.currentYearOrderCount), 'ri-calendar-check-line', 'orange'],
277
+ ['本年采购金额', currency(metrics.currentYearPurchaseAmountCny), 'ri-bar-chart-grouped-line', 'violet']
282
278
  ];
283
- return _jsxs("section", { className: "tcw-procurement-metrics", "aria-label": "\u91C7\u8D2D\u7EDF\u8BA1", children: [items.map(([label, value]) => _jsxs("div", { children: [_jsx("span", { children: label }), _jsx("strong", { children: value })] }, label)), _jsxs("div", { className: "tcw-procurement-top", children: [_jsx("span", { children: "\u91C7\u8D2D\u5546\u54C1TOP5" }), metrics.topProducts.length ? _jsx("ol", { children: metrics.topProducts.map((item) => _jsxs("li", { children: [_jsx("span", { children: item.productName }), _jsx("strong", { children: currency(item.amountCny) })] }, item.productName)) }) : _jsx("small", { children: "\u6682\u65E0\u5546\u54C1\u6570\u636E" })] })] });
279
+ return _jsxs("section", { className: "tcw-procurement-metrics", "aria-label": "\u91C7\u8D2D\u7EDF\u8BA1", children: [items.map(([label, value, icon, tone]) => _jsxs("div", { className: `tcw-procurement-metric tcw-procurement-metric--${tone}`, children: [_jsx("i", { className: `${icon} tcw-procurement-metric-icon`, "aria-hidden": "true" }), _jsx("span", { children: label }), _jsx("strong", { children: value })] }, label)), _jsxs("div", { className: "tcw-procurement-top", children: [_jsx("span", { children: "\u91C7\u8D2D\u5546\u54C1TOP5" }), metrics.topProducts.length ? _jsx("ol", { children: metrics.topProducts.map((item) => _jsxs("li", { children: [_jsx("span", { children: item.productName }), _jsx("strong", { children: currency(item.amountCny) })] }, item.productName)) }) : _jsx("small", { children: "\u6682\u65E0\u5546\u54C1\u6570\u636E" })] })] });
284
280
  }
285
281
  function SupplierDialog({ state, busy, onClose, onSave }) {
286
282
  const [value, setValue] = React.useState(state?.item ?? null);
@@ -291,7 +287,7 @@ function SupplierDialog({ state, busy, onClose, onSave }) {
291
287
  const fields = [
292
288
  ['code', '供应商代码'], ['name', '供应商名称', true], ['shortName', '简称'], ['countryOrRegion', '国别/地区'], ['contactName', '联系人'], ['phone', '联系电话'], ['wechat', '微信'], ['email', '邮箱'], ['address', '地址'], ['taxNumber', '税号'], ['paymentAccount', '收款账号'], ['bankName', '开户行'], ['taxpayerRating', '纳税等级'], ['website', '公司网站'], ['businessProducts', '经营产品']
293
289
  ];
294
- return _jsxs(Modal, { open: true, title: state.mode === 'detail' ? '供应商详情' : '供应商详情审核', size: "wide", onClose: onClose, footer: disabled ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) : _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || !value.name.trim(), onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: [_jsx(FieldGrid, { columns: 2, children: fields.map(([key, label, required]) => _jsx(FormField, { label: label, required: required, full: key === 'address' || key === 'businessProducts', children: _jsx("input", { disabled: disabled, value: String(value[key] ?? ''), onChange: (event) => setValue({ ...value, [key]: event.target.value }) }) }, key)) }), _jsxs("div", { className: "tcw-procurement-audit-meta", children: [_jsx(StatusTag, { tone: value.sanctionStatus === 'HIT' ? 'danger' : 'success', children: sanctionLabels[value.sanctionStatus ?? 'UNCHECKED'] }), _jsxs("span", { children: ["\u6700\u8FD1\u5236\u88C1\u68C0\u67E5\uFF1A", value.complianceCheckedAt || '尚未检查'] }), _jsxs("span", { children: ["\u6700\u540E\u7F16\u8F91\uFF1A", value.updatedByName || '—', " ", value.updatedAt || ''] })] })] });
290
+ return _jsxs(Modal, { open: true, title: state.mode === 'detail' ? '供应商详情' : '供应商详情审核', size: "wide", onClose: onClose, footer: disabled ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) : _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || !value.name.trim(), onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: [_jsxs("p", { className: "tcw-procurement-dialog-kicker", children: ["\u6765\u6E90\uFF1A\u91C7\u8D2D\u5408\u540C\u5F52\u5E76 ", _jsxs("span", { children: ["\u6700\u8FD1\u5236\u88C1\u68C0\u67E5\uFF1A", value.complianceCheckedAt ? formatBeijingDateTime(value.complianceCheckedAt) : '尚未检查'] })] }), _jsxs("div", { className: "tcw-procurement-supplier-review", children: [_jsx(FieldGrid, { columns: 1, children: fields.map(([key, label, required]) => _jsx(FormField, { label: label, required: required, children: _jsx("input", { disabled: disabled || key === 'code', value: String(value[key] ?? ''), onChange: (event) => setValue({ ...value, [key]: event.target.value }) }) }, key)) }), _jsxs("aside", { children: [_jsx(FormField, { label: "\u5907\u6CE8", children: _jsx("textarea", { rows: 6, disabled: disabled, value: value.remarks ?? '', onChange: (event) => setValue({ ...value, remarks: event.target.value }) }) }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "\u5236\u88C1\u98CE\u9669" }), _jsx("dd", { children: _jsx(StatusTag, { tone: value.sanctionStatus === 'HIT' ? 'danger' : 'success', children: sanctionLabels[value.sanctionStatus ?? 'UNCHECKED'] }) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u521B\u5EFA\u4EBA" }), _jsx("dd", { children: value.createdByName || '' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u521B\u5EFA\u65F6\u95F4" }), _jsx("dd", { children: formatBeijingDateTime(value.createdAt) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u6700\u540E\u7F16\u8F91\u4EBA" }), _jsx("dd", { children: value.updatedByName || '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u6700\u540E\u7F16\u8F91\u65F6\u95F4" }), _jsx("dd", { children: formatBeijingDateTime(value.updatedAt) })] })] }), _jsx(InfoNotice, { children: "\u5236\u88C1\u98CE\u9669\u4E3A\u7CFB\u7EDF\u6700\u8FD1\u4E00\u6B21\u68C0\u67E5\u7ED3\u679C\uFF0C\u4FDD\u5B58\u540E\u5C06\u89E6\u53D1\u65B0\u7684\u5236\u88C1\u68C0\u67E5\u3002" })] })] })] });
295
291
  }
296
292
  function OrderDialog({ state, lines, busy, onClose, onSave }) {
297
293
  const [value, setValue] = React.useState(state?.item ?? null);
@@ -303,7 +299,7 @@ function OrderDialog({ state, lines, busy, onClose, onSave }) {
303
299
  const error = amount(value.paidAmountCny) > amount(value.contractAmountCny) ? '已付款不能超过采购订单总额' : amount(value.refundedAmountCny) > amount(estimated) ? '已退税不能超过预计退税总额' : '';
304
300
  return _jsxs(Modal, { open: true, title: disabled ? '采购订单详情' : '采购订单审核', size: "wide", onClose: onClose, footer: disabled ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) : _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || Boolean(error) || !value.orderDate, onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: [error && _jsx(RiskNotice, { children: error }), _jsxs(FieldGrid, { columns: 2, children: [_jsx(FormField, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", required: true, children: _jsx("input", { disabled: true, value: value.contractNo }) }), _jsx(FormField, { label: "\u4E0B\u5355\u65E5\u671F", required: true, children: _jsx("input", { type: "date", disabled: disabled, value: value.orderDate, onChange: (event) => setValue({ ...value, orderDate: event.target.value }) }) }), _jsx(MoneyField, { label: "\u91C7\u8D2D\u8BA2\u5355\u603B\u989D", disabled: disabled, value: value.contractAmountCny, onChange: (next) => setValue({ ...value, contractAmountCny: next }) }), _jsx(MoneyField, { label: "\u5DF2\u4ED8\u6B3E", disabled: disabled, value: value.paidAmountCny, onChange: (next) => setValue({ ...value, paidAmountCny: next }) }), _jsx(MoneyField, { label: "\u672A\u4ED8\u6B3E", disabled: true, value: String(Math.max(amount(value.contractAmountCny) - amount(value.paidAmountCny), 0)) }), _jsx(MoneyField, { label: "\u5DF2\u9000\u7A0E", disabled: disabled, value: value.refundedAmountCny, onChange: (next) => setValue({ ...value, refundedAmountCny: next }) }), _jsx(MoneyField, { label: "\u9884\u8BA1\u9000\u7A0E\u603B\u989D", disabled: true, value: estimated }), _jsx(MoneyField, { label: "\u672A\u9000\u7A0E", disabled: true, value: String(Math.max(amount(estimated) - amount(value.refundedAmountCny), 0)) }), _jsx(FormField, { label: "\u5339\u914D\u5BA2\u6237\u5408\u540C", children: _jsx("input", { disabled: true, value: value.customerContractNo || '未关联' }) }), _jsx(FormField, { label: "\u6765\u6E90\u6587\u4EF6", children: _jsx("input", { disabled: true, value: value.sourceFileName || '—' }) })] })] });
305
301
  }
306
- function LineDialog({ state, order, busy, onAction, onClose, onSave }) {
302
+ export function LineDialog({ state, order, busy, onAction, onClose, onSave }) {
307
303
  const [value, setValue] = React.useState(state?.item ?? null);
308
304
  const [hsOpen, setHsOpen] = React.useState(false);
309
305
  React.useEffect(() => setValue(state?.item ?? null), [state]);
@@ -317,7 +313,7 @@ function LineDialog({ state, order, busy, onAction, onClose, onSave }) {
317
313
  return Promise.resolve({ success: false, message: '采购商品仅支持海关编码查询' });
318
314
  return onAction(actionKey, input);
319
315
  };
320
- return _jsxs(_Fragment, { children: [_jsxs(Modal, { open: true, title: state.mode === 'create' ? '新增商品' : '采购商品审核', size: "wide", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || errors.length > 0, onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: [_jsxs("div", { className: "tcw-procurement-source-label", children: ["\u6765\u6E90\u7C7B\u578B\uFF1A", sourceTypeLabels[value.sourceType]] }), errors.length > 0 && _jsx(RiskNotice, { children: errors.join(';') }), _jsxs(FieldGrid, { columns: 2, children: [_jsx(TextField, { label: "\u5546\u54C1\u540D\u79F0", required: true, value: value.productName, onChange: (next) => setValue({ ...value, productName: next }) }), _jsx(TextField, { label: "\u5546\u54C1\u63CF\u8FF0", value: value.productDescription, onChange: (next) => setValue({ ...value, productDescription: next }) }), _jsx(TextField, { label: "\u89C4\u683C\u578B\u53F7", value: value.modelSpecification, onChange: (next) => setValue({ ...value, modelSpecification: next }) }), _jsx(TextField, { label: "\u5355\u4F4D", required: true, value: value.unit, onChange: (next) => setValue({ ...value, unit: next }) }), _jsx(TextField, { label: "\u6570\u91CF", required: true, type: "number", value: value.quantity, onChange: (next) => setValue({ ...value, quantity: next, lineAmountTaxIncluded: String(amount(next) * amount(value.unitPriceTaxIncluded)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u5355\u4EF7\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.unitPriceTaxIncluded, onChange: (next) => setValue({ ...value, unitPriceTaxIncluded: next, lineAmountTaxIncluded: String(amount(value.quantity) * amount(next)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.lineAmountTaxIncluded || String(computedAmount), onChange: (next) => setValue({ ...value, lineAmountTaxIncluded: next }) }), _jsx(TextField, { label: "\u589E\u503C\u7A0E\u7387", required: true, type: "number", value: value.vatRate, onChange: (next) => setValue({ ...value, vatRate: next }) }), _jsx(FormField, { label: "\u6D77\u5173\u7F16\u7801", children: _jsxs("div", { className: "tcw-procurement-inline-field", children: [_jsx("input", { value: value.hsCode ?? '', onChange: (event) => setValue({ ...value, hsCode: event.target.value }) }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => setHsOpen(true), children: [_jsx("i", { className: "ri-search-line", "aria-hidden": "true" }), "HS\u7F16\u7801\u67E5\u8BE2"] })] }) }), _jsx(FormField, { label: "\u7F16\u7801\u786E\u8BA4\u72B6\u6001", children: _jsx("select", { value: value.hsCodeStatus ?? 'UNCONFIRMED', onChange: (event) => setValue({ ...value, hsCodeStatus: event.target.value }), children: enumOptions(hsStatusLabels).map((item) => _jsx("option", { value: item.value, children: item.label }, item.value)) }) }), _jsx(TextField, { label: "\u9000\u7A0E\u7387", required: true, type: "number", value: value.refundRate, onChange: (next) => setValue({ ...value, refundRate: next }) }), _jsx(TextField, { label: "\u9000\u7A0E\u653F\u7B56\u4F9D\u636E", required: true, value: value.refundPolicyReference, onChange: (next) => setValue({ ...value, refundPolicyReference: next }) }), _jsx(TextField, { label: "\u653F\u7B56\u65E5\u671F", required: true, type: "date", value: value.refundPolicyDate, onChange: (next) => setValue({ ...value, refundPolicyDate: next, refundValidationMethod: 'REVIEW_CONFIRMED' }) }), _jsx(TextField, { label: "\u82F1\u6587\u540D\u79F0", value: value.englishName, onChange: (next) => setValue({ ...value, englishName: next }) })] })] }), _jsx(HsCodeLookupDialog, { open: hsOpen, initialQuery: value.hsCode || value.englishName || value.productName, onAction: hsAction, onClose: () => setHsOpen(false) })] });
316
+ return _jsxs(_Fragment, { children: [_jsx(Modal, { open: true, title: state.mode === 'create' ? '新增商品' : '采购商品审核', size: "wide", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || errors.length > 0, onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: _jsxs("div", { className: "tcw-procurement-line-form", children: [_jsxs("div", { className: "tcw-procurement-source-label", children: ["\u6765\u6E90\u7C7B\u578B\uFF1A", sourceTypeLabels[value.sourceType]] }), state.mode === 'review' && errors.length > 0 && _jsx(RiskNotice, { children: errors.join(';') }), _jsxs(FieldGrid, { columns: 2, children: [_jsx(TextField, { label: "\u5546\u54C1\u540D\u79F0", required: true, value: value.productName, onChange: (next) => setValue({ ...value, productName: next }) }), _jsx(TextField, { label: "\u5546\u54C1\u63CF\u8FF0", value: value.productDescription, onChange: (next) => setValue({ ...value, productDescription: next }) }), _jsx(TextField, { label: "\u89C4\u683C\u578B\u53F7", value: value.modelSpecification, onChange: (next) => setValue({ ...value, modelSpecification: next }) }), _jsx(TextField, { label: "\u5355\u4F4D", required: true, value: value.unit, onChange: (next) => setValue({ ...value, unit: next }) }), _jsx(TextField, { label: "\u6570\u91CF", required: true, type: "number", value: value.quantity, onChange: (next) => setValue({ ...value, quantity: next, lineAmountTaxIncluded: String(amount(next) * amount(value.unitPriceTaxIncluded)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u5355\u4EF7\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.unitPriceTaxIncluded, onChange: (next) => setValue({ ...value, unitPriceTaxIncluded: next, lineAmountTaxIncluded: String(amount(value.quantity) * amount(next)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.lineAmountTaxIncluded || String(computedAmount), onChange: (next) => setValue({ ...value, lineAmountTaxIncluded: next }) }), _jsx(TextField, { label: "\u589E\u503C\u7A0E\u7387", required: true, type: "number", value: value.vatRate, onChange: (next) => setValue({ ...value, vatRate: next }) }), _jsx(FormField, { label: "\u6D77\u5173\u7F16\u7801", children: _jsxs("div", { className: "tcw-procurement-inline-field", children: [_jsx("input", { value: value.hsCode ?? '', onChange: (event) => setValue({ ...value, hsCode: event.target.value }) }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => setHsOpen(true), children: [_jsx("i", { className: "ri-search-line", "aria-hidden": "true" }), "HS\u7F16\u7801\u67E5\u8BE2"] })] }) }), _jsx(FormField, { label: "\u7F16\u7801\u786E\u8BA4\u72B6\u6001", children: _jsx("div", { className: "tcw-procurement-line-status", children: enumOptions(hsStatusLabels).map((item) => _jsxs("label", { children: [_jsx("input", { type: "radio", name: "line-hs-code-status", value: item.value, checked: (value.hsCodeStatus ?? 'UNCONFIRMED') === item.value, onChange: () => setValue({ ...value, hsCodeStatus: item.value }) }), item.label] }, item.value)) }) }), _jsx(TextField, { label: "\u9000\u7A0E\u7387", required: true, type: "number", value: value.refundRate, onChange: (next) => setValue({ ...value, refundRate: next }) }), _jsx(TextField, { label: "\u9000\u7A0E\u653F\u7B56\u4F9D\u636E", required: true, value: value.refundPolicyReference, onChange: (next) => setValue({ ...value, refundPolicyReference: next }) }), _jsx(TextField, { label: "\u653F\u7B56\u65E5\u671F", required: true, type: "date", value: value.refundPolicyDate, onChange: (next) => setValue({ ...value, refundPolicyDate: next, refundValidationMethod: 'REVIEW_CONFIRMED' }) }), _jsx(TextField, { label: "\u82F1\u6587\u540D\u79F0", value: value.englishName, onChange: (next) => setValue({ ...value, englishName: next }) })] })] }) }), _jsx(HsCodeLookupDialog, { open: hsOpen, initialQuery: value.hsCode || value.englishName || value.productName, onAction: hsAction, onClose: () => setHsOpen(false) })] });
321
317
  }
322
318
  function PurchaseContractReviewDialog({ open, task, draft, busy, onChange, onClose, onSave }) {
323
319
  const errors = draft ? validatePurchaseContractReview(draft) : [];
@@ -338,15 +334,15 @@ function SourcePreview({ draft, task }) {
338
334
  const location = draft.lines.find(line => line.sourceContent)?.sourceLocation;
339
335
  return _jsxs("div", { className: "tcw-procurement-source", children: [_jsx("strong", { children: draft.sourceFileName || task?.sourceFileName || '采购合同源文件' }), draft.sourceFileUrl ? _jsx("iframe", { src: draft.sourceFileUrl, title: "\u91C7\u8D2D\u5408\u540C\u6E90\u6587\u4EF6" }) : explicit ? _jsxs(_Fragment, { children: [_jsx("small", { children: location || '来源位置未标注' }), _jsx("pre", { children: explicit })] }) : _jsx("pre", { children: "\u6E90\u6587\u4EF6\u6682\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8" })] });
340
336
  }
341
- function DeleteDialog({ target, supplier, busy, onCancel, onConfirm }) {
337
+ export function DeleteDialog({ target, supplier, busy, onCancel, onConfirm }) {
342
338
  const isOrder = target?.entityType === 'purchaseOrder';
343
339
  const order = isOrder ? target.item : null;
344
340
  const name = target?.entityType === 'supplier' ? target.item.name : target?.entityType === 'purchaseOrder' ? target.item.contractNo : target?.item?.productName;
345
- return _jsx(ConfirmDialog, { open: Boolean(target), title: isOrder ? '确认删除采购订单?' : `确认删除${target?.entityType === 'supplier' ? '供应商' : '采购商品'}?`, danger: true, confirmText: "\u786E\u8BA4\u5220\u9664", confirmDisabled: busy, onCancel: onCancel, onConfirm: onConfirm, message: _jsxs("div", { className: "tcw-procurement-delete-summary", children: [_jsx("strong", { children: name || '当前记录' }), isOrder && _jsxs(_Fragment, { children: [_jsxs("span", { children: ["\u4F9B\u5E94\u5546\uFF1A", supplier?.name || '—'] }), _jsxs("span", { children: ["\u5546\u54C1\u6761\u6570\uFF1A", order?.lineCount ?? 0] }), _jsxs("span", { children: ["\u91C7\u8D2D\u91D1\u989D\uFF1A", currency(order?.contractAmountCny)] }), _jsxs("span", { children: ["\u5DF2\u4ED8\u6B3E\uFF1A", currency(order?.paidAmountCny)] }), _jsxs("span", { children: ["\u5DF2\u9000\u7A0E\uFF1A", currency(order?.refundedAmountCny)] }), _jsx("b", { children: "\u5220\u9664\u540E\u5C06\u540C\u65F6\u903B\u8F91\u5220\u9664\u8BE5\u8BA2\u5355\u53CA\u5168\u90E8\u5546\u54C1\u660E\u7EC6\uFF1B\u91CD\u65B0\u4E0A\u4F20\u540C\u7F16\u53F7\u5408\u540C\u4F1A\u4F5C\u4E3A\u5168\u65B0\u8BB0\u5F55\uFF0C\u4E0D\u8FC1\u79FB\u65E7\u8BA2\u5355\u4EBA\u5DE5\u6570\u636E\u3002" })] })] }) });
341
+ return _jsx(ConfirmDialog, { open: Boolean(target), title: isOrder ? '确认删除采购订单?' : `确认删除${target?.entityType === 'supplier' ? '供应商' : '采购商品'}?`, danger: true, confirmText: "\u786E\u8BA4\u5220\u9664", confirmDisabled: busy, onCancel: onCancel, onConfirm: onConfirm, message: isOrder ? _jsxs("div", { className: "tcw-procurement-delete-summary is-order", children: [_jsxs("strong", { className: "tcw-procurement-delete-warning", children: [_jsx("i", { className: "ri-error-warning-fill", "aria-hidden": "true" }), "\u5220\u9664\u540E\u5C06\u903B\u8F91\u5220\u9664\u8BE5\u91C7\u8D2D\u8BA2\u5355\u53CA\u5176\u5168\u90E8\u5546\u54C1\u660E\u7EC6\uFF0C\u8BF7\u8C28\u614E\u64CD\u4F5C\u3002"] }), _jsxs("dl", { className: "tcw-procurement-delete-details", children: [_jsxs("div", { children: [_jsx("dt", { children: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7" }), _jsx("dd", { children: name || '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u4F9B\u5E94\u5546" }), _jsx("dd", { children: supplier?.name || '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5546\u54C1\u6761\u6570" }), _jsx("dd", { children: order?.lineCount ?? 0 })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u91C7\u8D2D\u91D1\u989D" }), _jsx("dd", { children: currency(order?.contractAmountCny) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5DF2\u4ED8\u6B3E / \u5DF2\u9000\u7A0E" }), _jsxs("dd", { children: [currency(order?.paidAmountCny), " / ", currency(order?.refundedAmountCny)] })] })] }), _jsxs("p", { className: "tcw-procurement-delete-info", children: [_jsx("i", { className: "ri-information-line", "aria-hidden": "true" }), "\u91CD\u65B0\u4E0A\u4F20\u540C\u7F16\u53F7\u5408\u540C\u4F1A\u4F5C\u4E3A\u5168\u65B0\u8BB0\u5F55\uFF0C\u4E0D\u8FC1\u79FB\u65E7\u8BA2\u5355\u4EBA\u5DE5\u6570\u636E\u3002"] })] }) : _jsx("div", { className: "tcw-procurement-delete-summary", children: _jsx("strong", { children: name || '当前记录' }) }) });
346
342
  }
347
343
  function ReviewSection({ title, children }) { return _jsxs("section", { className: "tcw-procurement-review-section", children: [_jsx("h3", { children: title }), children] }); }
348
344
  function ContextValue({ label, value }) { return _jsxs("div", { children: [_jsx("span", { children: label }), _jsx("strong", { children: String(value || '—') })] }); }
349
- function LinkButton({ children, icon, onClick }) { return _jsxs("button", { type: "button", className: "tcw-procurement-link", onClick: onClick, children: [_jsx("i", { className: icon, "aria-hidden": "true" }), children] }); }
345
+ function LinkButton({ children, icon, title, onClick }) { return _jsxs("button", { type: "button", className: "tcw-procurement-link", title: title, onClick: onClick, children: [_jsx("i", { className: icon, "aria-hidden": "true" }), children] }); }
350
346
  function StatusTag({ children, tone }) { return _jsx("span", { className: `tcw-procurement-status tcw-procurement-status--${tone}`, children: children }); }
351
347
  function TextField({ label, value, required, type = 'text', onChange }) { return _jsx(FormField, { label: label, required: required, children: _jsx("input", { type: type, value: String(value ?? ''), onChange: (event) => onChange(event.target.value) }) }); }
352
348
  function MoneyField({ label, value, disabled, onChange }) { return _jsx(FormField, { label: label, children: _jsx("input", { type: "number", disabled: disabled, value: String(value ?? ''), onChange: (event) => onChange?.(event.target.value) }) }); }
@@ -362,9 +358,8 @@ function currency(value) { return `¥${amount(value).toLocaleString('zh-CN', { m
362
358
  function number(value) { return amount(value).toLocaleString('zh-CN'); }
363
359
  function percent(value) { return `${(rate(value) * 100).toLocaleString('zh-CN', { maximumFractionDigits: 4 })}%`; }
364
360
  function booleanOptions() { return [{ value: 'true', label: '是' }, { value: 'false', label: '否' }]; }
365
- function enumOptions(labels) { return Object.entries(labels).map(([value, label]) => ({ value, label: String(label) })); }
361
+ function enumOptions(labels) { return Object.entries(labels).map(([value, label]) => ({ value, label })); }
366
362
  function pagination(collection) { const total = collection.total ?? collection.items.length; const pageSize = collection.pageSize ?? PAGE_SIZE; return { page: collection.page ?? 1, pageSize, total, pageCount: Math.max(1, Math.ceil(total / pageSize)) }; }
367
363
  function assertSucceeded(result) { if (result.success === false)
368
364
  throw new Error(result.message || '采购操作失败'); }
369
- function errorMessage(cause) { return cause instanceof Error ? cause.message : '采购操作失败'; }
370
365
  //# sourceMappingURL=procurement.js.map