@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,361 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ERROR_CODES } from '@endora-commerce/contracts';
|
|
3
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
4
|
+
import { QuoteRequestReadService } from './services/quote-request-read-port.js';
|
|
5
|
+
import { registerQuoteRequestSalesChannelAttributions } from './services/sales-channel-attributions.js';
|
|
6
|
+
import { lazyPort, SettingNotRegistered, SettingOutOfScopeForChannel, } from '@endora-commerce/platform/kernel';
|
|
7
|
+
import { QUOTE_REQUESTS_SETTING_CODES } from '../manifest.js';
|
|
8
|
+
import { quoteRequestsModule } from './plugin.js';
|
|
9
|
+
import { QuoteRequest } from './entities/quote-request.entity.js';
|
|
10
|
+
import { QuoteRequestEvent } from './entities/quote-request-event.entity.js';
|
|
11
|
+
import { QuoteRequestItem } from './entities/quote-request-item.entity.js';
|
|
12
|
+
import { QuoteRequestNotificationEvent } from './entities/quote-request-notification-event.entity.js';
|
|
13
|
+
import { QuoteRequestRevision } from './entities/quote-request-revision.entity.js';
|
|
14
|
+
/**
|
|
15
|
+
* `setting_values.sales_channel_id` is a `uuid` column, so a channel id that is
|
|
16
|
+
* not one cannot match a row — PostgreSQL rejects the comparison outright. The
|
|
17
|
+
* check is here rather than in the settings service because this is the caller
|
|
18
|
+
* that has somewhere to degrade to.
|
|
19
|
+
*/
|
|
20
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
|
+
/**
|
|
22
|
+
* Conditions this module has already reported. Module scope and never reset, so
|
|
23
|
+
* the guard is deliberately **per process** rather than per read: an unresolved
|
|
24
|
+
* channel or a mis-scoped setting is a deployment fact that holds for every
|
|
25
|
+
* subsequent read, and RFQ settings are read on every quote creation and every
|
|
26
|
+
* storefront page load. One line per condition is what makes it findable; one
|
|
27
|
+
* line per read is what makes it invisible. Same shape as
|
|
28
|
+
* `transactional_emails`' `noTransportWarned`.
|
|
29
|
+
*/
|
|
30
|
+
const warnedConditions = new Set();
|
|
31
|
+
function warnOnce(condition, message) {
|
|
32
|
+
if (warnedConditions.has(condition))
|
|
33
|
+
return;
|
|
34
|
+
warnedConditions.add(condition);
|
|
35
|
+
console.warn(message);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The two actor-shaped names this module reads, defaulted here
|
|
39
|
+
* (`specs/117-instance-bring-up/` Phase 6, FR-033).
|
|
40
|
+
*
|
|
41
|
+
* They were contributed by `backend/src/composition.ts` and by
|
|
42
|
+
* `backend/test/helpers/test-server.ts` and by nothing else, so a composition
|
|
43
|
+
* that is neither of those two — which is every client instance, `composeApp`
|
|
44
|
+
* being the whole of what one calls — could not resolve them. `composeApp`'s
|
|
45
|
+
* own options say what that state is: `contribute` is *"the one slot where a
|
|
46
|
+
* value a module defaults may be overwritten"*, and a name **nothing** defaults
|
|
47
|
+
* supplied through it is a required value wearing an override's clothes.
|
|
48
|
+
*
|
|
49
|
+
* Both bodies are this module's own and carry no deployment judgement. They
|
|
50
|
+
* read `request.actor`, which is the platform's decoration since T118b, and
|
|
51
|
+
* three ports this manifest already declares as binding `dependencies`:
|
|
52
|
+
* `customer_accounts`, `admin_users` and `admin_roles`. A root that wants a
|
|
53
|
+
* different answer still contributes one — the test harness does, and its
|
|
54
|
+
* divergence is deliberate — which is what the window is for.
|
|
55
|
+
*/
|
|
56
|
+
function registerActorResolvers(ctx) {
|
|
57
|
+
ctx.di.register({
|
|
58
|
+
/**
|
|
59
|
+
* Who is asking, in this module's shape: the account, its Organization and
|
|
60
|
+
* whether the caller administers it.
|
|
61
|
+
*
|
|
62
|
+
* The Organization guard asserts an invariant rather than describing a
|
|
63
|
+
* business state (D-178) — `customer_accounts.organization_id` is `NOT NULL`
|
|
64
|
+
* and an individual is backed by a personal organisation — so a caller
|
|
65
|
+
* reaching it is a broken invariant and not a buyer who should be told to
|
|
66
|
+
* attach something they cannot attach. It is kept rather than deleted
|
|
67
|
+
* because `Actor`'s `organizationId` is `string | null` at this seam and
|
|
68
|
+
* `CustomerContext`'s is `string`.
|
|
69
|
+
*/
|
|
70
|
+
rfqCustomerContextResolver: ctx
|
|
71
|
+
.asFunction(() => {
|
|
72
|
+
const accounts = lazyPort(ctx, 'customerAccountReadPort');
|
|
73
|
+
return async (request) => {
|
|
74
|
+
const actor = request.actor;
|
|
75
|
+
if (actor?.kind !== 'customer') {
|
|
76
|
+
throw new HttpError(401, ERROR_CODES.UNAUTHORIZED, 'Customer session required.');
|
|
77
|
+
}
|
|
78
|
+
if (!actor.organizationId) {
|
|
79
|
+
throw new HttpError(500, ERROR_CODES.INTERNAL, 'Invariant violated: a customer account has no Organization (Principle XI).', { code: 'customer_account_organization_missing' });
|
|
80
|
+
}
|
|
81
|
+
const account = await accounts.findById(actor.customerAccountId);
|
|
82
|
+
return {
|
|
83
|
+
customerAccountId: actor.customerAccountId,
|
|
84
|
+
organizationId: actor.organizationId,
|
|
85
|
+
isOrgAdmin: account?.role === 'organization_admin',
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
})
|
|
89
|
+
.singleton(),
|
|
90
|
+
/**
|
|
91
|
+
* Who is administering, with the role the admin surface renders beside the
|
|
92
|
+
* RFQ and scopes its list by.
|
|
93
|
+
*
|
|
94
|
+
* `getById` rather than a nullable lookup, and it cannot 404 here:
|
|
95
|
+
* `admin_users_admin_role_fk` is `on delete restrict`, so a non-null
|
|
96
|
+
* `adminRoleId` names a row that exists.
|
|
97
|
+
*/
|
|
98
|
+
rfqAdminContextResolver: ctx
|
|
99
|
+
.asFunction(() => {
|
|
100
|
+
const admins = lazyPort(ctx, 'adminUserReadPort');
|
|
101
|
+
const roles = lazyPort(ctx, 'adminRolePort');
|
|
102
|
+
return async (request) => {
|
|
103
|
+
const actor = request.actor;
|
|
104
|
+
if (actor?.kind !== 'admin') {
|
|
105
|
+
throw new HttpError(401, ERROR_CODES.UNAUTHORIZED, 'Admin session required.');
|
|
106
|
+
}
|
|
107
|
+
const adminUser = await admins.findById(actor.adminUserId);
|
|
108
|
+
const role = adminUser?.adminRoleId ? await roles.getById(adminUser.adminRoleId) : null;
|
|
109
|
+
return {
|
|
110
|
+
adminUserId: actor.adminUserId,
|
|
111
|
+
isPlatformAdmin: role?.code === 'platform_admin',
|
|
112
|
+
roleLabel: role?.code === 'platform_admin'
|
|
113
|
+
? 'Platform administrator'
|
|
114
|
+
: role?.code === 'sales_representative'
|
|
115
|
+
? 'Sales representative'
|
|
116
|
+
: (role?.name ?? 'Administrator'),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.singleton(),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The rate a quoted line is priced at (`specs/117-instance-bring-up/` Phase 6,
|
|
125
|
+
* FR-033).
|
|
126
|
+
*
|
|
127
|
+
* Two ports, both already binding `dependencies` of this manifest: the
|
|
128
|
+
* organisation's VAT status and country from `organizations`, and the rate
|
|
129
|
+
* itself from `taxes`.
|
|
130
|
+
*
|
|
131
|
+
* **No `catch`, and that is the design** (issue #84). `taxRateFor` answers
|
|
132
|
+
* "nothing applies" as a *value* — `{ source: 'none' }`, with no rate to read —
|
|
133
|
+
* so the only errors that reach this seam are a failing database and a
|
|
134
|
+
* switched-off owner, and returning 0 for either would quote a zero-VAT price
|
|
135
|
+
* on an operator's behalf and call it an answer. Both owners are
|
|
136
|
+
* `nonDeactivatable`, so neither has an off state here; the refusal is what
|
|
137
|
+
* reaches a caller if that ever changes.
|
|
138
|
+
*/
|
|
139
|
+
function registerTaxRateResolver(ctx) {
|
|
140
|
+
ctx.di.register({
|
|
141
|
+
rfqTaxRateResolver: ctx
|
|
142
|
+
.asFunction(() => {
|
|
143
|
+
const taxProfiles = lazyPort(ctx, 'organizationTaxProfilePort');
|
|
144
|
+
const taxes = lazyPort(ctx, 'taxService');
|
|
145
|
+
return async (organizationId) => {
|
|
146
|
+
const org = await taxProfiles.taxProfileOf(organizationId);
|
|
147
|
+
const vatStatus = org?.vatStatus ?? 'vat_payer';
|
|
148
|
+
if (vatStatus !== 'vat_payer')
|
|
149
|
+
return 0;
|
|
150
|
+
const resolved = await taxes.taxRateFor({
|
|
151
|
+
country: org?.country ?? 'PL',
|
|
152
|
+
productType: 'simple',
|
|
153
|
+
vatStatus,
|
|
154
|
+
});
|
|
155
|
+
// `none` is the operator's own configuration state — `taxes` is
|
|
156
|
+
// present and holds no rule that applies and no default — so a quote
|
|
157
|
+
// is priced net and the quote view drops its VAT row rather than
|
|
158
|
+
// printing a 0% one.
|
|
159
|
+
return resolved.source === 'none' ? 0 : resolved.rate;
|
|
160
|
+
};
|
|
161
|
+
})
|
|
162
|
+
.singleton(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
export function registerModule(ctx) {
|
|
166
|
+
registerActorResolvers(ctx);
|
|
167
|
+
registerTaxRateResolver(ctx);
|
|
168
|
+
ctx.di.register({
|
|
169
|
+
quoteRequests: ctx
|
|
170
|
+
.asFunction(({ emFactory, eventBus, auditLogService }) => {
|
|
171
|
+
/**
|
|
172
|
+
* This module's own settings, read through the port it already holds,
|
|
173
|
+
* against the **resolved** sales channel.
|
|
174
|
+
*
|
|
175
|
+
* It used to pass the literal `'default'` as the `salesChannelId`, a
|
|
176
|
+
* spelling that came down from the composition root T132 lifted this
|
|
177
|
+
* out of. `'default'` is a channel *code*; `setting_values.sales_channel_id`
|
|
178
|
+
* is `uuid`, so PostgreSQL rejected every one of these reads
|
|
179
|
+
* (`invalid input syntax for type uuid: "default"`), the bare `catch`
|
|
180
|
+
* below swallowed it, and all four settings answered with their module
|
|
181
|
+
* fallback on every call — expiry never applied, RFQ numbers never
|
|
182
|
+
* carried the configured prefix or suffix, and an operator switching a
|
|
183
|
+
* storefront flag off changed nothing.
|
|
184
|
+
*
|
|
185
|
+
* What the handler may absorb is now enumerated, because a bare `catch`
|
|
186
|
+
* around a port call turns fail-closed into fail-open
|
|
187
|
+
* (`docs/docs/architecture/kernel.md`; the composition checklist's rule 7):
|
|
188
|
+
*
|
|
189
|
+
* - `SettingNotRegistered` — "no value configured yet", the normal
|
|
190
|
+
* state before the manifest reconciler has run. Quiet fallback.
|
|
191
|
+
* - `SettingOutOfScopeForChannel` — the operator scoped this setting to
|
|
192
|
+
* other channels, so no value applies here. The module default is the
|
|
193
|
+
* right answer, but the scoping is almost certainly unintended, so it
|
|
194
|
+
* is warned about.
|
|
195
|
+
* - everything else — a shape mismatch, a driver error, a
|
|
196
|
+
* `ModuleDisabledError` from a switched-off owner — propagates.
|
|
197
|
+
*/
|
|
198
|
+
const setting = async (code, schema, fallback) => {
|
|
199
|
+
const cradle = ctx.cradle();
|
|
200
|
+
const channelId = await cradle.settingsChannelResolver();
|
|
201
|
+
if (channelId === null || !UUID_PATTERN.test(channelId)) {
|
|
202
|
+
// Both roots answer this resolver with the system-default channel's
|
|
203
|
+
// id, falling back to `ORGANIZATIONS_SETTINGS_CHANNEL_ID` — an env
|
|
204
|
+
// knob whose own default is the string `'default'`. So "no channel"
|
|
205
|
+
// arrives here in two shapes, `null` and a non-uuid placeholder, and
|
|
206
|
+
// neither can address a `setting_values` row. Degrading to the
|
|
207
|
+
// module default keeps RFQs working, but silently is exactly the
|
|
208
|
+
// failure above, so it is reported.
|
|
209
|
+
warnOnce('unresolved-channel', `[quote_requests] no sales channel resolved for settings (got ` +
|
|
210
|
+
`${channelId === null ? 'null' : `"${channelId}"`}) — every ` +
|
|
211
|
+
`quote_requests.* setting falls back to its module default ` +
|
|
212
|
+
`(logged once per process).`);
|
|
213
|
+
return fallback;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return await cradle.settingsReadPort.get(code, channelId, schema);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error instanceof SettingNotRegistered)
|
|
220
|
+
return fallback;
|
|
221
|
+
if (error instanceof SettingOutOfScopeForChannel) {
|
|
222
|
+
warnOnce(`out-of-scope:${code}`, `[quote_requests] setting "${code}" is not in scope for sales channel ` +
|
|
223
|
+
`"${channelId}" — falling back to the module default ` +
|
|
224
|
+
`(logged once per process).`);
|
|
225
|
+
return fallback;
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
return quoteRequestsModule({
|
|
231
|
+
emFactory,
|
|
232
|
+
eventBus,
|
|
233
|
+
auditLog: auditLogService,
|
|
234
|
+
customFieldValues: lazyPort(ctx, 'customFieldValueService'),
|
|
235
|
+
requireAdmin: (permission) => async (req, reply) => ctx.cradle().requireAdmin(permission)(req, reply),
|
|
236
|
+
requireCustomer: (req, reply) => ctx.cradle().requireCustomer(req, reply),
|
|
237
|
+
resolveCustomerContext: (req) => ctx.cradle().rfqCustomerContextResolver(req),
|
|
238
|
+
resolveAdminContext: (req) => ctx.cradle().rfqAdminContextResolver(req),
|
|
239
|
+
resolveTaxRate: (organizationId) => ctx.cradle().rfqTaxRateResolver(organizationId),
|
|
240
|
+
// Issue #108 — `organizations`' scope, resolved. This used to be the
|
|
241
|
+
// two feature-056 subtree deps, out of which this module built its own
|
|
242
|
+
// `SalesRepAssignmentService`: one of three assemblies of one class,
|
|
243
|
+
// and the shape whose optional third argument `customers` omitted.
|
|
244
|
+
salesRepAssignment: lazyPort(ctx, 'organizationSalesRepScopePort'),
|
|
245
|
+
// Feature 075, Phase C — the five owners this module read out of
|
|
246
|
+
// directly, and the one it wrote into. Each was an `em.find` /
|
|
247
|
+
// `em.create` against another module's table, which no gate can see;
|
|
248
|
+
// all six are already binding `dependencies` of this manifest, and
|
|
249
|
+
// `carts.replaceItemsForCustomer` moves the quote-to-cart conversion
|
|
250
|
+
// inside the module that owns the tables (D-78 rule 1).
|
|
251
|
+
catalogProducts: lazyPort(ctx, 'catalogProductReadPort'),
|
|
252
|
+
// Issue #259 — the channel assortment gate on the three quote-line
|
|
253
|
+
// seams. A kernel registration, so there is no seventh module edge.
|
|
254
|
+
channelMembership: lazyPort(ctx, 'salesChannelMembershipPort'),
|
|
255
|
+
customerAccounts: lazyPort(ctx, 'customerAccountReadPort'),
|
|
256
|
+
organizations: lazyPort(ctx, 'organizationDetailsPort'),
|
|
257
|
+
adminUsers: lazyPort(ctx, 'adminUserReadPort'),
|
|
258
|
+
orders: lazyPort(ctx, 'orderReadPort'),
|
|
259
|
+
carts: lazyPort(ctx, 'cartWritePort'),
|
|
260
|
+
resolveExpiryDays: () => setting(QUOTE_REQUESTS_SETTING_CODES.EXPIRY_DAYS, z.number().int().nonnegative(), 0),
|
|
261
|
+
resolveBoolSetting: (key) => setting(key === 'show_add_to_quote_on_card'
|
|
262
|
+
? QUOTE_REQUESTS_SETTING_CODES.SHOW_ADD_TO_QUOTE_ON_CARD
|
|
263
|
+
: QUOTE_REQUESTS_SETTING_CODES.SHOW_ADD_TO_QUOTE_ON_PDP, z.boolean(), true),
|
|
264
|
+
resolveBusinessIdPrefix: () => setting(QUOTE_REQUESTS_SETTING_CODES.BUSINESS_ID_PREFIX, z.string(), ''),
|
|
265
|
+
resolveBusinessIdSuffix: () => setting(QUOTE_REQUESTS_SETTING_CODES.BUSINESS_ID_SUFFIX, z.string(), ''),
|
|
266
|
+
});
|
|
267
|
+
})
|
|
268
|
+
.singleton(),
|
|
269
|
+
});
|
|
270
|
+
// The type parameter is feature 075 Phase P's compile-time proof that the
|
|
271
|
+
// service still satisfies `RfqCustomerPort` — the two methods `carts`,
|
|
272
|
+
// `orders`, `shopping_lists`, `quick_order` and `customers` measurably call.
|
|
273
|
+
// Both already answer with contract DTOs, so no adapter was needed.
|
|
274
|
+
ctx.di.providePort('quoteRequestReadPort', ctx
|
|
275
|
+
.asFunction(({ emFactory }) => new QuoteRequestReadService(emFactory))
|
|
276
|
+
.singleton());
|
|
277
|
+
ctx.di.providePort('rfqService', ctx
|
|
278
|
+
.asFunction(({ quoteRequests }) => quoteRequests.handle().rfqService)
|
|
279
|
+
.singleton());
|
|
280
|
+
/**
|
|
281
|
+
* US5 — an order created from a quote request completes it (issue #107).
|
|
282
|
+
*
|
|
283
|
+
* This was a bare `eventBus.on` in the plugin body: with `quote_requests`
|
|
284
|
+
* switched off it still flipped the RFQ to Completed, appended a `completed`
|
|
285
|
+
* event row and enqueued the customer notification — while every route that
|
|
286
|
+
* could show the customer that quote request refused. `ctx.subscribe` makes
|
|
287
|
+
* the module's effective state decide.
|
|
288
|
+
*/
|
|
289
|
+
ctx.subscribe('order.created.v1', async (payload) => {
|
|
290
|
+
await ctx
|
|
291
|
+
.cradle()
|
|
292
|
+
.quoteRequests.handle()
|
|
293
|
+
.orderCompletionReactor.onOrderCreated(payload);
|
|
294
|
+
});
|
|
295
|
+
/**
|
|
296
|
+
* How many quote requests are attributed to a sales channel (feature 075,
|
|
297
|
+
* D-87).
|
|
298
|
+
*
|
|
299
|
+
* `sales_channels` used to count them itself, naming this module's table and
|
|
300
|
+
* this module's column in raw SQL. A read port would have been the wrong
|
|
301
|
+
* repair and this module is the reason: `sales_channels` is
|
|
302
|
+
* `nonDeactivatable`, and the lifecycle refuses to disable a module a
|
|
303
|
+
* non-deactivatable one depends on, so a `dependencies` entry pointing this
|
|
304
|
+
* way would have made this module permanently undeactivatable in order to
|
|
305
|
+
* count rows before a channel delete.
|
|
306
|
+
*
|
|
307
|
+
* A **contribution** hook: it pushes an inert counter into
|
|
308
|
+
* `salesChannelAttributionRegistry`, an ungated registry, and carries no
|
|
309
|
+
* presence probe (D-67/D-68). It must not carry one for a second reason here
|
|
310
|
+
* — the registry's policy is to honour an absent contributor, because the
|
|
311
|
+
* rows survive a deactivation and `quote_requests_sales_channel_fk` is
|
|
312
|
+
* `on delete restrict`, so a probe would turn a 422 naming this module into a
|
|
313
|
+
* raw constraint violation.
|
|
314
|
+
*/
|
|
315
|
+
ctx.onBoot(() => {
|
|
316
|
+
registerQuoteRequestSalesChannelAttributions(lazyPort(ctx, 'salesChannelAttributionRegistry'), ctx.cradle().emFactory);
|
|
317
|
+
});
|
|
318
|
+
ctx.routes(async (app) => {
|
|
319
|
+
await ctx.cradle().quoteRequests.register(app);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The module's persisted entity classes, on the `./backend` subpath, as one
|
|
324
|
+
* array and **no named class export** (D-168).
|
|
325
|
+
*
|
|
326
|
+
* This is the shape the platform already reads, in two places: the boot-time
|
|
327
|
+
* loader (`src/packages/package-runtime.ts`, `exported['entities']`) and the
|
|
328
|
+
* static declaration reader (`scripts/lib/package-declarations.ts`), which is
|
|
329
|
+
* the third source of `check:module-boundary`'s `table→owner` map and the
|
|
330
|
+
* package pass of `check-entity-tenant-classification`. `blog` published
|
|
331
|
+
* `export *` lines instead until D-168, which satisfied only the committed host
|
|
332
|
+
* registry — the one path that stops being taken the day the module is
|
|
333
|
+
* *installed* rather than linked, at which point the loader read `undefined`,
|
|
334
|
+
* returned `[]`, and registered zero entities without a word.
|
|
335
|
+
*
|
|
336
|
+
* The absence of a named export is the load-bearing half. With one,
|
|
337
|
+
* `import type { QuoteRequest } from '@endora-commerce/mod-quote-requests/backend'`
|
|
338
|
+
* compiles in any consumer — ours, a deployment's, a stranger's — and only a
|
|
339
|
+
* check whose population is *this* repository could ever object. Without it,
|
|
340
|
+
* that import is TS2459 in the consumer's own tree (the barrel imports the
|
|
341
|
+
* classes to build the array, so TypeScript gives the more precise "declares it
|
|
342
|
+
* locally, but it is not exported"), which is Principle I holding by
|
|
343
|
+
* construction. Nothing legitimate is lost: D-32 already forbids an ORM
|
|
344
|
+
* relation from another module into these classes, and every other cross-module
|
|
345
|
+
* read goes through a contract type — `QuoteRequestReadPort` and
|
|
346
|
+
* `RfqCustomerPort`, both published above.
|
|
347
|
+
*
|
|
348
|
+
* Identity matters more here than anywhere else in the package: MikroORM keys
|
|
349
|
+
* its metadata on the class, so a consumer that reached these files by a second
|
|
350
|
+
* specifier would register a second `QuoteRequest` and lose one of them at
|
|
351
|
+
* discovery (D-160.6, measured). One array behind one declared subpath is the
|
|
352
|
+
* only way in.
|
|
353
|
+
*/
|
|
354
|
+
export const entities = [
|
|
355
|
+
QuoteRequest,
|
|
356
|
+
QuoteRequestEvent,
|
|
357
|
+
QuoteRequestItem,
|
|
358
|
+
QuoteRequestNotificationEvent,
|
|
359
|
+
QuoteRequestRevision,
|
|
360
|
+
];
|
|
361
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql';
|
|
3
|
+
import type { EventBus } from '@endora-commerce/platform/events';
|
|
4
|
+
import type { AuditPort } from '@endora-commerce/platform/kernel';
|
|
5
|
+
import type { AdminUserReadPort, CartWritePort, CatalogProductReadPort, CustomerAccountReadPort, CustomFieldValuePort, OrderReadPort, OrganizationDetailsPort, SalesRepAssignmentPort } from '@endora-commerce/contracts';
|
|
6
|
+
import { RfqService } from './services/rfq-service.js';
|
|
7
|
+
import { RfqAdminService } from './services/rfq-admin-service.js';
|
|
8
|
+
import { RfqExpiryWorker } from './services/rfq-expiry-worker.js';
|
|
9
|
+
import { createOrderCompletionReactor } from './services/order-completion-reactor.js';
|
|
10
|
+
import { type CustomerContextResolver, type RequireCustomerGuard } from './routes.customer.js';
|
|
11
|
+
import { type AdminContextResolver } from './routes.admin.js';
|
|
12
|
+
import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
|
|
13
|
+
import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
|
|
14
|
+
export interface QuoteRequestsModuleOptions {
|
|
15
|
+
emFactory: () => EntityManager;
|
|
16
|
+
eventBus: EventBus;
|
|
17
|
+
requireCustomer: RequireCustomerGuard;
|
|
18
|
+
requireAdmin: RequireAdminFactory;
|
|
19
|
+
resolveCustomerContext: CustomerContextResolver;
|
|
20
|
+
resolveAdminContext: AdminContextResolver;
|
|
21
|
+
/** Reads the current `quote_requests.expiry_days` from the settings module. */
|
|
22
|
+
resolveExpiryDays: () => Promise<number>;
|
|
23
|
+
/** Reads the storefront-visibility flags from settings. */
|
|
24
|
+
resolveBoolSetting: (key: 'show_add_to_quote_on_card' | 'show_add_to_quote_on_pdp') => Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Reads the business Quote Request ID prefix/suffix from settings
|
|
27
|
+
* (`quote_requests.business_id.*`). Optional — when omitted, generated IDs
|
|
28
|
+
* are the bare sequence number.
|
|
29
|
+
*/
|
|
30
|
+
resolveBusinessIdPrefix?: () => Promise<string>;
|
|
31
|
+
resolveBusinessIdSuffix?: () => Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Feature 026 — refuses RFQ submission when the Customer's Organization is
|
|
34
|
+
* not `active`.
|
|
35
|
+
*/
|
|
36
|
+
assertOrganizationCanTransact?: (organizationId: string) => Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the VAT rate (fraction, e.g. `0.23`) applied to a quote's net
|
|
39
|
+
* prices for the given Organization. Supplied by composition (reads the
|
|
40
|
+
* Organization VAT status + tax rules).
|
|
41
|
+
*
|
|
42
|
+
* Required since issue #124: omitting it quoted every organization at 0% VAT
|
|
43
|
+
* and called that a price, which is indistinguishable from a deployment whose
|
|
44
|
+
* rules genuinely say 0%. An absent `taxes` module now reaches the caller as
|
|
45
|
+
* the 503 `MODULE_DISABLED` envelope through this resolver.
|
|
46
|
+
*/
|
|
47
|
+
resolveTaxRate: (organizationId: string) => Promise<number>;
|
|
48
|
+
/** Feature 054 — audits RFQ lifecycle writes co-transactionally when provided. */
|
|
49
|
+
auditLog?: AuditPort;
|
|
50
|
+
/** Feature 055 — validates + reads RFQ custom-field values on the admin edit path. */
|
|
51
|
+
customFieldValues?: CustomFieldValuePort;
|
|
52
|
+
/**
|
|
53
|
+
* Feature 075, Phase C — the five owners this module reads, and the one it
|
|
54
|
+
* writes. Each replaces an `em.find` against another module's table, which
|
|
55
|
+
* deactivation cannot reach; each owner is a binding dependency of this
|
|
56
|
+
* manifest, so every one of them fails closed.
|
|
57
|
+
*/
|
|
58
|
+
catalogProducts: CatalogProductReadPort;
|
|
59
|
+
/**
|
|
60
|
+
* The sanctioned bridge accessor (Constitution XII), for the assortment gate
|
|
61
|
+
* a quote line owes (issue #259). Kernel-registered, so it is not one of the
|
|
62
|
+
* six module edges above.
|
|
63
|
+
*/
|
|
64
|
+
channelMembership: SalesChannelMembershipPort;
|
|
65
|
+
customerAccounts: CustomerAccountReadPort;
|
|
66
|
+
organizations: OrganizationDetailsPort;
|
|
67
|
+
adminUsers: AdminUserReadPort;
|
|
68
|
+
orders: OrderReadPort;
|
|
69
|
+
carts: CartWritePort;
|
|
70
|
+
/**
|
|
71
|
+
* `organizations`' sales-rep assignment port (issue #108).
|
|
72
|
+
*
|
|
73
|
+
* This used to be `salesRepSubtree`, the feature-056 deps out of which this
|
|
74
|
+
* module built its **own** `SalesRepAssignmentService`. One class, assembled
|
|
75
|
+
* in three places — its owner and two consumers — and the assembly here was
|
|
76
|
+
* complete; the one in `customers` omitted the optional argument that carries
|
|
77
|
+
* the roll-up, which is what a copy makes possible. The scope is
|
|
78
|
+
* `organizations`', so it is resolved, not rebuilt.
|
|
79
|
+
*/
|
|
80
|
+
salesRepAssignment: SalesRepAssignmentPort;
|
|
81
|
+
}
|
|
82
|
+
export interface QuoteRequestsModuleHandle {
|
|
83
|
+
/** US5 reactor; `backend.ts` owns its subscription. */
|
|
84
|
+
orderCompletionReactor: ReturnType<typeof createOrderCompletionReactor>;
|
|
85
|
+
expiryWorker: RfqExpiryWorker;
|
|
86
|
+
rfqService: RfqService;
|
|
87
|
+
adminService: RfqAdminService;
|
|
88
|
+
salesRepAssignment: SalesRepAssignmentPort;
|
|
89
|
+
}
|
|
90
|
+
export declare function quoteRequestsModule(options: QuoteRequestsModuleOptions): {
|
|
91
|
+
register: (app: FastifyInstance) => Promise<void>;
|
|
92
|
+
handle: () => QuoteRequestsModuleHandle;
|
|
93
|
+
};
|
|
94
|
+
export type { CustomerContextResolver, AdminContextResolver, RequireCustomerGuard, FastifyRequest };
|
|
95
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { RfqService } from './services/rfq-service.js';
|
|
2
|
+
import { createQuoteRequestBusinessIdGenerator } from './services/quote-request-business-id-generator.js';
|
|
3
|
+
import { RfqAdminService } from './services/rfq-admin-service.js';
|
|
4
|
+
import { RfqEventService } from './services/rfq-event-service.js';
|
|
5
|
+
import { RfqRevisionService } from './services/rfq-revision-service.js';
|
|
6
|
+
import { RfqNotificationService } from './services/rfq-notification-service.js';
|
|
7
|
+
import { RfqExpiryWorker } from './services/rfq-expiry-worker.js';
|
|
8
|
+
import { createOrderCompletionReactor } from './services/order-completion-reactor.js';
|
|
9
|
+
import { registerQuoteRequestsCustomerRoutes, } from './routes.customer.js';
|
|
10
|
+
import { registerQuoteRequestsAdminRoutes, } from './routes.admin.js';
|
|
11
|
+
import { registerQuoteRequestsSalesRepRoutes } from './routes.sales-reps.js';
|
|
12
|
+
import { ModuleDisabledError } from '@endora-commerce/platform/kernel';
|
|
13
|
+
/**
|
|
14
|
+
* Conditions the storefront flag handler has already reported (D-43). Per
|
|
15
|
+
* process: these two flags are read on every product card render, so one line
|
|
16
|
+
* per condition is what makes a settings outage findable and one line per read
|
|
17
|
+
* is what buries it.
|
|
18
|
+
*/
|
|
19
|
+
const storefrontFlagWarned = new Set();
|
|
20
|
+
export function quoteRequestsModule(options) {
|
|
21
|
+
const eventService = new RfqEventService(options.emFactory);
|
|
22
|
+
const revisionService = new RfqRevisionService(options.emFactory);
|
|
23
|
+
const notificationService = new RfqNotificationService(options.emFactory);
|
|
24
|
+
const salesRepAssignment = options.salesRepAssignment;
|
|
25
|
+
// Business Quote Request ID generator — adapts the composition-wired
|
|
26
|
+
// prefix/suffix resolver closures (SettingsService-backed) to the
|
|
27
|
+
// generator's settings port; the generator itself draws the sequence.
|
|
28
|
+
const businessIdGenerator = createQuoteRequestBusinessIdGenerator(options.resolveBusinessIdPrefix || options.resolveBusinessIdSuffix
|
|
29
|
+
? {
|
|
30
|
+
resolvePrefix: () => options.resolveBusinessIdPrefix?.() ?? Promise.resolve(''),
|
|
31
|
+
resolveSuffix: () => options.resolveBusinessIdSuffix?.() ?? Promise.resolve(''),
|
|
32
|
+
}
|
|
33
|
+
: undefined);
|
|
34
|
+
const rfqService = new RfqService({
|
|
35
|
+
emFactory: options.emFactory,
|
|
36
|
+
events: options.eventBus,
|
|
37
|
+
eventService,
|
|
38
|
+
revisionService,
|
|
39
|
+
notificationService,
|
|
40
|
+
salesRepAssignment,
|
|
41
|
+
catalogProducts: options.catalogProducts,
|
|
42
|
+
channelMembership: options.channelMembership,
|
|
43
|
+
customerAccounts: options.customerAccounts,
|
|
44
|
+
adminUsers: options.adminUsers,
|
|
45
|
+
carts: options.carts,
|
|
46
|
+
businessId: businessIdGenerator,
|
|
47
|
+
resolveTaxRate: options.resolveTaxRate,
|
|
48
|
+
...(options.auditLog ? { auditLog: options.auditLog } : {}),
|
|
49
|
+
});
|
|
50
|
+
const adminService = new RfqAdminService({
|
|
51
|
+
emFactory: options.emFactory,
|
|
52
|
+
events: options.eventBus,
|
|
53
|
+
rfqService,
|
|
54
|
+
eventService,
|
|
55
|
+
revisionService,
|
|
56
|
+
notificationService,
|
|
57
|
+
salesRepAssignment,
|
|
58
|
+
catalogProducts: options.catalogProducts,
|
|
59
|
+
customerAccounts: options.customerAccounts,
|
|
60
|
+
organizations: options.organizations,
|
|
61
|
+
adminUsers: options.adminUsers,
|
|
62
|
+
...(options.auditLog ? { auditLog: options.auditLog } : {}),
|
|
63
|
+
...(options.customFieldValues ? { customFieldValues: options.customFieldValues } : {}),
|
|
64
|
+
});
|
|
65
|
+
const expiryWorker = new RfqExpiryWorker({
|
|
66
|
+
emFactory: options.emFactory,
|
|
67
|
+
events: options.eventBus,
|
|
68
|
+
eventService,
|
|
69
|
+
notificationService,
|
|
70
|
+
salesRepAssignment,
|
|
71
|
+
adminUsers: options.adminUsers,
|
|
72
|
+
resolveExpiryDays: options.resolveExpiryDays,
|
|
73
|
+
});
|
|
74
|
+
// US5 — the `order.created.v1` reactor. `backend.ts` registers it through
|
|
75
|
+
// `ctx.subscribe`, so a switched-off module completes no quote request.
|
|
76
|
+
const orderCompletionReactor = createOrderCompletionReactor({
|
|
77
|
+
emFactory: options.emFactory,
|
|
78
|
+
orders: options.orders,
|
|
79
|
+
eventService,
|
|
80
|
+
notificationService,
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
register: async (app) => {
|
|
84
|
+
await registerQuoteRequestsCustomerRoutes(app, {
|
|
85
|
+
rfqService,
|
|
86
|
+
requireCustomer: options.requireCustomer,
|
|
87
|
+
resolveCustomerContext: options.resolveCustomerContext,
|
|
88
|
+
...(options.assertOrganizationCanTransact
|
|
89
|
+
? { assertOrganizationCanTransact: options.assertOrganizationCanTransact }
|
|
90
|
+
: {}),
|
|
91
|
+
});
|
|
92
|
+
await registerQuoteRequestsAdminRoutes(app, {
|
|
93
|
+
adminService,
|
|
94
|
+
requireAdmin: options.requireAdmin,
|
|
95
|
+
resolveAdminContext: options.resolveAdminContext,
|
|
96
|
+
});
|
|
97
|
+
// D-166 — this module's one endpoint of the sales-rep pair, in this
|
|
98
|
+
// module's own file. It used to `import()` `organizations`' route file
|
|
99
|
+
// and register all four of its endpoints, which is what put a
|
|
100
|
+
// `quote_requests` gate on the assignment screen and an entry in each
|
|
101
|
+
// module's cross-module-import shard. The three endpoints that qualify an
|
|
102
|
+
// organisation are registered by `organizations` from its own
|
|
103
|
+
// `backend.ts` now, under a code that module declares.
|
|
104
|
+
await registerQuoteRequestsSalesRepRoutes(app, {
|
|
105
|
+
emFactory: options.emFactory,
|
|
106
|
+
requireAdmin: options.requireAdmin,
|
|
107
|
+
salesRepAssignment,
|
|
108
|
+
organizations: options.organizations,
|
|
109
|
+
});
|
|
110
|
+
// Storefront-public Quote Requests settings (FR-032 / FR-033) so the
|
|
111
|
+
// storefront can show/hide "Add to quote" buttons without going through
|
|
112
|
+
// the admin-gated settings endpoint. Reads through `resolveBoolSetting`,
|
|
113
|
+
// supplied by `backend.ts`, which already routes the two conditions D-43
|
|
114
|
+
// allows to the module default.
|
|
115
|
+
//
|
|
116
|
+
// The policy — a settings outage must not hide "Add to quote" — is right
|
|
117
|
+
// and stays. What was wrong is that a bare `catch` answered `true` for a
|
|
118
|
+
// **switched-off module** too, which Constitution XVII rule 5 forbids: a
|
|
119
|
+
// module that is off contributes no storefront element. So
|
|
120
|
+
// `ModuleDisabledError` is re-thrown, and anything else is reported once
|
|
121
|
+
// before the fail-open, because a fail-open nobody can see is the defect
|
|
122
|
+
// this whole cluster is about.
|
|
123
|
+
app.get('/api/v1/storefront/settings/quote-requests', async (_request, reply) => {
|
|
124
|
+
reply.header('cache-control', 'public, max-age=60');
|
|
125
|
+
try {
|
|
126
|
+
const card = await options.resolveBoolSetting('show_add_to_quote_on_card');
|
|
127
|
+
const pdp = await options.resolveBoolSetting('show_add_to_quote_on_pdp');
|
|
128
|
+
return { data: { showAddToQuoteOnCard: card, showAddToQuoteOnPdp: pdp } };
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof ModuleDisabledError)
|
|
132
|
+
throw error;
|
|
133
|
+
const name = error.name ?? 'Error';
|
|
134
|
+
if (!storefrontFlagWarned.has(name)) {
|
|
135
|
+
storefrontFlagWarned.add(name);
|
|
136
|
+
app.log.warn({ err: error }, '[quote_requests] storefront flag settings unreadable — showing the ' +
|
|
137
|
+
'quote affordances (logged once per condition per process)');
|
|
138
|
+
}
|
|
139
|
+
return { data: { showAddToQuoteOnCard: true, showAddToQuoteOnPdp: true } };
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
handle: () => ({
|
|
144
|
+
orderCompletionReactor,
|
|
145
|
+
expiryWorker,
|
|
146
|
+
rfqService,
|
|
147
|
+
adminService,
|
|
148
|
+
salesRepAssignment,
|
|
149
|
+
}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
|