@revenexx/cover 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/api/account/context.get.ts +1 -1
- package/server/api/cart/calculate.post.ts +1 -1
- package/server/api/cart/export.post.ts +1 -1
- package/server/api/orders/index.post.ts +27 -2
- package/server/api/requisitions/index.post.ts +1 -1
- package/server/data/forms/checkout/add-address.json +2 -2
- package/server/interfaces/b2bContext.ts +7 -3
- package/server/services/MockB2BContextService.ts +6 -4
- package/server/utils/revenexxSdk.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/cover",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Cover \u2014 revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI components, theming tokens and stores shared by the demo shop, custom storefronts and the Blokkli theme.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
@@ -7,7 +7,7 @@ export default defineEventHandler(async (event) => {
|
|
|
7
7
|
const user = await getAuthService(event).me(event);
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
|
-
return await getB2BContextService(event).getContext(user?.$id ?? null);
|
|
10
|
+
return await getB2BContextService(event).getContext(user?.$id ?? null, user?.role ?? null);
|
|
11
11
|
}
|
|
12
12
|
catch (err) {
|
|
13
13
|
getLogService().error("Service error: account/context", toErrorContext(err));
|
|
@@ -15,7 +15,7 @@ export default defineEventHandler(async (event) => {
|
|
|
15
15
|
const user = await getAuthService(event).me(event);
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
const context = await getB2BContextService(event).getContext(user?.$id ?? null);
|
|
18
|
+
const context = await getB2BContextService(event).getContext(user?.$id ?? null, user?.role ?? null);
|
|
19
19
|
return await getCartCalculationService().calculate(body, context, locale);
|
|
20
20
|
}
|
|
21
21
|
catch (err) {
|
|
@@ -14,7 +14,7 @@ export default defineEventHandler(async (event) => {
|
|
|
14
14
|
|
|
15
15
|
const locale = resolveLocale(event);
|
|
16
16
|
const user = await getAuthService(event).me(event);
|
|
17
|
-
const context = await getB2BContextService(event).getContext(user?.$id ?? null);
|
|
17
|
+
const context = await getB2BContextService(event).getContext(user?.$id ?? null, user?.role ?? null);
|
|
18
18
|
|
|
19
19
|
if (!context.settings.cart.exportEnabled) {
|
|
20
20
|
throw createError({ statusCode: 403, message: "Cart export is disabled" });
|
|
@@ -38,6 +38,30 @@ function isNonEmptyString(v: unknown): v is string {
|
|
|
38
38
|
return typeof v === "string" && v.trim().length > 0;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Customer addresses may carry the country as a display name ("Deutschland",
|
|
42
|
+
// "Austria") — the payments app's eligibility check expects ISO 3166-1
|
|
43
|
+
// alpha-2 codes. Two-letter values pass through unchanged.
|
|
44
|
+
const COUNTRY_NAME_TO_ISO: Record<string, string> = {
|
|
45
|
+
deutschland: "DE", germany: "DE",
|
|
46
|
+
österreich: "AT", austria: "AT",
|
|
47
|
+
schweiz: "CH", switzerland: "CH",
|
|
48
|
+
niederlande: "NL", netherlands: "NL",
|
|
49
|
+
frankreich: "FR", france: "FR",
|
|
50
|
+
italien: "IT", italy: "IT",
|
|
51
|
+
spanien: "ES", spain: "ES",
|
|
52
|
+
belgien: "BE", belgium: "BE",
|
|
53
|
+
polen: "PL", poland: "PL",
|
|
54
|
+
tschechien: "CZ", "czech republic": "CZ", czechia: "CZ",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function toIsoCountry(value: unknown): string {
|
|
58
|
+
const raw = String(value ?? "").trim();
|
|
59
|
+
if (/^[A-Za-z]{2}$/.test(raw)) {
|
|
60
|
+
return raw.toUpperCase();
|
|
61
|
+
}
|
|
62
|
+
return COUNTRY_NAME_TO_ISO[raw.toLowerCase()] ?? raw;
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
function validateAddress(address: Record<string, unknown>, customerType: "business" | "private"): string | null {
|
|
42
66
|
const requiredFields = customerType === "private"
|
|
43
67
|
? REQUIRED_ADDRESS_FIELDS_PRIVATE
|
|
@@ -122,7 +146,7 @@ export default defineEventHandler(async (event) => {
|
|
|
122
146
|
// Approval limits are checked at order time: exceeding a blocking
|
|
123
147
|
// limit turns the order into an approval request (it does not fail).
|
|
124
148
|
const user = await getAuthService(event).me(event);
|
|
125
|
-
const context = await getB2BContextService(event).getContext(user?.$id ?? null);
|
|
149
|
+
const context = await getB2BContextService(event).getContext(user?.$id ?? null, user?.role ?? null);
|
|
126
150
|
const locale = resolveLocale(event);
|
|
127
151
|
const calculation = await getCartCalculationService().calculate(
|
|
128
152
|
{
|
|
@@ -240,7 +264,7 @@ export default defineEventHandler(async (event) => {
|
|
|
240
264
|
const billing = address?.billingAddressSameAsShipping === false
|
|
241
265
|
? address?.billing as Record<string, unknown> | undefined
|
|
242
266
|
: address;
|
|
243
|
-
const country =
|
|
267
|
+
const country = toIsoCountry(body.billingCountry ?? billing?.country ?? "");
|
|
244
268
|
try {
|
|
245
269
|
const created = await useRevenexxSdk().payments.paymentsCreate({
|
|
246
270
|
methodCode: payment.method,
|
|
@@ -273,6 +297,7 @@ export default defineEventHandler(async (event) => {
|
|
|
273
297
|
throw err;
|
|
274
298
|
}
|
|
275
299
|
if (err instanceof RevenexxException && (err.code === 400 || err.code === 422)) {
|
|
300
|
+
getLogService().error("Payment rejected", apiErrorContext(err));
|
|
276
301
|
throw createError({ status: 422, message: err.message });
|
|
277
302
|
}
|
|
278
303
|
getLogService().error("Payment creation failed", apiErrorContext(err));
|
|
@@ -25,7 +25,7 @@ export default defineEventHandler(async (event) => {
|
|
|
25
25
|
throw createError({ statusCode: 400, message: "workflowId and items[] required" });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const context = await getB2BContextService(event).getContext(user.$id);
|
|
28
|
+
const context = await getB2BContextService(event).getContext(user.$id, user.role ?? null);
|
|
29
29
|
if (!context.settings.workflows.enabled) {
|
|
30
30
|
throw createError({ statusCode: 403, message: "Workflows are disabled" });
|
|
31
31
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"$formkit": "text",
|
|
8
8
|
"name": "postalCode",
|
|
9
9
|
"label": "fields.postalCode",
|
|
10
|
-
"validation": "required|matches:/^[0-9]
|
|
11
|
-
"validationMessages": { "matches": "validation.postalCodeFormat" },
|
|
10
|
+
"validation": "required|matches:/^[0-9]+$/|length:4,10",
|
|
11
|
+
"validationMessages": { "matches": "validation.postalCodeFormat", "length": "validation.postalCodeFormat" },
|
|
12
12
|
"autocomplete": "postal-code",
|
|
13
13
|
"inputmode": "numeric"
|
|
14
14
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { B2BContext, Requisition } from "../../app/interfaces/b2b";
|
|
1
|
+
import type { B2BContext, B2BRole, Requisition } from "../../app/interfaces/b2b";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Service contract for the per-user B2B context: organization settings,
|
|
@@ -7,8 +7,12 @@ import type { B2BContext, Requisition } from "../../app/interfaces/b2b";
|
|
|
7
7
|
* Register the active implementation via app.config → b2bService.
|
|
8
8
|
*/
|
|
9
9
|
export interface IB2BContextService {
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Context for a user; `null` userId returns the guest context.
|
|
12
|
+
* `fallbackRole` is the authenticated session's role — it applies when
|
|
13
|
+
* the user has no persona record (live logins against the public API).
|
|
14
|
+
*/
|
|
15
|
+
getContext(userId: string | null, fallbackRole?: B2BRole | null): Promise<B2BContext>;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { B2BContext, ApprovalLimit, ApprovalWorkflow, CostCenter, OrganizationSettings, Requisition } from "../../app/interfaces/b2b";
|
|
1
|
+
import type { B2BContext, B2BRole, ApprovalLimit, ApprovalWorkflow, CostCenter, OrganizationSettings, Requisition } from "../../app/interfaces/b2b";
|
|
2
2
|
import type { AuthRole } from "../../app/interfaces/auth";
|
|
3
3
|
|
|
4
4
|
import type { IB2BContextService, IRequisitionService } from "../interfaces/b2bContext";
|
|
@@ -30,7 +30,7 @@ const GUEST_ROLE = null;
|
|
|
30
30
|
export class MockB2BContextService implements IB2BContextService {
|
|
31
31
|
constructor(private readonly settingsOverride?: Partial<OrganizationSettings>) {}
|
|
32
32
|
|
|
33
|
-
async getContext(userId: string | null): Promise<B2BContext> {
|
|
33
|
+
async getContext(userId: string | null, fallbackRole?: B2BRole | null): Promise<B2BContext> {
|
|
34
34
|
const org = await readCoverConfigJson<OrganizationConfig>("account/organization.json");
|
|
35
35
|
const settings = this.mergeSettings(org.settings);
|
|
36
36
|
|
|
@@ -47,6 +47,8 @@ export class MockB2BContextService implements IB2BContextService {
|
|
|
47
47
|
|
|
48
48
|
const personas = await readCoverConfigJson<PersonaConfig[]>("account/personas.json");
|
|
49
49
|
const persona = personas.find(p => p.id === userId);
|
|
50
|
+
// Live logins have no persona record — the session's role applies.
|
|
51
|
+
const role = persona?.role ?? fallbackRole ?? GUEST_ROLE;
|
|
50
52
|
const overrides = await readGovernanceOverrides();
|
|
51
53
|
|
|
52
54
|
const baseWorkflows = await readCoverConfigJson<ApprovalWorkflow[]>("account/workflows.json");
|
|
@@ -67,7 +69,7 @@ export class MockB2BContextService implements IB2BContextService {
|
|
|
67
69
|
// Approval limits only apply when actually ordering — requesters never
|
|
68
70
|
// order directly, so their context carries none (limits are not
|
|
69
71
|
// checked for requisitions).
|
|
70
|
-
const limitsApply = settings.approvals.enabled &&
|
|
72
|
+
const limitsApply = settings.approvals.enabled && role !== "requester";
|
|
71
73
|
|
|
72
74
|
const costCenterLimits = limitsApply
|
|
73
75
|
? costCenters.flatMap(cc => (cc.limit ? [cc.limit] : []))
|
|
@@ -83,7 +85,7 @@ export class MockB2BContextService implements IB2BContextService {
|
|
|
83
85
|
: [];
|
|
84
86
|
|
|
85
87
|
return {
|
|
86
|
-
role
|
|
88
|
+
role,
|
|
87
89
|
settings,
|
|
88
90
|
costCenters,
|
|
89
91
|
defaultCostCenterId: settings.costCenters.enabled
|
|
@@ -84,7 +84,7 @@ export function apiAuthErrorType(err: RevenexxException): string {
|
|
|
84
84
|
/** Diagnostic fields for structured logs — never includes the API key. */
|
|
85
85
|
export function apiErrorContext(err: unknown): Record<string, unknown> {
|
|
86
86
|
if (err instanceof RevenexxException) {
|
|
87
|
-
return { apiStatus: err.code, apiType: err.type, apiMessage: err.message };
|
|
87
|
+
return { apiStatus: err.code, apiType: err.type, apiMessage: err.message, apiResponse: String(err.response ?? "").slice(0, 600) };
|
|
88
88
|
}
|
|
89
89
|
return { errorMessage: err instanceof Error ? err.message : String(err) };
|
|
90
90
|
}
|