@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,487 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { ERROR_CODES, isCustomFieldValidationFailure, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
5
+ import { QuoteRequest } from '../entities/quote-request.entity.js';
6
+ import { QuoteRequestItem } from '../entities/quote-request-item.entity.js';
7
+ import {} from './rfq-service.js';
8
+ import { raisedOnChannelId } from './raised-on-channel.js';
9
+ export class RfqAdminService {
10
+ deps;
11
+ constructor(deps) {
12
+ this.deps = deps;
13
+ }
14
+ /** Feature 054 — co-transactional RFQ audit on `em` (actor from context). */
15
+ #audit(em, action, objectId, stateBefore, stateAfter) {
16
+ if (this.deps.auditLog) {
17
+ recordAuditFromContext(this.deps.auditLog, em, {
18
+ action,
19
+ objectType: 'quote_request',
20
+ objectId,
21
+ stateBefore,
22
+ stateAfter,
23
+ });
24
+ }
25
+ }
26
+ // -------------------------------------------------------------------------
27
+ // Read paths
28
+ // -------------------------------------------------------------------------
29
+ async listAll(ctx, filter = {}) {
30
+ const em = this.deps.emFactory();
31
+ const where = {};
32
+ if (filter.organizationId)
33
+ where['organizationId'] = filter.organizationId;
34
+ if (filter.status)
35
+ where['status'] = filter.status;
36
+ let rfqs = await em.find(QuoteRequest, where, {
37
+ orderBy: { createdAt: 'desc' },
38
+ });
39
+ // Visibility scoping (skipped for platform admin)
40
+ if (!ctx.isPlatformAdmin) {
41
+ const visible = await Promise.all(rfqs.map((r) => this.deps.salesRepAssignment.canSeeOrganization(ctx.adminUserId, r.organizationId)));
42
+ rfqs = rfqs.filter((_, i) => visible[i]);
43
+ }
44
+ if (filter.scope === 'mine') {
45
+ const assigned = new Set(await this.deps.salesRepAssignment.listAssignedOrganizationIds(ctx.adminUserId));
46
+ rfqs = rfqs.filter((r) => assigned.has(r.organizationId));
47
+ }
48
+ else if (filter.scope === 'unassigned') {
49
+ const flagged = await Promise.all(rfqs.map((r) => this.isUnassigned(em, r.organizationId)));
50
+ rfqs = rfqs.filter((_, i) => flagged[i]);
51
+ }
52
+ if (rfqs.length === 0)
53
+ return [];
54
+ const items = await em.find(QuoteRequestItem, {
55
+ quoteRequestId: { $in: rfqs.map((r) => r.id) },
56
+ });
57
+ const itemsByRfq = groupBy(items, (i) => i.quoteRequestId);
58
+ const orgs = await this.deps.organizations.findByIds(rfqs.map((r) => r.organizationId));
59
+ const orgById = new Map(orgs.map((o) => [o.id, o]));
60
+ const customers = await this.deps.customerAccounts.findByIds(rfqs.map((r) => r.customerAccountId));
61
+ const customerById = new Map(customers.map((c) => [c.id, c]));
62
+ // Resolve the VAT rate once per distinct Organization (mirrors Orders).
63
+ const taxRateByOrg = new Map();
64
+ for (const orgId of new Set(rfqs.map((r) => r.organizationId))) {
65
+ taxRateByOrg.set(orgId, await this.deps.rfqService.taxRateForOrganization(orgId));
66
+ }
67
+ return rfqs.map((rfq) => {
68
+ const rfqItems = itemsByRfq.get(rfq.id) ?? [];
69
+ const customer = customerById.get(rfq.customerAccountId);
70
+ const org = orgById.get(rfq.organizationId);
71
+ const totalAtAgreedPrice = rfqItems.every((it) => it.agreedUnitPrice != null)
72
+ ? rfqItems.reduce((sum, it) => sum + Number(it.agreedUnitPrice ?? 0) * it.quantity, 0)
73
+ : null;
74
+ const totalAtCustomerPrice = rfqItems.every((it) => it.desiredUnitPrice != null)
75
+ ? rfqItems.reduce((sum, it) => sum + Number(it.desiredUnitPrice ?? 0) * it.quantity, 0)
76
+ : null;
77
+ return {
78
+ id: rfq.id,
79
+ businessId: rfq.businessId,
80
+ organizationId: rfq.organizationId,
81
+ customerAccountId: rfq.customerAccountId,
82
+ status: rfq.status,
83
+ awaitingCustomerRevisionAcceptance: rfq.awaitingCustomerRevisionAcceptance,
84
+ lineCount: rfqItems.length,
85
+ totalAtCustomerPrice,
86
+ totalAtAgreedPrice,
87
+ taxRate: taxRateByOrg.get(rfq.organizationId) ?? 0,
88
+ currency: rfqItems[0]?.lineCurrency ?? 'PLN',
89
+ submittedAt: rfq.submittedAt?.toISOString() ?? null,
90
+ expiresAt: rfq.expiresAt?.toISOString() ?? null,
91
+ createdAt: rfq.createdAt.toISOString(),
92
+ updatedAt: rfq.updatedAt.toISOString(),
93
+ version: rfq.version,
94
+ organizationName: org?.name ?? '',
95
+ customerDisplayName: customer ? customerDisplayName(customer) : null,
96
+ };
97
+ });
98
+ }
99
+ async getById(ctx, rfqId) {
100
+ const em = this.deps.emFactory();
101
+ const rfq = await this.findVisibleForAdmin(em, ctx, rfqId);
102
+ const dto = await this.deps.rfqService.serializeFull(em, rfq,
103
+ /* includeFullActorIdentity */ true);
104
+ // Enrich with Organization + Customer display fields so the admin detail
105
+ // can render them by name (parity with the Order detail view).
106
+ const [org, customer] = await Promise.all([
107
+ this.deps.organizations.findById(rfq.organizationId),
108
+ this.deps.customerAccounts.findById(rfq.customerAccountId),
109
+ ]);
110
+ return {
111
+ ...dto,
112
+ organization: org
113
+ ? {
114
+ id: org.id,
115
+ name: org.name,
116
+ legalName: org.legalName ?? null,
117
+ taxId: org.taxId,
118
+ vatStatus: org.vatStatus,
119
+ }
120
+ : null,
121
+ customer: customer
122
+ ? {
123
+ id: customer.id,
124
+ firstName: customer.firstName ?? null,
125
+ lastName: customer.lastName ?? null,
126
+ email: customer.email,
127
+ }
128
+ : null,
129
+ };
130
+ }
131
+ // -------------------------------------------------------------------------
132
+ // Approve (US2)
133
+ // -------------------------------------------------------------------------
134
+ async approve(ctx, rfqId, expectedVersion, note) {
135
+ const em = this.deps.emFactory();
136
+ const rfq = await this.findVisibleForAdmin(em, ctx, rfqId);
137
+ if (rfq.status !== 'Pending') {
138
+ throw new HttpError(409, ERROR_CODES.RFQ_NOT_QUOTED, 'Quote Request is not pending approval.');
139
+ }
140
+ this.assertVersion(rfq, expectedVersion);
141
+ const now = new Date();
142
+ rfq.status = 'Approved';
143
+ rfq.approvedAt = now;
144
+ rfq.awaitingCustomerRevisionAcceptance = false;
145
+ rfq.assignedAdminUserId = ctx.adminUserId;
146
+ rfq.version += 1;
147
+ this.#audit(em, 'quote_request.approve', rfq.id, { status: 'Pending' }, { status: 'Approved' });
148
+ await em.flush();
149
+ if (note && note.trim().length > 0) {
150
+ await this.deps.eventService.append({
151
+ quoteRequestId: rfq.id,
152
+ eventType: 'note-added',
153
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
154
+ payload: { type: 'note-added', note },
155
+ });
156
+ }
157
+ const evt = await this.deps.eventService.append({
158
+ quoteRequestId: rfq.id,
159
+ eventType: 'approved',
160
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
161
+ payload: { type: 'approved', approvedBy: 'admin' },
162
+ });
163
+ await this.deps.notificationService.enqueue({
164
+ quoteRequestId: rfq.id,
165
+ sourceEventId: evt.id,
166
+ recipients: [{ customerAccountId: rfq.customerAccountId }],
167
+ channels: ['email', 'in_app'],
168
+ });
169
+ this.deps.events.emit('rfq.approved.v1', {
170
+ eventId: randomUUID(),
171
+ occurredAt: now.toISOString(),
172
+ rfqId: rfq.id,
173
+ });
174
+ return this.deps.rfqService.serializeFull(em, rfq, true);
175
+ }
176
+ // -------------------------------------------------------------------------
177
+ // Cancel (US2)
178
+ // -------------------------------------------------------------------------
179
+ async cancel(ctx, rfqId, expectedVersion, reason) {
180
+ const em = this.deps.emFactory();
181
+ const rfq = await this.findVisibleForAdmin(em, ctx, rfqId);
182
+ if (rfq.status !== 'Pending' && rfq.status !== 'Created from admin') {
183
+ throw new HttpError(409, ERROR_CODES.RFQ_NOT_QUOTED, 'Quote Request cannot be canceled in its current state.');
184
+ }
185
+ this.assertVersion(rfq, expectedVersion);
186
+ const now = new Date();
187
+ rfq.status = 'Canceled';
188
+ rfq.canceledAt = now;
189
+ rfq.awaitingCustomerRevisionAcceptance = false;
190
+ if (reason)
191
+ rfq.cancellationReason = reason;
192
+ rfq.assignedAdminUserId = ctx.adminUserId;
193
+ rfq.version += 1;
194
+ this.#audit(em, 'quote_request.cancel', rfq.id, null, { status: 'Canceled', reason: reason ?? null });
195
+ await em.flush();
196
+ const evt = await this.deps.eventService.append({
197
+ quoteRequestId: rfq.id,
198
+ eventType: 'canceled',
199
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
200
+ payload: { type: 'canceled', canceledBy: 'admin', reason: reason ?? null },
201
+ });
202
+ await this.deps.notificationService.enqueue({
203
+ quoteRequestId: rfq.id,
204
+ sourceEventId: evt.id,
205
+ recipients: [{ customerAccountId: rfq.customerAccountId }],
206
+ channels: ['email', 'in_app'],
207
+ });
208
+ this.deps.events.emit('rfq.canceled.v1', {
209
+ eventId: randomUUID(),
210
+ occurredAt: now.toISOString(),
211
+ rfqId: rfq.id,
212
+ reason: reason ?? null,
213
+ });
214
+ return this.deps.rfqService.serializeFull(em, rfq, true);
215
+ }
216
+ // -------------------------------------------------------------------------
217
+ // Internal-side claim (informational; visibility is per-org, not per-rfq)
218
+ // -------------------------------------------------------------------------
219
+ async assign(ctx, rfqId, targetAdminUserId) {
220
+ const em = this.deps.emFactory();
221
+ const rfq = await this.findVisibleForAdmin(em, ctx, rfqId);
222
+ const previousAssignee = rfq.assignedAdminUserId;
223
+ rfq.assignedAdminUserId = targetAdminUserId;
224
+ rfq.version += 1;
225
+ this.#audit(em, 'quote_request.assign', rfq.id, { assignedAdminUserId: previousAssignee }, {
226
+ assignedAdminUserId: targetAdminUserId,
227
+ });
228
+ await em.flush();
229
+ return this.deps.rfqService.serializeFull(em, rfq, true);
230
+ }
231
+ // -------------------------------------------------------------------------
232
+ // Modify (US3) — also handles edits to Created from admin (US4)
233
+ // -------------------------------------------------------------------------
234
+ async modify(ctx, rfqId, expectedVersion, body) {
235
+ const em = this.deps.emFactory();
236
+ const rfq = await this.findVisibleForAdmin(em, ctx, rfqId);
237
+ if (rfq.status !== 'Pending' && rfq.status !== 'Created from admin') {
238
+ throw new HttpError(409, ERROR_CODES.RFQ_NOT_QUOTED, 'Quote Request is not editable.');
239
+ }
240
+ this.assertVersion(rfq, expectedVersion);
241
+ if (body.headerNote !== undefined)
242
+ rfq.headerNote = body.headerNote ?? null;
243
+ // Feature 055 — validate + merge custom-field values (host owns the write).
244
+ if (body.customFieldValues !== undefined && this.deps.customFieldValues) {
245
+ try {
246
+ rfq.customFieldValues = await this.deps.customFieldValues.validateAndMerge('quote_request', rfq.customFieldValues ?? {}, body.customFieldValues);
247
+ }
248
+ catch (err) {
249
+ if (isCustomFieldValidationFailure(err)) {
250
+ throw new HttpError(422, ERROR_CODES.CUSTOM_FIELD_VALUE_INVALID, 'One or more custom fields are invalid.', err.errors.map((e) => ({ path: e.field, issue: e.message })));
251
+ }
252
+ throw err;
253
+ }
254
+ }
255
+ if (body.items) {
256
+ const products = await this.deps.catalogProducts.findByIds(body.items.map((it) => it.productId));
257
+ const productById = new Map(products.map((p) => [p.id, p]));
258
+ if (productById.size !== new Set(body.items.map((it) => it.productId)).size) {
259
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'One or more products do not exist.');
260
+ }
261
+ const existing = await em.find(QuoteRequestItem, { quoteRequestId: rfq.id });
262
+ await em.removeAndFlush(existing);
263
+ const items = [];
264
+ for (const lineInput of body.items) {
265
+ const product = productById.get(lineInput.productId);
266
+ items.push(em.create(QuoteRequestItem, {
267
+ quoteRequestId: rfq.id,
268
+ productId: product.id,
269
+ productName: anyLocaleValue(product.name),
270
+ productSlug: product.slug,
271
+ variantId: lineInput.variantId ?? null,
272
+ quantity: lineInput.quantity,
273
+ agreedUnitPrice: lineInput.agreedUnitPrice !== undefined && lineInput.agreedUnitPrice !== null
274
+ ? lineInput.agreedUnitPrice.toFixed(2)
275
+ : null,
276
+ lineNote: lineInput.lineNote ?? null,
277
+ lineCurrency: 'PLN',
278
+ }));
279
+ }
280
+ await em.persistAndFlush(items);
281
+ }
282
+ rfq.currentRevisionNumber += 1;
283
+ rfq.awaitingCustomerRevisionAcceptance = true;
284
+ rfq.assignedAdminUserId = ctx.adminUserId;
285
+ if (body.expiresInDays !== undefined) {
286
+ rfq.expiresAt = new Date(Date.now() + body.expiresInDays * 86_400_000);
287
+ }
288
+ rfq.version += 1;
289
+ this.#audit(em, 'quote_request.modify', rfq.id, null, {
290
+ currentRevisionNumber: rfq.currentRevisionNumber,
291
+ status: rfq.status,
292
+ });
293
+ await em.flush();
294
+ const items = await em.find(QuoteRequestItem, { quoteRequestId: rfq.id });
295
+ const previousRevision = await this.deps.revisionService.byNumber(rfq.id, rfq.currentRevisionNumber - 1);
296
+ const revision = await this.deps.revisionService.record({
297
+ rfq,
298
+ items,
299
+ actor: { adminUserId: ctx.adminUserId },
300
+ previousRevisionId: previousRevision?.id ?? null,
301
+ });
302
+ const diff = previousRevision
303
+ ? this.deps.revisionService.diffRevisions({
304
+ beforeHeaderNote: previousRevision.headerNoteSnapshot ?? null,
305
+ afterHeaderNote: rfq.headerNote ?? null,
306
+ beforeItems: previousRevision.itemsSnapshot,
307
+ afterItems: revision.itemsSnapshot,
308
+ })
309
+ : [];
310
+ const evt = await this.deps.eventService.append({
311
+ quoteRequestId: rfq.id,
312
+ eventType: 'modified',
313
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
314
+ payload: { type: 'modified', diff },
315
+ revisionId: revision.id,
316
+ });
317
+ await this.deps.notificationService.enqueue({
318
+ quoteRequestId: rfq.id,
319
+ sourceEventId: evt.id,
320
+ recipients: [{ customerAccountId: rfq.customerAccountId }],
321
+ channels: ['email', 'in_app'],
322
+ });
323
+ this.deps.events.emit('rfq.modified.v1', {
324
+ eventId: randomUUID(),
325
+ occurredAt: new Date().toISOString(),
326
+ rfqId: rfq.id,
327
+ revisionNumber: rfq.currentRevisionNumber,
328
+ });
329
+ return this.deps.rfqService.serializeFull(em, rfq, true);
330
+ }
331
+ // -------------------------------------------------------------------------
332
+ // Create on behalf (US4)
333
+ // -------------------------------------------------------------------------
334
+ async createOnBehalf(ctx, body) {
335
+ const em = this.deps.emFactory();
336
+ // Visibility on the target organization (platform_admin always passes).
337
+ if (!ctx.isPlatformAdmin &&
338
+ !(await this.deps.salesRepAssignment.canSeeOrganization(ctx.adminUserId, body.organizationId))) {
339
+ throw new HttpError(403, ERROR_CODES.FORBIDDEN, 'Not authorized for this organization.');
340
+ }
341
+ // The membership half of the old `findOne(CustomerAccount, { id,
342
+ // organizationId })` is asserted here rather than in the query: the record
343
+ // carries `organizationId`, so the port does not need a second lookup shape
344
+ // and the two failures still answer with one message, as they did.
345
+ const customer = await this.deps.customerAccounts.findById(body.customerAccountId);
346
+ if (!customer || customer.organizationId !== body.organizationId) {
347
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Customer does not belong to the organization.');
348
+ }
349
+ // **Neither scoping axis is applied to these lines, and that is the
350
+ // decision rather than the gap.** `RfqService.createForCustomer` filters by
351
+ // `isProductVisibleTo` (issue #227) and by the request's channel assortment
352
+ // (issue #259); this path filters by neither.
353
+ //
354
+ // It is an operator surface. The admin names the organisation, the customer
355
+ // and the lines, having chosen them from an admin catalogue that shows the
356
+ // whole assortment — so there is no buyer whose audience could answer, and
357
+ // the channel the admin's own request happens to have resolved (the
358
+ // resolver's step-4 default, unless the SPA sent `X-Sales-Channel`) is not
359
+ // a statement about where this quote should be sellable. The
360
+ // `quote_requests:write` permission is the enforcement, exactly as
361
+ // `quick_order`'s `'unrestricted'` import arm says of itself.
362
+ //
363
+ // The channel this quote *is* raised on is recorded below and is the
364
+ // honest answer to "where did this come from", not a filter.
365
+ const products = await this.deps.catalogProducts.findByIds(body.items.map((it) => it.productId));
366
+ const productById = new Map(products.map((p) => [p.id, p]));
367
+ if (productById.size !== new Set(body.items.map((it) => it.productId)).size) {
368
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, 'One or more products do not exist.');
369
+ }
370
+ const businessId = await this.deps.rfqService.generateBusinessId(em);
371
+ const rfq = em.create(QuoteRequest, {
372
+ ...(businessId ? { businessId } : {}),
373
+ organizationId: body.organizationId,
374
+ customerAccountId: body.customerAccountId,
375
+ // The channel the admin's own request resolved to. Admin paths take the
376
+ // resolver's step-4 fallback unless the SPA sends `X-Sales-Channel`, so
377
+ // this is the system default until it does — which is the honest answer
378
+ // for "where was this raised", not a channel this module picked.
379
+ salesChannelId: raisedOnChannelId(),
380
+ createdByAdminUserId: ctx.adminUserId,
381
+ assignedAdminUserId: ctx.adminUserId,
382
+ status: 'Created from admin',
383
+ headerNote: body.headerNote ?? null,
384
+ currentRevisionNumber: 1,
385
+ lastCustomerSeenRevisionNumber: 0,
386
+ // The customer must accept or reject the quote the admin prepared, so it
387
+ // surfaces the Accept/Reject controls on the storefront (the same flag the
388
+ // admin's later revisions set). Without this the customer had no way to
389
+ // act on an admin-created quote.
390
+ awaitingCustomerRevisionAcceptance: true,
391
+ expiresAt: body.expiresInDays !== undefined
392
+ ? new Date(Date.now() + body.expiresInDays * 86_400_000)
393
+ : null,
394
+ });
395
+ await em.persistAndFlush(rfq);
396
+ const items = [];
397
+ for (const lineInput of body.items) {
398
+ const product = productById.get(lineInput.productId);
399
+ items.push(em.create(QuoteRequestItem, {
400
+ quoteRequestId: rfq.id,
401
+ productId: product.id,
402
+ productName: anyLocaleValue(product.name),
403
+ productSlug: product.slug,
404
+ variantId: lineInput.variantId ?? null,
405
+ quantity: lineInput.quantity,
406
+ agreedUnitPrice: lineInput.agreedUnitPrice.toFixed(2),
407
+ lineNote: lineInput.lineNote ?? null,
408
+ lineCurrency: 'PLN',
409
+ }));
410
+ }
411
+ this.#audit(em, 'quote_request.create_on_behalf', rfq.id, null, {
412
+ status: rfq.status,
413
+ itemCount: items.length,
414
+ salesChannelId: rfq.salesChannelId ?? null,
415
+ });
416
+ await em.persistAndFlush(items);
417
+ const revision = await this.deps.revisionService.record({
418
+ rfq,
419
+ items,
420
+ actor: { adminUserId: ctx.adminUserId },
421
+ });
422
+ await this.deps.eventService.append({
423
+ quoteRequestId: rfq.id,
424
+ eventType: 'created',
425
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
426
+ payload: { type: 'created' },
427
+ revisionId: revision.id,
428
+ });
429
+ const evt = await this.deps.eventService.append({
430
+ quoteRequestId: rfq.id,
431
+ eventType: 'note-added',
432
+ actor: { adminUserId: ctx.adminUserId, roleLabel: ctx.roleLabel },
433
+ payload: { type: 'note-added', note: 'Quote Request created on customer behalf — awaiting customer acceptance.' },
434
+ revisionId: revision.id,
435
+ });
436
+ await this.deps.notificationService.enqueue({
437
+ quoteRequestId: rfq.id,
438
+ sourceEventId: evt.id,
439
+ recipients: [{ customerAccountId: rfq.customerAccountId }],
440
+ channels: ['email', 'in_app'],
441
+ });
442
+ return this.deps.rfqService.serializeFull(em, rfq, true);
443
+ }
444
+ // -------------------------------------------------------------------------
445
+ // Helpers
446
+ // -------------------------------------------------------------------------
447
+ async findVisibleForAdmin(em, ctx, rfqId) {
448
+ const rfq = await em.findOne(QuoteRequest, { id: rfqId });
449
+ if (!rfq)
450
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, 'Quote Request not found.');
451
+ if (!ctx.isPlatformAdmin &&
452
+ !(await this.deps.salesRepAssignment.canSeeOrganization(ctx.adminUserId, rfq.organizationId))) {
453
+ throw new HttpError(403, ERROR_CODES.FORBIDDEN, 'Not authorized for this Quote Request.');
454
+ }
455
+ return rfq;
456
+ }
457
+ assertVersion(rfq, expectedVersion) {
458
+ if (expectedVersion !== null && rfq.version !== expectedVersion) {
459
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Quote Request was updated concurrently.');
460
+ }
461
+ }
462
+ async isUnassigned(em, organizationId) {
463
+ const reps = await this.deps.salesRepAssignment.listForOrganization(organizationId);
464
+ void em;
465
+ return reps.length === 0;
466
+ }
467
+ }
468
+ function anyLocaleValue(blob) {
469
+ const k = Object.keys(blob)[0];
470
+ return k ? (blob[k] ?? '') : '';
471
+ }
472
+ function customerDisplayName(c) {
473
+ return [c.firstName, c.lastName].filter(Boolean).join(' ') || c.email;
474
+ }
475
+ function groupBy(arr, key) {
476
+ const out = new Map();
477
+ for (const v of arr) {
478
+ const k = key(v);
479
+ const existing = out.get(k);
480
+ if (existing)
481
+ existing.push(v);
482
+ else
483
+ out.set(k, [v]);
484
+ }
485
+ return out;
486
+ }
487
+ //# sourceMappingURL=rfq-admin-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfq-admin-service.js","sourceRoot":"","sources":["../../../src/backend/services/rfq-admin-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { QuoteRequestEvent, type QuoteRequestEventType } from '../entities/quote-request-event.entity.js';
3
+ /**
4
+ * RfqEventService — append-only writer for the change log that drives
5
+ * the storefront and admin history block (FR-035 / FR-037).
6
+ *
7
+ * Every state-changing call in RfqService / RfqAdminService MUST funnel
8
+ * through this service; it is the only authorised writer of
9
+ * quote_request_events. The schema check on `event_type` lives at the
10
+ * DB level (migration 029); the discriminated payload shape is enforced
11
+ * by the route validators upstream (Zod schemas in @endora-commerce/contracts).
12
+ */
13
+ export interface RfqEventActor {
14
+ adminUserId?: string | null;
15
+ customerAccountId?: string | null;
16
+ /** Display label shown to the customer ('Sales representative', 'Customer', …). */
17
+ roleLabel?: string | null;
18
+ }
19
+ export interface AppendEventInput {
20
+ quoteRequestId: string;
21
+ eventType: QuoteRequestEventType;
22
+ actor: RfqEventActor;
23
+ payload: Record<string, unknown>;
24
+ revisionId?: string | null;
25
+ }
26
+ export declare class RfqEventService {
27
+ private readonly emFactory;
28
+ constructor(emFactory: () => EntityManager);
29
+ append(input: AppendEventInput): Promise<QuoteRequestEvent>;
30
+ listForRfq(quoteRequestId: string, limit?: number): Promise<QuoteRequestEvent[]>;
31
+ }
32
+ //# sourceMappingURL=rfq-event-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfq-event-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/rfq-event-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAE1G;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,qBAAqB,CAAC;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiB3D,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;CAQpF"}
@@ -0,0 +1,28 @@
1
+ import { QuoteRequestEvent } from '../entities/quote-request-event.entity.js';
2
+ export class RfqEventService {
3
+ emFactory;
4
+ constructor(emFactory) {
5
+ this.emFactory = emFactory;
6
+ }
7
+ async append(input) {
8
+ // command-coverage-ignore: this IS the RFQ's own append-only event timeline
9
+ // (the domain's audit trail); the parent RFQ action carries the audit entry.
10
+ const em = this.emFactory();
11
+ const event = em.create(QuoteRequestEvent, {
12
+ quoteRequestId: input.quoteRequestId,
13
+ eventType: input.eventType,
14
+ actorAdminUserId: input.actor.adminUserId ?? null,
15
+ actorCustomerAccountId: input.actor.customerAccountId ?? null,
16
+ actorRoleLabel: input.actor.roleLabel ?? null,
17
+ payload: input.payload,
18
+ revisionId: input.revisionId ?? null,
19
+ });
20
+ await em.persistAndFlush(event);
21
+ return event;
22
+ }
23
+ async listForRfq(quoteRequestId, limit = 100) {
24
+ const em = this.emFactory();
25
+ return em.find(QuoteRequestEvent, { quoteRequestId }, { orderBy: { createdAt: 'asc' }, limit });
26
+ }
27
+ }
28
+ //# sourceMappingURL=rfq-event-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfq-event-service.js","sourceRoot":"","sources":["../../../src/backend/services/rfq-event-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAA8B,MAAM,2CAA2C,CAAC;AA2B1G,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,MAAM,CAAC,KAAuB;QAClC,4EAA4E;QAC5E,6EAA6E;QAC7E,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE;YACzC,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,gBAAgB,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,IAAI,IAAI;YACjD,sBAAsB,EAAE,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,IAAI;YAC7D,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI;YAC7C,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;SACrC,CAAC,CAAC;QACH,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,cAAsB,EAAE,KAAK,GAAG,GAAG;QAClD,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC,IAAI,CACZ,iBAAiB,EACjB,EAAE,cAAc,EAAE,EAClB,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CACzC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,50 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AdminUserReadPort, SalesRepAssignmentPort } from '@endora-commerce/contracts';
3
+ import type { RfqEventBus } from './rfq-service.js';
4
+ import type { RfqEventService } from './rfq-event-service.js';
5
+ import type { RfqNotificationService } from './rfq-notification-service.js';
6
+ /**
7
+ * RfqExpiryWorker — feature 008 sweep that flips Pending and
8
+ * Created from admin Quote Requests past the configured expiry to
9
+ * `Expired`, writes a corresponding event row, and fans out
10
+ * notifications to both parties (FR-030).
11
+ *
12
+ * `expiryDays = 0` disables the sweep entirely. Otherwise the worker
13
+ * scans rows where `updated_at < now() - INTERVAL '<expiryDays> days'`,
14
+ * which catches modifications + draft submissions equally.
15
+ *
16
+ * Public `sweep()` is a plain async function so tests can drive it
17
+ * directly without Redis/BullMQ.
18
+ */
19
+ declare module './rfq-service.js' {
20
+ interface RfqEvents {
21
+ 'rfq.expired.v1': import('@endora-commerce/platform/events').EventBase & {
22
+ rfqId: string;
23
+ };
24
+ }
25
+ }
26
+ export interface RfqExpiryWorkerDeps {
27
+ emFactory: () => EntityManager;
28
+ events: RfqEventBus;
29
+ eventService: RfqEventService;
30
+ notificationService: RfqNotificationService;
31
+ salesRepAssignment: SalesRepAssignmentPort;
32
+ /**
33
+ * Feature 075, Phase C — the unassigned-organisation fan-out. It was
34
+ * `em.find(AdminUser, {})` against `admin_users`' table; `listAll` is the
35
+ * read that module published for exactly this, and its doc comment says so.
36
+ */
37
+ adminUsers: AdminUserReadPort;
38
+ /** Resolves the current `quote_requests.expiryDays` from settings.
39
+ * Implementation lives in plugin.ts so the worker isn't coupled to
40
+ * the settings module's read API. */
41
+ resolveExpiryDays: () => Promise<number>;
42
+ }
43
+ export declare class RfqExpiryWorker {
44
+ private readonly deps;
45
+ constructor(deps: RfqExpiryWorkerDeps);
46
+ sweep(now?: Date): Promise<{
47
+ expiredCount: number;
48
+ }>;
49
+ }
50
+ //# sourceMappingURL=rfq-expiry-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfq-expiry-worker.d.ts","sourceRoot":"","sources":["../../../src/backend/services/rfq-expiry-worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,sBAAsB,EAAyB,MAAM,+BAA+B,CAAC;AAGnG;;;;;;;;;;;;GAYG;AAEH,OAAO,QAAQ,kBAAkB,CAAC;IAChC,UAAU,SAAS;QACjB,gBAAgB,EAAE,OAAO,kCAAkC,EAAE,SAAS,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KAC5F;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,eAAe,CAAC;IAC9B,mBAAmB,EAAE,sBAAsB,CAAC;IAC5C,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;yCAEqC;IACrC,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,mBAAmB;IAEhD,KAAK,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CA8DvE"}