@endora-commerce/mod-credit-limits 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/admin/index.d.ts +31 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +32 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/CreditLimitsPage.d.ts +4 -0
  8. package/dist/admin/pages/CreditLimitsPage.d.ts.map +1 -0
  9. package/dist/admin/pages/CreditLimitsPage.js +144 -0
  10. package/dist/admin/pages/CreditLimitsPage.js.map +1 -0
  11. package/dist/backend/entities/credit-limit-reservation.entity.d.ts +35 -0
  12. package/dist/backend/entities/credit-limit-reservation.entity.d.ts.map +1 -0
  13. package/dist/backend/entities/credit-limit-reservation.entity.js +99 -0
  14. package/dist/backend/entities/credit-limit-reservation.entity.js.map +1 -0
  15. package/dist/backend/entities/credit-limit-return-topup.entity.d.ts +29 -0
  16. package/dist/backend/entities/credit-limit-return-topup.entity.d.ts.map +1 -0
  17. package/dist/backend/entities/credit-limit-return-topup.entity.js +71 -0
  18. package/dist/backend/entities/credit-limit-return-topup.entity.js.map +1 -0
  19. package/dist/backend/entities/credit-limit.entity.d.ts +21 -0
  20. package/dist/backend/entities/credit-limit.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/credit-limit.entity.js +66 -0
  22. package/dist/backend/entities/credit-limit.entity.js.map +1 -0
  23. package/dist/backend/index.d.ts +80 -0
  24. package/dist/backend/index.d.ts.map +1 -0
  25. package/dist/backend/index.js +112 -0
  26. package/dist/backend/index.js.map +1 -0
  27. package/dist/backend/routes.d.ts +59 -0
  28. package/dist/backend/routes.d.ts.map +1 -0
  29. package/dist/backend/routes.js +114 -0
  30. package/dist/backend/routes.js.map +1 -0
  31. package/dist/backend/services/credit-limit-read.d.ts +23 -0
  32. package/dist/backend/services/credit-limit-read.d.ts.map +1 -0
  33. package/dist/backend/services/credit-limit-read.js +35 -0
  34. package/dist/backend/services/credit-limit-read.js.map +1 -0
  35. package/dist/backend/services/credit-limit-service.d.ts +179 -0
  36. package/dist/backend/services/credit-limit-service.d.ts.map +1 -0
  37. package/dist/backend/services/credit-limit-service.js +487 -0
  38. package/dist/backend/services/credit-limit-service.js.map +1 -0
  39. package/dist/backend/services/credit-topup.d.ts +33 -0
  40. package/dist/backend/services/credit-topup.d.ts.map +1 -0
  41. package/dist/backend/services/credit-topup.js +46 -0
  42. package/dist/backend/services/credit-topup.js.map +1 -0
  43. package/dist/manifest.d.ts +169 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +138 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/migrations/20260425T063333_credit_limits_init.d.ts +15 -0
  48. package/dist/migrations/20260425T063333_credit_limits_init.d.ts.map +1 -0
  49. package/dist/migrations/20260425T063333_credit_limits_init.js +56 -0
  50. package/dist/migrations/20260425T063333_credit_limits_init.js.map +1 -0
  51. package/dist/migrations/20260817T201111_credit_limits_return_topups.d.ts +17 -0
  52. package/dist/migrations/20260817T201111_credit_limits_return_topups.d.ts.map +1 -0
  53. package/dist/migrations/20260817T201111_credit_limits_return_topups.js +36 -0
  54. package/dist/migrations/20260817T201111_credit_limits_return_topups.js.map +1 -0
  55. package/dist/migrations/20260818T081252_credit_limits_credit_limit_reservation_order_fk.d.ts +44 -0
  56. package/dist/migrations/20260818T081252_credit_limits_credit_limit_reservation_order_fk.d.ts.map +1 -0
  57. package/dist/migrations/20260818T081252_credit_limits_credit_limit_reservation_order_fk.js +81 -0
  58. package/dist/migrations/20260818T081252_credit_limits_credit_limit_reservation_order_fk.js.map +1 -0
  59. package/dist/migrations/20260821T140323_credit_limits_reservation_reserving_organization.d.ts +29 -0
  60. package/dist/migrations/20260821T140323_credit_limits_reservation_reserving_organization.d.ts.map +1 -0
  61. package/dist/migrations/20260821T140323_credit_limits_reservation_reserving_organization.js +45 -0
  62. package/dist/migrations/20260821T140323_credit_limits_reservation_reserving_organization.js.map +1 -0
  63. package/dist/migrations/index.d.ts +39 -0
  64. package/dist/migrations/index.d.ts.map +1 -0
  65. package/dist/migrations/index.js +44 -0
  66. package/dist/migrations/index.js.map +1 -0
  67. package/dist/ports/index.d.ts +93 -0
  68. package/dist/ports/index.d.ts.map +1 -0
  69. package/dist/ports/index.js +2 -0
  70. package/dist/ports/index.js.map +1 -0
  71. package/docs/credit_limits.md +53 -0
  72. package/i18n/en.json +9 -0
  73. package/i18n/pl.json +9 -0
  74. package/package.json +92 -0
  75. package/tailwind.css +14 -0
