@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.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/index.d.ts +26 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +56 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/RfqCreatePage.d.ts +9 -0
- package/dist/admin/pages/RfqCreatePage.d.ts.map +1 -0
- package/dist/admin/pages/RfqCreatePage.js +163 -0
- package/dist/admin/pages/RfqCreatePage.js.map +1 -0
- package/dist/admin/pages/RfqDetail.d.ts +9 -0
- package/dist/admin/pages/RfqDetail.d.ts.map +1 -0
- package/dist/admin/pages/RfqDetail.js +399 -0
- package/dist/admin/pages/RfqDetail.js.map +1 -0
- package/dist/admin/pages/RfqList.d.ts +9 -0
- package/dist/admin/pages/RfqList.d.ts.map +1 -0
- package/dist/admin/pages/RfqList.js +214 -0
- package/dist/admin/pages/RfqList.js.map +1 -0
- package/dist/admin/validity.d.ts +52 -0
- package/dist/admin/validity.d.ts.map +1 -0
- package/dist/admin/validity.js +61 -0
- package/dist/admin/validity.js.map +1 -0
- package/dist/backend/entities/quote-request-event.entity.d.ts +24 -0
- package/dist/backend/entities/quote-request-event.entity.d.ts.map +1 -0
- package/dist/backend/entities/quote-request-event.entity.js +67 -0
- package/dist/backend/entities/quote-request-event.entity.js.map +1 -0
- package/dist/backend/entities/quote-request-item.entity.d.ts +34 -0
- package/dist/backend/entities/quote-request-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/quote-request-item.entity.js +119 -0
- package/dist/backend/entities/quote-request-item.entity.js.map +1 -0
- package/dist/backend/entities/quote-request-notification-event.entity.d.ts +23 -0
- package/dist/backend/entities/quote-request-notification-event.entity.d.ts.map +1 -0
- package/dist/backend/entities/quote-request-notification-event.entity.js +72 -0
- package/dist/backend/entities/quote-request-notification-event.entity.js.map +1 -0
- package/dist/backend/entities/quote-request-revision.entity.d.ts +35 -0
- package/dist/backend/entities/quote-request-revision.entity.d.ts.map +1 -0
- package/dist/backend/entities/quote-request-revision.entity.js +67 -0
- package/dist/backend/entities/quote-request-revision.entity.js.map +1 -0
- package/dist/backend/entities/quote-request.entity.d.ts +90 -0
- package/dist/backend/entities/quote-request.entity.d.ts.map +1 -0
- package/dist/backend/entities/quote-request.entity.js +191 -0
- package/dist/backend/entities/quote-request.entity.js.map +1 -0
- package/dist/backend/index.d.ts +120 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +361 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +95 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +152 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +13 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +82 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.customer.d.ts +18 -0
- package/dist/backend/routes.customer.d.ts.map +1 -0
- package/dist/backend/routes.customer.js +98 -0
- package/dist/backend/routes.customer.js.map +1 -0
- package/dist/backend/routes.sales-reps.d.ts +12 -0
- package/dist/backend/routes.sales-reps.d.ts.map +1 -0
- package/dist/backend/routes.sales-reps.js +57 -0
- package/dist/backend/routes.sales-reps.js.map +1 -0
- package/dist/backend/services/order-completion-reactor.d.ts +32 -0
- package/dist/backend/services/order-completion-reactor.d.ts.map +1 -0
- package/dist/backend/services/order-completion-reactor.js +57 -0
- package/dist/backend/services/order-completion-reactor.js.map +1 -0
- package/dist/backend/services/quote-request-business-id-generator.d.ts +46 -0
- package/dist/backend/services/quote-request-business-id-generator.d.ts.map +1 -0
- package/dist/backend/services/quote-request-business-id-generator.js +69 -0
- package/dist/backend/services/quote-request-business-id-generator.js.map +1 -0
- package/dist/backend/services/quote-request-read-port.d.ts +36 -0
- package/dist/backend/services/quote-request-read-port.d.ts.map +1 -0
- package/dist/backend/services/quote-request-read-port.js +96 -0
- package/dist/backend/services/quote-request-read-port.js.map +1 -0
- package/dist/backend/services/raised-on-channel.d.ts +23 -0
- package/dist/backend/services/raised-on-channel.d.ts.map +1 -0
- package/dist/backend/services/raised-on-channel.js +26 -0
- package/dist/backend/services/raised-on-channel.js.map +1 -0
- package/dist/backend/services/rfq-admin-service.d.ts +75 -0
- package/dist/backend/services/rfq-admin-service.d.ts.map +1 -0
- package/dist/backend/services/rfq-admin-service.js +487 -0
- package/dist/backend/services/rfq-admin-service.js.map +1 -0
- package/dist/backend/services/rfq-event-service.d.ts +32 -0
- package/dist/backend/services/rfq-event-service.d.ts.map +1 -0
- package/dist/backend/services/rfq-event-service.js +28 -0
- package/dist/backend/services/rfq-event-service.js.map +1 -0
- package/dist/backend/services/rfq-expiry-worker.d.ts +50 -0
- package/dist/backend/services/rfq-expiry-worker.d.ts.map +1 -0
- package/dist/backend/services/rfq-expiry-worker.js +70 -0
- package/dist/backend/services/rfq-expiry-worker.js.map +1 -0
- package/dist/backend/services/rfq-notification-service.d.ts +36 -0
- package/dist/backend/services/rfq-notification-service.d.ts.map +1 -0
- package/dist/backend/services/rfq-notification-service.js +52 -0
- package/dist/backend/services/rfq-notification-service.js.map +1 -0
- package/dist/backend/services/rfq-revision-service.d.ts +70 -0
- package/dist/backend/services/rfq-revision-service.d.ts.map +1 -0
- package/dist/backend/services/rfq-revision-service.js +120 -0
- package/dist/backend/services/rfq-revision-service.js.map +1 -0
- package/dist/backend/services/rfq-service.d.ts +140 -0
- package/dist/backend/services/rfq-service.d.ts.map +1 -0
- package/dist/backend/services/rfq-service.js +786 -0
- package/dist/backend/services/rfq-service.js.map +1 -0
- package/dist/backend/services/sales-channel-attributions.d.ts +38 -0
- package/dist/backend/services/sales-channel-attributions.d.ts.map +1 -0
- package/dist/backend/services/sales-channel-attributions.js +48 -0
- package/dist/backend/services/sales-channel-attributions.js.map +1 -0
- package/dist/manifest.d.ts +203 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +277 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260424T190112_quote_requests_init.d.ts +19 -0
- package/dist/migrations/20260424T190112_quote_requests_init.d.ts.map +1 -0
- package/dist/migrations/20260424T190112_quote_requests_init.js +65 -0
- package/dist/migrations/20260424T190112_quote_requests_init.js.map +1 -0
- package/dist/migrations/20260503T141344_quote_requests_workflow.d.ts +37 -0
- package/dist/migrations/20260503T141344_quote_requests_workflow.d.ts.map +1 -0
- package/dist/migrations/20260503T141344_quote_requests_workflow.js +251 -0
- package/dist/migrations/20260503T141344_quote_requests_workflow.js.map +1 -0
- package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.d.ts +10 -0
- package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.d.ts.map +1 -0
- package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.js +16 -0
- package/dist/migrations/20260611T140415_quote_requests_qr_item_packaging.js.map +1 -0
- package/dist/migrations/20260611T140417_quote_requests_business_id.d.ts +24 -0
- package/dist/migrations/20260611T140417_quote_requests_business_id.d.ts.map +1 -0
- package/dist/migrations/20260611T140417_quote_requests_business_id.js +34 -0
- package/dist/migrations/20260611T140417_quote_requests_business_id.js.map +1 -0
- package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.d.ts +21 -0
- package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.d.ts.map +1 -0
- package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.js +29 -0
- package/dist/migrations/20260617T095510_quote_requests_backfill_admin_created_awaiting.js.map +1 -0
- package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.d.ts +12 -0
- package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.d.ts.map +1 -0
- package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.js +16 -0
- package/dist/migrations/20260718T200342_quote_requests_quote_request_custom_field_values.js.map +1 -0
- package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.d.ts +34 -0
- package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.d.ts.map +1 -0
- package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.js +57 -0
- package/dist/migrations/20260912T125614_quote_requests_quote_request_channel_attribution.js.map +1 -0
- package/dist/migrations/index.d.ts +53 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +61 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/status-mapping.d.ts +18 -0
- package/dist/migrations/status-mapping.d.ts.map +1 -0
- package/dist/migrations/status-mapping.js +28 -0
- package/dist/migrations/status-mapping.js.map +1 -0
- package/docs/quote_requests.md +168 -0
- package/i18n/en.json +14 -0
- package/i18n/pl.json +14 -0
- package/package.json +99 -0
- 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"}
|