@litusarchieve18/agricore-utils 1.0.26 → 1.0.28
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/README.md +6 -0
- package/dist/{chunk-GZRAFNPA.mjs → chunk-Y5KVDVNY.mjs} +2 -1
- package/dist/{constants-C0zV05Cf.d.mts → constants-CrAPx_xU.d.mts} +61 -1
- package/dist/{constants-C0zV05Cf.d.ts → constants-CrAPx_xU.d.ts} +61 -1
- package/dist/constants.d.mts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.mjs +1 -1
- package/dist/index.d.mts +43 -6
- package/dist/index.d.ts +43 -6
- package/dist/index.js +243 -5
- package/dist/index.mjs +237 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -183,6 +183,12 @@ Run the publish command:
|
|
|
183
183
|
Bash
|
|
184
184
|
npm publish --access public
|
|
185
185
|
📝 Version History
|
|
186
|
+
V1.0.29 —
|
|
187
|
+
|
|
188
|
+
V1.0.28 — altered the logic and added scope aware filtering and write-time validation in priceHelpers.ts
|
|
189
|
+
|
|
190
|
+
V1.0.27 — adding get price helper, and adding adapter pattern to handle the conflicting price
|
|
191
|
+
|
|
186
192
|
V1.0.26 — updating evaluatebaseAccess to try look up the parent scope given the current scope is not
|
|
187
193
|
|
|
188
194
|
V1.0.25 — adding privileges and changing RESOURCE_PATHS
|
|
@@ -64,7 +64,8 @@ var ERROR_DICTIONARY = {
|
|
|
64
64
|
"400": { title: "Data Conflict", text: "A conflict occurred with an existing record. Please review and try again." },
|
|
65
65
|
"402": { title: "Duplicate Entry", text: "A record with this name or identifier already exists." },
|
|
66
66
|
"403": { title: "Edit Conflict", text: "This record was recently updated by someone else. Please refresh and try again." },
|
|
67
|
-
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." }
|
|
67
|
+
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." },
|
|
68
|
+
"405": { title: "Pricing Conflict", text: "Multiple active prices were found for this product. An administrator must resolve the conflict before this price can be used." }
|
|
68
69
|
};
|
|
69
70
|
var UserRole = {
|
|
70
71
|
OWNER: "owner",
|
|
@@ -206,6 +206,66 @@ type ProductUnitType = typeof ProductUnit[keyof typeof ProductUnit];
|
|
|
206
206
|
type CatalogSyncStatusType = typeof CatalogSyncStatus[keyof typeof CatalogSyncStatus];
|
|
207
207
|
type StorageType = typeof StoragePhases[keyof typeof StoragePhases];
|
|
208
208
|
type StockPolicyTierType = typeof StockPolicyTier[keyof typeof StockPolicyTier];
|
|
209
|
+
interface PricingContext {
|
|
210
|
+
productId: string;
|
|
211
|
+
companyId: string;
|
|
212
|
+
facilityId?: string;
|
|
213
|
+
accountCode?: string | null;
|
|
214
|
+
contractReferenceNumber?: string | null;
|
|
215
|
+
evaluationDate?: Date;
|
|
216
|
+
}
|
|
217
|
+
interface PriceResolutionResult {
|
|
218
|
+
unitPriceCents: number;
|
|
219
|
+
minimumOrderQuantity: number;
|
|
220
|
+
leadTimeDays: number | null;
|
|
221
|
+
priceSource: 'EXPLICIT_CONTRACT' | 'ACCOUNT_MATRIX' | 'INTERNAL_DEFAULT' | 'PRODUCT_FALLBACK';
|
|
222
|
+
sourceRecordId: string;
|
|
223
|
+
}
|
|
224
|
+
interface PricingDataSource {
|
|
225
|
+
findContractPrice(args: {
|
|
226
|
+
productId: string;
|
|
227
|
+
contractReferenceNumber: string;
|
|
228
|
+
targetDate: Date;
|
|
229
|
+
}): Promise<MatrixRow | null>;
|
|
230
|
+
findAccountPrice(args: {
|
|
231
|
+
productId: string;
|
|
232
|
+
accountCode: string;
|
|
233
|
+
targetDate: Date;
|
|
234
|
+
}): Promise<MatrixRow | null>;
|
|
235
|
+
findDefaultPrice(args: {
|
|
236
|
+
productId: string;
|
|
237
|
+
targetDate: Date;
|
|
238
|
+
}): Promise<MatrixRow | null>;
|
|
239
|
+
findBaseProduct(args: {
|
|
240
|
+
productId: string;
|
|
241
|
+
}): Promise<BaseProductRow | null>;
|
|
242
|
+
}
|
|
243
|
+
interface MatrixRow {
|
|
244
|
+
canonicalId: string;
|
|
245
|
+
matrixUnitPriceCents: number;
|
|
246
|
+
minimumOrderQuantity: number;
|
|
247
|
+
leadTimeDays: number | null;
|
|
248
|
+
}
|
|
249
|
+
interface BaseProductRow {
|
|
250
|
+
canonicalId: string;
|
|
251
|
+
unitPriceCents: number;
|
|
252
|
+
}
|
|
253
|
+
type Tier = 'EXPLICIT_CONTRACT' | 'ACCOUNT_MATRIX' | 'INTERNAL_DEFAULT';
|
|
254
|
+
interface PriceConflictCandidate {
|
|
255
|
+
sourceRecordId: string;
|
|
256
|
+
unitPriceCents: number;
|
|
257
|
+
validFrom: string;
|
|
258
|
+
validTo: string | null;
|
|
259
|
+
}
|
|
260
|
+
interface ShadowedCleanMatch {
|
|
261
|
+
tier: Tier;
|
|
262
|
+
unitPriceCents: number;
|
|
263
|
+
sourceRecordId: string;
|
|
264
|
+
matchedOn: {
|
|
265
|
+
accountCode?: string;
|
|
266
|
+
contractReferenceNumber?: string;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
209
269
|
|
|
210
270
|
declare const MOD: {
|
|
211
271
|
readonly OPERATIONS: "OPR";
|
|
@@ -686,4 +746,4 @@ declare const StockPolicyTier: {
|
|
|
686
746
|
readonly BY_LOCATION: 3;
|
|
687
747
|
};
|
|
688
748
|
|
|
689
|
-
export {
|
|
749
|
+
export { MimeType as $, ACCESS_RANK as A, BarcodeNamespace as B, CAR_SUPPORTED_EVIDENCE_MIME_TYPES as C, type CompanyConfig as D, type CorrectiveActionCompileTaskMetadata as E, type CorrectiveActionGenerationSource as F, type CorrectiveActionStatus as G, CropCycleStatus as H, DOCUMENT_DISPLAY_INFO as I, DOCUMENT_MENU_MAP as J, DocumentCategory as K, DocumentType as L, ERROR_DICTIONARY as M, EVERYONE as N, EmitterType as O, EnabledModule as P, FORM_REGISTRY as Q, FacilityType as R, type FileUploadTaskType as S, type FormMeta as T, HarvestMethod as U, type InfrastructureFields as V, LinkedStatus as W, MOD as X, MODULE_LABELS as Y, type MatrixRow as Z, MicroclimateZone as _, ACT as a, type ModCode as a0, NettingType as a1, type NormalizedSignatureValue as a2, NotificationMode as a3, OrderStatus as a4, type PermissionLevel as a5, PhysicalState as a6, PlantingMethod as a7, type PriceConflictCandidate as a8, type PriceResolutionResult as a9, type StockPolicyTierType as aA, StoragePhases as aB, type StorageType as aC, TASK_TYPE_REGISTRY as aD, TEMPLATE_SCOPE_LEVELS as aE, type TabConfig as aF, TargetEntityType as aG, TaskStatus as aH, TaskType as aI, type TaskTypeConfig as aJ, type TemplateScopeLevel as aK, type Tier as aL, TransactionType as aM, TrellisType as aN, UserPrivilege as aO, type UserPrivilegeType as aP, UserRole as aQ, type UserRoleAssignment as aR, type UserRoleType as aS, VigorRating as aT, WaterSource as aU, type WireSession as aV, type PricingContext as aa, type PricingDataSource as ab, ProductType as ac, type ProductTypeType as ad, ProductUnit as ae, type ProductUnitType as af, RESOURCE_MODULE_MAP as ag, RESOURCE_PATHS as ah, RESOURCE_REQUIREMENTS as ai, ROLE_SECTIONS_BY_KEY as aj, RelationshipType as ak, type ResolvedError as al, type ResourceOverrides as am, type ResourceRequirement as an, type RoleAssignment as ao, RowOrientation as ap, SENTINEL_UUID as aq, SESSION_SCHEMA as ar, SIGNATURE_MODES as as, type SchemaField as at, type ScopeType as au, type ShadowedCleanMatch as av, type SignatureInput as aw, type SignatureMode as ax, SoilTexture as ay, StockPolicyTier as az, AccessLevel as b, type AccessLevelType as c, type AgriCoreSession as d, ApprovalStatus as e, ApprovalType as f, AuditStatus as g, AuditType as h, type AuthScopeFields as i, type AvailableScope as j, type BarcodeNamespaceType as k, BarcodeType as l, type BarcodeTypeType as m, type Base44Id as n, type BaseProductRow as o, CAR_SUPPORTED_SIGNATURE_MIME_TYPES as p, CAT as q, CATEGORY_TABS as r, CORRECTIVE_ACTION_GENERATION_SOURCES as s, CORRECTIVE_ACTION_STATUSES as t, type CanonicalId as u, type CarSupportedEvidenceMimeType as v, type CarSupportedSignatureMimeType as w, type CarTemplateFileMetadata as x, CatalogSyncStatus as y, type CatalogSyncStatusType as z };
|
|
@@ -206,6 +206,66 @@ type ProductUnitType = typeof ProductUnit[keyof typeof ProductUnit];
|
|
|
206
206
|
type CatalogSyncStatusType = typeof CatalogSyncStatus[keyof typeof CatalogSyncStatus];
|
|
207
207
|
type StorageType = typeof StoragePhases[keyof typeof StoragePhases];
|
|
208
208
|
type StockPolicyTierType = typeof StockPolicyTier[keyof typeof StockPolicyTier];
|
|
209
|
+
interface PricingContext {
|
|
210
|
+
productId: string;
|
|
211
|
+
companyId: string;
|
|
212
|
+
facilityId?: string;
|
|
213
|
+
accountCode?: string | null;
|
|
214
|
+
contractReferenceNumber?: string | null;
|
|
215
|
+
evaluationDate?: Date;
|
|
216
|
+
}
|
|
217
|
+
interface PriceResolutionResult {
|
|
218
|
+
unitPriceCents: number;
|
|
219
|
+
minimumOrderQuantity: number;
|
|
220
|
+
leadTimeDays: number | null;
|
|
221
|
+
priceSource: 'EXPLICIT_CONTRACT' | 'ACCOUNT_MATRIX' | 'INTERNAL_DEFAULT' | 'PRODUCT_FALLBACK';
|
|
222
|
+
sourceRecordId: string;
|
|
223
|
+
}
|
|
224
|
+
interface PricingDataSource {
|
|
225
|
+
findContractPrice(args: {
|
|
226
|
+
productId: string;
|
|
227
|
+
contractReferenceNumber: string;
|
|
228
|
+
targetDate: Date;
|
|
229
|
+
}): Promise<MatrixRow | null>;
|
|
230
|
+
findAccountPrice(args: {
|
|
231
|
+
productId: string;
|
|
232
|
+
accountCode: string;
|
|
233
|
+
targetDate: Date;
|
|
234
|
+
}): Promise<MatrixRow | null>;
|
|
235
|
+
findDefaultPrice(args: {
|
|
236
|
+
productId: string;
|
|
237
|
+
targetDate: Date;
|
|
238
|
+
}): Promise<MatrixRow | null>;
|
|
239
|
+
findBaseProduct(args: {
|
|
240
|
+
productId: string;
|
|
241
|
+
}): Promise<BaseProductRow | null>;
|
|
242
|
+
}
|
|
243
|
+
interface MatrixRow {
|
|
244
|
+
canonicalId: string;
|
|
245
|
+
matrixUnitPriceCents: number;
|
|
246
|
+
minimumOrderQuantity: number;
|
|
247
|
+
leadTimeDays: number | null;
|
|
248
|
+
}
|
|
249
|
+
interface BaseProductRow {
|
|
250
|
+
canonicalId: string;
|
|
251
|
+
unitPriceCents: number;
|
|
252
|
+
}
|
|
253
|
+
type Tier = 'EXPLICIT_CONTRACT' | 'ACCOUNT_MATRIX' | 'INTERNAL_DEFAULT';
|
|
254
|
+
interface PriceConflictCandidate {
|
|
255
|
+
sourceRecordId: string;
|
|
256
|
+
unitPriceCents: number;
|
|
257
|
+
validFrom: string;
|
|
258
|
+
validTo: string | null;
|
|
259
|
+
}
|
|
260
|
+
interface ShadowedCleanMatch {
|
|
261
|
+
tier: Tier;
|
|
262
|
+
unitPriceCents: number;
|
|
263
|
+
sourceRecordId: string;
|
|
264
|
+
matchedOn: {
|
|
265
|
+
accountCode?: string;
|
|
266
|
+
contractReferenceNumber?: string;
|
|
267
|
+
};
|
|
268
|
+
}
|
|
209
269
|
|
|
210
270
|
declare const MOD: {
|
|
211
271
|
readonly OPERATIONS: "OPR";
|
|
@@ -686,4 +746,4 @@ declare const StockPolicyTier: {
|
|
|
686
746
|
readonly BY_LOCATION: 3;
|
|
687
747
|
};
|
|
688
748
|
|
|
689
|
-
export {
|
|
749
|
+
export { MimeType as $, ACCESS_RANK as A, BarcodeNamespace as B, CAR_SUPPORTED_EVIDENCE_MIME_TYPES as C, type CompanyConfig as D, type CorrectiveActionCompileTaskMetadata as E, type CorrectiveActionGenerationSource as F, type CorrectiveActionStatus as G, CropCycleStatus as H, DOCUMENT_DISPLAY_INFO as I, DOCUMENT_MENU_MAP as J, DocumentCategory as K, DocumentType as L, ERROR_DICTIONARY as M, EVERYONE as N, EmitterType as O, EnabledModule as P, FORM_REGISTRY as Q, FacilityType as R, type FileUploadTaskType as S, type FormMeta as T, HarvestMethod as U, type InfrastructureFields as V, LinkedStatus as W, MOD as X, MODULE_LABELS as Y, type MatrixRow as Z, MicroclimateZone as _, ACT as a, type ModCode as a0, NettingType as a1, type NormalizedSignatureValue as a2, NotificationMode as a3, OrderStatus as a4, type PermissionLevel as a5, PhysicalState as a6, PlantingMethod as a7, type PriceConflictCandidate as a8, type PriceResolutionResult as a9, type StockPolicyTierType as aA, StoragePhases as aB, type StorageType as aC, TASK_TYPE_REGISTRY as aD, TEMPLATE_SCOPE_LEVELS as aE, type TabConfig as aF, TargetEntityType as aG, TaskStatus as aH, TaskType as aI, type TaskTypeConfig as aJ, type TemplateScopeLevel as aK, type Tier as aL, TransactionType as aM, TrellisType as aN, UserPrivilege as aO, type UserPrivilegeType as aP, UserRole as aQ, type UserRoleAssignment as aR, type UserRoleType as aS, VigorRating as aT, WaterSource as aU, type WireSession as aV, type PricingContext as aa, type PricingDataSource as ab, ProductType as ac, type ProductTypeType as ad, ProductUnit as ae, type ProductUnitType as af, RESOURCE_MODULE_MAP as ag, RESOURCE_PATHS as ah, RESOURCE_REQUIREMENTS as ai, ROLE_SECTIONS_BY_KEY as aj, RelationshipType as ak, type ResolvedError as al, type ResourceOverrides as am, type ResourceRequirement as an, type RoleAssignment as ao, RowOrientation as ap, SENTINEL_UUID as aq, SESSION_SCHEMA as ar, SIGNATURE_MODES as as, type SchemaField as at, type ScopeType as au, type ShadowedCleanMatch as av, type SignatureInput as aw, type SignatureMode as ax, SoilTexture as ay, StockPolicyTier as az, AccessLevel as b, type AccessLevelType as c, type AgriCoreSession as d, ApprovalStatus as e, ApprovalType as f, AuditStatus as g, AuditType as h, type AuthScopeFields as i, type AvailableScope as j, type BarcodeNamespaceType as k, BarcodeType as l, type BarcodeTypeType as m, type Base44Id as n, type BaseProductRow as o, CAR_SUPPORTED_SIGNATURE_MIME_TYPES as p, CAT as q, CATEGORY_TABS as r, CORRECTIVE_ACTION_GENERATION_SOURCES as s, CORRECTIVE_ACTION_STATUSES as t, type CanonicalId as u, type CarSupportedEvidenceMimeType as v, type CarSupportedSignatureMimeType as w, type CarTemplateFileMetadata as x, CatalogSyncStatus as y, type CatalogSyncStatusType as z };
|
package/dist/constants.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, B as BarcodeNamespace, l as BarcodeType, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES,
|
|
1
|
+
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, B as BarcodeNamespace, l as BarcodeType, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES, p as CAR_SUPPORTED_SIGNATURE_MIME_TYPES, q as CAT, r as CATEGORY_TABS, s as CORRECTIVE_ACTION_GENERATION_SOURCES, t as CORRECTIVE_ACTION_STATUSES, y as CatalogSyncStatus, H as CropCycleStatus, I as DOCUMENT_DISPLAY_INFO, J as DOCUMENT_MENU_MAP, K as DocumentCategory, L as DocumentType, M as ERROR_DICTIONARY, N as EVERYONE, O as EmitterType, P as EnabledModule, Q as FORM_REGISTRY, R as FacilityType, U as HarvestMethod, W as LinkedStatus, X as MOD, Y as MODULE_LABELS, _ as MicroclimateZone, $ as MimeType, a0 as ModCode, a1 as NettingType, a3 as NotificationMode, a4 as OrderStatus, a6 as PhysicalState, a7 as PlantingMethod, ac as ProductType, ae as ProductUnit, ag as RESOURCE_MODULE_MAP, ah as RESOURCE_PATHS, ai as RESOURCE_REQUIREMENTS, aj as ROLE_SECTIONS_BY_KEY, ak as RelationshipType, an as ResourceRequirement, ap as RowOrientation, aq as SENTINEL_UUID, ar as SESSION_SCHEMA, as as SIGNATURE_MODES, ay as SoilTexture, az as StockPolicyTier, aB as StoragePhases, aD as TASK_TYPE_REGISTRY, aE as TEMPLATE_SCOPE_LEVELS, aG as TargetEntityType, aH as TaskStatus, aI as TaskType, aM as TransactionType, aN as TrellisType, aO as UserPrivilege, aQ as UserRole, aT as VigorRating, aU as WaterSource } from './constants-CrAPx_xU.mjs';
|
package/dist/constants.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, B as BarcodeNamespace, l as BarcodeType, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES,
|
|
1
|
+
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, B as BarcodeNamespace, l as BarcodeType, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES, p as CAR_SUPPORTED_SIGNATURE_MIME_TYPES, q as CAT, r as CATEGORY_TABS, s as CORRECTIVE_ACTION_GENERATION_SOURCES, t as CORRECTIVE_ACTION_STATUSES, y as CatalogSyncStatus, H as CropCycleStatus, I as DOCUMENT_DISPLAY_INFO, J as DOCUMENT_MENU_MAP, K as DocumentCategory, L as DocumentType, M as ERROR_DICTIONARY, N as EVERYONE, O as EmitterType, P as EnabledModule, Q as FORM_REGISTRY, R as FacilityType, U as HarvestMethod, W as LinkedStatus, X as MOD, Y as MODULE_LABELS, _ as MicroclimateZone, $ as MimeType, a0 as ModCode, a1 as NettingType, a3 as NotificationMode, a4 as OrderStatus, a6 as PhysicalState, a7 as PlantingMethod, ac as ProductType, ae as ProductUnit, ag as RESOURCE_MODULE_MAP, ah as RESOURCE_PATHS, ai as RESOURCE_REQUIREMENTS, aj as ROLE_SECTIONS_BY_KEY, ak as RelationshipType, an as ResourceRequirement, ap as RowOrientation, aq as SENTINEL_UUID, ar as SESSION_SCHEMA, as as SIGNATURE_MODES, ay as SoilTexture, az as StockPolicyTier, aB as StoragePhases, aD as TASK_TYPE_REGISTRY, aE as TEMPLATE_SCOPE_LEVELS, aG as TargetEntityType, aH as TaskStatus, aI as TaskType, aM as TransactionType, aN as TrellisType, aO as UserPrivilege, aQ as UserRole, aT as VigorRating, aU as WaterSource } from './constants-CrAPx_xU.js';
|
package/dist/constants.js
CHANGED
|
@@ -150,7 +150,8 @@ var ERROR_DICTIONARY = {
|
|
|
150
150
|
"400": { title: "Data Conflict", text: "A conflict occurred with an existing record. Please review and try again." },
|
|
151
151
|
"402": { title: "Duplicate Entry", text: "A record with this name or identifier already exists." },
|
|
152
152
|
"403": { title: "Edit Conflict", text: "This record was recently updated by someone else. Please refresh and try again." },
|
|
153
|
-
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." }
|
|
153
|
+
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." },
|
|
154
|
+
"405": { title: "Pricing Conflict", text: "Multiple active prices were found for this product. An administrator must resolve the conflict before this price can be used." }
|
|
154
155
|
};
|
|
155
156
|
var UserRole = {
|
|
156
157
|
OWNER: "owner",
|
package/dist/constants.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as AgriCoreSession,
|
|
2
|
-
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, i as AuthScopeFields, B as BarcodeNamespace, k as BarcodeNamespaceType, l as BarcodeType, m as BarcodeTypeType, n as Base44Id, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES,
|
|
1
|
+
import { d as AgriCoreSession, aR as UserRoleAssignment, aL as Tier, a8 as PriceConflictCandidate, av as ShadowedCleanMatch, ab as PricingDataSource, aa as PricingContext, a9 as PriceResolutionResult, au as ScopeType, aV as WireSession, an as ResourceRequirement, c as AccessLevelType, j as AvailableScope, V as InfrastructureFields, T as FormMeta, aF as TabConfig, al as ResolvedError } from './constants-CrAPx_xU.mjs';
|
|
2
|
+
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, i as AuthScopeFields, B as BarcodeNamespace, k as BarcodeNamespaceType, l as BarcodeType, m as BarcodeTypeType, n as Base44Id, o as BaseProductRow, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES, p as CAR_SUPPORTED_SIGNATURE_MIME_TYPES, q as CAT, r as CATEGORY_TABS, s as CORRECTIVE_ACTION_GENERATION_SOURCES, t as CORRECTIVE_ACTION_STATUSES, u as CanonicalId, v as CarSupportedEvidenceMimeType, w as CarSupportedSignatureMimeType, x as CarTemplateFileMetadata, y as CatalogSyncStatus, z as CatalogSyncStatusType, D as CompanyConfig, E as CorrectiveActionCompileTaskMetadata, F as CorrectiveActionGenerationSource, G as CorrectiveActionStatus, H as CropCycleStatus, I as DOCUMENT_DISPLAY_INFO, J as DOCUMENT_MENU_MAP, K as DocumentCategory, L as DocumentType, M as ERROR_DICTIONARY, N as EVERYONE, O as EmitterType, P as EnabledModule, Q as FORM_REGISTRY, R as FacilityType, S as FileUploadTaskType, U as HarvestMethod, W as LinkedStatus, X as MOD, Y as MODULE_LABELS, Z as MatrixRow, _ as MicroclimateZone, $ as MimeType, a0 as ModCode, a1 as NettingType, a2 as NormalizedSignatureValue, a3 as NotificationMode, a4 as OrderStatus, a5 as PermissionLevel, a6 as PhysicalState, a7 as PlantingMethod, ac as ProductType, ad as ProductTypeType, ae as ProductUnit, af as ProductUnitType, ag as RESOURCE_MODULE_MAP, ah as RESOURCE_PATHS, ai as RESOURCE_REQUIREMENTS, aj as ROLE_SECTIONS_BY_KEY, ak as RelationshipType, am as ResourceOverrides, ao as RoleAssignment, ap as RowOrientation, aq as SENTINEL_UUID, ar as SESSION_SCHEMA, as as SIGNATURE_MODES, at as SchemaField, aw as SignatureInput, ax as SignatureMode, ay as SoilTexture, az as StockPolicyTier, aA as StockPolicyTierType, aB as StoragePhases, aC as StorageType, aD as TASK_TYPE_REGISTRY, aE as TEMPLATE_SCOPE_LEVELS, aG as TargetEntityType, aH as TaskStatus, aI as TaskType, aJ as TaskTypeConfig, aK as TemplateScopeLevel, aM as TransactionType, aN as TrellisType, aO as UserPrivilege, aP as UserPrivilegeType, aQ as UserRole, aS as UserRoleType, aT as VigorRating, aU as WaterSource } from './constants-CrAPx_xU.mjs';
|
|
3
3
|
|
|
4
4
|
declare const AgriLogger: {
|
|
5
5
|
log(level: "INFO" | "WARN" | "ERROR", message: string, meta: any): void;
|
|
@@ -18,6 +18,42 @@ declare function transformRoleAssignments(assignments: UserRoleAssignment[]): Re
|
|
|
18
18
|
privileges: string[];
|
|
19
19
|
}>;
|
|
20
20
|
|
|
21
|
+
declare class PriceAmbiguityError extends Error {
|
|
22
|
+
readonly productId: string;
|
|
23
|
+
readonly tier: Tier;
|
|
24
|
+
readonly candidates: PriceConflictCandidate[];
|
|
25
|
+
readonly matchedOn: {
|
|
26
|
+
accountCode?: string;
|
|
27
|
+
contractReferenceNumber?: string;
|
|
28
|
+
};
|
|
29
|
+
constructor(productId: string, tier: Tier, candidates: PriceConflictCandidate[], matchedOn?: {
|
|
30
|
+
accountCode?: string;
|
|
31
|
+
contractReferenceNumber?: string;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
declare class AggregatePriceAmbiguityError extends Error {
|
|
35
|
+
readonly productId: string;
|
|
36
|
+
readonly conflicts: PriceAmbiguityError[];
|
|
37
|
+
readonly shadowedCleanMatches: ShadowedCleanMatch[];
|
|
38
|
+
constructor(productId: string, conflicts: PriceAmbiguityError[], shadowedCleanMatches?: ShadowedCleanMatch[]);
|
|
39
|
+
}
|
|
40
|
+
declare function resolvePrice(dataSource: PricingDataSource, context: PricingContext): Promise<PriceResolutionResult>;
|
|
41
|
+
interface PricingAdapterScope {
|
|
42
|
+
authScopeId: string;
|
|
43
|
+
companyId?: string;
|
|
44
|
+
}
|
|
45
|
+
declare function createBase44PricingAdapter(db: any, scope: PricingAdapterScope): PricingDataSource;
|
|
46
|
+
interface OverlapCheckScope extends PricingAdapterScope {
|
|
47
|
+
productId: string;
|
|
48
|
+
accountCode?: string;
|
|
49
|
+
contractReferenceNumber?: string;
|
|
50
|
+
matrixType?: 'procurement_buy' | 'contract_packing_charge';
|
|
51
|
+
}
|
|
52
|
+
declare function findOverlappingPriceWindows(db: any, scope: OverlapCheckScope, newRow: {
|
|
53
|
+
validFrom: string;
|
|
54
|
+
validTo: string | null;
|
|
55
|
+
}, excludeId?: string): Promise<PriceConflictCandidate[]>;
|
|
56
|
+
|
|
21
57
|
declare const Validator: {
|
|
22
58
|
/** * 1. Email: The classic. Uses a standard RFC 5322 regex.
|
|
23
59
|
*/
|
|
@@ -84,15 +120,16 @@ declare class AppError extends Error {
|
|
|
84
120
|
status: number;
|
|
85
121
|
appCode: string;
|
|
86
122
|
isOperational: boolean;
|
|
87
|
-
|
|
123
|
+
details?: unknown;
|
|
124
|
+
constructor(status: number, mod: string, cat: string, act: string, message: string, details?: unknown);
|
|
88
125
|
}
|
|
89
126
|
declare const ErrorFactory: {
|
|
90
127
|
unauthorized: (mod: string, msg?: string) => AppError;
|
|
91
128
|
forbidden: (mod: string, act: string, msg?: string) => AppError;
|
|
92
129
|
badRequest: (mod: string, act: string, msg?: string) => AppError;
|
|
93
130
|
notFound: (mod: string, act?: "00", msg?: string) => AppError;
|
|
94
|
-
conflict: (mod: string, act: string, msg?: string) => AppError;
|
|
95
|
-
integrity: (mod: string, act?: "00", msg?: string) => AppError;
|
|
131
|
+
conflict: (mod: string, act: string, msg?: string, details?: unknown) => AppError;
|
|
132
|
+
integrity: (mod: string, act?: "00", msg?: string, details?: unknown) => AppError;
|
|
96
133
|
subscription: (mod: string, msg?: string) => AppError;
|
|
97
134
|
internal: (mod: string, msg?: string) => AppError;
|
|
98
135
|
};
|
|
@@ -178,4 +215,4 @@ declare function calculateFileMetadataMatch(criteria: {
|
|
|
178
215
|
contextMatch?: string[];
|
|
179
216
|
}, fileMetadata: Record<string, any>, auditContext: Record<string, any>): number;
|
|
180
217
|
|
|
181
|
-
export { AccessLevelType, AgriCoreSession, AgriLogger, AppError, AvailableScope, Converter, ErrorFactory, FormMeta, InfrastructureFields, ResolvedError, ResourceRequirement, ScopeType, TabConfig, UserRoleAssignment, Validator, WireSession, assertTenantBoundary, calculateFileMetadataMatch, decodeSession, encodeSession, evaluateBaseAccess, extractAppCode, findSpecificOverride, generateCanonicalId, generateDocumentPath, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getPrivilegesAt, getRoleAt, getScopeAncestry, getTaskTypeOptions, handleAppErrorResponse, hasPrivilegeAt, hasRoleAt, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, transformRoleAssignments, verifyAndExtractSession, withAgriLogging };
|
|
218
|
+
export { AccessLevelType, AggregatePriceAmbiguityError, AgriCoreSession, AgriLogger, AppError, AvailableScope, Converter, ErrorFactory, FormMeta, InfrastructureFields, type OverlapCheckScope, PriceAmbiguityError, PriceConflictCandidate, PriceResolutionResult, type PricingAdapterScope, PricingContext, PricingDataSource, ResolvedError, ResourceRequirement, ScopeType, ShadowedCleanMatch, TabConfig, Tier, UserRoleAssignment, Validator, WireSession, assertTenantBoundary, calculateFileMetadataMatch, createBase44PricingAdapter, decodeSession, encodeSession, evaluateBaseAccess, extractAppCode, findOverlappingPriceWindows, findSpecificOverride, generateCanonicalId, generateDocumentPath, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getPrivilegesAt, getRoleAt, getScopeAncestry, getTaskTypeOptions, handleAppErrorResponse, hasPrivilegeAt, hasRoleAt, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolvePrice, transformRoleAssignments, verifyAndExtractSession, withAgriLogging };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as AgriCoreSession,
|
|
2
|
-
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, i as AuthScopeFields, B as BarcodeNamespace, k as BarcodeNamespaceType, l as BarcodeType, m as BarcodeTypeType, n as Base44Id, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES,
|
|
1
|
+
import { d as AgriCoreSession, aR as UserRoleAssignment, aL as Tier, a8 as PriceConflictCandidate, av as ShadowedCleanMatch, ab as PricingDataSource, aa as PricingContext, a9 as PriceResolutionResult, au as ScopeType, aV as WireSession, an as ResourceRequirement, c as AccessLevelType, j as AvailableScope, V as InfrastructureFields, T as FormMeta, aF as TabConfig, al as ResolvedError } from './constants-CrAPx_xU.js';
|
|
2
|
+
export { A as ACCESS_RANK, a as ACT, b as AccessLevel, e as ApprovalStatus, f as ApprovalType, g as AuditStatus, h as AuditType, i as AuthScopeFields, B as BarcodeNamespace, k as BarcodeNamespaceType, l as BarcodeType, m as BarcodeTypeType, n as Base44Id, o as BaseProductRow, C as CAR_SUPPORTED_EVIDENCE_MIME_TYPES, p as CAR_SUPPORTED_SIGNATURE_MIME_TYPES, q as CAT, r as CATEGORY_TABS, s as CORRECTIVE_ACTION_GENERATION_SOURCES, t as CORRECTIVE_ACTION_STATUSES, u as CanonicalId, v as CarSupportedEvidenceMimeType, w as CarSupportedSignatureMimeType, x as CarTemplateFileMetadata, y as CatalogSyncStatus, z as CatalogSyncStatusType, D as CompanyConfig, E as CorrectiveActionCompileTaskMetadata, F as CorrectiveActionGenerationSource, G as CorrectiveActionStatus, H as CropCycleStatus, I as DOCUMENT_DISPLAY_INFO, J as DOCUMENT_MENU_MAP, K as DocumentCategory, L as DocumentType, M as ERROR_DICTIONARY, N as EVERYONE, O as EmitterType, P as EnabledModule, Q as FORM_REGISTRY, R as FacilityType, S as FileUploadTaskType, U as HarvestMethod, W as LinkedStatus, X as MOD, Y as MODULE_LABELS, Z as MatrixRow, _ as MicroclimateZone, $ as MimeType, a0 as ModCode, a1 as NettingType, a2 as NormalizedSignatureValue, a3 as NotificationMode, a4 as OrderStatus, a5 as PermissionLevel, a6 as PhysicalState, a7 as PlantingMethod, ac as ProductType, ad as ProductTypeType, ae as ProductUnit, af as ProductUnitType, ag as RESOURCE_MODULE_MAP, ah as RESOURCE_PATHS, ai as RESOURCE_REQUIREMENTS, aj as ROLE_SECTIONS_BY_KEY, ak as RelationshipType, am as ResourceOverrides, ao as RoleAssignment, ap as RowOrientation, aq as SENTINEL_UUID, ar as SESSION_SCHEMA, as as SIGNATURE_MODES, at as SchemaField, aw as SignatureInput, ax as SignatureMode, ay as SoilTexture, az as StockPolicyTier, aA as StockPolicyTierType, aB as StoragePhases, aC as StorageType, aD as TASK_TYPE_REGISTRY, aE as TEMPLATE_SCOPE_LEVELS, aG as TargetEntityType, aH as TaskStatus, aI as TaskType, aJ as TaskTypeConfig, aK as TemplateScopeLevel, aM as TransactionType, aN as TrellisType, aO as UserPrivilege, aP as UserPrivilegeType, aQ as UserRole, aS as UserRoleType, aT as VigorRating, aU as WaterSource } from './constants-CrAPx_xU.js';
|
|
3
3
|
|
|
4
4
|
declare const AgriLogger: {
|
|
5
5
|
log(level: "INFO" | "WARN" | "ERROR", message: string, meta: any): void;
|
|
@@ -18,6 +18,42 @@ declare function transformRoleAssignments(assignments: UserRoleAssignment[]): Re
|
|
|
18
18
|
privileges: string[];
|
|
19
19
|
}>;
|
|
20
20
|
|
|
21
|
+
declare class PriceAmbiguityError extends Error {
|
|
22
|
+
readonly productId: string;
|
|
23
|
+
readonly tier: Tier;
|
|
24
|
+
readonly candidates: PriceConflictCandidate[];
|
|
25
|
+
readonly matchedOn: {
|
|
26
|
+
accountCode?: string;
|
|
27
|
+
contractReferenceNumber?: string;
|
|
28
|
+
};
|
|
29
|
+
constructor(productId: string, tier: Tier, candidates: PriceConflictCandidate[], matchedOn?: {
|
|
30
|
+
accountCode?: string;
|
|
31
|
+
contractReferenceNumber?: string;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
declare class AggregatePriceAmbiguityError extends Error {
|
|
35
|
+
readonly productId: string;
|
|
36
|
+
readonly conflicts: PriceAmbiguityError[];
|
|
37
|
+
readonly shadowedCleanMatches: ShadowedCleanMatch[];
|
|
38
|
+
constructor(productId: string, conflicts: PriceAmbiguityError[], shadowedCleanMatches?: ShadowedCleanMatch[]);
|
|
39
|
+
}
|
|
40
|
+
declare function resolvePrice(dataSource: PricingDataSource, context: PricingContext): Promise<PriceResolutionResult>;
|
|
41
|
+
interface PricingAdapterScope {
|
|
42
|
+
authScopeId: string;
|
|
43
|
+
companyId?: string;
|
|
44
|
+
}
|
|
45
|
+
declare function createBase44PricingAdapter(db: any, scope: PricingAdapterScope): PricingDataSource;
|
|
46
|
+
interface OverlapCheckScope extends PricingAdapterScope {
|
|
47
|
+
productId: string;
|
|
48
|
+
accountCode?: string;
|
|
49
|
+
contractReferenceNumber?: string;
|
|
50
|
+
matrixType?: 'procurement_buy' | 'contract_packing_charge';
|
|
51
|
+
}
|
|
52
|
+
declare function findOverlappingPriceWindows(db: any, scope: OverlapCheckScope, newRow: {
|
|
53
|
+
validFrom: string;
|
|
54
|
+
validTo: string | null;
|
|
55
|
+
}, excludeId?: string): Promise<PriceConflictCandidate[]>;
|
|
56
|
+
|
|
21
57
|
declare const Validator: {
|
|
22
58
|
/** * 1. Email: The classic. Uses a standard RFC 5322 regex.
|
|
23
59
|
*/
|
|
@@ -84,15 +120,16 @@ declare class AppError extends Error {
|
|
|
84
120
|
status: number;
|
|
85
121
|
appCode: string;
|
|
86
122
|
isOperational: boolean;
|
|
87
|
-
|
|
123
|
+
details?: unknown;
|
|
124
|
+
constructor(status: number, mod: string, cat: string, act: string, message: string, details?: unknown);
|
|
88
125
|
}
|
|
89
126
|
declare const ErrorFactory: {
|
|
90
127
|
unauthorized: (mod: string, msg?: string) => AppError;
|
|
91
128
|
forbidden: (mod: string, act: string, msg?: string) => AppError;
|
|
92
129
|
badRequest: (mod: string, act: string, msg?: string) => AppError;
|
|
93
130
|
notFound: (mod: string, act?: "00", msg?: string) => AppError;
|
|
94
|
-
conflict: (mod: string, act: string, msg?: string) => AppError;
|
|
95
|
-
integrity: (mod: string, act?: "00", msg?: string) => AppError;
|
|
131
|
+
conflict: (mod: string, act: string, msg?: string, details?: unknown) => AppError;
|
|
132
|
+
integrity: (mod: string, act?: "00", msg?: string, details?: unknown) => AppError;
|
|
96
133
|
subscription: (mod: string, msg?: string) => AppError;
|
|
97
134
|
internal: (mod: string, msg?: string) => AppError;
|
|
98
135
|
};
|
|
@@ -178,4 +215,4 @@ declare function calculateFileMetadataMatch(criteria: {
|
|
|
178
215
|
contextMatch?: string[];
|
|
179
216
|
}, fileMetadata: Record<string, any>, auditContext: Record<string, any>): number;
|
|
180
217
|
|
|
181
|
-
export { AccessLevelType, AgriCoreSession, AgriLogger, AppError, AvailableScope, Converter, ErrorFactory, FormMeta, InfrastructureFields, ResolvedError, ResourceRequirement, ScopeType, TabConfig, UserRoleAssignment, Validator, WireSession, assertTenantBoundary, calculateFileMetadataMatch, decodeSession, encodeSession, evaluateBaseAccess, extractAppCode, findSpecificOverride, generateCanonicalId, generateDocumentPath, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getPrivilegesAt, getRoleAt, getScopeAncestry, getTaskTypeOptions, handleAppErrorResponse, hasPrivilegeAt, hasRoleAt, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, transformRoleAssignments, verifyAndExtractSession, withAgriLogging };
|
|
218
|
+
export { AccessLevelType, AggregatePriceAmbiguityError, AgriCoreSession, AgriLogger, AppError, AvailableScope, Converter, ErrorFactory, FormMeta, InfrastructureFields, type OverlapCheckScope, PriceAmbiguityError, PriceConflictCandidate, PriceResolutionResult, type PricingAdapterScope, PricingContext, PricingDataSource, ResolvedError, ResourceRequirement, ScopeType, ShadowedCleanMatch, TabConfig, Tier, UserRoleAssignment, Validator, WireSession, assertTenantBoundary, calculateFileMetadataMatch, createBase44PricingAdapter, decodeSession, encodeSession, evaluateBaseAccess, extractAppCode, findOverlappingPriceWindows, findSpecificOverride, generateCanonicalId, generateDocumentPath, generateInfrastructureFields, getAcceptedMimeTypes, getApproveAction, getFormMeta, getFormsGroupedByModule, getPrivilegesAt, getRoleAt, getScopeAncestry, getTaskTypeOptions, handleAppErrorResponse, hasPrivilegeAt, hasRoleAt, isTabVisible, isTabWritable, resolveAccess, resolveAppError, resolveEffectiveAccess, resolveError, resolvePrice, transformRoleAssignments, verifyAndExtractSession, withAgriLogging };
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
ACCESS_RANK: () => ACCESS_RANK,
|
|
24
24
|
ACT: () => ACT,
|
|
25
25
|
AccessLevel: () => AccessLevel,
|
|
26
|
+
AggregatePriceAmbiguityError: () => AggregatePriceAmbiguityError,
|
|
26
27
|
AgriLogger: () => AgriLogger,
|
|
27
28
|
AppError: () => AppError,
|
|
28
29
|
ApprovalStatus: () => ApprovalStatus,
|
|
@@ -62,6 +63,7 @@ __export(index_exports, {
|
|
|
62
63
|
OrderStatus: () => OrderStatus,
|
|
63
64
|
PhysicalState: () => PhysicalState,
|
|
64
65
|
PlantingMethod: () => PlantingMethod,
|
|
66
|
+
PriceAmbiguityError: () => PriceAmbiguityError,
|
|
65
67
|
ProductType: () => ProductType,
|
|
66
68
|
ProductUnit: () => ProductUnit,
|
|
67
69
|
RESOURCE_MODULE_MAP: () => RESOURCE_MODULE_MAP,
|
|
@@ -90,10 +92,12 @@ __export(index_exports, {
|
|
|
90
92
|
WaterSource: () => WaterSource,
|
|
91
93
|
assertTenantBoundary: () => assertTenantBoundary,
|
|
92
94
|
calculateFileMetadataMatch: () => calculateFileMetadataMatch,
|
|
95
|
+
createBase44PricingAdapter: () => createBase44PricingAdapter,
|
|
93
96
|
decodeSession: () => decodeSession,
|
|
94
97
|
encodeSession: () => encodeSession,
|
|
95
98
|
evaluateBaseAccess: () => evaluateBaseAccess,
|
|
96
99
|
extractAppCode: () => extractAppCode,
|
|
100
|
+
findOverlappingPriceWindows: () => findOverlappingPriceWindows,
|
|
97
101
|
findSpecificOverride: () => findSpecificOverride,
|
|
98
102
|
generateCanonicalId: () => generateCanonicalId,
|
|
99
103
|
generateDocumentPath: () => generateDocumentPath,
|
|
@@ -115,6 +119,7 @@ __export(index_exports, {
|
|
|
115
119
|
resolveAppError: () => resolveAppError,
|
|
116
120
|
resolveEffectiveAccess: () => resolveEffectiveAccess,
|
|
117
121
|
resolveError: () => resolveError,
|
|
122
|
+
resolvePrice: () => resolvePrice,
|
|
118
123
|
transformRoleAssignments: () => transformRoleAssignments,
|
|
119
124
|
verifyAndExtractSession: () => verifyAndExtractSession,
|
|
120
125
|
withAgriLogging: () => withAgriLogging
|
|
@@ -247,6 +252,224 @@ function transformRoleAssignments(assignments) {
|
|
|
247
252
|
return result;
|
|
248
253
|
}
|
|
249
254
|
|
|
255
|
+
// src/priceHelpers.ts
|
|
256
|
+
var PriceAmbiguityError = class extends Error {
|
|
257
|
+
constructor(productId, tier, candidates, matchedOn = {}) {
|
|
258
|
+
const keyDescription = matchedOn.contractReferenceNumber ? `contract ${matchedOn.contractReferenceNumber}` : matchedOn.accountCode ? `account ${matchedOn.accountCode}` : "the system default";
|
|
259
|
+
super(
|
|
260
|
+
`Ambiguous pricing for product ${productId} at tier ${tier} (${keyDescription}): ${candidates.length} overlapping active prices found (${candidates.map((c) => `$${(c.unitPriceCents / 100).toFixed(2)}`).join(", ")}).`
|
|
261
|
+
);
|
|
262
|
+
this.productId = productId;
|
|
263
|
+
this.tier = tier;
|
|
264
|
+
this.candidates = candidates;
|
|
265
|
+
this.matchedOn = matchedOn;
|
|
266
|
+
this.name = "PriceAmbiguityError";
|
|
267
|
+
}
|
|
268
|
+
productId;
|
|
269
|
+
tier;
|
|
270
|
+
candidates;
|
|
271
|
+
matchedOn;
|
|
272
|
+
};
|
|
273
|
+
var AggregatePriceAmbiguityError = class extends Error {
|
|
274
|
+
constructor(productId, conflicts, shadowedCleanMatches = []) {
|
|
275
|
+
const tierList = conflicts.map((c) => c.tier).join(", ");
|
|
276
|
+
const note = shadowedCleanMatches.length > 0 ? ` Note: ${shadowedCleanMatches.length} other tier(s) had a clean price available but were not used, since a higher-priority tier must be resolved first.` : "";
|
|
277
|
+
super(
|
|
278
|
+
`Pricing for product ${productId} could not be resolved: ambiguous prices found at ${conflicts.length} tier(s) [${tierList}].${note}`
|
|
279
|
+
);
|
|
280
|
+
this.productId = productId;
|
|
281
|
+
this.conflicts = conflicts;
|
|
282
|
+
this.shadowedCleanMatches = shadowedCleanMatches;
|
|
283
|
+
this.name = "AggregatePriceAmbiguityError";
|
|
284
|
+
}
|
|
285
|
+
productId;
|
|
286
|
+
conflicts;
|
|
287
|
+
shadowedCleanMatches;
|
|
288
|
+
};
|
|
289
|
+
function fromMatrixRow(row, priceSource) {
|
|
290
|
+
return {
|
|
291
|
+
unitPriceCents: row.matrixUnitPriceCents,
|
|
292
|
+
minimumOrderQuantity: row.minimumOrderQuantity,
|
|
293
|
+
leadTimeDays: row.leadTimeDays,
|
|
294
|
+
priceSource,
|
|
295
|
+
sourceRecordId: row.canonicalId
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async function resolvePrice(dataSource, context) {
|
|
299
|
+
const targetDate = context.evaluationDate ?? /* @__PURE__ */ new Date();
|
|
300
|
+
const conflicts = [];
|
|
301
|
+
const shadowedCleanMatches = [];
|
|
302
|
+
async function tryTier(lookup) {
|
|
303
|
+
try {
|
|
304
|
+
return await lookup();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (err instanceof PriceAmbiguityError) {
|
|
307
|
+
conflicts.push(err);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function handleCleanRow(row, tier, matchedOn) {
|
|
314
|
+
if (!row) return null;
|
|
315
|
+
if (conflicts.length === 0) return fromMatrixRow(row, tier);
|
|
316
|
+
shadowedCleanMatches.push({
|
|
317
|
+
tier,
|
|
318
|
+
unitPriceCents: row.matrixUnitPriceCents,
|
|
319
|
+
sourceRecordId: row.canonicalId,
|
|
320
|
+
matchedOn
|
|
321
|
+
});
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
if (context.contractReferenceNumber) {
|
|
325
|
+
const row = await tryTier(
|
|
326
|
+
() => dataSource.findContractPrice({
|
|
327
|
+
productId: context.productId,
|
|
328
|
+
contractReferenceNumber: context.contractReferenceNumber,
|
|
329
|
+
targetDate
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
const result = handleCleanRow(row, "EXPLICIT_CONTRACT", {
|
|
333
|
+
contractReferenceNumber: context.contractReferenceNumber
|
|
334
|
+
});
|
|
335
|
+
if (result) return result;
|
|
336
|
+
}
|
|
337
|
+
if (context.accountCode && context.accountCode !== "INTERNAL_DEFAULT") {
|
|
338
|
+
const row = await tryTier(
|
|
339
|
+
() => dataSource.findAccountPrice({
|
|
340
|
+
productId: context.productId,
|
|
341
|
+
accountCode: context.accountCode,
|
|
342
|
+
targetDate
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
const result = handleCleanRow(row, "ACCOUNT_MATRIX", { accountCode: context.accountCode });
|
|
346
|
+
if (result) return result;
|
|
347
|
+
}
|
|
348
|
+
const defaultRow = await tryTier(
|
|
349
|
+
() => dataSource.findDefaultPrice({ productId: context.productId, targetDate })
|
|
350
|
+
);
|
|
351
|
+
const defaultResult = handleCleanRow(defaultRow, "INTERNAL_DEFAULT", {});
|
|
352
|
+
if (defaultResult) return defaultResult;
|
|
353
|
+
if (conflicts.length > 0) {
|
|
354
|
+
throw new AggregatePriceAmbiguityError(context.productId, conflicts, shadowedCleanMatches);
|
|
355
|
+
}
|
|
356
|
+
const baseProduct = await dataSource.findBaseProduct({ productId: context.productId });
|
|
357
|
+
if (baseProduct) {
|
|
358
|
+
return {
|
|
359
|
+
unitPriceCents: baseProduct.unitPriceCents,
|
|
360
|
+
minimumOrderQuantity: 1,
|
|
361
|
+
leadTimeDays: null,
|
|
362
|
+
priceSource: "PRODUCT_FALLBACK",
|
|
363
|
+
sourceRecordId: baseProduct.canonicalId
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
throw new Error(`Critical Architecture Error: Product ${context.productId} not found in system catalog.`);
|
|
367
|
+
}
|
|
368
|
+
var ACTIVE_ROW_LOOKBACK = 5;
|
|
369
|
+
var SORT_MOST_RECENT_FIRST = "-validFrom";
|
|
370
|
+
function pickActiveRow(rows, targetIso, productId, tier, matchedOn) {
|
|
371
|
+
const activeMatches = rows.filter(
|
|
372
|
+
(r) => r.validTo === null || new Date(r.validTo).toISOString() >= targetIso
|
|
373
|
+
);
|
|
374
|
+
if (activeMatches.length > 1) {
|
|
375
|
+
throw new PriceAmbiguityError(
|
|
376
|
+
productId,
|
|
377
|
+
tier,
|
|
378
|
+
activeMatches.map((r) => ({
|
|
379
|
+
sourceRecordId: r.canonicalId,
|
|
380
|
+
unitPriceCents: r.matrixUnitPriceCents,
|
|
381
|
+
validFrom: r.validFrom,
|
|
382
|
+
validTo: r.validTo
|
|
383
|
+
})),
|
|
384
|
+
matchedOn
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return activeMatches[0] ?? null;
|
|
388
|
+
}
|
|
389
|
+
function createBase44PricingAdapter(db, scope) {
|
|
390
|
+
const scopeFilter = {
|
|
391
|
+
authScopeId: scope.authScopeId,
|
|
392
|
+
...scope.companyId ? { companyId: scope.companyId } : {}
|
|
393
|
+
};
|
|
394
|
+
return {
|
|
395
|
+
async findContractPrice({ productId, contractReferenceNumber, targetDate }) {
|
|
396
|
+
const targetIso = targetDate.toISOString();
|
|
397
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
398
|
+
{
|
|
399
|
+
...scopeFilter,
|
|
400
|
+
productId,
|
|
401
|
+
contractReferenceNumber,
|
|
402
|
+
matrixType: "procurement_buy",
|
|
403
|
+
validFrom_lte: targetIso
|
|
404
|
+
},
|
|
405
|
+
SORT_MOST_RECENT_FIRST,
|
|
406
|
+
ACTIVE_ROW_LOOKBACK
|
|
407
|
+
);
|
|
408
|
+
return pickActiveRow(rows, targetIso, productId, "EXPLICIT_CONTRACT", {
|
|
409
|
+
contractReferenceNumber
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
async findAccountPrice({ productId, accountCode, targetDate }) {
|
|
413
|
+
const targetIso = targetDate.toISOString();
|
|
414
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
415
|
+
{ ...scopeFilter, productId, accountCode, validFrom_lte: targetIso },
|
|
416
|
+
SORT_MOST_RECENT_FIRST,
|
|
417
|
+
ACTIVE_ROW_LOOKBACK
|
|
418
|
+
);
|
|
419
|
+
return pickActiveRow(rows, targetIso, productId, "ACCOUNT_MATRIX", { accountCode });
|
|
420
|
+
},
|
|
421
|
+
async findDefaultPrice({ productId, targetDate }) {
|
|
422
|
+
const targetIso = targetDate.toISOString();
|
|
423
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
424
|
+
{ ...scopeFilter, productId, accountCode: "INTERNAL_DEFAULT", validFrom_lte: targetIso },
|
|
425
|
+
SORT_MOST_RECENT_FIRST,
|
|
426
|
+
ACTIVE_ROW_LOOKBACK
|
|
427
|
+
);
|
|
428
|
+
return pickActiveRow(rows, targetIso, productId, "INTERNAL_DEFAULT", {});
|
|
429
|
+
},
|
|
430
|
+
async findBaseProduct({ productId }) {
|
|
431
|
+
const rows = await db.CompanyProduct.filter({ id: productId, authScopeId: scope.authScopeId });
|
|
432
|
+
return rows[0] ?? null;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function rangesOverlap(aFrom, aTo, bFrom, bTo) {
|
|
437
|
+
const aStart = new Date(aFrom).getTime();
|
|
438
|
+
const aEnd = aTo ? new Date(aTo).getTime() : Infinity;
|
|
439
|
+
const bStart = new Date(bFrom).getTime();
|
|
440
|
+
const bEnd = bTo ? new Date(bTo).getTime() : Infinity;
|
|
441
|
+
return aStart <= bEnd && bStart <= aEnd;
|
|
442
|
+
}
|
|
443
|
+
async function findOverlappingPriceWindows(db, scope, newRow, excludeId) {
|
|
444
|
+
const { productId, accountCode, contractReferenceNumber, matrixType, authScopeId, companyId } = scope;
|
|
445
|
+
const scopeFilter = {
|
|
446
|
+
authScopeId,
|
|
447
|
+
...companyId ? { companyId } : {}
|
|
448
|
+
};
|
|
449
|
+
const existingRows = await db.ProductPriceMatrix.filter(
|
|
450
|
+
{
|
|
451
|
+
...scopeFilter,
|
|
452
|
+
productId,
|
|
453
|
+
...accountCode ? { accountCode } : {},
|
|
454
|
+
...contractReferenceNumber ? { contractReferenceNumber } : {},
|
|
455
|
+
...matrixType ? { matrixType } : {}
|
|
456
|
+
},
|
|
457
|
+
SORT_MOST_RECENT_FIRST,
|
|
458
|
+
// Write-time validation must see the product's full pricing history in
|
|
459
|
+
// this scope, not just rows near "now" — ACTIVE_ROW_LOOKBACK (5) is
|
|
460
|
+
// read-time-specific and too shallow here.
|
|
461
|
+
100
|
|
462
|
+
);
|
|
463
|
+
return existingRows.filter(
|
|
464
|
+
(r) => r.canonicalId !== excludeId && rangesOverlap(newRow.validFrom, newRow.validTo, r.validFrom, r.validTo)
|
|
465
|
+
).map((r) => ({
|
|
466
|
+
sourceRecordId: r.canonicalId,
|
|
467
|
+
unitPriceCents: r.matrixUnitPriceCents,
|
|
468
|
+
validFrom: r.validFrom,
|
|
469
|
+
validTo: r.validTo
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
|
|
250
473
|
// src/validators.ts
|
|
251
474
|
var Validator = {
|
|
252
475
|
/** * 1. Email: The classic. Uses a standard RFC 5322 regex.
|
|
@@ -436,7 +659,8 @@ var ERROR_DICTIONARY = {
|
|
|
436
659
|
"400": { title: "Data Conflict", text: "A conflict occurred with an existing record. Please review and try again." },
|
|
437
660
|
"402": { title: "Duplicate Entry", text: "A record with this name or identifier already exists." },
|
|
438
661
|
"403": { title: "Edit Conflict", text: "This record was recently updated by someone else. Please refresh and try again." },
|
|
439
|
-
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." }
|
|
662
|
+
"404": { title: "Record In Use", text: "This record cannot be deleted because it is referenced by other data." },
|
|
663
|
+
"405": { title: "Pricing Conflict", text: "Multiple active prices were found for this product. An administrator must resolve the conflict before this price can be used." }
|
|
440
664
|
};
|
|
441
665
|
var UserRole = {
|
|
442
666
|
OWNER: "owner",
|
|
@@ -1333,11 +1557,14 @@ var AppError = class extends Error {
|
|
|
1333
1557
|
status;
|
|
1334
1558
|
appCode;
|
|
1335
1559
|
isOperational;
|
|
1336
|
-
|
|
1560
|
+
details;
|
|
1561
|
+
// optional structured payload, e.g. conflicting price candidates
|
|
1562
|
+
constructor(status, mod, cat, act, message, details) {
|
|
1337
1563
|
super(message);
|
|
1338
1564
|
this.status = status;
|
|
1339
1565
|
this.appCode = `${mod}-${status}-${cat}${act}`;
|
|
1340
1566
|
this.isOperational = true;
|
|
1567
|
+
this.details = details;
|
|
1341
1568
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
1342
1569
|
}
|
|
1343
1570
|
};
|
|
@@ -1346,15 +1573,21 @@ var ErrorFactory = {
|
|
|
1346
1573
|
forbidden: (mod, act, msg = "Access denied") => new AppError(403, mod, CAT.PERMISSION, act, msg),
|
|
1347
1574
|
badRequest: (mod, act, msg = "Bad request") => new AppError(400, mod, CAT.VALIDATION, act, msg),
|
|
1348
1575
|
notFound: (mod, act = ACT.GENERIC, msg = "Record not found") => new AppError(404, mod, CAT.VALIDATION, act, msg),
|
|
1349
|
-
conflict: (mod, act, msg = "Record already exists") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
1350
|
-
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
1576
|
+
conflict: (mod, act, msg = "Record already exists", details) => new AppError(409, mod, CAT.INTEGRITY, act, msg, details),
|
|
1577
|
+
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation", details) => new AppError(409, mod, CAT.INTEGRITY, act, msg, details),
|
|
1351
1578
|
subscription: (mod, msg = "Module locked or subscription required") => new AppError(403, mod, CAT.SUBSCRIPTION, ACT.GENERIC, msg),
|
|
1352
1579
|
internal: (mod, msg = "Internal server error") => new AppError(500, mod, CAT.INTEGRITY, ACT.GENERIC, msg)
|
|
1353
1580
|
};
|
|
1354
1581
|
var handleAppErrorResponse = (error, mod = MOD.ADMIN) => {
|
|
1355
1582
|
if (error instanceof AppError && error.isOperational) {
|
|
1356
1583
|
return Response.json(
|
|
1357
|
-
{
|
|
1584
|
+
{
|
|
1585
|
+
error: {
|
|
1586
|
+
appCode: error.appCode,
|
|
1587
|
+
message: error.message,
|
|
1588
|
+
...error.details ? { details: error.details } : {}
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1358
1591
|
{ status: error.status }
|
|
1359
1592
|
);
|
|
1360
1593
|
}
|
|
@@ -1718,6 +1951,7 @@ function calculateFileMetadataMatch(criteria, fileMetadata, auditContext) {
|
|
|
1718
1951
|
ACCESS_RANK,
|
|
1719
1952
|
ACT,
|
|
1720
1953
|
AccessLevel,
|
|
1954
|
+
AggregatePriceAmbiguityError,
|
|
1721
1955
|
AgriLogger,
|
|
1722
1956
|
AppError,
|
|
1723
1957
|
ApprovalStatus,
|
|
@@ -1757,6 +1991,7 @@ function calculateFileMetadataMatch(criteria, fileMetadata, auditContext) {
|
|
|
1757
1991
|
OrderStatus,
|
|
1758
1992
|
PhysicalState,
|
|
1759
1993
|
PlantingMethod,
|
|
1994
|
+
PriceAmbiguityError,
|
|
1760
1995
|
ProductType,
|
|
1761
1996
|
ProductUnit,
|
|
1762
1997
|
RESOURCE_MODULE_MAP,
|
|
@@ -1785,10 +2020,12 @@ function calculateFileMetadataMatch(criteria, fileMetadata, auditContext) {
|
|
|
1785
2020
|
WaterSource,
|
|
1786
2021
|
assertTenantBoundary,
|
|
1787
2022
|
calculateFileMetadataMatch,
|
|
2023
|
+
createBase44PricingAdapter,
|
|
1788
2024
|
decodeSession,
|
|
1789
2025
|
encodeSession,
|
|
1790
2026
|
evaluateBaseAccess,
|
|
1791
2027
|
extractAppCode,
|
|
2028
|
+
findOverlappingPriceWindows,
|
|
1792
2029
|
findSpecificOverride,
|
|
1793
2030
|
generateCanonicalId,
|
|
1794
2031
|
generateDocumentPath,
|
|
@@ -1810,6 +2047,7 @@ function calculateFileMetadataMatch(criteria, fileMetadata, auditContext) {
|
|
|
1810
2047
|
resolveAppError,
|
|
1811
2048
|
resolveEffectiveAccess,
|
|
1812
2049
|
resolveError,
|
|
2050
|
+
resolvePrice,
|
|
1813
2051
|
transformRoleAssignments,
|
|
1814
2052
|
verifyAndExtractSession,
|
|
1815
2053
|
withAgriLogging
|
package/dist/index.mjs
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
UserRole,
|
|
63
63
|
VigorRating,
|
|
64
64
|
WaterSource
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-Y5KVDVNY.mjs";
|
|
66
66
|
|
|
67
67
|
// src/index.ts
|
|
68
68
|
import { jwtVerify } from "jose";
|
|
@@ -192,6 +192,224 @@ function transformRoleAssignments(assignments) {
|
|
|
192
192
|
return result;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
// src/priceHelpers.ts
|
|
196
|
+
var PriceAmbiguityError = class extends Error {
|
|
197
|
+
constructor(productId, tier, candidates, matchedOn = {}) {
|
|
198
|
+
const keyDescription = matchedOn.contractReferenceNumber ? `contract ${matchedOn.contractReferenceNumber}` : matchedOn.accountCode ? `account ${matchedOn.accountCode}` : "the system default";
|
|
199
|
+
super(
|
|
200
|
+
`Ambiguous pricing for product ${productId} at tier ${tier} (${keyDescription}): ${candidates.length} overlapping active prices found (${candidates.map((c) => `$${(c.unitPriceCents / 100).toFixed(2)}`).join(", ")}).`
|
|
201
|
+
);
|
|
202
|
+
this.productId = productId;
|
|
203
|
+
this.tier = tier;
|
|
204
|
+
this.candidates = candidates;
|
|
205
|
+
this.matchedOn = matchedOn;
|
|
206
|
+
this.name = "PriceAmbiguityError";
|
|
207
|
+
}
|
|
208
|
+
productId;
|
|
209
|
+
tier;
|
|
210
|
+
candidates;
|
|
211
|
+
matchedOn;
|
|
212
|
+
};
|
|
213
|
+
var AggregatePriceAmbiguityError = class extends Error {
|
|
214
|
+
constructor(productId, conflicts, shadowedCleanMatches = []) {
|
|
215
|
+
const tierList = conflicts.map((c) => c.tier).join(", ");
|
|
216
|
+
const note = shadowedCleanMatches.length > 0 ? ` Note: ${shadowedCleanMatches.length} other tier(s) had a clean price available but were not used, since a higher-priority tier must be resolved first.` : "";
|
|
217
|
+
super(
|
|
218
|
+
`Pricing for product ${productId} could not be resolved: ambiguous prices found at ${conflicts.length} tier(s) [${tierList}].${note}`
|
|
219
|
+
);
|
|
220
|
+
this.productId = productId;
|
|
221
|
+
this.conflicts = conflicts;
|
|
222
|
+
this.shadowedCleanMatches = shadowedCleanMatches;
|
|
223
|
+
this.name = "AggregatePriceAmbiguityError";
|
|
224
|
+
}
|
|
225
|
+
productId;
|
|
226
|
+
conflicts;
|
|
227
|
+
shadowedCleanMatches;
|
|
228
|
+
};
|
|
229
|
+
function fromMatrixRow(row, priceSource) {
|
|
230
|
+
return {
|
|
231
|
+
unitPriceCents: row.matrixUnitPriceCents,
|
|
232
|
+
minimumOrderQuantity: row.minimumOrderQuantity,
|
|
233
|
+
leadTimeDays: row.leadTimeDays,
|
|
234
|
+
priceSource,
|
|
235
|
+
sourceRecordId: row.canonicalId
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function resolvePrice(dataSource, context) {
|
|
239
|
+
const targetDate = context.evaluationDate ?? /* @__PURE__ */ new Date();
|
|
240
|
+
const conflicts = [];
|
|
241
|
+
const shadowedCleanMatches = [];
|
|
242
|
+
async function tryTier(lookup) {
|
|
243
|
+
try {
|
|
244
|
+
return await lookup();
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (err instanceof PriceAmbiguityError) {
|
|
247
|
+
conflicts.push(err);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function handleCleanRow(row, tier, matchedOn) {
|
|
254
|
+
if (!row) return null;
|
|
255
|
+
if (conflicts.length === 0) return fromMatrixRow(row, tier);
|
|
256
|
+
shadowedCleanMatches.push({
|
|
257
|
+
tier,
|
|
258
|
+
unitPriceCents: row.matrixUnitPriceCents,
|
|
259
|
+
sourceRecordId: row.canonicalId,
|
|
260
|
+
matchedOn
|
|
261
|
+
});
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
if (context.contractReferenceNumber) {
|
|
265
|
+
const row = await tryTier(
|
|
266
|
+
() => dataSource.findContractPrice({
|
|
267
|
+
productId: context.productId,
|
|
268
|
+
contractReferenceNumber: context.contractReferenceNumber,
|
|
269
|
+
targetDate
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
const result = handleCleanRow(row, "EXPLICIT_CONTRACT", {
|
|
273
|
+
contractReferenceNumber: context.contractReferenceNumber
|
|
274
|
+
});
|
|
275
|
+
if (result) return result;
|
|
276
|
+
}
|
|
277
|
+
if (context.accountCode && context.accountCode !== "INTERNAL_DEFAULT") {
|
|
278
|
+
const row = await tryTier(
|
|
279
|
+
() => dataSource.findAccountPrice({
|
|
280
|
+
productId: context.productId,
|
|
281
|
+
accountCode: context.accountCode,
|
|
282
|
+
targetDate
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
const result = handleCleanRow(row, "ACCOUNT_MATRIX", { accountCode: context.accountCode });
|
|
286
|
+
if (result) return result;
|
|
287
|
+
}
|
|
288
|
+
const defaultRow = await tryTier(
|
|
289
|
+
() => dataSource.findDefaultPrice({ productId: context.productId, targetDate })
|
|
290
|
+
);
|
|
291
|
+
const defaultResult = handleCleanRow(defaultRow, "INTERNAL_DEFAULT", {});
|
|
292
|
+
if (defaultResult) return defaultResult;
|
|
293
|
+
if (conflicts.length > 0) {
|
|
294
|
+
throw new AggregatePriceAmbiguityError(context.productId, conflicts, shadowedCleanMatches);
|
|
295
|
+
}
|
|
296
|
+
const baseProduct = await dataSource.findBaseProduct({ productId: context.productId });
|
|
297
|
+
if (baseProduct) {
|
|
298
|
+
return {
|
|
299
|
+
unitPriceCents: baseProduct.unitPriceCents,
|
|
300
|
+
minimumOrderQuantity: 1,
|
|
301
|
+
leadTimeDays: null,
|
|
302
|
+
priceSource: "PRODUCT_FALLBACK",
|
|
303
|
+
sourceRecordId: baseProduct.canonicalId
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
throw new Error(`Critical Architecture Error: Product ${context.productId} not found in system catalog.`);
|
|
307
|
+
}
|
|
308
|
+
var ACTIVE_ROW_LOOKBACK = 5;
|
|
309
|
+
var SORT_MOST_RECENT_FIRST = "-validFrom";
|
|
310
|
+
function pickActiveRow(rows, targetIso, productId, tier, matchedOn) {
|
|
311
|
+
const activeMatches = rows.filter(
|
|
312
|
+
(r) => r.validTo === null || new Date(r.validTo).toISOString() >= targetIso
|
|
313
|
+
);
|
|
314
|
+
if (activeMatches.length > 1) {
|
|
315
|
+
throw new PriceAmbiguityError(
|
|
316
|
+
productId,
|
|
317
|
+
tier,
|
|
318
|
+
activeMatches.map((r) => ({
|
|
319
|
+
sourceRecordId: r.canonicalId,
|
|
320
|
+
unitPriceCents: r.matrixUnitPriceCents,
|
|
321
|
+
validFrom: r.validFrom,
|
|
322
|
+
validTo: r.validTo
|
|
323
|
+
})),
|
|
324
|
+
matchedOn
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return activeMatches[0] ?? null;
|
|
328
|
+
}
|
|
329
|
+
function createBase44PricingAdapter(db, scope) {
|
|
330
|
+
const scopeFilter = {
|
|
331
|
+
authScopeId: scope.authScopeId,
|
|
332
|
+
...scope.companyId ? { companyId: scope.companyId } : {}
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
async findContractPrice({ productId, contractReferenceNumber, targetDate }) {
|
|
336
|
+
const targetIso = targetDate.toISOString();
|
|
337
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
338
|
+
{
|
|
339
|
+
...scopeFilter,
|
|
340
|
+
productId,
|
|
341
|
+
contractReferenceNumber,
|
|
342
|
+
matrixType: "procurement_buy",
|
|
343
|
+
validFrom_lte: targetIso
|
|
344
|
+
},
|
|
345
|
+
SORT_MOST_RECENT_FIRST,
|
|
346
|
+
ACTIVE_ROW_LOOKBACK
|
|
347
|
+
);
|
|
348
|
+
return pickActiveRow(rows, targetIso, productId, "EXPLICIT_CONTRACT", {
|
|
349
|
+
contractReferenceNumber
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
async findAccountPrice({ productId, accountCode, targetDate }) {
|
|
353
|
+
const targetIso = targetDate.toISOString();
|
|
354
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
355
|
+
{ ...scopeFilter, productId, accountCode, validFrom_lte: targetIso },
|
|
356
|
+
SORT_MOST_RECENT_FIRST,
|
|
357
|
+
ACTIVE_ROW_LOOKBACK
|
|
358
|
+
);
|
|
359
|
+
return pickActiveRow(rows, targetIso, productId, "ACCOUNT_MATRIX", { accountCode });
|
|
360
|
+
},
|
|
361
|
+
async findDefaultPrice({ productId, targetDate }) {
|
|
362
|
+
const targetIso = targetDate.toISOString();
|
|
363
|
+
const rows = await db.ProductPriceMatrix.filter(
|
|
364
|
+
{ ...scopeFilter, productId, accountCode: "INTERNAL_DEFAULT", validFrom_lte: targetIso },
|
|
365
|
+
SORT_MOST_RECENT_FIRST,
|
|
366
|
+
ACTIVE_ROW_LOOKBACK
|
|
367
|
+
);
|
|
368
|
+
return pickActiveRow(rows, targetIso, productId, "INTERNAL_DEFAULT", {});
|
|
369
|
+
},
|
|
370
|
+
async findBaseProduct({ productId }) {
|
|
371
|
+
const rows = await db.CompanyProduct.filter({ id: productId, authScopeId: scope.authScopeId });
|
|
372
|
+
return rows[0] ?? null;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function rangesOverlap(aFrom, aTo, bFrom, bTo) {
|
|
377
|
+
const aStart = new Date(aFrom).getTime();
|
|
378
|
+
const aEnd = aTo ? new Date(aTo).getTime() : Infinity;
|
|
379
|
+
const bStart = new Date(bFrom).getTime();
|
|
380
|
+
const bEnd = bTo ? new Date(bTo).getTime() : Infinity;
|
|
381
|
+
return aStart <= bEnd && bStart <= aEnd;
|
|
382
|
+
}
|
|
383
|
+
async function findOverlappingPriceWindows(db, scope, newRow, excludeId) {
|
|
384
|
+
const { productId, accountCode, contractReferenceNumber, matrixType, authScopeId, companyId } = scope;
|
|
385
|
+
const scopeFilter = {
|
|
386
|
+
authScopeId,
|
|
387
|
+
...companyId ? { companyId } : {}
|
|
388
|
+
};
|
|
389
|
+
const existingRows = await db.ProductPriceMatrix.filter(
|
|
390
|
+
{
|
|
391
|
+
...scopeFilter,
|
|
392
|
+
productId,
|
|
393
|
+
...accountCode ? { accountCode } : {},
|
|
394
|
+
...contractReferenceNumber ? { contractReferenceNumber } : {},
|
|
395
|
+
...matrixType ? { matrixType } : {}
|
|
396
|
+
},
|
|
397
|
+
SORT_MOST_RECENT_FIRST,
|
|
398
|
+
// Write-time validation must see the product's full pricing history in
|
|
399
|
+
// this scope, not just rows near "now" — ACTIVE_ROW_LOOKBACK (5) is
|
|
400
|
+
// read-time-specific and too shallow here.
|
|
401
|
+
100
|
|
402
|
+
);
|
|
403
|
+
return existingRows.filter(
|
|
404
|
+
(r) => r.canonicalId !== excludeId && rangesOverlap(newRow.validFrom, newRow.validTo, r.validFrom, r.validTo)
|
|
405
|
+
).map((r) => ({
|
|
406
|
+
sourceRecordId: r.canonicalId,
|
|
407
|
+
unitPriceCents: r.matrixUnitPriceCents,
|
|
408
|
+
validFrom: r.validFrom,
|
|
409
|
+
validTo: r.validTo
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
|
|
195
413
|
// src/validators.ts
|
|
196
414
|
var Validator = {
|
|
197
415
|
/** * 1. Email: The classic. Uses a standard RFC 5322 regex.
|
|
@@ -320,11 +538,14 @@ var AppError = class extends Error {
|
|
|
320
538
|
status;
|
|
321
539
|
appCode;
|
|
322
540
|
isOperational;
|
|
323
|
-
|
|
541
|
+
details;
|
|
542
|
+
// optional structured payload, e.g. conflicting price candidates
|
|
543
|
+
constructor(status, mod, cat, act, message, details) {
|
|
324
544
|
super(message);
|
|
325
545
|
this.status = status;
|
|
326
546
|
this.appCode = `${mod}-${status}-${cat}${act}`;
|
|
327
547
|
this.isOperational = true;
|
|
548
|
+
this.details = details;
|
|
328
549
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
329
550
|
}
|
|
330
551
|
};
|
|
@@ -333,15 +554,21 @@ var ErrorFactory = {
|
|
|
333
554
|
forbidden: (mod, act, msg = "Access denied") => new AppError(403, mod, CAT.PERMISSION, act, msg),
|
|
334
555
|
badRequest: (mod, act, msg = "Bad request") => new AppError(400, mod, CAT.VALIDATION, act, msg),
|
|
335
556
|
notFound: (mod, act = ACT.GENERIC, msg = "Record not found") => new AppError(404, mod, CAT.VALIDATION, act, msg),
|
|
336
|
-
conflict: (mod, act, msg = "Record already exists") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
337
|
-
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation") => new AppError(409, mod, CAT.INTEGRITY, act, msg),
|
|
557
|
+
conflict: (mod, act, msg = "Record already exists", details) => new AppError(409, mod, CAT.INTEGRITY, act, msg, details),
|
|
558
|
+
integrity: (mod, act = ACT.GENERIC, msg = "Data integrity violation", details) => new AppError(409, mod, CAT.INTEGRITY, act, msg, details),
|
|
338
559
|
subscription: (mod, msg = "Module locked or subscription required") => new AppError(403, mod, CAT.SUBSCRIPTION, ACT.GENERIC, msg),
|
|
339
560
|
internal: (mod, msg = "Internal server error") => new AppError(500, mod, CAT.INTEGRITY, ACT.GENERIC, msg)
|
|
340
561
|
};
|
|
341
562
|
var handleAppErrorResponse = (error, mod = MOD.ADMIN) => {
|
|
342
563
|
if (error instanceof AppError && error.isOperational) {
|
|
343
564
|
return Response.json(
|
|
344
|
-
{
|
|
565
|
+
{
|
|
566
|
+
error: {
|
|
567
|
+
appCode: error.appCode,
|
|
568
|
+
message: error.message,
|
|
569
|
+
...error.details ? { details: error.details } : {}
|
|
570
|
+
}
|
|
571
|
+
},
|
|
345
572
|
{ status: error.status }
|
|
346
573
|
);
|
|
347
574
|
}
|
|
@@ -704,6 +931,7 @@ export {
|
|
|
704
931
|
ACCESS_RANK,
|
|
705
932
|
ACT,
|
|
706
933
|
AccessLevel,
|
|
934
|
+
AggregatePriceAmbiguityError,
|
|
707
935
|
AgriLogger,
|
|
708
936
|
AppError,
|
|
709
937
|
ApprovalStatus,
|
|
@@ -743,6 +971,7 @@ export {
|
|
|
743
971
|
OrderStatus,
|
|
744
972
|
PhysicalState,
|
|
745
973
|
PlantingMethod,
|
|
974
|
+
PriceAmbiguityError,
|
|
746
975
|
ProductType,
|
|
747
976
|
ProductUnit,
|
|
748
977
|
RESOURCE_MODULE_MAP,
|
|
@@ -771,10 +1000,12 @@ export {
|
|
|
771
1000
|
WaterSource,
|
|
772
1001
|
assertTenantBoundary,
|
|
773
1002
|
calculateFileMetadataMatch,
|
|
1003
|
+
createBase44PricingAdapter,
|
|
774
1004
|
decodeSession,
|
|
775
1005
|
encodeSession,
|
|
776
1006
|
evaluateBaseAccess,
|
|
777
1007
|
extractAppCode,
|
|
1008
|
+
findOverlappingPriceWindows,
|
|
778
1009
|
findSpecificOverride,
|
|
779
1010
|
generateCanonicalId,
|
|
780
1011
|
generateDocumentPath,
|
|
@@ -796,6 +1027,7 @@ export {
|
|
|
796
1027
|
resolveAppError,
|
|
797
1028
|
resolveEffectiveAccess,
|
|
798
1029
|
resolveError,
|
|
1030
|
+
resolvePrice,
|
|
799
1031
|
transformRoleAssignments,
|
|
800
1032
|
verifyAndExtractSession,
|
|
801
1033
|
withAgriLogging
|