@endora-commerce/mod-quote-requests 0.100.0

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 (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/index.d.ts +26 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +56 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/RfqCreatePage.d.ts +9 -0
  8. package/dist/admin/pages/RfqCreatePage.d.ts.map +1 -0
  9. package/dist/admin/pages/RfqCreatePage.js +163 -0
  10. package/dist/admin/pages/RfqCreatePage.js.map +1 -0
  11. package/dist/admin/pages/RfqDetail.d.ts +9 -0
  12. package/dist/admin/pages/RfqDetail.d.ts.map +1 -0
  13. package/dist/admin/pages/RfqDetail.js +399 -0
  14. package/dist/admin/pages/RfqDetail.js.map +1 -0
  15. package/dist/admin/pages/RfqList.d.ts +9 -0
  16. package/dist/admin/pages/RfqList.d.ts.map +1 -0
  17. package/dist/admin/pages/RfqList.js +214 -0
  18. package/dist/admin/pages/RfqList.js.map +1 -0
  19. package/dist/admin/validity.d.ts +52 -0
  20. package/dist/admin/validity.d.ts.map +1 -0
  21. package/dist/admin/validity.js +61 -0
  22. package/dist/admin/validity.js.map +1 -0
  23. package/dist/backend/entities/quote-request-event.entity.d.ts +24 -0
  24. package/dist/backend/entities/quote-request-event.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/quote-request-event.entity.js +67 -0
  26. package/dist/backend/entities/quote-request-event.entity.js.map +1 -0
  27. package/dist/backend/entities/quote-request-item.entity.d.ts +34 -0
  28. package/dist/backend/entities/quote-request-item.entity.d.ts.map +1 -0
  29. package/dist/backend/entities/quote-request-item.entity.js +119 -0
  30. package/dist/backend/entities/quote-request-item.entity.js.map +1 -0
  31. package/dist/backend/entities/quote-request-notification-event.entity.d.ts +23 -0
  32. package/dist/backend/entities/quote-request-notification-event.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/quote-request-notification-event.entity.js +72 -0
  34. package/dist/backend/entities/quote-request-notification-event.entity.js.map +1 -0
  35. package/dist/backend/entities/quote-request-revision.entity.d.ts +35 -0
  36. package/dist/backend/entities/quote-request-revision.entity.d.ts.map +1 -0
  37. package/dist/backend/entities/quote-request-revision.entity.js +67 -0
  38. package/dist/backend/entities/quote-request-revision.entity.js.map +1 -0
  39. package/dist/backend/entities/quote-request.entity.d.ts +90 -0
  40. package/dist/backend/entities/quote-request.entity.d.ts.map +1 -0
  41. package/dist/backend/entities/quote-request.entity.js +191 -0
  42. package/dist/backend/entities/quote-request.entity.js.map +1 -0
  43. package/dist/backend/index.d.ts +120 -0
  44. package/dist/backend/index.d.ts.map +1 -0
  45. package/dist/backend/index.js +361 -0
  46. package/dist/backend/index.js.map +1 -0
  47. package/dist/backend/plugin.d.ts +95 -0
  48. package/dist/backend/plugin.d.ts.map +1 -0
  49. package/dist/backend/plugin.js +152 -0
  50. package/dist/backend/plugin.js.map +1 -0
  51. package/dist/backend/routes.admin.d.ts +13 -0
  52. package/dist/backend/routes.admin.d.ts.map +1 -0
  53. package/dist/backend/routes.admin.js +82 -0
  54. package/dist/backend/routes.admin.js.map +1 -0
  55. package/dist/backend/routes.customer.d.ts +18 -0
  56. package/dist/backend/routes.customer.d.ts.map +1 -0
  57. package/dist/backend/routes.customer.js +98 -0
  58. package/dist/backend/routes.customer.js.map +1 -0
  59. package/dist/backend/routes.sales-reps.d.ts +12 -0
  60. package/dist/backend/routes.sales-reps.d.ts.map +1 -0
  61. package/dist/backend/routes.sales-reps.js +57 -0
  62. package/dist/backend/routes.sales-reps.js.map +1 -0
  63. package/dist/backend/services/order-completion-reactor.d.ts +32 -0
  64. package/dist/backend/services/order-completion-reactor.d.ts.map +1 -0
  65. package/dist/backend/services/order-completion-reactor.js +57 -0
  66. package/dist/backend/services/order-completion-reactor.js.map +1 -0
  67. package/dist/backend/services/quote-request-business-id-generator.d.ts +46 -0
  68. package/dist/backend/services/quote-request-business-id-generator.d.ts.map +1 -0
  69. package/dist/backend/services/quote-request-business-id-generator.js +69 -0
  70. package/dist/backend/services/quote-request-business-id-generator.js.map +1 -0
  71. package/dist/backend/services/quote-request-read-port.d.ts +36 -0
  72. package/dist/backend/services/quote-request-read-port.d.ts.map +1 -0
  73. package/dist/backend/services/quote-request-read-port.js +96 -0
  74. package/dist/backend/services/quote-request-read-port.js.map +1 -0
  75. package/dist/backend/services/raised-on-channel.d.ts +23 -0
  76. package/dist/backend/services/raised-on-channel.d.ts.map +1 -0
  77. package/dist/backend/services/raised-on-channel.js +26 -0
  78. package/dist/backend/services/raised-on-channel.js.map +1 -0
  79. package/dist/backend/services/rfq-admin-service.d.ts +75 -0
  80. package/dist/backend/services/rfq-admin-service.d.ts.map +1 -0
  81. package/dist/backend/services/rfq-admin-service.js +487 -0
  82. package/dist/backend/services/rfq-admin-service.js.map +1 -0
  83. package/dist/backend/services/rfq-event-service.d.ts +32 -0
  84. package/dist/backend/services/rfq-event-service.d.ts.map +1 -0
  85. package/dist/backend/services/rfq-event-service.js +28 -0
  86. package/dist/backend/services/rfq-event-service.js.map +1 -0
  87. package/dist/backend/services/rfq-expiry-worker.d.ts +50 -0
  88. package/dist/backend/services/rfq-expiry-worker.d.ts.map +1 -0
  89. package/dist/backend/services/rfq-expiry-worker.js +70 -0
  90. package/dist/backend/services/rfq-expiry-worker.js.map +1 -0
  91. package/dist/backend/services/rfq-notification-service.d.ts +36 -0
  92. package/dist/backend/services/rfq-notification-service.d.ts.map +1 -0
  93. package/dist/backend/services/rfq-notification-service.js +52 -0
  94. package/dist/backend/services/rfq-notification-service.js.map +1 -0
  95. package/dist/backend/services/rfq-revision-service.d.ts +70 -0
  96. package/dist/backend/services/rfq-revision-service.d.ts.map +1 -0
  97. package/dist/backend/services/rfq-revision-service.js +120 -0
  98. package/dist/backend/services/rfq-revision-service.js.map +1 -0
  99. package/dist/backend/services/rfq-service.d.ts +140 -0
  100. package/dist/backend/services/rfq-service.d.ts.map +1 -0
  101. package/dist/backend/services/rfq-service.js +786 -0
  102. package/dist/backend/services/rfq-service.js.map +1 -0
  103. package/dist/backend/services/sales-channel-attributions.d.ts +38 -0
  104. package/dist/backend/services/sales-channel-attributions.d.ts.map +1 -0
  105. package/dist/backend/services/sales-channel-attributions.js +48 -0
  106. package/dist/backend/services/sales-channel-attributions.js.map +1 -0
  107. package/dist/manifest.d.ts +203 -0
  108. package/dist/manifest.d.ts.map +1 -0
  109. package/dist/manifest.js +277 -0
  110. package/dist/manifest.js.map +1 -0
  111. package/dist/migrations/20260424T190112_quote_requests_init.d.ts +19 -0
  112. package/dist/migrations/20260424T190112_quote_requests_init.d.ts.map +1 -0
  113. package/dist/migrations/20260424T190112_quote_requests_init.js +65 -0
  114. package/dist/migrations/20260424T190112_quote_requests_init.js.map +1 -0
  115. package/dist/migrations/20260503T141344_quote_requests_workflow.d.ts +37 -0
  116. package/dist/migrations/20260503T141344_quote_requests_workflow.d.ts.map +1 -0
  117. package/dist/migrations/20260503T141344_quote_requests_workflow.js +251 -0
  118. package/dist/migrations/20260503T141344_quote_requests_workflow.js.map +1 -0
  119. package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.d.ts +10 -0
  120. package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.d.ts.map +1 -0
  121. package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.js +16 -0
  122. package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.js.map +1 -0
  123. package/dist/migrations/20260611T140417_quote_requests_business_id.d.ts +24 -0
  124. package/dist/migrations/20260611T140417_quote_requests_business_id.d.ts.map +1 -0
  125. package/dist/migrations/20260611T140417_quote_requests_business_id.js +34 -0
  126. package/dist/migrations/20260611T140417_quote_requests_business_id.js.map +1 -0
  127. package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.d.ts +21 -0
  128. package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.d.ts.map +1 -0
  129. package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.js +29 -0
  130. package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.js.map +1 -0
  131. package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.d.ts +12 -0
  132. package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.d.ts.map +1 -0
  133. package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.js +16 -0
  134. package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.js.map +1 -0
  135. package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.d.ts +34 -0
  136. package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.d.ts.map +1 -0
  137. package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.js +57 -0
  138. package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.js.map +1 -0
  139. package/dist/migrations/index.d.ts +53 -0
  140. package/dist/migrations/index.d.ts.map +1 -0
  141. package/dist/migrations/index.js +61 -0
  142. package/dist/migrations/index.js.map +1 -0
  143. package/dist/migrations/status-mapping.d.ts +18 -0
  144. package/dist/migrations/status-mapping.d.ts.map +1 -0
  145. package/dist/migrations/status-mapping.js +28 -0
  146. package/dist/migrations/status-mapping.js.map +1 -0
  147. package/docs/quote_requests.md +168 -0
  148. package/i18n/en.json +14 -0
  149. package/i18n/pl.json +14 -0
  150. package/package.json +99 -0
  151. package/tailwind.css +14 -0
@@ -0,0 +1,13 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { RfqAdminService, AdminContext } from './services/rfq-admin-service.js';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ export interface AdminContextResolver {
5
+ (request: FastifyRequest): Promise<AdminContext>;
6
+ }
7
+ export interface QuoteRequestsAdminDeps {
8
+ adminService: RfqAdminService;
9
+ requireAdmin: RequireAdminFactory;
10
+ resolveAdminContext: AdminContextResolver;
11
+ }
12
+ export declare function registerQuoteRequestsAdminRoutes(app: FastifyInstance, deps: QuoteRequestsAdminDeps): Promise<void>;
13
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAC;AAS7E,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAwB,MAAM,iCAAiC,CAAC;AAC3G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,eAAe,CAAC;IAC9B,YAAY,EAAE,mBAAmB,CAAC;IAClC,mBAAmB,EAAE,oBAAoB,CAAC;CAC3C;AAcD,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAqGf"}
@@ -0,0 +1,82 @@
1
+ import { adminApproveQuoteRequestSchema, adminAssignQuoteRequestSchema, adminCancelQuoteRequestSchema, adminCreateQuoteRequestSchema, adminPatchQuoteRequestSchema, rfqStatusSchema, } from '@endora-commerce/contracts';
2
+ function parseIfMatch(request) {
3
+ const header = request.headers['if-match'];
4
+ if (!header || typeof header !== 'string')
5
+ return null;
6
+ const trimmed = header.replace(/^"|"$/g, '');
7
+ const n = Number.parseInt(trimmed, 10);
8
+ return Number.isFinite(n) ? n : null;
9
+ }
10
+ function setEtag(reply, version) {
11
+ reply.header('etag', `"${version}"`);
12
+ }
13
+ export async function registerQuoteRequestsAdminRoutes(app, deps) {
14
+ const { adminService, requireAdmin, resolveAdminContext } = deps;
15
+ const guard = requireAdmin('rfqs:handle');
16
+ app.get('/api/v1/admin/quote-requests', { preHandler: guard }, async (request) => {
17
+ const ctx = await resolveAdminContext(request);
18
+ const q = (request.query ?? {});
19
+ const status = q['status']
20
+ ? q['status']
21
+ .split(',')
22
+ .map((s) => s.trim())
23
+ .filter((s) => rfqStatusSchema.safeParse(s).success)
24
+ : undefined;
25
+ const scope = q['assignmentScope'] ?? undefined;
26
+ const data = await adminService.listAll(ctx, {
27
+ ...(q['organizationId'] ? { organizationId: q['organizationId'] } : {}),
28
+ ...(status && status.length > 0 ? { status: status } : {}),
29
+ ...(scope ? { scope } : {}),
30
+ });
31
+ return {
32
+ data,
33
+ pagination: { limit: 50, nextCursor: null, hasMore: false },
34
+ };
35
+ });
36
+ app.get('/api/v1/admin/quote-requests/:id', { preHandler: guard }, async (request, reply) => {
37
+ const ctx = await resolveAdminContext(request);
38
+ const rfq = await adminService.getById(ctx, request.params.id);
39
+ setEtag(reply, rfq.version);
40
+ return { data: rfq };
41
+ });
42
+ app.post('/api/v1/admin/quote-requests', { preHandler: guard, schema: { body: adminCreateQuoteRequestSchema } }, async (request, reply) => {
43
+ const ctx = await resolveAdminContext(request);
44
+ const body = adminCreateQuoteRequestSchema.parse(request.body);
45
+ const rfq = await adminService.createOnBehalf(ctx, body);
46
+ setEtag(reply, rfq.version);
47
+ reply.code(201);
48
+ return { data: rfq };
49
+ });
50
+ app.patch('/api/v1/admin/quote-requests/:id', { preHandler: guard, schema: { body: adminPatchQuoteRequestSchema } }, async (request, reply) => {
51
+ const ctx = await resolveAdminContext(request);
52
+ const body = adminPatchQuoteRequestSchema.parse(request.body);
53
+ const expectedVersion = parseIfMatch(request);
54
+ const rfq = await adminService.modify(ctx, request.params.id, expectedVersion, body);
55
+ setEtag(reply, rfq.version);
56
+ return { data: rfq };
57
+ });
58
+ app.post('/api/v1/admin/quote-requests/:id/approve', { preHandler: guard, schema: { body: adminApproveQuoteRequestSchema.optional() } }, async (request, reply) => {
59
+ const ctx = await resolveAdminContext(request);
60
+ const body = adminApproveQuoteRequestSchema.parse(request.body ?? {});
61
+ const expectedVersion = parseIfMatch(request);
62
+ const rfq = await adminService.approve(ctx, request.params.id, expectedVersion, body.note);
63
+ setEtag(reply, rfq.version);
64
+ return { data: rfq };
65
+ });
66
+ app.post('/api/v1/admin/quote-requests/:id/cancel', { preHandler: guard, schema: { body: adminCancelQuoteRequestSchema.optional() } }, async (request, reply) => {
67
+ const ctx = await resolveAdminContext(request);
68
+ const body = adminCancelQuoteRequestSchema.parse(request.body ?? {});
69
+ const expectedVersion = parseIfMatch(request);
70
+ const rfq = await adminService.cancel(ctx, request.params.id, expectedVersion, body.reason);
71
+ setEtag(reply, rfq.version);
72
+ return { data: rfq };
73
+ });
74
+ app.post('/api/v1/admin/quote-requests/:id/assign', { preHandler: guard, schema: { body: adminAssignQuoteRequestSchema } }, async (request, reply) => {
75
+ const ctx = await resolveAdminContext(request);
76
+ const body = adminAssignQuoteRequestSchema.parse(request.body);
77
+ const rfq = await adminService.assign(ctx, request.params.id, body.adminUserId);
78
+ setEtag(reply, rfq.version);
79
+ return { data: rfq };
80
+ });
81
+ }
82
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2
+ import type { RfqService, CustomerContext } from './services/rfq-service.js';
3
+ export type RequireCustomerGuard = (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
4
+ export interface CustomerContextResolver {
5
+ (request: FastifyRequest): Promise<CustomerContext>;
6
+ }
7
+ export interface QuoteRequestsCustomerDeps {
8
+ rfqService: RfqService;
9
+ requireCustomer: RequireCustomerGuard;
10
+ resolveCustomerContext: CustomerContextResolver;
11
+ /**
12
+ * Optional gate — refuses submission when the Customer's Organization
13
+ * is not `active` (feature 026 US1 / US3).
14
+ */
15
+ assertOrganizationCanTransact?: (organizationId: string) => Promise<void>;
16
+ }
17
+ export declare function registerQuoteRequestsCustomerRoutes(app: FastifyInstance, deps: QuoteRequestsCustomerDeps): Promise<void>;
18
+ //# sourceMappingURL=routes.customer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.customer.d.ts","sourceRoot":"","sources":["../../src/backend/routes.customer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAU7E,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG7E,MAAM,MAAM,oBAAoB,GAAG,CACjC,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,YAAY,KAChB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,uBAAuB;IACtC,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,oBAAoB,CAAC;IACtC,sBAAsB,EAAE,uBAAuB,CAAC;IAChD;;;OAGG;IACH,6BAA6B,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAcD,wBAAsB,mCAAmC,CACvD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAoIf"}
@@ -0,0 +1,98 @@
1
+ import { createQuoteRequestSchema, patchQuoteRequestSchema, acceptRevisionSchema, rejectRevisionSchema, resubmitQuoteRequestSchema, ERROR_CODES, OrganizationCannotTransactError, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ function parseIfMatch(request) {
4
+ const header = request.headers['if-match'];
5
+ if (!header || typeof header !== 'string')
6
+ return null;
7
+ const trimmed = header.replace(/^"|"$/g, '');
8
+ const n = Number.parseInt(trimmed, 10);
9
+ return Number.isFinite(n) ? n : null;
10
+ }
11
+ function setEtag(reply, version) {
12
+ reply.header('etag', `"${version}"`);
13
+ }
14
+ export async function registerQuoteRequestsCustomerRoutes(app, deps) {
15
+ const { rfqService, requireCustomer, resolveCustomerContext } = deps;
16
+ const { assertOrganizationCanTransact } = deps;
17
+ const guardCanTransact = async (organizationId) => {
18
+ if (!assertOrganizationCanTransact)
19
+ return;
20
+ try {
21
+ await assertOrganizationCanTransact(organizationId);
22
+ }
23
+ catch (err) {
24
+ if (err instanceof OrganizationCannotTransactError) {
25
+ throw new HttpError(423, ERROR_CODES.FORBIDDEN, 'Your Organization cannot submit Quote Requests in its current status.', { code: 'organization_cannot_transact', status: err.status });
26
+ }
27
+ throw err;
28
+ }
29
+ };
30
+ // GET /api/v1/quote-requests — list visible Quote Requests for the caller
31
+ app.get('/api/v1/quote-requests', { preHandler: requireCustomer }, async (request) => {
32
+ const ctx = await resolveCustomerContext(request);
33
+ const data = await rfqService.listForCustomer(ctx);
34
+ return {
35
+ data,
36
+ pagination: { limit: 50, nextCursor: null, hasMore: false },
37
+ };
38
+ });
39
+ // GET /api/v1/quote-requests/:id — detail
40
+ app.get('/api/v1/quote-requests/:id', { preHandler: requireCustomer }, async (request, reply) => {
41
+ const ctx = await resolveCustomerContext(request);
42
+ const rfq = await rfqService.getForCustomer(request.params.id, ctx);
43
+ setEtag(reply, rfq.version);
44
+ return { data: rfq };
45
+ });
46
+ // POST /api/v1/quote-requests — create + submit a new Quote Request
47
+ app.post('/api/v1/quote-requests', { preHandler: requireCustomer, schema: { body: createQuoteRequestSchema } }, async (request, reply) => {
48
+ const ctx = await resolveCustomerContext(request);
49
+ await guardCanTransact(ctx.organizationId);
50
+ const body = createQuoteRequestSchema.parse(request.body);
51
+ const rfq = await rfqService.createForCustomer(ctx, body);
52
+ setEtag(reply, rfq.version);
53
+ reply.code(201);
54
+ return { data: rfq };
55
+ });
56
+ // PATCH /api/v1/quote-requests/:id — customer-side draft edit
57
+ app.patch('/api/v1/quote-requests/:id', { preHandler: requireCustomer, schema: { body: patchQuoteRequestSchema } }, async (request, reply) => {
58
+ const ctx = await resolveCustomerContext(request);
59
+ const body = patchQuoteRequestSchema.parse(request.body);
60
+ const expectedVersion = parseIfMatch(request);
61
+ const rfq = await rfqService.patchDraft(request.params.id, ctx, body, expectedVersion);
62
+ setEtag(reply, rfq.version);
63
+ return { data: rfq };
64
+ });
65
+ // POST /api/v1/quote-requests/:id/accept-revision
66
+ app.post('/api/v1/quote-requests/:id/accept-revision', { preHandler: requireCustomer, schema: { body: acceptRevisionSchema } }, async (request, reply) => {
67
+ const ctx = await resolveCustomerContext(request);
68
+ const body = acceptRevisionSchema.parse(request.body);
69
+ const rfq = await rfqService.acceptRevision(request.params.id, ctx, body.expectedRevisionNumber);
70
+ setEtag(reply, rfq.version);
71
+ return { data: rfq };
72
+ });
73
+ // POST /api/v1/quote-requests/:id/reject-revision
74
+ app.post('/api/v1/quote-requests/:id/reject-revision', { preHandler: requireCustomer, schema: { body: rejectRevisionSchema } }, async (request, reply) => {
75
+ const ctx = await resolveCustomerContext(request);
76
+ const body = rejectRevisionSchema.parse(request.body);
77
+ const rfq = await rfqService.rejectRevision(request.params.id, ctx, body.expectedRevisionNumber, body.reason);
78
+ setEtag(reply, rfq.version);
79
+ return { data: rfq };
80
+ });
81
+ // POST /api/v1/quote-requests/:id/convert-to-order — start checkout
82
+ app.post('/api/v1/quote-requests/:id/convert-to-order', { preHandler: requireCustomer }, async (request, reply) => {
83
+ const ctx = await resolveCustomerContext(request);
84
+ const result = await rfqService.convertToOrder(request.params.id, ctx);
85
+ reply.code(200);
86
+ return { data: result };
87
+ });
88
+ // POST /api/v1/quote-requests/:id/resubmit — clone into a new Pending RFQ
89
+ app.post('/api/v1/quote-requests/:id/resubmit', { preHandler: requireCustomer, schema: { body: resubmitQuoteRequestSchema.optional() } }, async (request, reply) => {
90
+ const ctx = await resolveCustomerContext(request);
91
+ const body = resubmitQuoteRequestSchema.parse(request.body ?? {});
92
+ const rfq = await rfqService.resubmit(request.params.id, ctx, body);
93
+ setEtag(reply, rfq.version);
94
+ reply.code(201);
95
+ return { data: rfq };
96
+ });
97
+ }
98
+ //# sourceMappingURL=routes.customer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.customer.js","sourceRoot":"","sources":["../../src/backend/routes.customer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ import type { OrganizationDetailsPort, SalesRepAssignmentPort } from '@endora-commerce/contracts';
4
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ export interface QuoteRequestsSalesRepRoutesDeps {
6
+ emFactory: () => EntityManager;
7
+ requireAdmin: RequireAdminFactory;
8
+ salesRepAssignment: SalesRepAssignmentPort;
9
+ organizations: OrganizationDetailsPort;
10
+ }
11
+ export declare function registerQuoteRequestsSalesRepRoutes(app: FastifyInstance, deps: QuoteRequestsSalesRepRoutesDeps): Promise<void>;
12
+ //# sourceMappingURL=routes.sales-reps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.sales-reps.d.ts","sourceRoot":"","sources":["../../src/backend/routes.sales-reps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAgC5E,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C,aAAa,EAAE,uBAAuB,CAAC;CACxC;AAED,wBAAsB,mCAAmC,CACvD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,+BAA+B,GACpC,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,57 @@
1
+ import { QuoteRequest } from './entities/quote-request.entity.js';
2
+ /**
3
+ * `GET /api/v1/admin/sales-reps/:adminUserId/organizations` — the reverse side
4
+ * of the sales-rep assignment relation, with the open quote-request count per
5
+ * organisation (feature 008 / T077; moved here by D-166).
6
+ *
7
+ * It sat in `organizations/routes.sales-reps.ts` with the three assignment
8
+ * endpoints, in a file `quote_requests` registered through a dynamic
9
+ * `import()`. That arrangement is what two of `organizations`' three
10
+ * cross-module-import ledger entries described, and this is the endpoint that
11
+ * made one of them: the open-RFQ count is **this** module's fact, so counting
12
+ * it there meant `organizations` naming `quote_requests`' entity, and repairing
13
+ * it in place by resolving `quoteRequestReadPort` from `organizations` would
14
+ * have closed a manifest cycle (`quote_requests` already declares
15
+ * `organizations`) for a question `organizations` does not own.
16
+ *
17
+ * Here it is built from two published ports and this module's own entity, and
18
+ * it names no file in another module's directory at all. It stays gated
19
+ * `rfqs:handle`, and that is the point of the split: the code is declared
20
+ * `module: 'quote_requests'`, so it disappears from `/admin-roles` when this
21
+ * module is switched off — which is correct precisely because this route
22
+ * disappears with it. The three endpoints that must **not** disappear are
23
+ * `organizations`', under `organizations:assign-sales-rep`.
24
+ *
25
+ * The statuses counted as "open" are the three the RFQ workflow treats as live;
26
+ * the list is the one this endpoint has always used.
27
+ */
28
+ /** The statuses a quote request is counted under while it is still live. */
29
+ const OPEN_STATUSES = ['Pending', 'Created from admin', 'Approved'];
30
+ export async function registerQuoteRequestsSalesRepRoutes(app, deps) {
31
+ const { emFactory, requireAdmin, salesRepAssignment, organizations } = deps;
32
+ const guard = requireAdmin('rfqs:handle');
33
+ app.get('/api/v1/admin/sales-reps/:adminUserId/organizations', { preHandler: guard }, async (request) => {
34
+ const orgIds = await salesRepAssignment.listAssignedOrganizationIds(request.params.adminUserId);
35
+ if (orgIds.length === 0)
36
+ return { data: [] };
37
+ const orgs = await organizations.findByIds(orgIds);
38
+ const em = emFactory();
39
+ const open = await em.find(QuoteRequest, {
40
+ organizationId: { $in: orgIds },
41
+ status: { $in: [...OPEN_STATUSES] },
42
+ });
43
+ const countByOrg = new Map();
44
+ for (const r of open) {
45
+ countByOrg.set(r.organizationId, (countByOrg.get(r.organizationId) ?? 0) + 1);
46
+ }
47
+ return {
48
+ data: orgs.map((o) => ({
49
+ organizationId: o.id,
50
+ name: o.name,
51
+ openRfqCount: countByOrg.get(o.id) ?? 0,
52
+ assignedAt: o.createdAt.toISOString(),
53
+ })),
54
+ };
55
+ });
56
+ }
57
+ //# sourceMappingURL=routes.sales-reps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.sales-reps.js","sourceRoot":"","sources":["../../src/backend/routes.sales-reps.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGlE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,4EAA4E;AAC5E,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,oBAAoB,EAAE,UAAU,CAAU,CAAC;AAS7E,MAAM,CAAC,KAAK,UAAU,mCAAmC,CACvD,GAAoB,EACpB,IAAqC;IAErC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAE1C,GAAG,CAAC,GAAG,CACL,qDAAqD,EACrD,EAAE,UAAU,EAAE,KAAK,EAAE,EACrB,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,2BAA2B,CACjE,OAAO,CAAC,MAAM,CAAC,WAAW,CAC3B,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAE7C,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE;YACvC,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;YAC/B,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,aAAa,CAAC,EAAE;SACpC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,CAAC;QACD,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrB,cAAc,EAAE,CAAC,CAAC,EAAE;gBACpB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,YAAY,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;gBACvC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;aACtC,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { OrderReadPort } from '@endora-commerce/contracts';
3
+ import type { RfqEventService } from './rfq-event-service.js';
4
+ import type { RfqNotificationService } from './rfq-notification-service.js';
5
+ export interface OrderCompletionReactorDeps {
6
+ emFactory: () => EntityManager;
7
+ /**
8
+ * Feature 075, Phase C — the order row the event names, read over `orders`'
9
+ * published port instead of `em.findOne(Order, …)` against its table.
10
+ * Deactivation drops no tables, so the reactor kept completing quote requests
11
+ * from a module an operator had switched off; over the port the read fails
12
+ * closed, and `orders` is a binding dependency of this manifest.
13
+ */
14
+ orders: OrderReadPort;
15
+ eventService: RfqEventService;
16
+ notificationService: RfqNotificationService;
17
+ }
18
+ /**
19
+ * US5 — when an order is created carrying `sourceQuoteRequestId`, flip the
20
+ * originating RFQ to Completed and notify both parties (FR-007 + FR-028).
21
+ *
22
+ * The order-creation flow lives in `orders`, so this observes `order.created.v1`
23
+ * rather than coupling the two modules. The **subscription** lives in this
24
+ * module's `backend.ts` and goes through `ctx.subscribe` (issue #107): it was a
25
+ * bare `eventBus.on` in the plugin body, which meant a switched-off
26
+ * `quote_requests` still completed quote requests, appended an event row and
27
+ * enqueued a customer notification.
28
+ */
29
+ export declare function createOrderCompletionReactor(deps: OrderCompletionReactorDeps): {
30
+ onOrderCreated: (payload: unknown) => Promise<void>;
31
+ };
32
+ //# sourceMappingURL=order-completion-reactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-completion-reactor.d.ts","sourceRoot":"","sources":["../../../src/backend/services/order-completion-reactor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAE5E,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B;;;;;;OAMG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB,YAAY,EAAE,eAAe,CAAC;IAC9B,mBAAmB,EAAE,sBAAsB,CAAC;CAC7C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,0BAA0B,GAAG;IAC9E,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD,CAyCA"}
@@ -0,0 +1,57 @@
1
+ import { QuoteRequest } from '../entities/quote-request.entity.js';
2
+ /**
3
+ * US5 — when an order is created carrying `sourceQuoteRequestId`, flip the
4
+ * originating RFQ to Completed and notify both parties (FR-007 + FR-028).
5
+ *
6
+ * The order-creation flow lives in `orders`, so this observes `order.created.v1`
7
+ * rather than coupling the two modules. The **subscription** lives in this
8
+ * module's `backend.ts` and goes through `ctx.subscribe` (issue #107): it was a
9
+ * bare `eventBus.on` in the plugin body, which meant a switched-off
10
+ * `quote_requests` still completed quote requests, appended an event row and
11
+ * enqueued a customer notification.
12
+ */
13
+ export function createOrderCompletionReactor(deps) {
14
+ return {
15
+ async onOrderCreated(payload) {
16
+ // command-coverage-ignore: **this marker is new; the write is not.** It
17
+ // appears only because issue #107 lifted this handler out of a plugin-body
18
+ // closure, where `check-command-coverage` could not see it — the same
19
+ // completion has been written on every quote-to-order conversion since
20
+ // US5. Read it as a write becoming visible, not as a new unaudited one.
21
+ //
22
+ // The disposition: a system reaction to an already-committed order, not an
23
+ // operator-initiated write. The placement that emits the event runs
24
+ // through the Command Bus, and the completion is appended to the RFQ's own
25
+ // event timeline below with `actor: System`, which is this module's audit
26
+ // trail — the same reasoning as its four sibling markers
27
+ // (`rfq-expiry-worker`, `rfq-event-service`, `rfq-revision-service`,
28
+ // `rfq-notification-service`).
29
+ const em = deps.emFactory();
30
+ const orderId = payload.orderId;
31
+ const order = await deps.orders.findById(orderId);
32
+ if (!order || !order.sourceQuoteRequestId)
33
+ return;
34
+ const rfq = await em.findOne(QuoteRequest, { id: order.sourceQuoteRequestId });
35
+ if (!rfq || rfq.status === 'Completed')
36
+ return;
37
+ rfq.status = 'Completed';
38
+ rfq.completedAt = new Date();
39
+ rfq.convertedOrderId = order.id;
40
+ rfq.version += 1;
41
+ await em.flush();
42
+ const evt = await deps.eventService.append({
43
+ quoteRequestId: rfq.id,
44
+ eventType: 'completed',
45
+ actor: { roleLabel: 'System' },
46
+ payload: { type: 'completed', orderId: order.id },
47
+ });
48
+ await deps.notificationService.enqueue({
49
+ quoteRequestId: rfq.id,
50
+ sourceEventId: evt.id,
51
+ recipients: [{ customerAccountId: rfq.customerAccountId }],
52
+ channels: ['email', 'in_app'],
53
+ });
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=order-completion-reactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-completion-reactor.js","sourceRoot":"","sources":["../../../src/backend/services/order-completion-reactor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAkBnE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAgC;IAG3E,OAAO;QACL,KAAK,CAAC,cAAc,CAAC,OAAgB;YACnC,wEAAwE;YACxE,2EAA2E;YAC3E,sEAAsE;YACtE,uEAAuE;YACvE,wEAAwE;YACxE,EAAE;YACF,2EAA2E;YAC3E,oEAAoE;YACpE,2EAA2E;YAC3E,0EAA0E;YAC1E,yDAAyD;YACzD,qEAAqE;YACrE,+BAA+B;YAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAI,OAA+B,CAAC,OAAO,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,oBAAoB;gBAAE,OAAO;YAClD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW;gBAAE,OAAO;YAC/C,GAAG,CAAC,MAAM,GAAG,WAAW,CAAC;YACzB,GAAG,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC;YAC7B,GAAG,CAAC,gBAAgB,GAAG,KAAK,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACjB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;gBACzC,cAAc,EAAE,GAAG,CAAC,EAAE;gBACtB,SAAS,EAAE,WAAW;gBACtB,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;gBAC9B,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE;aAClD,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;gBACrC,cAAc,EAAE,GAAG,CAAC,EAAE;gBACtB,aAAa,EAAE,GAAG,CAAC,EAAE;gBACrB,UAAU,EAAE,CAAC,EAAE,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,EAAE,CAAC;gBAC1D,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;aAC9B,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ /**
3
+ * Resolves the admin-configurable business-ID prefix/suffix for Quote
4
+ * Requests. In production these wrap
5
+ * `SettingsService.get('quote_requests.business_id.*')` (wired in
6
+ * composition); the generator treats any resolver failure as an empty string
7
+ * so RFQ creation never fails because a setting is missing. Unlike Orders,
8
+ * Quote Requests are not Sales-Channel scoped, so the resolver takes no
9
+ * arguments.
10
+ */
11
+ export interface QuoteRequestBusinessIdSettingsResolver {
12
+ resolvePrefix(): Promise<string>;
13
+ resolveSuffix(): Promise<string>;
14
+ }
15
+ /** Draws the next value from the monotonic business-ID sequence. */
16
+ export type DrawSequence = (em: EntityManager) => Promise<string>;
17
+ /**
18
+ * Generates the customer-facing business Quote Request ID:
19
+ * `${prefix}${sequence}${suffix}`. The numeric core comes from the
20
+ * `quote_requests_business_id_seq` Postgres sequence (monotonic, never
21
+ * resets), so the ID is unique regardless of later prefix/suffix changes. The
22
+ * sequence draw is injected so the generator is unit-testable without a
23
+ * database. Mirrors `orders/services/business-id-generator.ts`.
24
+ */
25
+ export declare class QuoteRequestBusinessIdGenerator {
26
+ private readonly drawSequence;
27
+ private readonly settings?;
28
+ constructor(drawSequence: DrawSequence, settings?: QuoteRequestBusinessIdSettingsResolver | undefined);
29
+ generate(em: EntityManager): Promise<string>;
30
+ /**
31
+ * Resolve a prefix/suffix, swallowing any resolver failure as `''`.
32
+ *
33
+ * The catch-all is deliberate and stays (feature 072, D-43): `drawSequence`
34
+ * runs before it, so a throw rolls the transaction back after `nextval` has
35
+ * been consumed — the sequence gaps and the RFQ is refused over a cosmetic
36
+ * prefix. What changes is that it is observable, and that a disabled module
37
+ * still refuses rather than being turned into a missing affix.
38
+ */
39
+ private resolve;
40
+ }
41
+ /**
42
+ * Production factory: draws `nextval('quote_requests_business_id_seq')` on the
43
+ * EntityManager's connection.
44
+ */
45
+ export declare function createQuoteRequestBusinessIdGenerator(settings?: QuoteRequestBusinessIdSettingsResolver): QuoteRequestBusinessIdGenerator;
46
+ //# sourceMappingURL=quote-request-business-id-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-request-business-id-generator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/quote-request-business-id-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAS3D;;;;;;;;GAQG;AACH,MAAM,WAAW,sCAAsC;IACrD,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,oEAAoE;AACpE,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,+BAA+B;IAExC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,YAAY,EAAE,YAAY,EAC1B,QAAQ,CAAC,EAAE,sCAAsC,YAAA;IAG9D,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAOlD;;;;;;;;OAQG;YACW,OAAO;CAqBtB;AAED;;;GAGG;AACH,wBAAgB,qCAAqC,CACnD,QAAQ,CAAC,EAAE,sCAAsC,GAChD,+BAA+B,CAQjC"}
@@ -0,0 +1,69 @@
1
+ import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
2
+ /**
3
+ * Conditions already reported. Per process and never reset, so a settings
4
+ * outage costs one line rather than one per quote request.
5
+ */
6
+ const warnedConditions = new Set();
7
+ /**
8
+ * Generates the customer-facing business Quote Request ID:
9
+ * `${prefix}${sequence}${suffix}`. The numeric core comes from the
10
+ * `quote_requests_business_id_seq` Postgres sequence (monotonic, never
11
+ * resets), so the ID is unique regardless of later prefix/suffix changes. The
12
+ * sequence draw is injected so the generator is unit-testable without a
13
+ * database. Mirrors `orders/services/business-id-generator.ts`.
14
+ */
15
+ export class QuoteRequestBusinessIdGenerator {
16
+ drawSequence;
17
+ settings;
18
+ constructor(drawSequence, settings) {
19
+ this.drawSequence = drawSequence;
20
+ this.settings = settings;
21
+ }
22
+ async generate(em) {
23
+ const sequence = await this.drawSequence(em);
24
+ const prefix = await this.resolve((s) => s.resolvePrefix());
25
+ const suffix = await this.resolve((s) => s.resolveSuffix());
26
+ return `${prefix}${sequence}${suffix}`;
27
+ }
28
+ /**
29
+ * Resolve a prefix/suffix, swallowing any resolver failure as `''`.
30
+ *
31
+ * The catch-all is deliberate and stays (feature 072, D-43): `drawSequence`
32
+ * runs before it, so a throw rolls the transaction back after `nextval` has
33
+ * been consumed — the sequence gaps and the RFQ is refused over a cosmetic
34
+ * prefix. What changes is that it is observable, and that a disabled module
35
+ * still refuses rather than being turned into a missing affix.
36
+ */
37
+ async resolve(pick) {
38
+ if (!this.settings)
39
+ return '';
40
+ try {
41
+ return (await pick(this.settings)) ?? '';
42
+ }
43
+ catch (error) {
44
+ if (error instanceof ModuleDisabledError)
45
+ throw error;
46
+ const name = error.name ?? 'Error';
47
+ const condition = `rfq-business-id:${name}`;
48
+ if (!warnedConditions.has(condition)) {
49
+ warnedConditions.add(condition);
50
+ console.warn(`[quote_requests] business-ID affix settings unreadable (${name}) — quote ` +
51
+ `requests are numbered without the configured prefix/suffix ` +
52
+ `(logged once per process).`);
53
+ }
54
+ return '';
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Production factory: draws `nextval('quote_requests_business_id_seq')` on the
60
+ * EntityManager's connection.
61
+ */
62
+ export function createQuoteRequestBusinessIdGenerator(settings) {
63
+ const draw = async (em) => {
64
+ const rows = (await em.execute(`select nextval('quote_requests_business_id_seq') as n`));
65
+ return String(rows[0]?.n ?? '');
66
+ };
67
+ return new QuoteRequestBusinessIdGenerator(draw, settings);
68
+ }
69
+ //# sourceMappingURL=quote-request-business-id-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-request-business-id-generator.js","sourceRoot":"","sources":["../../../src/backend/services/quote-request-business-id-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE;;;GAGG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;AAmB3C;;;;;;;GAOG;AACH,MAAM,OAAO,+BAA+B;IAEvB;IACA;IAFnB,YACmB,YAA0B,EAC1B,QAAiD;QADjD,iBAAY,GAAZ,YAAY,CAAc;QAC1B,aAAQ,GAAR,QAAQ,CAAyC;IACjE,CAAC;IAEJ,KAAK,CAAC,QAAQ,CAAC,EAAiB;QAC9B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC;QAC5D,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;IACzC,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,OAAO,CACnB,IAAoE;QAEpE,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,mBAAmB;gBAAE,MAAM,KAAK,CAAC;YACtD,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,IAAI,OAAO,CAAC;YAC1D,MAAM,SAAS,GAAG,mBAAmB,IAAI,EAAE,CAAC;YAC5C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrC,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAChC,OAAO,CAAC,IAAI,CACV,2DAA2D,IAAI,YAAY;oBACzE,6DAA6D;oBAC7D,4BAA4B,CAC/B,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,qCAAqC,CACnD,QAAiD;IAEjD,MAAM,IAAI,GAAiB,KAAK,EAAE,EAAE,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAC5B,uDAAuD,CACxD,CAAkC,CAAC;QACpC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC;IACF,OAAO,IAAI,+BAA+B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { QuoteRequestLineRecord, QuoteRequestReadPort, QuoteRequestRecord } from '@endora-commerce/contracts';
3
+ import { QuoteRequest } from '../entities/quote-request.entity.js';
4
+ /**
5
+ * The row-level read model `quote_requests` publishes (feature 075, Phase P).
6
+ *
7
+ * Two consumers, two questions. `carts` resolves the quote a cart was
8
+ * converted from; `organizations` counts the open quotes per organisation on
9
+ * the sales-rep screen, where the three open statuses had been written out as
10
+ * a literal array in a module that does not own the lifecycle.
11
+ *
12
+ * The full customer-facing projection stays `RfqService` — this is the row,
13
+ * for the callers that only need to count or cross-reference one.
14
+ */
15
+ export declare class QuoteRequestReadService implements QuoteRequestReadPort {
16
+ private readonly emFactory;
17
+ constructor(emFactory: () => EntityManager);
18
+ findById(id: string): Promise<QuoteRequestRecord | null>;
19
+ /**
20
+ * The business id is unique by construction (`QR-<uuid>` unless a caller
21
+ * passes one) and the column carries a unique index, so `findOne` is the
22
+ * whole of the lookup.
23
+ */
24
+ findByBusinessId(businessId: string): Promise<QuoteRequestRecord | null>;
25
+ listOpenForOrganizations(organizationIds: readonly string[]): Promise<QuoteRequestRecord[]>;
26
+ /**
27
+ * Added in feature 075's `carts` cut. The quote-to-cart conversion read
28
+ * `QuoteRequestItem` itself; the money columns are deliberately not on the
29
+ * record, because that conversion re-resolves every price against the
30
+ * buyer's current list (FR-017) and a caller that cannot see the quoted
31
+ * price cannot carry it over by accident.
32
+ */
33
+ listItems(quoteRequestId: string): Promise<QuoteRequestLineRecord[]>;
34
+ }
35
+ export declare function toQuoteRequestRecord(quote: QuoteRequest): QuoteRequestRecord;
36
+ //# sourceMappingURL=quote-request-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-request-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/quote-request-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAGnE;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,oBAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAK9D;;;;OAIG;IACG,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAKxE,wBAAwB,CAC5B,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAYhC;;;;;;OAMG;IACG,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;CAoB3E;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,kBAAkB,CA0B5E"}