@classytic/flow 0.1.4
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/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/dist/allocation-policy-my_HfzdV.d.mts +23 -0
- package/dist/base-MWBqRFM2.mjs +16 -0
- package/dist/catalog-bridge-K8bdkncJ.d.mts +29 -0
- package/dist/cost-layer.port-iH9pvZqB.d.mts +30 -0
- package/dist/cost-layer.service-BQ1bs-XN.mjs +86 -0
- package/dist/cost-layer.service-DWmo9dQz.d.mts +53 -0
- package/dist/count.port-BRqwGbi3.d.mts +57 -0
- package/dist/counting/index.d.mts +2 -0
- package/dist/counting/index.mjs +2 -0
- package/dist/counting.service-BiQXqorv.mjs +232 -0
- package/dist/counting.service-CpAxU2G0.d.mts +74 -0
- package/dist/domain/contracts/index.d.mts +3 -0
- package/dist/domain/contracts/index.mjs +1 -0
- package/dist/domain/enums/index.d.mts +2 -0
- package/dist/domain/enums/index.mjs +4 -0
- package/dist/domain/index.d.mts +24 -0
- package/dist/domain/index.mjs +10 -0
- package/dist/domain/policies/index.d.mts +4 -0
- package/dist/domain/policies/index.mjs +1 -0
- package/dist/domain-D5cpMpR0.mjs +96 -0
- package/dist/domain-errors-D7S9ydNF.mjs +133 -0
- package/dist/enums-C3_z6aHC.mjs +82 -0
- package/dist/event-bus-BNmyoJb4.mjs +37 -0
- package/dist/event-bus-Um_xrcMY.d.mts +21 -0
- package/dist/event-emitter.port-BFh2pasY.d.mts +183 -0
- package/dist/event-types-BSqQOvXv.mjs +29 -0
- package/dist/events/index.d.mts +3 -0
- package/dist/events/index.mjs +3 -0
- package/dist/idempotency.port-CTC70JON.d.mts +55 -0
- package/dist/index-Bia4m8d2.d.mts +67 -0
- package/dist/index-BmNm3oNU2.d.mts +107 -0
- package/dist/index-C5PciI9P.d.mts +203 -0
- package/dist/index-CMTUKEK_.d.mts +308 -0
- package/dist/index-C_aEnozN.d.mts +220 -0
- package/dist/index-CulWO137.d.mts +107 -0
- package/dist/index-DFF0GJ4J.d.mts +36 -0
- package/dist/index-DsE7lZdO.d.mts +11 -0
- package/dist/index-DwO9IdNa.d.mts +1 -0
- package/dist/index-dtWUZr2a2.d.mts +350 -0
- package/dist/index.d.mts +128 -0
- package/dist/index.mjs +102 -0
- package/dist/insufficient-stock.error-Dyr4BYaV.mjs +15 -0
- package/dist/location.port-CValXIpb.d.mts +52 -0
- package/dist/lot.port-ChsmvZqs.d.mts +32 -0
- package/dist/models/index.d.mts +2 -0
- package/dist/models/index.mjs +2 -0
- package/dist/models-CHTMbp-G.mjs +1020 -0
- package/dist/move-group.port-DHGoQA3d.d.mts +56 -0
- package/dist/move-status-DkaFp2GD.mjs +38 -0
- package/dist/move.port-Qg1CYp7h.d.mts +89 -0
- package/dist/package.service-4tcAwBbr.mjs +95 -0
- package/dist/package.service-C605NaBQ.d.mts +42 -0
- package/dist/packaging/index.d.mts +2 -0
- package/dist/packaging/index.mjs +2 -0
- package/dist/procurement/index.d.mts +2 -0
- package/dist/procurement/index.mjs +2 -0
- package/dist/quant.port-BBa66PBT.d.mts +42 -0
- package/dist/removal-policy-BItBB8FD.d.mts +29 -0
- package/dist/replenishment-rule.port-DnEYtbyD.d.mts +78 -0
- package/dist/replenishment.service-BT9P-HKM.mjs +284 -0
- package/dist/replenishment.service-HO0sDhB_.d.mts +89 -0
- package/dist/reporting/index.d.mts +2 -0
- package/dist/reporting/index.mjs +2 -0
- package/dist/reporting-CL5ffrKM.mjs +243 -0
- package/dist/repositories/index.d.mts +2 -0
- package/dist/repositories/index.mjs +2 -0
- package/dist/repositories-nZXJKvLW.mjs +842 -0
- package/dist/reservation-status-ZfuTaWG0.mjs +22 -0
- package/dist/reservation.port-l9NFQ0si.d.mts +85 -0
- package/dist/reservations/index.d.mts +2 -0
- package/dist/reservations/index.mjs +2 -0
- package/dist/reservations-Cg4wN0QB.mjs +112 -0
- package/dist/routing/index.d.mts +362 -0
- package/dist/routing/index.mjs +582 -0
- package/dist/runtime-config-C0ggPkiK.mjs +40 -0
- package/dist/runtime-config-CQLtPPqY.d.mts +38 -0
- package/dist/scan-token-CNM9QVLY.d.mts +26 -0
- package/dist/scanning/index.d.mts +45 -0
- package/dist/scanning/index.mjs +228 -0
- package/dist/services/index.d.mts +8 -0
- package/dist/services/index.mjs +8 -0
- package/dist/services-_lLO4Xbl.mjs +1009 -0
- package/dist/stock-move-group-C0DqUfPY.mjs +88 -0
- package/dist/stock-package-BIarxbDS.d.mts +19 -0
- package/dist/stock-quant-CZhgvTu7.d.mts +41 -0
- package/dist/tenant-guard-6Ne-BILP.mjs +12 -0
- package/dist/tenant-isolation.error-D3OcKUdx.mjs +11 -0
- package/dist/trace.service-B9vAh-l-.d.mts +55 -0
- package/dist/trace.service-DE6Eh8_8.mjs +71 -0
- package/dist/traceability/index.d.mts +2 -0
- package/dist/traceability/index.mjs +2 -0
- package/dist/types/index.d.mts +2 -0
- package/dist/types/index.mjs +1 -0
- package/dist/unit-of-work.port-CWEkrDKu.d.mts +17 -0
- package/dist/valuation/index.d.mts +78 -0
- package/dist/valuation/index.mjs +103 -0
- package/dist/valuation-policy-Dco8c9Vw.d.mts +14 -0
- package/dist/virtual-locations-B9zXqPdi.d.mts +38 -0
- package/package.json +155 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { t as FlowError } from "./base-MWBqRFM2.mjs";
|
|
2
|
+
import { o as MoveAlreadyPostedError } from "./domain-errors-D7S9ydNF.mjs";
|
|
3
|
+
import { r as isValidMoveTransition } from "./move-status-DkaFp2GD.mjs";
|
|
4
|
+
//#region src/domain/constants/virtual-locations.ts
|
|
5
|
+
/** Package defaults — used when the host app doesn't provide overrides. */
|
|
6
|
+
const DEFAULT_VIRTUAL_LOCATIONS = {
|
|
7
|
+
vendor: "vendor",
|
|
8
|
+
customer: "customer",
|
|
9
|
+
adjustment: "inventory_loss",
|
|
10
|
+
transit: "transit",
|
|
11
|
+
scrap: "scrap"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the final virtual location map by merging host-app overrides
|
|
15
|
+
* on top of the package defaults.
|
|
16
|
+
*/
|
|
17
|
+
function resolveVirtualLocations(overrides) {
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
...DEFAULT_VIRTUAL_LOCATIONS,
|
|
20
|
+
...overrides
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the list of location IDs that should skip the negative-stock guard
|
|
25
|
+
* because they represent stock entering or leaving the system boundary.
|
|
26
|
+
*/
|
|
27
|
+
function buildVirtualSourceTypes(locations) {
|
|
28
|
+
return Object.freeze([
|
|
29
|
+
locations.vendor,
|
|
30
|
+
locations.customer,
|
|
31
|
+
locations.adjustment,
|
|
32
|
+
locations.scrap,
|
|
33
|
+
locations.transit
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/domain/errors/invalid-transition.error.ts
|
|
38
|
+
var InvalidTransitionError = class extends FlowError {
|
|
39
|
+
code = "INVALID_TRANSITION";
|
|
40
|
+
httpStatus = 422;
|
|
41
|
+
constructor(entityType, entityId, from, to) {
|
|
42
|
+
super(`Invalid transition for ${entityType} ${entityId}: ${from} → ${to}`);
|
|
43
|
+
this.entityType = entityType;
|
|
44
|
+
this.entityId = entityId;
|
|
45
|
+
this.from = from;
|
|
46
|
+
this.to = to;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/domain/errors/reservation-expired.error.ts
|
|
51
|
+
var ReservationExpiredError = class extends FlowError {
|
|
52
|
+
code = "RESERVATION_EXPIRED";
|
|
53
|
+
httpStatus = 410;
|
|
54
|
+
constructor(reservationId) {
|
|
55
|
+
super(`Reservation ${reservationId} has expired and can no longer be consumed`);
|
|
56
|
+
this.reservationId = reservationId;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/domain/entities/stock-move.ts
|
|
61
|
+
/**
|
|
62
|
+
* Validate a move status transition. Throws if invalid.
|
|
63
|
+
*/
|
|
64
|
+
function assertMoveTransition(move, targetStatus) {
|
|
65
|
+
if (move.status === "done") throw new MoveAlreadyPostedError(move._id);
|
|
66
|
+
if (!isValidMoveTransition(move.status, targetStatus)) throw new InvalidTransitionError("StockMove", move._id, move.status, targetStatus);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/domain/entities/stock-move-group.ts
|
|
70
|
+
const MOVE_GROUP_STATUS_TRANSITIONS = {
|
|
71
|
+
draft: ["confirmed", "cancelled"],
|
|
72
|
+
confirmed: [
|
|
73
|
+
"allocated",
|
|
74
|
+
"in_progress",
|
|
75
|
+
"cancelled"
|
|
76
|
+
],
|
|
77
|
+
allocated: ["in_progress", "cancelled"],
|
|
78
|
+
in_progress: [
|
|
79
|
+
"done",
|
|
80
|
+
"partially_done",
|
|
81
|
+
"cancelled"
|
|
82
|
+
],
|
|
83
|
+
partially_done: ["done", "cancelled"],
|
|
84
|
+
done: [],
|
|
85
|
+
cancelled: []
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
export { DEFAULT_VIRTUAL_LOCATIONS as a, InvalidTransitionError as i, assertMoveTransition as n, buildVirtualSourceTypes as o, ReservationExpiredError as r, resolveVirtualLocations as s, MOVE_GROUP_STATUS_TRANSITIONS as t };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/domain/entities/stock-package.d.ts
|
|
2
|
+
interface StockPackage {
|
|
3
|
+
_id: string;
|
|
4
|
+
organizationId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
barcode: string;
|
|
7
|
+
packageTypeId?: string;
|
|
8
|
+
parentPackageId?: string;
|
|
9
|
+
locationId?: string;
|
|
10
|
+
destinationLocationId?: string;
|
|
11
|
+
baseWeight?: number;
|
|
12
|
+
maxWeight?: number;
|
|
13
|
+
packageUse: 'disposable' | 'reusable';
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
createdAt?: Date;
|
|
16
|
+
updatedAt?: Date;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { StockPackage as t };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { i as StockStatus } from "./index-CulWO137.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/domain/entities/stock-quant.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Materialized stock balance per unique identity tuple.
|
|
6
|
+
* Derived from move history, not the source of truth.
|
|
7
|
+
*
|
|
8
|
+
* Unique identity: { organizationId, skuRef, locationId, lotId, ownerRef }
|
|
9
|
+
*/
|
|
10
|
+
interface StockQuant {
|
|
11
|
+
_id: string;
|
|
12
|
+
organizationId: string;
|
|
13
|
+
skuRef: string;
|
|
14
|
+
locationId: string;
|
|
15
|
+
lotId?: string;
|
|
16
|
+
ownerRef?: string;
|
|
17
|
+
stockStatus: StockStatus;
|
|
18
|
+
quantityOnHand: number;
|
|
19
|
+
quantityReserved: number;
|
|
20
|
+
quantityAvailable: number;
|
|
21
|
+
quantityIncoming?: number;
|
|
22
|
+
quantityOutgoing?: number;
|
|
23
|
+
inDate: Date;
|
|
24
|
+
lotExpiresAt?: Date;
|
|
25
|
+
unitCost?: number;
|
|
26
|
+
lastMovementAt?: Date;
|
|
27
|
+
createdAt?: Date;
|
|
28
|
+
updatedAt?: Date;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The 5-field unique identity for a quant.
|
|
32
|
+
*/
|
|
33
|
+
interface QuantIdentity {
|
|
34
|
+
organizationId: string;
|
|
35
|
+
skuRef: string;
|
|
36
|
+
locationId: string;
|
|
37
|
+
lotId?: string;
|
|
38
|
+
ownerRef?: string;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { StockQuant as n, QuantIdentity as t };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as TenantIsolationError } from "./tenant-isolation.error-D3OcKUdx.mjs";
|
|
2
|
+
//#region src/utils/tenant-guard.ts
|
|
3
|
+
/**
|
|
4
|
+
* Assert that the given context carries a valid organizationId.
|
|
5
|
+
* Must be called at the entry point of every service method to enforce
|
|
6
|
+
* tenant isolation before any database access.
|
|
7
|
+
*/
|
|
8
|
+
function assertTenantContext(ctx) {
|
|
9
|
+
if (!ctx.organizationId) throw new TenantIsolationError();
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { assertTenantContext as t };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { t as FlowError } from "./base-MWBqRFM2.mjs";
|
|
2
|
+
//#region src/domain/errors/tenant-isolation.error.ts
|
|
3
|
+
var TenantIsolationError = class extends FlowError {
|
|
4
|
+
code = "TENANT_ISOLATION";
|
|
5
|
+
httpStatus = 403;
|
|
6
|
+
constructor(message = "Tenant isolation violation: organizationId mismatch or missing") {
|
|
7
|
+
super(message);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { TenantIsolationError as t };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { n as MovePort, r as StockMove } from "./move.port-Qg1CYp7h.mjs";
|
|
2
|
+
import { t as FlowContext } from "./index-DFF0GJ4J.mjs";
|
|
3
|
+
import { n as StockLot, t as LotPort } from "./lot.port-ChsmvZqs.mjs";
|
|
4
|
+
import { r as QuantPort } from "./quant.port-BBa66PBT.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/traceability/trace.service.d.ts
|
|
7
|
+
interface TraceResult {
|
|
8
|
+
lot: StockLot;
|
|
9
|
+
currentLocations: Array<{
|
|
10
|
+
locationId: string;
|
|
11
|
+
quantity: number;
|
|
12
|
+
}>;
|
|
13
|
+
movementHistory: StockMove[];
|
|
14
|
+
totalQuantity: number;
|
|
15
|
+
}
|
|
16
|
+
interface RecallResult {
|
|
17
|
+
lot: StockLot;
|
|
18
|
+
affectedLocations: Array<{
|
|
19
|
+
locationId: string;
|
|
20
|
+
quantity: number;
|
|
21
|
+
}>;
|
|
22
|
+
shippedMoves: StockMove[];
|
|
23
|
+
totalInWarehouse: number;
|
|
24
|
+
totalShipped: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* TraceService — lot and serial traceability queries.
|
|
28
|
+
* Aggregates data from lots, moves, and quants to produce full trace and recall reports.
|
|
29
|
+
*/
|
|
30
|
+
declare class TraceService {
|
|
31
|
+
private lotPort;
|
|
32
|
+
private movePort;
|
|
33
|
+
private quantPort;
|
|
34
|
+
constructor(lotPort: LotPort, movePort: MovePort, quantPort: QuantPort);
|
|
35
|
+
/**
|
|
36
|
+
* Trace a lot: find current locations, quantities, and full movement history.
|
|
37
|
+
*/
|
|
38
|
+
traceLot(lotCode: string, skuRef: string, ctx: FlowContext): Promise<TraceResult | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Trace a serial: identical to traceLot but resolves by serial code.
|
|
41
|
+
*/
|
|
42
|
+
traceSerial(serialCode: string, skuRef: string, ctx: FlowContext): Promise<TraceResult | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Recall a lot: determine affected warehouse locations and shipments to external locations.
|
|
45
|
+
*/
|
|
46
|
+
recallLot(lotCode: string, skuRef: string, ctx: FlowContext): Promise<RecallResult | null>;
|
|
47
|
+
private buildTrace;
|
|
48
|
+
/**
|
|
49
|
+
* Find all moves that reference a lot — either through trackingAssignments.lotId
|
|
50
|
+
* or via the move-level filter.
|
|
51
|
+
*/
|
|
52
|
+
private findMovesForLot;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { TraceResult as n, TraceService as r, RecallResult as t };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/traceability/trace.service.ts
|
|
2
|
+
/**
|
|
3
|
+
* TraceService — lot and serial traceability queries.
|
|
4
|
+
* Aggregates data from lots, moves, and quants to produce full trace and recall reports.
|
|
5
|
+
*/
|
|
6
|
+
var TraceService = class {
|
|
7
|
+
constructor(lotPort, movePort, quantPort) {
|
|
8
|
+
this.lotPort = lotPort;
|
|
9
|
+
this.movePort = movePort;
|
|
10
|
+
this.quantPort = quantPort;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Trace a lot: find current locations, quantities, and full movement history.
|
|
14
|
+
*/
|
|
15
|
+
async traceLot(lotCode, skuRef, ctx) {
|
|
16
|
+
const lot = await this.lotPort.findByCode(lotCode, skuRef, ctx);
|
|
17
|
+
if (!lot) return null;
|
|
18
|
+
return this.buildTrace(lot, ctx);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Trace a serial: identical to traceLot but resolves by serial code.
|
|
22
|
+
*/
|
|
23
|
+
async traceSerial(serialCode, skuRef, ctx) {
|
|
24
|
+
const lot = await this.lotPort.findBySerial(serialCode, skuRef, ctx);
|
|
25
|
+
if (!lot) return null;
|
|
26
|
+
return this.buildTrace(lot, ctx);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Recall a lot: determine affected warehouse locations and shipments to external locations.
|
|
30
|
+
*/
|
|
31
|
+
async recallLot(lotCode, skuRef, ctx) {
|
|
32
|
+
const lot = await this.lotPort.findByCode(lotCode, skuRef, ctx);
|
|
33
|
+
if (!lot) return null;
|
|
34
|
+
const [quants, moves] = await Promise.all([this.quantPort.findMany({ lotId: lot._id }, ctx), this.findMovesForLot(lot._id, ctx)]);
|
|
35
|
+
const affectedLocations = quants.filter((q) => q.quantityOnHand > 0).map((q) => ({
|
|
36
|
+
locationId: q.locationId,
|
|
37
|
+
quantity: q.quantityOnHand
|
|
38
|
+
}));
|
|
39
|
+
const totalInWarehouse = affectedLocations.reduce((sum, l) => sum + l.quantity, 0);
|
|
40
|
+
const shippedMoves = moves.filter((m) => m.operationType === "shipment");
|
|
41
|
+
return {
|
|
42
|
+
lot,
|
|
43
|
+
affectedLocations,
|
|
44
|
+
shippedMoves,
|
|
45
|
+
totalInWarehouse,
|
|
46
|
+
totalShipped: shippedMoves.reduce((sum, m) => sum + (m.quantityDone ?? m.quantityPlanned), 0)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async buildTrace(lot, ctx) {
|
|
50
|
+
const [quants, moves] = await Promise.all([this.quantPort.findMany({ lotId: lot._id }, ctx), this.findMovesForLot(lot._id, ctx)]);
|
|
51
|
+
const currentLocations = quants.filter((q) => q.quantityOnHand > 0).map((q) => ({
|
|
52
|
+
locationId: q.locationId,
|
|
53
|
+
quantity: q.quantityOnHand
|
|
54
|
+
}));
|
|
55
|
+
return {
|
|
56
|
+
lot,
|
|
57
|
+
currentLocations,
|
|
58
|
+
movementHistory: moves,
|
|
59
|
+
totalQuantity: currentLocations.reduce((sum, l) => sum + l.quantity, 0)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Find all moves that reference a lot — either through trackingAssignments.lotId
|
|
64
|
+
* or via the move-level filter.
|
|
65
|
+
*/
|
|
66
|
+
async findMovesForLot(lotId, ctx) {
|
|
67
|
+
return this.movePort.findMany({ "trackingAssignments.lotId": lotId }, ctx);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
export { TraceService as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/domain/ports/unit-of-work.port.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Opaque transaction session handle.
|
|
4
|
+
* Services never inspect this. Repositories cast to mongoose.ClientSession.
|
|
5
|
+
*/
|
|
6
|
+
interface TransactionSession {
|
|
7
|
+
readonly _brand: 'TransactionSession';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Unit of Work — provides transactional boundaries.
|
|
11
|
+
* All state mutations that must be atomic go through withTransaction().
|
|
12
|
+
*/
|
|
13
|
+
interface UnitOfWork {
|
|
14
|
+
withTransaction<T>(fn: (session: TransactionSession) => Promise<T>): Promise<T>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { UnitOfWork as n, TransactionSession as t };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { n as CostLayer } from "../cost-layer.port-iH9pvZqB.mjs";
|
|
2
|
+
import { a as FifoEngine, i as ConsumptionResult, n as CreateCostLayerInput, r as InventoryValuation, t as CostLayerService } from "../cost-layer.service-DWmo9dQz.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/valuation/fefo.engine.d.ts
|
|
5
|
+
declare class FefoEngine {
|
|
6
|
+
private fifoEngine;
|
|
7
|
+
consume(layers: CostLayer[], quantity: number): ConsumptionResult;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/valuation/landed-cost.service.d.ts
|
|
11
|
+
type AllocationMethod = 'by_value' | 'by_quantity' | 'by_weight' | 'by_volume' | 'equal';
|
|
12
|
+
interface LandedCostInput {
|
|
13
|
+
totalCost: number;
|
|
14
|
+
method: AllocationMethod;
|
|
15
|
+
items: Array<{
|
|
16
|
+
skuRef: string;
|
|
17
|
+
quantity: number;
|
|
18
|
+
value: number;
|
|
19
|
+
weight?: number;
|
|
20
|
+
volume?: number;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
interface LandedCostAllocation {
|
|
24
|
+
items: Array<{
|
|
25
|
+
skuRef: string;
|
|
26
|
+
allocatedCost: number;
|
|
27
|
+
newUnitCost: number;
|
|
28
|
+
}>;
|
|
29
|
+
totalAllocated: number;
|
|
30
|
+
}
|
|
31
|
+
interface PostCapitalizeInput {
|
|
32
|
+
additionalCost: number;
|
|
33
|
+
method: AllocationMethod;
|
|
34
|
+
items: Array<{
|
|
35
|
+
skuRef: string;
|
|
36
|
+
currentQuantity: number;
|
|
37
|
+
quantitySold: number;
|
|
38
|
+
currentUnitCost: number;
|
|
39
|
+
originalReceiptQuantity: number;
|
|
40
|
+
value?: number;
|
|
41
|
+
weight?: number;
|
|
42
|
+
volume?: number;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
interface PostCapitalizeItem {
|
|
46
|
+
skuRef: string;
|
|
47
|
+
allocatedCost: number;
|
|
48
|
+
additionalCostPerUnit: number;
|
|
49
|
+
newUnitCost: number;
|
|
50
|
+
cogsAdjustment: number;
|
|
51
|
+
remainingStockAdjustment: number;
|
|
52
|
+
}
|
|
53
|
+
interface PostCapitalizeResult {
|
|
54
|
+
items: PostCapitalizeItem[];
|
|
55
|
+
totalAdditionalCost: number;
|
|
56
|
+
totalCogsAdjustment: number;
|
|
57
|
+
totalRemainingStockAdjustment: number;
|
|
58
|
+
}
|
|
59
|
+
declare class LandedCostService {
|
|
60
|
+
allocate(input: LandedCostInput): LandedCostAllocation;
|
|
61
|
+
/**
|
|
62
|
+
* Post-capitalization — retroactively apply late cost invoices to already-received stock.
|
|
63
|
+
*
|
|
64
|
+
* When a freight/duty invoice arrives weeks after goods are received,
|
|
65
|
+
* this method distributes the cost across the original receipt items,
|
|
66
|
+
* recalculates unit costs, and separates the adjustment into:
|
|
67
|
+
* - COGS adjustment (for units already sold)
|
|
68
|
+
* - Remaining stock adjustment (for units still in inventory)
|
|
69
|
+
*/
|
|
70
|
+
postCapitalize(input: PostCapitalizeInput): PostCapitalizeResult;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/valuation/wac.engine.d.ts
|
|
74
|
+
declare class WacEngine {
|
|
75
|
+
calculate(oldQty: number, oldCost: number, inQty: number, inCost: number): number;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { type AllocationMethod, type ConsumptionResult, CostLayerService, type CreateCostLayerInput, FefoEngine, FifoEngine, type InventoryValuation, type LandedCostAllocation, type LandedCostInput, LandedCostService, WacEngine };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { n as FefoEngine, r as FifoEngine, t as CostLayerService } from "../cost-layer.service-BQ1bs-XN.mjs";
|
|
2
|
+
//#region src/valuation/landed-cost.service.ts
|
|
3
|
+
var LandedCostService = class {
|
|
4
|
+
allocate(input) {
|
|
5
|
+
const { totalCost, method, items } = input;
|
|
6
|
+
let allocations;
|
|
7
|
+
switch (method) {
|
|
8
|
+
case "by_value": {
|
|
9
|
+
const total = items.reduce((s, i) => s + i.value, 0);
|
|
10
|
+
allocations = items.map((i) => total > 0 ? i.value / total * totalCost : 0);
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
case "by_quantity": {
|
|
14
|
+
const total = items.reduce((s, i) => s + i.quantity, 0);
|
|
15
|
+
allocations = items.map((i) => total > 0 ? i.quantity / total * totalCost : 0);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case "by_weight": {
|
|
19
|
+
const total = items.reduce((s, i) => s + (i.weight ?? 0), 0);
|
|
20
|
+
allocations = items.map((i) => total > 0 ? (i.weight ?? 0) / total * totalCost : 0);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case "by_volume": {
|
|
24
|
+
const total = items.reduce((s, i) => s + (i.volume ?? 0), 0);
|
|
25
|
+
allocations = items.map((i) => total > 0 ? (i.volume ?? 0) / total * totalCost : 0);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "equal":
|
|
29
|
+
allocations = items.length > 0 ? items.map(() => totalCost / items.length) : [];
|
|
30
|
+
break;
|
|
31
|
+
default: allocations = items.map(() => 0);
|
|
32
|
+
}
|
|
33
|
+
const diff = totalCost - allocations.reduce((s, a) => s + a, 0);
|
|
34
|
+
if (allocations.length > 0 && Math.abs(diff) > .001) allocations[allocations.length - 1] += diff;
|
|
35
|
+
return {
|
|
36
|
+
items: items.map((item, i) => ({
|
|
37
|
+
skuRef: item.skuRef,
|
|
38
|
+
allocatedCost: Math.round(allocations[i] * 100) / 100,
|
|
39
|
+
newUnitCost: item.quantity > 0 ? Math.round((item.value + allocations[i]) / item.quantity * 100) / 100 : 0
|
|
40
|
+
})),
|
|
41
|
+
totalAllocated: totalCost
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Post-capitalization — retroactively apply late cost invoices to already-received stock.
|
|
46
|
+
*
|
|
47
|
+
* When a freight/duty invoice arrives weeks after goods are received,
|
|
48
|
+
* this method distributes the cost across the original receipt items,
|
|
49
|
+
* recalculates unit costs, and separates the adjustment into:
|
|
50
|
+
* - COGS adjustment (for units already sold)
|
|
51
|
+
* - Remaining stock adjustment (for units still in inventory)
|
|
52
|
+
*/
|
|
53
|
+
postCapitalize(input) {
|
|
54
|
+
const { additionalCost, method, items } = input;
|
|
55
|
+
const allocationItems = items.map((item) => ({
|
|
56
|
+
skuRef: item.skuRef,
|
|
57
|
+
quantity: item.originalReceiptQuantity,
|
|
58
|
+
value: item.currentUnitCost * item.originalReceiptQuantity,
|
|
59
|
+
weight: item.weight,
|
|
60
|
+
volume: item.volume
|
|
61
|
+
}));
|
|
62
|
+
const allocation = this.allocate({
|
|
63
|
+
totalCost: additionalCost,
|
|
64
|
+
method,
|
|
65
|
+
items: allocationItems
|
|
66
|
+
});
|
|
67
|
+
const resultItems = items.map((item, i) => {
|
|
68
|
+
const allocatedCost = allocation.items[i].allocatedCost;
|
|
69
|
+
const additionalCostPerUnit = item.originalReceiptQuantity > 0 ? Math.round(allocatedCost / item.originalReceiptQuantity * 100) / 100 : 0;
|
|
70
|
+
const newUnitCost = Math.round((item.currentUnitCost + additionalCostPerUnit) * 100) / 100;
|
|
71
|
+
const cogsAdjustment = Math.round(item.quantitySold * additionalCostPerUnit * 100) / 100;
|
|
72
|
+
const remainingStockAdjustment = Math.round((allocatedCost - cogsAdjustment) * 100) / 100;
|
|
73
|
+
return {
|
|
74
|
+
skuRef: item.skuRef,
|
|
75
|
+
allocatedCost,
|
|
76
|
+
additionalCostPerUnit,
|
|
77
|
+
newUnitCost,
|
|
78
|
+
cogsAdjustment,
|
|
79
|
+
remainingStockAdjustment
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const totalSplit = resultItems.reduce((s, i) => s + i.cogsAdjustment + i.remainingStockAdjustment, 0);
|
|
83
|
+
const crossItemDiff = Math.round((additionalCost - totalSplit) * 100) / 100;
|
|
84
|
+
if (resultItems.length > 0 && Math.abs(crossItemDiff) > 0) resultItems[resultItems.length - 1].remainingStockAdjustment = Math.round((resultItems[resultItems.length - 1].remainingStockAdjustment + crossItemDiff) * 100) / 100;
|
|
85
|
+
return {
|
|
86
|
+
items: resultItems,
|
|
87
|
+
totalAdditionalCost: additionalCost,
|
|
88
|
+
totalCogsAdjustment: resultItems.reduce((s, i) => s + i.cogsAdjustment, 0),
|
|
89
|
+
totalRemainingStockAdjustment: resultItems.reduce((s, i) => s + i.remainingStockAdjustment, 0)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/valuation/wac.engine.ts
|
|
95
|
+
var WacEngine = class {
|
|
96
|
+
calculate(oldQty, oldCost, inQty, inCost) {
|
|
97
|
+
const totalQty = oldQty + inQty;
|
|
98
|
+
if (totalQty <= 0) return oldCost;
|
|
99
|
+
return Math.round((oldQty * oldCost + inQty * inCost) / totalQty * 1e4) / 1e4;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { CostLayerService, FefoEngine, FifoEngine, LandedCostService, WacEngine };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/domain/policies/valuation-policy.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Valuation policy — config object, not a strategy interface.
|
|
4
|
+
* The valuation engine reads this.
|
|
5
|
+
*/
|
|
6
|
+
interface ValuationPolicy {
|
|
7
|
+
method: 'wac' | 'fifo' | 'fefo' | 'specific' | 'standard';
|
|
8
|
+
scope: 'organization' | 'node' | 'sku';
|
|
9
|
+
landedCostAllocation: 'by_value' | 'by_quantity' | 'by_weight' | 'by_volume' | 'equal';
|
|
10
|
+
negativeCostHandling: 'freeze_last' | 'zero' | 'error';
|
|
11
|
+
interNodeTransferCost: 'sender_cost' | 'standard_cost' | 'transfer_price';
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ValuationPolicy as t };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/domain/constants/virtual-locations.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Virtual location sentinels used by Flow services.
|
|
4
|
+
*
|
|
5
|
+
* These are string IDs that represent logical locations outside the
|
|
6
|
+
* physical warehouse graph. They appear in StockMove.sourceLocationId
|
|
7
|
+
* and StockMove.destinationLocationId but do not correspond to real
|
|
8
|
+
* Location documents.
|
|
9
|
+
*
|
|
10
|
+
* Host apps override these defaults via `FlowConfig.virtualLocations`.
|
|
11
|
+
*/
|
|
12
|
+
/** Shape of the virtual location map — every key has a string sentinel value. */
|
|
13
|
+
interface VirtualLocationMap {
|
|
14
|
+
/** Source location for inbound receipts from external vendors. */
|
|
15
|
+
vendor: string;
|
|
16
|
+
/** Source location for customer returns / destination for outbound shipments. */
|
|
17
|
+
customer: string;
|
|
18
|
+
/** Catch-all for inventory adjustments (shrinkage, found stock, count reconciliation). */
|
|
19
|
+
adjustment: string;
|
|
20
|
+
/** Transit location for in-flight inter-branch transfers. */
|
|
21
|
+
transit: string;
|
|
22
|
+
/** Scrap / write-off destination. */
|
|
23
|
+
scrap: string;
|
|
24
|
+
}
|
|
25
|
+
/** Package defaults — used when the host app doesn't provide overrides. */
|
|
26
|
+
declare const DEFAULT_VIRTUAL_LOCATIONS: VirtualLocationMap;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the final virtual location map by merging host-app overrides
|
|
29
|
+
* on top of the package defaults.
|
|
30
|
+
*/
|
|
31
|
+
declare function resolveVirtualLocations(overrides?: Partial<VirtualLocationMap>): VirtualLocationMap;
|
|
32
|
+
/**
|
|
33
|
+
* Build the list of location IDs that should skip the negative-stock guard
|
|
34
|
+
* because they represent stock entering or leaving the system boundary.
|
|
35
|
+
*/
|
|
36
|
+
declare function buildVirtualSourceTypes(locations: VirtualLocationMap): readonly string[];
|
|
37
|
+
//#endregion
|
|
38
|
+
export { resolveVirtualLocations as i, VirtualLocationMap as n, buildVirtualSourceTypes as r, DEFAULT_VIRTUAL_LOCATIONS as t };
|