@@ -0,0 +1,66 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
11
+ import { randomUUID } from 'crypto';
12
+ import { OrgScoped } from '@endora-commerce/platform/tenancy';
13
+ /**
14
+ * Per-Organization credit limit grant. One row per organization (enforced by
15
+ * the migration's unique index). `grantedAmount = 0` is the soft-revoke shape;
16
+ * the row is preserved for audit history.
17
+ *
18
+ * `@OrgScoped` (feature 050): reads are auto-confined to the ambient tenant. The
19
+ * grant/adjust routes additionally gate on org membership because inserts are not
20
+ * reachable by the column filter.
21
+ */
22
+ let CreditLimit = class CreditLimit {
23
+ [OptionalProps];
24
+ id = randomUUID();
25
+ organizationId;
26
+ grantedAmount;
27
+ currency;
28
+ grantedByAdminUserId;
29
+ createdAt = new Date();
30
+ updatedAt = new Date();
31
+ };
32
+ __decorate([
33
+ PrimaryKey({ type: 'uuid' }),
34
+ __metadata("design:type", String)
35
+ ], CreditLimit.prototype, "id", void 0);
36
+ __decorate([
37
+ Property({ type: 'uuid' }),
38
+ Index(),
39
+ __metadata("design:type", String)
40
+ ], CreditLimit.prototype, "organizationId", void 0);
41
+ __decorate([
42
+ Property({ type: 'decimal', precision: 14, scale: 2 }),
43
+ __metadata("design:type", String)
44
+ ], CreditLimit.prototype, "grantedAmount", void 0);
45
+ __decorate([
46
+ Property({ type: 'string', length: 3 }),
47
+ __metadata("design:type", String)
48
+ ], CreditLimit.prototype, "currency", void 0);
49
+ __decorate([
50
+ Property({ type: 'uuid', nullable: true }),
51
+ __metadata("design:type", Object)
52
+ ], CreditLimit.prototype, "grantedByAdminUserId", void 0);
53
+ __decorate([
54
+ Property({ type: 'datetime', onCreate: () => new Date() }),
55
+ __metadata("design:type", Date)
56
+ ], CreditLimit.prototype, "createdAt", void 0);
57
+ __decorate([
58
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
59
+ __metadata("design:type", Date)
60
+ ], CreditLimit.prototype, "updatedAt", void 0);
61
+ CreditLimit = __decorate([
62
+ OrgScoped(),
63
+ Entity({ tableName: 'credit_limits' })
64
+ ], CreditLimit);
65
+ export { CreditLimit };
66
+ //# sourceMappingURL=credit-limit.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credit-limit.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/credit-limit.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAE9D;;;;;;;;GAQG;AAGI,IAAM,WAAW,GAAjB,MAAM,WAAW;IACtB,CAAC,aAAa,CAAC,CAA6D;IAG5E,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,cAAc,CAAU;IAGxB,aAAa,CAAU;IAGvB,QAAQ,CAAU;IAGlB,oBAAoB,CAAiB;IAGrC,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AApBC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;mDACgB;AAGxB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;;kDAChC;AAGvB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;;6CACtB;AAGlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yDACN;AAGrC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;8CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;8CAAc;AAvBlB,WAAW;IAFvB,SAAS,EAAE;IACX,MAAM,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;GAC1B,WAAW,CAwBvB"}
@@ -0,0 +1,80 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyRequest } from 'fastify';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { EventBus } from '@endora-commerce/platform/events';
5
+ import type { ModuleContext, RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ import type { OrganizationInheritancePort } from '@endora-commerce/contracts';
7
+ import { CreditLimitService } from './services/credit-limit-service.js';
8
+ import { CreditLimit } from './entities/credit-limit.entity.js';
9
+ import { CreditLimitReservation } from './entities/credit-limit-reservation.entity.js';
10
+ import { CreditLimitReturnTopup } from './entities/credit-limit-return-topup.entity.js';
11
+ /**
12
+ * `credit_limits` — an optional Command Bus on the one write that must be
13
+ * audited (feature 072, wave 2, T101).
14
+ *
15
+ * `CreditLimitService` took `commandBus` as an optional constructor argument,
16
+ * documented as "audits `adjust` co-transactionally when provided". Adjusting a
17
+ * customer's credit limit is precisely the class of write Constitution XIII
18
+ * exists for — an admin moving how much money an organization may spend before
19
+ * paying — and the optional form means the audited path is the one you get by
20
+ * remembering. Both roots passed it, so nothing was unaudited; the shape goes
21
+ * anyway, for the same reason as the twelve before it.
22
+ *
23
+ * `inheritance` was optional too, and its absence is quieter and stranger:
24
+ * "Absent ⇒ flat behavior", meaning a descendant organization with no own limit
25
+ * transacts against nothing instead of against its nearest ancestor's. Not an
26
+ * error, not a refusal — a different and more permissive credit policy, chosen
27
+ * by omission.
28
+ *
29
+ * `creditLimitService` is a **port**: `orders` reserves against it and the
30
+ * credit-topup payment provider draws on it, both across a module boundary, so
31
+ * an operator switching credit limits off should get the explicit 503 rather
32
+ * than a service that reports no limit — which on a checkout path means
33
+ * unlimited credit.
34
+ */
35
+ export interface CreditLimitsCradle {
36
+ readonly emFactory: () => EntityManager;
37
+ readonly eventBus: EventBus;
38
+ readonly commandBus: CommandBus;
39
+ readonly requireAdmin: RequireAdminFactory;
40
+ readonly requireCustomer: (request: FastifyRequest) => Promise<void>;
41
+ /** How this composition resolves the calling customer; root-shaped. */
42
+ readonly customerContextResolver: (request: FastifyRequest) => {
43
+ customerAccountId: string;
44
+ organizationId: string;
45
+ };
46
+ /**
47
+ * Who the acting admin is, from the production actor (feature 080, T051).
48
+ * Root-supplied, like the other request resolvers; both roots answer it from
49
+ * `request.actor` and throw 401 for a non-admin.
50
+ */
51
+ readonly adminContextResolver: (req: FastifyRequest) => {
52
+ adminUserId: string;
53
+ };
54
+ /** A port `organizations` provides — read it per call, never captured. */
55
+ readonly organizationInheritancePort: OrganizationInheritancePort;
56
+ readonly creditLimitService: CreditLimitService;
57
+ }
58
+ export declare function registerModule(ctx: ModuleContext): void;
59
+ /**
60
+ * The module's persisted entity classes, on the `./backend` subpath, as one
61
+ * array and **no named class export** (D-168).
62
+ *
63
+ * This is the shape the platform reads when the package is *installed*: the
64
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
65
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
66
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
67
+ * the package pass of `check-entity-tenant-classification`. A missing array is
68
+ * answered with `[]` — zero entities registered, no error anywhere.
69
+ *
70
+ * Three classes, and this module is where the absent named export and a foreign
71
+ * key had to be shown to coexist (D-169): `credit_limit_reservations` carries
72
+ * `credit_limit_reservations_order_fk` into `orders.id`, and a constraint is
73
+ * between two column names — it needs the **table**, never the class. Nothing
74
+ * another module can legitimately do with `CreditLimitReservation` is lost.
75
+ * D-32 forbids an ORM relation into it; what `orders` actually needs is the
76
+ * *method* placement calls, which leaves this package as `CreditLimitPort` on
77
+ * the type-only `./ports` subpath.
78
+ */
79
+ export declare const entities: (typeof CreditLimit | typeof CreditLimitReservation | typeof CreditLimitReturnTopup)[];
80
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE3F,OAAO,KAAK,EAGV,2BAA2B,EAC5B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAA4B,MAAM,oCAAoC,CAAC;AAIlG,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAExF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,uEAAuE;IACvE,QAAQ,CAAC,uBAAuB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK;QAC7D,iBAAiB,EAAE,MAAM,CAAC;QAC1B,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF,0EAA0E;IAC1E,QAAQ,CAAC,2BAA2B,EAAE,2BAA2B,CAAC;IAClE,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyGvD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,QAAQ,wFAAgE,CAAC"}
@@ -0,0 +1,112 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { CreditLimitService } from './services/credit-limit-service.js';
3
+ import { CreditLimitReadService } from './services/credit-limit-read.js';
4
+ import { CreditTopupProvider } from './services/credit-topup.js';
5
+ import { registerCreditLimitsRoutes } from './routes.js';
6
+ import { CreditLimit } from './entities/credit-limit.entity.js';
7
+ import { CreditLimitReservation } from './entities/credit-limit-reservation.entity.js';
8
+ import { CreditLimitReturnTopup } from './entities/credit-limit-return-topup.entity.js';
9
+ export function registerModule(ctx) {
10
+ ctx.di.providePort('creditLimitService', ctx
11
+ .asFunction(({ emFactory, eventBus, commandBus }) => new CreditLimitService(emFactory, eventBus, commandBus,
12
+ // Resolved per call, not destructured. `organizationInheritancePort`
13
+ // is a port `organizations` provides, which makes it a transient gate
14
+ // on that module's effective state: capturing it in this singleton's
15
+ // factory is refused by awilix strict mode, and a captured gate would
16
+ // keep answering after an operator switched `organizations` off.
17
+ // Resolving at the point of use also means this registration does not
18
+ // care which module registered first — registration resolves nothing.
19
+ // `creditOwner` is the only method reached, and since Phase C the
20
+ // delegate is typed by `organizations`' published contract rather
21
+ // than cast onto its service class, so this module names nothing in
22
+ // that module's directory.
23
+ {
24
+ creditOwner: (orgId) => ctx.cradle().organizationInheritancePort.creditOwner(orgId),
25
+ }))
26
+ .singleton());
27
+ /**
28
+ * The membership read `organizations` needs to resolve an inherited limit
29
+ * (feature 077, D-87).
30
+ *
31
+ * `OrganizationInheritanceService.creditOwner` walks an ancestor chain and
32
+ * has to know which of those organisations hold a row here. It selected from
33
+ * this module's table to find out — a statement naming no import specifier,
34
+ * so the boundary compiled and nothing gated it, and the answer stayed the
35
+ * same after an operator switched credit limits off.
36
+ *
37
+ * A **read** port and deliberately not a method on `creditLimitService`: that
38
+ * service is the gated write seam whose `getForOrganization` asks
39
+ * `organizations` the very question this answers, so widening it would put
40
+ * both directions on one name. Separate names keep the two hops legible —
41
+ * this module asks who the owner is, `organizations` asks who holds a row —
42
+ * and neither recurses.
43
+ *
44
+ * `filters: { org: false }` is load-bearing rather than defensive: the holder
45
+ * is by definition an ancestor outside the caller's tenant scope, which is
46
+ * the whole point of the inheritance, so the org filter would answer the
47
+ * empty set for every descendant.
48
+ */
49
+ ctx.di.providePort('creditLimitReadPort', ctx
50
+ .asFunction(({ emFactory }) => new CreditLimitReadService(emFactory))
51
+ .singleton());
52
+ /**
53
+ * How a return settlement credits an organization (feature 046 R7), as this
54
+ * module's port instead of a class both roots constructed (T143c).
55
+ *
56
+ * The provider is a thin rule over `CreditLimitService` and lives in this
57
+ * module already; what a root added was a second, ungated way to reach it, so
58
+ * a settlement kept topping up a grant with `credit_limits` switched off. The
59
+ * service is still handed in as an accessor, for the reason the routes below
60
+ * take it lazily: it is this module's own gated port, and resolving it while
61
+ * this registration is built asks the gate at composition time.
62
+ */
63
+ ctx.di.providePort('creditTopupPort', ctx
64
+ .asFunction(() => {
65
+ const creditLimitService = lazyPort(ctx, 'creditLimitService');
66
+ return new CreditTopupProvider(() => creditLimitService);
67
+ })
68
+ .singleton());
69
+ ctx.routes(async (app) => {
70
+ const { requireCustomer, requireAdmin, customerContextResolver } = ctx.cradle();
71
+ await registerCreditLimitsRoutes(app, {
72
+ // Lazily, even though the module owns this port: `providePort` gates on
73
+ // the module's effective state and route *registration* happens whatever
74
+ // that state is — only requests are gated. Destructuring it here asked
75
+ // the gate while `buildServer` was wiring the app, so an operator who had
76
+ // switched `credit_limits` off stopped the backend from starting instead
77
+ // of stopping its routes. Inside a handler the gate is open by
78
+ // construction, so nothing else changes.
79
+ creditLimitService: lazyPort(ctx, 'creditLimitService'),
80
+ // `organizations`' port, lazily for the same reason and one more: it is
81
+ // another module's gate, so a captured reference would keep answering
82
+ // after an operator switched that module off.
83
+ organizationDetailsPort: lazyPort(ctx, 'organizationDetailsPort'),
84
+ requireCustomer,
85
+ requireAdmin,
86
+ resolveCustomerContext: customerContextResolver,
87
+ resolveAdminUserId: (request) => ctx.cradle().adminContextResolver(request).adminUserId,
88
+ });
89
+ });
90
+ }
91
+ /**
92
+ * The module's persisted entity classes, on the `./backend` subpath, as one
93
+ * array and **no named class export** (D-168).
94
+ *
95
+ * This is the shape the platform reads when the package is *installed*: the
96
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
97
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
98
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
99
+ * the package pass of `check-entity-tenant-classification`. A missing array is
100
+ * answered with `[]` — zero entities registered, no error anywhere.
101
+ *
102
+ * Three classes, and this module is where the absent named export and a foreign
103
+ * key had to be shown to coexist (D-169): `credit_limit_reservations` carries
104
+ * `credit_limit_reservations_order_fk` into `orders.id`, and a constraint is
105
+ * between two column names — it needs the **table**, never the class. Nothing
106
+ * another module can legitimately do with `CreditLimitReservation` is lost.
107
+ * D-32 forbids an ORM relation into it; what `orders` actually needs is the
108
+ * *method* placement calls, which leaves this package as `CreditLimitPort` on
109
+ * the type-only `./ports` subpath.
110
+ */
111
+ export const entities = [CreditLimit, CreditLimitReservation, CreditLimitReturnTopup];
112
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAM5D,OAAO,EAAE,kBAAkB,EAA4B,MAAM,oCAAoC,CAAC;AAClG,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AAiDxF,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,oBAAoB,EACpB,GAAG;SACA,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAsB,EAAE,EAAE,CAC1D,IAAI,kBAAkB,CACpB,SAAS,EACT,QAA0C,EAC1C,UAAU;IACV,qEAAqE;IACrE,sEAAsE;IACtE,qEAAqE;IACrE,sEAAsE;IACtE,iEAAiE;IACjE,sEAAsE;IACtE,sEAAsE;IACtE,kEAAkE;IAClE,kEAAkE;IAClE,oEAAoE;IACpE,2BAA2B;IAC3B;QACE,WAAW,EAAE,CAAC,KAAa,EAAE,EAAE,CAC7B,GAAG,CAAC,MAAM,EAAsB,CAAC,2BAA2B,CAAC,WAAW,CAAC,KAAK,CAAC;KAClF,CACF,CACJ;SACA,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,qBAAqB,EACrB,GAAG;SACA,UAAU,CACT,CAAC,EAAE,SAAS,EAAsB,EAAE,EAAE,CAAC,IAAI,sBAAsB,CAAC,SAAS,CAAC,CAC7E;SACA,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;;;OAUG;IACH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,iBAAiB,EACjB,GAAG;SACA,UAAU,CAAC,GAAG,EAAE;QACf,MAAM,kBAAkB,GAAG,QAAQ,CAAqB,GAAG,EAAE,oBAAoB,CAAC,CAAC;QACnF,OAAO,IAAI,mBAAmB,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,CAAC;IAC3D,CAAC,CAAC;SACD,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EAAE,eAAe,EAAE,YAAY,EAAE,uBAAuB,EAAE,GAC9D,GAAG,CAAC,MAAM,EAAsB,CAAC;QACnC,MAAM,0BAA0B,CAAC,GAAG,EAAE;YACpC,wEAAwE;YACxE,yEAAyE;YACzE,uEAAuE;YACvE,0EAA0E;YAC1E,yEAAyE;YACzE,+DAA+D;YAC/D,yCAAyC;YACzC,kBAAkB,EAAE,QAAQ,CAAqB,GAAG,EAAE,oBAAoB,CAAC;YAC3E,wEAAwE;YACxE,sEAAsE;YACtE,8CAA8C;YAC9C,uBAAuB,EAAE,QAAQ,CAA0B,GAAG,EAAE,yBAAyB,CAAC;YAC1F,eAAe;YACf,YAAY;YACZ,sBAAsB,EAAE,uBAAuB;YAC/C,kBAAkB,EAAE,CAAC,OAAO,EAAE,EAAE,CAC9B,GAAG,CAAC,MAAM,EAAsB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC,WAAW;SAC7E,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,sBAAsB,EAAE,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,59 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import { type OrganizationDetailsPort } from '@endora-commerce/contracts';
3
+ import type { CreditLimitService } from './services/credit-limit-service.js';
4
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * `grantedByAdminUserId` is supplied by the host through `resolveAdminUserId`,
7
+ * not read off the request here.
8
+ *
9
+ * This module used to import `testAdminUserId` from the host's
10
+ * `test-actor-carrier.ts`, which `host-package.md` §1.4j classifies **A** —
11
+ * host-internal, deliberately absent from `@endora-commerce/platform/http`'s
12
+ * barrel, because it exists to narrow *this repository's* test-harness Fastify
13
+ * augmentation and an installed package has no relationship to it. Packaging
14
+ * this module is the first time that classification had to bite.
15
+ *
16
+ * The repair is not the carrier-declared-locally idiom §1.4n used for
17
+ * `productAudienceOf`, because that would have preserved a live defect: nothing
18
+ * under `src/` ever writes `request.testActor`, so a production grant recorded
19
+ * **no** `grantedByAdminUserId` at all — the audit trail for a financial grant,
20
+ * empty, with every harness test green because the harness mirrors its actor
21
+ * onto both fields. The host now injects the resolver, so the production path
22
+ * and the harness path read the same thing.
23
+ */
24
+ export interface CreditLimitsDeps {
25
+ creditLimitService: CreditLimitService;
26
+ /**
27
+ * `organizationDetailsPort`, owned by `organizations` — the roster's one
28
+ * question about that table (feature 075, Phase C). It replaces an
29
+ * `em.find(Organization, …)`, which linked this module against another
30
+ * module's entity class at build time and kept answering with `organizations`
31
+ * switched off. The port fails the request closed instead.
32
+ */
33
+ organizationDetailsPort: OrganizationDetailsPort;
34
+ requireCustomer: (req: FastifyRequest, reply: unknown) => Promise<void>;
35
+ requireAdmin: RequireAdminFactory;
36
+ resolveCustomerContext: (req: FastifyRequest) => {
37
+ customerAccountId: string;
38
+ organizationId: string;
39
+ };
40
+ /**
41
+ * The acting admin's id, resolved from the request the guard in front of
42
+ * these routes has already accepted.
43
+ *
44
+ * Injected rather than read here, because the read used to be
45
+ * `testAdminUserId(request)` — `http/test-actor-carrier`, which
46
+ * `contracts/host-package.md` §1.4j classifies **A**: the file exists to
47
+ * narrow this repository's test-harness Fastify augmentation, and an
48
+ * installed package has no relationship to that harness. It also answered
49
+ * `undefined` in production for every request, because nothing under `src/`
50
+ * writes `request.testActor`.
51
+ *
52
+ * Both composition roots supply it from `adminContextResolver`, which reads
53
+ * the production actor and throws 401 for a non-admin. Every call site sits
54
+ * behind `requireAdmin(...)`, so the actor is an admin by the time it runs.
55
+ */
56
+ resolveAdminUserId: (request: FastifyRequest) => string;
57
+ }
58
+ export declare function registerCreditLimitsRoutes(app: FastifyInstance, deps: CreditLimitsDeps): Promise<void>;
59
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAIL,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC;;;;;;OAMG;IACH,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,eAAe,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,YAAY,EAAE,mBAAmB,CAAC;IAClC,sBAAsB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK;QAC/C,iBAAiB,EAAE,MAAM,CAAC;QAC1B,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,MAAM,CAAC;CACzD;AAED,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAyJf"}
@@ -0,0 +1,114 @@
1
+ import { ERROR_CODES, adjustCreditLimitRequestSchema, grantCreditLimitRequestSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { isOrgInScope } from '@endora-commerce/platform/tenancy';
4
+ export async function registerCreditLimitsRoutes(app, deps) {
5
+ const { creditLimitService, organizationDetailsPort, requireCustomer, requireAdmin, resolveCustomerContext, resolveAdminUserId, } = deps;
6
+ /** Resolve organization names for the given ids (read-only, missing ⇒ absent). */
7
+ const loadOrgNames = async (ids) => {
8
+ const unique = [...new Set(ids)].filter(Boolean);
9
+ if (unique.length === 0)
10
+ return new Map();
11
+ const orgs = await organizationDetailsPort.findByIds(unique);
12
+ return new Map(orgs.map((o) => [o.id, o.name]));
13
+ };
14
+ app.get('/api/v1/me/credit-limit', { preHandler: requireCustomer }, async (request) => {
15
+ const ctx = resolveCustomerContext(request);
16
+ const limit = await creditLimitService.getForOrganization(ctx.organizationId);
17
+ if (!limit) {
18
+ throw new HttpError(404, ERROR_CODES.CREDIT_LIMIT_NOT_GRANTED, 'Credit limit has not been granted for this organization.');
19
+ }
20
+ const reservations = await creditLimitService.listActiveReservations(limit.id);
21
+ return { data: serializeView(limit, reservations) };
22
+ });
23
+ app.get('/api/v1/admin/credit-limits', { preHandler: requireAdmin('credit_limits:manage') }, async () => {
24
+ const rows = await creditLimitService.listAll();
25
+ const names = await loadOrgNames(rows.map((l) => l.organizationId));
26
+ const data = await Promise.all(rows.map(async (l) => {
27
+ const reservations = await creditLimitService.listActiveReservations(l.id);
28
+ return serializeView(l, reservations, names.get(l.organizationId) ?? null);
29
+ }));
30
+ return { data };
31
+ });
32
+ app.post('/api/v1/admin/organizations/:id/credit-limit', {
33
+ preHandler: requireAdmin('credit_limits:manage'),
34
+ schema: { body: grantCreditLimitRequestSchema },
35
+ }, async (request, reply) => {
36
+ const body = grantCreditLimitRequestSchema.parse(request.body);
37
+ // Feature 050 — inserts are not reachable by the org column filter, so gate
38
+ // the target org explicitly; out-of-scope responds as "not granted" (FR-008).
39
+ if (!isOrgInScope(request.params.id)) {
40
+ throw new HttpError(404, ERROR_CODES.CREDIT_LIMIT_NOT_GRANTED, 'Credit limit has not been granted for this organization.');
41
+ }
42
+ const existing = await creditLimitService.getForOrganization(request.params.id);
43
+ if (existing) {
44
+ throw new HttpError(409, ERROR_CODES.CREDIT_LIMIT_ALREADY_GRANTED, 'A credit limit is already granted; use PATCH to adjust it.');
45
+ }
46
+ const limit = await creditLimitService.grant({
47
+ organizationId: request.params.id,
48
+ grantedAmount: body.grantedAmount,
49
+ currency: body.currency,
50
+ grantedByAdminUserId: resolveAdminUserId(request),
51
+ });
52
+ reply.status(201);
53
+ const names = await loadOrgNames([request.params.id]);
54
+ return { data: serializeView(limit, [], names.get(request.params.id) ?? null) };
55
+ });
56
+ app.get('/api/v1/admin/organizations/:id/credit-limit', { preHandler: requireAdmin('credit_limits:manage') }, async (request) => {
57
+ const limit = await creditLimitService.getForOrganization(request.params.id);
58
+ if (!limit) {
59
+ throw new HttpError(404, ERROR_CODES.CREDIT_LIMIT_NOT_GRANTED, 'Credit limit has not been granted for this organization.');
60
+ }
61
+ const reservations = await creditLimitService.listActiveReservations(limit.id);
62
+ const names = await loadOrgNames([request.params.id]);
63
+ return { data: serializeView(limit, reservations, names.get(request.params.id) ?? null) };
64
+ });
65
+ app.patch('/api/v1/admin/organizations/:id/credit-limit', {
66
+ preHandler: requireAdmin('credit_limits:manage'),
67
+ schema: { body: adjustCreditLimitRequestSchema },
68
+ }, async (request) => {
69
+ const body = adjustCreditLimitRequestSchema.parse(request.body);
70
+ // Feature 050 — gate the target org (see POST grant above).
71
+ if (!isOrgInScope(request.params.id)) {
72
+ throw new HttpError(404, ERROR_CODES.CREDIT_LIMIT_NOT_GRANTED, 'Credit limit has not been granted for this organization.');
73
+ }
74
+ try {
75
+ const result = await creditLimitService.adjust({
76
+ organizationId: request.params.id,
77
+ grantedAmount: body.grantedAmount,
78
+ ...(body.allowOverAllocation !== undefined
79
+ ? { allowOverAllocation: body.allowOverAllocation }
80
+ : {}),
81
+ });
82
+ if (!result.ok) {
83
+ throw new HttpError(409, ERROR_CODES.ADJUSTMENT_BELOW_ACTIVE, 'Cannot reduce credit limit below the sum of active reservations.');
84
+ }
85
+ const reservations = await creditLimitService.listActiveReservations(result.limit.id);
86
+ const names = await loadOrgNames([request.params.id]);
87
+ return { data: serializeView(result.limit, reservations, names.get(request.params.id) ?? null) };
88
+ }
89
+ catch (err) {
90
+ if (err instanceof Error && err.message === 'CREDIT_LIMIT_NOT_GRANTED') {
91
+ throw new HttpError(404, ERROR_CODES.CREDIT_LIMIT_NOT_GRANTED, 'Credit limit has not been granted for this organization.');
92
+ }
93
+ throw err;
94
+ }
95
+ });
96
+ }
97
+ function serializeView(limit, reservations, organizationName) {
98
+ const granted = Number(limit.grantedAmount);
99
+ const reservedSum = reservations.reduce((acc, r) => acc + Number(r.amount), 0);
100
+ return {
101
+ organizationId: limit.organizationId,
102
+ organizationName: organizationName ?? null,
103
+ grantedAmount: granted,
104
+ availableAmount: granted - reservedSum,
105
+ currency: limit.currency,
106
+ activeReservations: reservations.map((r) => ({
107
+ orderId: r.orderId,
108
+ amount: Number(r.amount),
109
+ createdAt: r.createdAt.toISOString(),
110
+ })),
111
+ grantedAt: limit.createdAt.toISOString(),
112
+ };
113
+ }
114
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CreditLimitReadPort } from '@endora-commerce/contracts';
3
+ /**
4
+ * `creditLimitReadPort` — the membership read `organizations` needs to resolve
5
+ * an inherited limit (feature 077, D-87).
6
+ *
7
+ * `OrganizationInheritanceService.creditOwner` walks an ancestor chain and has
8
+ * to know which of those organisations hold a row here. It selected from this
9
+ * module's table to find out — a statement naming no import specifier, so the
10
+ * boundary compiled and returned rows whatever state this module was in.
11
+ *
12
+ * A class rather than a closure in `backend/index.ts` for the reason
13
+ * `OrganizationDetailsService` is one: several suites construct the consumer by
14
+ * hand and have to supply what the container supplies, and the point of a cut
15
+ * is that the **owner's own** implementation answers the question — a stub that
16
+ * agrees with the test's expectations cannot show it.
17
+ */
18
+ export declare class CreditLimitReadService implements CreditLimitReadPort {
19
+ private readonly emFactory;
20
+ constructor(emFactory: () => EntityManager);
21
+ organizationsWithLimit(organizationIds: readonly string[]): Promise<string[]>;
22
+ }
23
+ //# sourceMappingURL=credit-limit-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credit-limit-read.d.ts","sourceRoot":"","sources":["../../../src/backend/services/credit-limit-read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,sBAAuB,YAAW,mBAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,sBAAsB,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAcpF"}
@@ -0,0 +1,35 @@
1
+ import { CreditLimit } from '../entities/credit-limit.entity.js';
2
+ /**
3
+ * `creditLimitReadPort` — the membership read `organizations` needs to resolve
4
+ * an inherited limit (feature 077, D-87).
5
+ *
6
+ * `OrganizationInheritanceService.creditOwner` walks an ancestor chain and has
7
+ * to know which of those organisations hold a row here. It selected from this
8
+ * module's table to find out — a statement naming no import specifier, so the
9
+ * boundary compiled and returned rows whatever state this module was in.
10
+ *
11
+ * A class rather than a closure in `backend/index.ts` for the reason
12
+ * `OrganizationDetailsService` is one: several suites construct the consumer by
13
+ * hand and have to supply what the container supplies, and the point of a cut
14
+ * is that the **owner's own** implementation answers the question — a stub that
15
+ * agrees with the test's expectations cannot show it.
16
+ */
17
+ export class CreditLimitReadService {
18
+ emFactory;
19
+ constructor(emFactory) {
20
+ this.emFactory = emFactory;
21
+ }
22
+ async organizationsWithLimit(organizationIds) {
23
+ if (organizationIds.length === 0)
24
+ return [];
25
+ const rows = await this.emFactory().find(CreditLimit, { organizationId: { $in: [...organizationIds] } },
26
+ // Load-bearing rather than defensive: the holder is by definition an
27
+ // ancestor outside the caller's tenant scope, which is the whole point of
28
+ // the inheritance, so `@OrgScoped` would answer the empty set for every
29
+ // descendant and the feature would silently stop working.
30
+ { filters: { org: false } });
31
+ // Ids, not rows. The consumer gets a value it cannot mutate and flush.
32
+ return rows.map((row) => row.organizationId);
33
+ }
34
+ }
35
+ //# sourceMappingURL=credit-limit-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credit-limit-read.js","sourceRoot":"","sources":["../../../src/backend/services/credit-limit-read.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,sBAAsB;IACJ;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,sBAAsB,CAAC,eAAkC;QAC7D,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACtC,WAAW,EACX,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,EAAE;QACjD,qEAAqE;QACrE,0EAA0E;QAC1E,wEAAwE;QACxE,0DAA0D;QAC1D,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CAC5B,CAAC;QACF,uEAAuE;QACvE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC/C,CAAC;CACF"}