@mj-biz-apps/sales-core-entities-server 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +183 -0
- package/dist/CloseDealOperation.d.ts +176 -0
- package/dist/CloseDealOperation.d.ts.map +1 -0
- package/dist/CloseDealOperation.js +1107 -0
- package/dist/CloseDealOperation.js.map +1 -0
- package/dist/CloseWonTaskService.d.ts +249 -0
- package/dist/CloseWonTaskService.d.ts.map +1 -0
- package/dist/CloseWonTaskService.js +460 -0
- package/dist/CloseWonTaskService.js.map +1 -0
- package/dist/DealEntityServer.d.ts +625 -0
- package/dist/DealEntityServer.d.ts.map +1 -0
- package/dist/DealEntityServer.js +1529 -0
- package/dist/DealEntityServer.js.map +1 -0
- package/dist/LiveContractsSeam.d.ts +212 -0
- package/dist/LiveContractsSeam.d.ts.map +1 -0
- package/dist/LiveContractsSeam.js +362 -0
- package/dist/LiveContractsSeam.js.map +1 -0
- package/dist/LiveOrdersSeam.d.ts +68 -0
- package/dist/LiveOrdersSeam.d.ts.map +1 -0
- package/dist/LiveOrdersSeam.js +80 -0
- package/dist/LiveOrdersSeam.js.map +1 -0
- package/dist/SaveDealOperation.d.ts +45 -0
- package/dist/SaveDealOperation.d.ts.map +1 -0
- package/dist/SaveDealOperation.js +268 -0
- package/dist/SaveDealOperation.js.map +1 -0
- package/dist/SequenceService.d.ts +39 -0
- package/dist/SequenceService.d.ts.map +1 -0
- package/dist/SequenceService.js +44 -0
- package/dist/SequenceService.js.map +1 -0
- package/dist/activities/ActivityIngestService.d.ts +111 -0
- package/dist/activities/ActivityIngestService.d.ts.map +1 -0
- package/dist/activities/ActivityIngestService.js +485 -0
- package/dist/activities/ActivityIngestService.js.map +1 -0
- package/dist/activities/ActivityReader.d.ts +68 -0
- package/dist/activities/ActivityReader.d.ts.map +1 -0
- package/dist/activities/ActivityReader.js +147 -0
- package/dist/activities/ActivityReader.js.map +1 -0
- package/dist/activities/ActivitySource.d.ts +142 -0
- package/dist/activities/ActivitySource.d.ts.map +1 -0
- package/dist/activities/ActivitySource.js +2 -0
- package/dist/activities/ActivitySource.js.map +1 -0
- package/dist/activities/ActivitySyncJob.d.ts +72 -0
- package/dist/activities/ActivitySyncJob.d.ts.map +1 -0
- package/dist/activities/ActivitySyncJob.js +146 -0
- package/dist/activities/ActivitySyncJob.js.map +1 -0
- package/dist/activities/ActivityWriterService.d.ts +119 -0
- package/dist/activities/ActivityWriterService.d.ts.map +1 -0
- package/dist/activities/ActivityWriterService.js +270 -0
- package/dist/activities/ActivityWriterService.js.map +1 -0
- package/dist/activities/DealMatcher.d.ts +58 -0
- package/dist/activities/DealMatcher.d.ts.map +1 -0
- package/dist/activities/DealMatcher.js +109 -0
- package/dist/activities/DealMatcher.js.map +1 -0
- package/dist/activities/FixtureActivitySource.d.ts +37 -0
- package/dist/activities/FixtureActivitySource.d.ts.map +1 -0
- package/dist/activities/FixtureActivitySource.js +58 -0
- package/dist/activities/FixtureActivitySource.js.map +1 -0
- package/dist/activities/GraphMessageMapper.d.ts +47 -0
- package/dist/activities/GraphMessageMapper.d.ts.map +1 -0
- package/dist/activities/GraphMessageMapper.js +141 -0
- package/dist/activities/GraphMessageMapper.js.map +1 -0
- package/dist/activities/ImportedGraphActivitySource.d.ts +16 -0
- package/dist/activities/ImportedGraphActivitySource.d.ts.map +1 -0
- package/dist/activities/ImportedGraphActivitySource.js +84 -0
- package/dist/activities/ImportedGraphActivitySource.js.map +1 -0
- package/dist/activities/MSGraphActivitySource.d.ts +67 -0
- package/dist/activities/MSGraphActivitySource.d.ts.map +1 -0
- package/dist/activities/MSGraphActivitySource.js +186 -0
- package/dist/activities/MSGraphActivitySource.js.map +1 -0
- package/dist/activities/MSGraphCalendarSource.d.ts +72 -0
- package/dist/activities/MSGraphCalendarSource.d.ts.map +1 -0
- package/dist/activities/MSGraphCalendarSource.js +225 -0
- package/dist/activities/MSGraphCalendarSource.js.map +1 -0
- package/dist/activities/RelevanceFilter.d.ts +101 -0
- package/dist/activities/RelevanceFilter.d.ts.map +1 -0
- package/dist/activities/RelevanceFilter.js +148 -0
- package/dist/activities/RelevanceFilter.js.map +1 -0
- package/dist/activities/activity-vocabulary.d.ts +103 -0
- package/dist/activities/activity-vocabulary.d.ts.map +1 -0
- package/dist/activities/activity-vocabulary.js +79 -0
- package/dist/activities/activity-vocabulary.js.map +1 -0
- package/dist/forecast/FixtureForecastSource.d.ts +22 -0
- package/dist/forecast/FixtureForecastSource.d.ts.map +1 -0
- package/dist/forecast/FixtureForecastSource.js +16 -0
- package/dist/forecast/FixtureForecastSource.js.map +1 -0
- package/dist/forecast/ForecastSnapshotJob.d.ts +78 -0
- package/dist/forecast/ForecastSnapshotJob.d.ts.map +1 -0
- package/dist/forecast/ForecastSnapshotJob.js +225 -0
- package/dist/forecast/ForecastSnapshotJob.js.map +1 -0
- package/dist/forecast/ForecastSource.d.ts +74 -0
- package/dist/forecast/ForecastSource.d.ts.map +1 -0
- package/dist/forecast/ForecastSource.js +25 -0
- package/dist/forecast/ForecastSource.js.map +1 -0
- package/dist/forecast/QueryForecastSource.d.ts +76 -0
- package/dist/forecast/QueryForecastSource.d.ts.map +1 -0
- package/dist/forecast/QueryForecastSource.js +164 -0
- package/dist/forecast/QueryForecastSource.js.map +1 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/orders-availability.d.ts +15 -0
- package/dist/orders-availability.d.ts.map +1 -0
- package/dist/orders-availability.js +47 -0
- package/dist/orders-availability.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview What is left of the orders handoff: a wrapper that forwards the CONTRACT route.
|
|
3
|
+
*
|
|
4
|
+
* ── THIS CLASS NO LONGER TOUCHES ORDERS, AND THAT IS THE POINT ─────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* It used to create the order a won deal earned, through orders' own entity graph. Close-won does not
|
|
7
|
+
* create an order any more — the order is embedded on the deal from creation — so `CreateOrder` and
|
|
8
|
+
* `PreviewOrderMoney` described a route that no longer exists. Both are gone, along with the money
|
|
9
|
+
* boundary essay that justified them.
|
|
10
|
+
*
|
|
11
|
+
* ── ONE OF THEM WAS A LIVE HAZARD, NOT JUST DEAD WEIGHT ────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* Both passed `DiscountPct` straight through to `OrderLine`, and the seam input documented no units.
|
|
14
|
+
* Orders stores a FRACTION (0.25 for a quarter off); the caller that used to feed this sent a rep-
|
|
15
|
+
* entered PERCENTAGE (25). With the caller deleted the conversion went with it, leaving two exported,
|
|
16
|
+
* typechecked methods that would write a 25% discount as 2500% — except `CK_OrderLine_DiscountPct`
|
|
17
|
+
* caps it, so the realistic outcome was a 50% discount recorded for a half-percent one, saved
|
|
18
|
+
* cleanly, with nothing to notice.
|
|
19
|
+
*
|
|
20
|
+
* That is precisely what `scripts/assert-discount-conversion.mjs` exists to prevent, and it was
|
|
21
|
+
* sitting on a published interface with no test on it since the CW and D check bundles were retired.
|
|
22
|
+
* Deleting the methods removes the hazard outright rather than documenting it.
|
|
23
|
+
*
|
|
24
|
+
* ── WHY THE CLASS SURVIVES AT ALL ──────────────────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* `CreateContractFromDeal` and `RenewContractTerm` below are pure delegation to the contracts seam it
|
|
27
|
+
* is handed. So this is now a pass-through, and `CloseDealOperation.resolveSeam` could return that
|
|
28
|
+
* contracts seam directly instead of wrapping it — at which point this whole file goes. That edit is
|
|
29
|
+
* in `CloseDealOperation.ts`, which another session is merging, so it is left stated rather than made.
|
|
30
|
+
*
|
|
31
|
+
* `OrdersIsInstalled()` is the one thing here still doing work: the close reads it to decide whether
|
|
32
|
+
* an orders-dependent route is available at all.
|
|
33
|
+
*
|
|
34
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
35
|
+
*/
|
|
36
|
+
import { RunView, type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
37
|
+
import { type IDownstreamSeam, type ContractsCreateFromDealSeamInput, type ContractsRenewTermSeamInput, type ContractsSeamResult } from '@mj-biz-apps/sales-entities';
|
|
38
|
+
/** Orders' entity names, in one place so a rename is one edit rather than a hunt. */
|
|
39
|
+
declare const E_ORDER_HEADER = "MJ_BizApps_Orders: Order Headers";
|
|
40
|
+
declare const E_ORDER_LINE = "MJ_BizApps_Orders: Order Lines";
|
|
41
|
+
/**
|
|
42
|
+
* True when orders is actually installed here.
|
|
43
|
+
*
|
|
44
|
+
* Checked against METADATA rather than by catching an error, for the same reason the product picker
|
|
45
|
+
* does: `RunView`/`GetEntityObject` against an unregistered entity does not fail cleanly — it logs and
|
|
46
|
+
* leaves the caller guessing. Sales is designed to run standalone, so orders being absent is a
|
|
47
|
+
* supported state, not a fault.
|
|
48
|
+
*/
|
|
49
|
+
export declare function OrdersIsInstalled(): boolean;
|
|
50
|
+
export declare class LiveOrdersSeam implements IDownstreamSeam {
|
|
51
|
+
private readonly user;
|
|
52
|
+
private readonly provider;
|
|
53
|
+
/**
|
|
54
|
+
* The contract half is delegated to whatever the composer supplied — the live contracts seam when
|
|
55
|
+
* contracts is installed, the stub when it is not. This class owns ORDERS and nothing else; it used
|
|
56
|
+
* to hold a private stub, which quietly made it the authority on a downstream it knows nothing
|
|
57
|
+
* about. See `CompositeDownstreamSeam`.
|
|
58
|
+
*/
|
|
59
|
+
private readonly contracts;
|
|
60
|
+
constructor(user: UserInfo, provider: IMetadataProvider, contracts?: Pick<IDownstreamSeam, 'CreateContractFromDeal' | 'RenewContractTerm'>);
|
|
61
|
+
CreateContractFromDeal(input: ContractsCreateFromDealSeamInput): Promise<ContractsSeamResult>;
|
|
62
|
+
RenewContractTerm(input: ContractsRenewTermSeamInput): Promise<ContractsSeamResult>;
|
|
63
|
+
}
|
|
64
|
+
/** Anti-tree-shaking anchor, in the house style. */
|
|
65
|
+
export declare function LoadLiveOrdersSeam(): void;
|
|
66
|
+
/** Kept for the seam test, which reads back what orders actually wrote. */
|
|
67
|
+
export { E_ORDER_HEADER, E_ORDER_LINE, RunView };
|
|
68
|
+
//# sourceMappingURL=LiveOrdersSeam.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveOrdersSeam.d.ts","sourceRoot":"","sources":["../src/LiveOrdersSeam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAGH,OAAO,EAEP,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EAChB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACH,KAAK,eAAe,EAEpB,KAAK,gCAAgC,EACrC,KAAK,2BAA2B,EAChC,KAAK,mBAAmB,EAE3B,MAAM,6BAA6B,CAAC;AAErC,qFAAqF;AACrF,QAAA,MAAM,cAAc,qCAAqC,CAAC;AAC1D,QAAA,MAAM,YAAY,mCAAmC,CAAC;AA0BtD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,qBAAa,cAAe,YAAW,eAAe;IAW9C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAV7B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwE;gBAG7E,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,EAC5C,SAAS,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,wBAAwB,GAAG,mBAAmB,CAAC;IAO9E,sBAAsB,CAAC,KAAK,EAAE,gCAAgC,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI7F,iBAAiB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAG7F;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,2EAA2E;AAC3E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview What is left of the orders handoff: a wrapper that forwards the CONTRACT route.
|
|
3
|
+
*
|
|
4
|
+
* ── THIS CLASS NO LONGER TOUCHES ORDERS, AND THAT IS THE POINT ─────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* It used to create the order a won deal earned, through orders' own entity graph. Close-won does not
|
|
7
|
+
* create an order any more — the order is embedded on the deal from creation — so `CreateOrder` and
|
|
8
|
+
* `PreviewOrderMoney` described a route that no longer exists. Both are gone, along with the money
|
|
9
|
+
* boundary essay that justified them.
|
|
10
|
+
*
|
|
11
|
+
* ── ONE OF THEM WAS A LIVE HAZARD, NOT JUST DEAD WEIGHT ────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* Both passed `DiscountPct` straight through to `OrderLine`, and the seam input documented no units.
|
|
14
|
+
* Orders stores a FRACTION (0.25 for a quarter off); the caller that used to feed this sent a rep-
|
|
15
|
+
* entered PERCENTAGE (25). With the caller deleted the conversion went with it, leaving two exported,
|
|
16
|
+
* typechecked methods that would write a 25% discount as 2500% — except `CK_OrderLine_DiscountPct`
|
|
17
|
+
* caps it, so the realistic outcome was a 50% discount recorded for a half-percent one, saved
|
|
18
|
+
* cleanly, with nothing to notice.
|
|
19
|
+
*
|
|
20
|
+
* That is precisely what `scripts/assert-discount-conversion.mjs` exists to prevent, and it was
|
|
21
|
+
* sitting on a published interface with no test on it since the CW and D check bundles were retired.
|
|
22
|
+
* Deleting the methods removes the hazard outright rather than documenting it.
|
|
23
|
+
*
|
|
24
|
+
* ── WHY THE CLASS SURVIVES AT ALL ──────────────────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* `CreateContractFromDeal` and `RenewContractTerm` below are pure delegation to the contracts seam it
|
|
27
|
+
* is handed. So this is now a pass-through, and `CloseDealOperation.resolveSeam` could return that
|
|
28
|
+
* contracts seam directly instead of wrapping it — at which point this whole file goes. That edit is
|
|
29
|
+
* in `CloseDealOperation.ts`, which another session is merging, so it is left stated rather than made.
|
|
30
|
+
*
|
|
31
|
+
* `OrdersIsInstalled()` is the one thing here still doing work: the close reads it to decide whether
|
|
32
|
+
* an orders-dependent route is available at all.
|
|
33
|
+
*
|
|
34
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
35
|
+
*/
|
|
36
|
+
import { Metadata, RunView, } from '@memberjunction/core';
|
|
37
|
+
import { StubDownstreamSeam, } from '@mj-biz-apps/sales-entities';
|
|
38
|
+
/** Orders' entity names, in one place so a rename is one edit rather than a hunt. */
|
|
39
|
+
const E_ORDER_HEADER = 'MJ_BizApps_Orders: Order Headers';
|
|
40
|
+
const E_ORDER_LINE = 'MJ_BizApps_Orders: Order Lines';
|
|
41
|
+
/**
|
|
42
|
+
* The operation that prices a draft — orders' CLASS REGISTRATION key, not its display name.
|
|
43
|
+
*
|
|
44
|
+
* Metadata calls it "Price Order"; the ClassFactory key is `Orders.PriceOrder`. Invoking by key keeps
|
|
45
|
+
* this a SOFT dependency: sales names a string, never imports orders' TypeScript, so a deployment
|
|
46
|
+
* without orders still compiles and runs.
|
|
47
|
+
*/
|
|
48
|
+
const OP_PRICE_ORDER = 'Orders.PriceOrder';
|
|
49
|
+
/**
|
|
50
|
+
* True when orders is actually installed here.
|
|
51
|
+
*
|
|
52
|
+
* Checked against METADATA rather than by catching an error, for the same reason the product picker
|
|
53
|
+
* does: `RunView`/`GetEntityObject` against an unregistered entity does not fail cleanly — it logs and
|
|
54
|
+
* leaves the caller guessing. Sales is designed to run standalone, so orders being absent is a
|
|
55
|
+
* supported state, not a fault.
|
|
56
|
+
*/
|
|
57
|
+
export function OrdersIsInstalled() {
|
|
58
|
+
const md = new Metadata();
|
|
59
|
+
return md.Entities.some((e) => e.Name === E_ORDER_HEADER) && md.Entities.some((e) => e.Name === E_ORDER_LINE);
|
|
60
|
+
}
|
|
61
|
+
export class LiveOrdersSeam {
|
|
62
|
+
constructor(user, provider, contracts) {
|
|
63
|
+
this.user = user;
|
|
64
|
+
this.provider = provider;
|
|
65
|
+
this.contracts = contracts ?? new StubDownstreamSeam();
|
|
66
|
+
}
|
|
67
|
+
CreateContractFromDeal(input) {
|
|
68
|
+
return this.contracts.CreateContractFromDeal(input);
|
|
69
|
+
}
|
|
70
|
+
RenewContractTerm(input) {
|
|
71
|
+
return this.contracts.RenewContractTerm(input);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Anti-tree-shaking anchor, in the house style. */
|
|
75
|
+
export function LoadLiveOrdersSeam() {
|
|
76
|
+
/* keeps the import alive */
|
|
77
|
+
}
|
|
78
|
+
/** Kept for the seam test, which reads back what orders actually wrote. */
|
|
79
|
+
export { E_ORDER_HEADER, E_ORDER_LINE, RunView };
|
|
80
|
+
//# sourceMappingURL=LiveOrdersSeam.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveOrdersSeam.js","sourceRoot":"","sources":["../src/LiveOrdersSeam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAEH,QAAQ,EACR,OAAO,GAIV,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAMH,kBAAkB,GACrB,MAAM,6BAA6B,CAAC;AAErC,qFAAqF;AACrF,MAAM,cAAc,GAAG,kCAAkC,CAAC;AAC1D,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAiB3C;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC7B,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC1B,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;AAClH,CAAC;AAED,MAAM,OAAO,cAAc;IAUvB,YACqB,IAAc,EACd,QAA2B,EAC5C,SAAiF;QAFhE,SAAI,GAAJ,IAAI,CAAU;QACd,aAAQ,GAAR,QAAQ,CAAmB;QAG5C,IAAI,CAAC,SAAS,GAAG,SAAS,IAAI,IAAI,kBAAkB,EAAE,CAAC;IAC3D,CAAC;IAIM,sBAAsB,CAAC,KAAuC;QACjE,OAAO,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IAEM,iBAAiB,CAAC,KAAkC;QACvD,OAAO,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;CACJ;AAED,oDAAoD;AACpD,MAAM,UAAU,kBAAkB;IAC9B,4BAA4B;AAChC,CAAC;AAED,2EAA2E;AAC3E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `Sales.SaveDeal` — the server half of browser-side deal entry.
|
|
3
|
+
*
|
|
4
|
+
* The client mutates a `DealDraft`, serializes it with `ToSaveInput()`, and calls this. This file
|
|
5
|
+
* rehydrates that payload into a real `DealEntityServer` tree and saves it, which puts the write on the
|
|
6
|
+
* SAME path an Action or an agent would take — so every rule enforced in the entity subclass applies
|
|
7
|
+
* here too, for free.
|
|
8
|
+
*
|
|
9
|
+
* WHY REHYDRATION RATHER THAN A BULK WRITE. It would be faster to build the SQL directly. It would also
|
|
10
|
+
* be a second implementation of "what it means to save a deal", and the second one is the one that
|
|
11
|
+
* eventually disagrees with the first. Going through the entity objects means validation, the owner
|
|
12
|
+
* stamp, and later the close lock all happen without this file knowing they exist.
|
|
13
|
+
*
|
|
14
|
+
* PRICES NOTHING. Every figure it accepts is either an INPUT to the pricing engine
|
|
15
|
+
* (`RequestedDiscountPct`, `OverrideUnitPrice`) or a transcription of a signed document
|
|
16
|
+
* (`AnnualGrossFees`, `DiscountAmount`, `Total`). The four `Resolved*` columns are not writable through
|
|
17
|
+
* this operation at all — they come only from an `Orders.PreviewOrder` response, and accepting them
|
|
18
|
+
* here would be the hole through which locally computed money enters the app.
|
|
19
|
+
*
|
|
20
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
21
|
+
*/
|
|
22
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
23
|
+
import { SalesSaveDealOperation as SalesSaveDealOperationBase, type SalesSaveDealInput, type SalesSaveDealOutput } from '@mj-biz-apps/sales-entities';
|
|
24
|
+
export declare class SaveDealOperation extends SalesSaveDealOperationBase {
|
|
25
|
+
protected InternalExecute(input: SalesSaveDealInput, provider: IMetadataProvider, user: UserInfo): Promise<SalesSaveDealOutput>;
|
|
26
|
+
/** The pipeline owns the selling company. NOT NULL on Pipeline, so a successful read always has one. */
|
|
27
|
+
private resolvePipelineCompany;
|
|
28
|
+
/**
|
|
29
|
+
* Reconciles the submitted lines against what is stored.
|
|
30
|
+
*
|
|
31
|
+
* The array is the COMPLETE DESIRED SET: a stored line whose ID is absent from it is removed. That
|
|
32
|
+
* is what lets a workspace holding the whole tree just send its current state, with no delta
|
|
33
|
+
* bookkeeping in the browser — and `RemoveLine` queues the deletion so it happens inside the same
|
|
34
|
+
* transaction as the inserts.
|
|
35
|
+
*/
|
|
36
|
+
private applyLines;
|
|
37
|
+
/** Identical semantics to {@link applyLines}. */
|
|
38
|
+
private applySchedule;
|
|
39
|
+
/**
|
|
40
|
+
* Rewrites a collection in place to match `ordered`. In place because the getters expose the live
|
|
41
|
+
* arrays the entity's `Save()` walks; replacing them would leave `Save()` writing the old order.
|
|
42
|
+
*/
|
|
43
|
+
private reorder;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=SaveDealOperation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveDealOperation.d.ts","sourceRoot":"","sources":["../src/SaveDealOperation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAGH,iBAAiB,EAGjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACH,sBAAsB,IAAI,0BAA0B,EAIpD,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAG3B,MAAM,6BAA6B,CAAC;AA6ErC,qBACa,iBAAkB,SAAQ,0BAA0B;cAC7C,eAAe,CAC3B,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GACf,OAAO,CAAC,mBAAmB,CAAC;IAiG/B,wGAAwG;YAC1F,sBAAsB;IAuBpC;;;;;;;OAOG;YACW,UAAU;IAgCxB,iDAAiD;YACnC,aAAa;IAgC3B;;;OAGG;IACH,OAAO,CAAC,OAAO;CAIlB"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview `Sales.SaveDeal` — the server half of browser-side deal entry.
|
|
9
|
+
*
|
|
10
|
+
* The client mutates a `DealDraft`, serializes it with `ToSaveInput()`, and calls this. This file
|
|
11
|
+
* rehydrates that payload into a real `DealEntityServer` tree and saves it, which puts the write on the
|
|
12
|
+
* SAME path an Action or an agent would take — so every rule enforced in the entity subclass applies
|
|
13
|
+
* here too, for free.
|
|
14
|
+
*
|
|
15
|
+
* WHY REHYDRATION RATHER THAN A BULK WRITE. It would be faster to build the SQL directly. It would also
|
|
16
|
+
* be a second implementation of "what it means to save a deal", and the second one is the one that
|
|
17
|
+
* eventually disagrees with the first. Going through the entity objects means validation, the owner
|
|
18
|
+
* stamp, and later the close lock all happen without this file knowing they exist.
|
|
19
|
+
*
|
|
20
|
+
* PRICES NOTHING. Every figure it accepts is either an INPUT to the pricing engine
|
|
21
|
+
* (`RequestedDiscountPct`, `OverrideUnitPrice`) or a transcription of a signed document
|
|
22
|
+
* (`AnnualGrossFees`, `DiscountAmount`, `Total`). The four `Resolved*` columns are not writable through
|
|
23
|
+
* this operation at all — they come only from an `Orders.PreviewOrder` response, and accepting them
|
|
24
|
+
* here would be the hole through which locally computed money enters the app.
|
|
25
|
+
*
|
|
26
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
27
|
+
*/
|
|
28
|
+
import { BaseRemotableOperation, LogError, } from '@memberjunction/core';
|
|
29
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
30
|
+
import { SalesSaveDealOperation as SalesSaveDealOperationBase, } from '@mj-biz-apps/sales-entities';
|
|
31
|
+
const DEAL_ENTITY = 'MJ_BizApps_Sales: Deals';
|
|
32
|
+
const PIPELINE_ENTITY = 'MJ_BizApps_Sales: Pipelines';
|
|
33
|
+
/** Header fields copied straight through. Listed rather than spread so an unexpected key cannot ride in. */
|
|
34
|
+
const HEADER_FIELDS = [
|
|
35
|
+
'Name',
|
|
36
|
+
'PipelineID',
|
|
37
|
+
'PipelineStageID',
|
|
38
|
+
'DealTypeID',
|
|
39
|
+
'DealStatusTypeID',
|
|
40
|
+
'AccountID',
|
|
41
|
+
'PrimaryContactID',
|
|
42
|
+
'BillingContactID',
|
|
43
|
+
'Amount',
|
|
44
|
+
'CurrencyID',
|
|
45
|
+
'TermMonths',
|
|
46
|
+
'EstimatedProjectWeeks',
|
|
47
|
+
'ExecutionDate',
|
|
48
|
+
'StartDate',
|
|
49
|
+
'ExpectedCloseDate',
|
|
50
|
+
'Probability',
|
|
51
|
+
'ForecastCategoryTypeID',
|
|
52
|
+
'LeadSourceTypeID',
|
|
53
|
+
'AutoRenew',
|
|
54
|
+
'AnnualIncreasePctOverride',
|
|
55
|
+
'CancellationNoticeDaysOverride',
|
|
56
|
+
'PaymentMethod',
|
|
57
|
+
'ContractVariances',
|
|
58
|
+
'Description',
|
|
59
|
+
'NextStep',
|
|
60
|
+
'NextStepDate',
|
|
61
|
+
];
|
|
62
|
+
/** Line fields copied straight through. Note the absence of every `Resolved*` column — see the header. */
|
|
63
|
+
const LINE_FIELDS = [
|
|
64
|
+
'ProductID',
|
|
65
|
+
'ProductName',
|
|
66
|
+
'DealLineTypeID',
|
|
67
|
+
'Quantity',
|
|
68
|
+
'RequestedDiscountPct',
|
|
69
|
+
'OverrideUnitPrice',
|
|
70
|
+
'AnnualGrossFees',
|
|
71
|
+
'DiscountAmount',
|
|
72
|
+
'Total',
|
|
73
|
+
'TermMonths',
|
|
74
|
+
'ServicePeriodStart',
|
|
75
|
+
'ServicePeriodEnd',
|
|
76
|
+
'Description',
|
|
77
|
+
];
|
|
78
|
+
const SCHEDULE_FIELDS = ['PaymentDate', 'Amount', 'Description'];
|
|
79
|
+
function issue(Section, Message, Field = null) {
|
|
80
|
+
return { Section, Field, ClientKey: null, Severity: 'error', Message };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Assigns a subset of fields from a plain payload onto an entity.
|
|
84
|
+
*
|
|
85
|
+
* `undefined` is SKIPPED and `null` is APPLIED, and the difference matters: omitting a key means "leave
|
|
86
|
+
* this alone" while sending null means "clear it". Collapsing the two would make a partial update wipe
|
|
87
|
+
* every field the caller did not mention.
|
|
88
|
+
*/
|
|
89
|
+
function assignFields(target, source, fields) {
|
|
90
|
+
const bag = source;
|
|
91
|
+
const entity = target;
|
|
92
|
+
for (const field of fields) {
|
|
93
|
+
if (bag[field] !== undefined) {
|
|
94
|
+
entity[field] = bag[field];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let SaveDealOperation = class SaveDealOperation extends SalesSaveDealOperationBase {
|
|
99
|
+
async InternalExecute(input, provider, user) {
|
|
100
|
+
const empty = {
|
|
101
|
+
Success: false,
|
|
102
|
+
Created: false,
|
|
103
|
+
Issues: [],
|
|
104
|
+
Lines: [],
|
|
105
|
+
PaymentSchedule: [],
|
|
106
|
+
};
|
|
107
|
+
if (!input?.Name?.trim()) {
|
|
108
|
+
return { ...empty, Issues: [issue('party', 'A deal needs a name.', 'Name')] };
|
|
109
|
+
}
|
|
110
|
+
if (!input.PipelineID) {
|
|
111
|
+
return { ...empty, Issues: [issue('party', 'A deal needs a pipeline.', 'PipelineID')] };
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const deal = await provider.GetEntityObject(DEAL_ENTITY, user);
|
|
115
|
+
const created = !input.ID;
|
|
116
|
+
if (created) {
|
|
117
|
+
deal.NewRecord();
|
|
118
|
+
}
|
|
119
|
+
else if (!(await deal.Load(input.ID))) {
|
|
120
|
+
return { ...empty, Issues: [issue('party', `Deal ${input.ID} was not found.`, 'ID')] };
|
|
121
|
+
}
|
|
122
|
+
assignFields(deal, input, HEADER_FIELDS);
|
|
123
|
+
// CompanyID must equal the pipeline's company — a CHECK cannot reach across the FK to
|
|
124
|
+
// compare them, so it is resolved here rather than trusted from the client. An explicitly
|
|
125
|
+
// supplied value is ignored in favour of the pipeline's, because the pipeline is the only
|
|
126
|
+
// source that can be right.
|
|
127
|
+
const companyID = await this.resolvePipelineCompany(input.PipelineID, provider, user);
|
|
128
|
+
if (!companyID) {
|
|
129
|
+
return {
|
|
130
|
+
...empty,
|
|
131
|
+
Issues: [issue('party', 'That pipeline could not be read, so the selling company is unknown.', 'PipelineID')],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
deal.CompanyID = companyID;
|
|
135
|
+
// Children are attached BEFORE Save(), because Save() is the transaction: the header and
|
|
136
|
+
// every child land together or not at all.
|
|
137
|
+
if (input.Lines !== undefined) {
|
|
138
|
+
await this.applyLines(deal, input.Lines, provider, user);
|
|
139
|
+
}
|
|
140
|
+
if (input.PaymentSchedule !== undefined) {
|
|
141
|
+
await this.applySchedule(deal, input.PaymentSchedule, provider, user);
|
|
142
|
+
}
|
|
143
|
+
if (!(await deal.Save())) {
|
|
144
|
+
return {
|
|
145
|
+
...empty,
|
|
146
|
+
Issues: [
|
|
147
|
+
issue('party', `The deal could not be saved: ${deal.LatestResult?.CompleteMessage ?? 'unknown error'}`),
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// The owner is expressed as INTENT and materialized as a DealTeamMember row, from which the
|
|
152
|
+
// denormalized Deal.OwnerEmployeeID stamp is derived. It runs after the save because the
|
|
153
|
+
// team row needs a persisted DealID.
|
|
154
|
+
if (input.OwnerEmployeeID) {
|
|
155
|
+
await deal.SetOwner(input.OwnerEmployeeID, user);
|
|
156
|
+
if (!(await deal.Save())) {
|
|
157
|
+
return {
|
|
158
|
+
...empty,
|
|
159
|
+
DealID: deal.ID,
|
|
160
|
+
Created: created,
|
|
161
|
+
Issues: [issue('party', 'The deal saved, but the owner stamp could not be written.', 'OwnerEmployeeID')],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
Success: true,
|
|
167
|
+
DealID: deal.ID,
|
|
168
|
+
DealNumber: deal.DealNumber ?? null,
|
|
169
|
+
Created: created,
|
|
170
|
+
Issues: [],
|
|
171
|
+
Lines: deal.Lines.map((line, i) => ({
|
|
172
|
+
ClientKey: input.Lines?.[i]?.ClientKey ?? null,
|
|
173
|
+
ID: line.ID,
|
|
174
|
+
DisplayOrder: line.DisplayOrder,
|
|
175
|
+
})),
|
|
176
|
+
PaymentSchedule: deal.PaymentSchedule.map((row, i) => ({
|
|
177
|
+
ClientKey: input.PaymentSchedule?.[i]?.ClientKey ?? null,
|
|
178
|
+
ID: row.ID,
|
|
179
|
+
DisplayOrder: row.DisplayOrder,
|
|
180
|
+
})),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
LogError(`Sales.SaveDeal failed: ${err}`);
|
|
185
|
+
return { ...empty, Issues: [issue('party', `The deal could not be saved: ${err}`)] };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** The pipeline owns the selling company. NOT NULL on Pipeline, so a successful read always has one. */
|
|
189
|
+
async resolvePipelineCompany(pipelineID, provider, user) {
|
|
190
|
+
const view = provider;
|
|
191
|
+
const result = await view.RunView({
|
|
192
|
+
EntityName: PIPELINE_ENTITY,
|
|
193
|
+
ExtraFilter: `ID = '${pipelineID}'`,
|
|
194
|
+
ResultType: 'simple',
|
|
195
|
+
Fields: ['CompanyID'],
|
|
196
|
+
}, user);
|
|
197
|
+
if (!result.Success) {
|
|
198
|
+
LogError(`Sales.SaveDeal: pipeline lookup failed: ${result.ErrorMessage}`);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const row = (result.Results ?? [])[0];
|
|
202
|
+
return row?.CompanyID ?? null;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Reconciles the submitted lines against what is stored.
|
|
206
|
+
*
|
|
207
|
+
* The array is the COMPLETE DESIRED SET: a stored line whose ID is absent from it is removed. That
|
|
208
|
+
* is what lets a workspace holding the whole tree just send its current state, with no delta
|
|
209
|
+
* bookkeeping in the browser — and `RemoveLine` queues the deletion so it happens inside the same
|
|
210
|
+
* transaction as the inserts.
|
|
211
|
+
*/
|
|
212
|
+
async applyLines(deal, lines, provider, user) {
|
|
213
|
+
await deal.LoadLines(user);
|
|
214
|
+
const keep = new Set(lines.map((l) => l.ID).filter((id) => !!id));
|
|
215
|
+
for (let i = deal.Lines.length - 1; i >= 0; i--) {
|
|
216
|
+
if (!keep.has(deal.Lines[i].ID)) {
|
|
217
|
+
deal.RemoveLine(i);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const byID = new Map(deal.Lines.map((l) => [l.ID, l]));
|
|
221
|
+
const ordered = [];
|
|
222
|
+
for (const incoming of lines) {
|
|
223
|
+
let entity = incoming.ID ? byID.get(incoming.ID) : undefined;
|
|
224
|
+
if (!entity) {
|
|
225
|
+
entity = await deal.CreateLine(user);
|
|
226
|
+
}
|
|
227
|
+
assignFields(entity, incoming, LINE_FIELDS);
|
|
228
|
+
ordered.push(entity);
|
|
229
|
+
}
|
|
230
|
+
// Rebuild the collection in the caller's order — the array's order is what DisplayOrder is
|
|
231
|
+
// derived from on save, so submitted order is the authority on how lines read.
|
|
232
|
+
this.reorder(deal.Lines, ordered);
|
|
233
|
+
}
|
|
234
|
+
/** Identical semantics to {@link applyLines}. */
|
|
235
|
+
async applySchedule(deal, rows, provider, user) {
|
|
236
|
+
await deal.LoadPaymentSchedule(user);
|
|
237
|
+
const keep = new Set(rows.map((r) => r.ID).filter((id) => !!id));
|
|
238
|
+
for (let i = deal.PaymentSchedule.length - 1; i >= 0; i--) {
|
|
239
|
+
if (!keep.has(deal.PaymentSchedule[i].ID)) {
|
|
240
|
+
deal.RemoveScheduleRow(i);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const byID = new Map(deal.PaymentSchedule.map((r) => [r.ID, r]));
|
|
244
|
+
const ordered = [];
|
|
245
|
+
for (const incoming of rows) {
|
|
246
|
+
let entity = incoming.ID ? byID.get(incoming.ID) : undefined;
|
|
247
|
+
if (!entity) {
|
|
248
|
+
entity = await deal.CreateScheduleRow(user);
|
|
249
|
+
}
|
|
250
|
+
assignFields(entity, incoming, SCHEDULE_FIELDS);
|
|
251
|
+
ordered.push(entity);
|
|
252
|
+
}
|
|
253
|
+
this.reorder(deal.PaymentSchedule, ordered);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Rewrites a collection in place to match `ordered`. In place because the getters expose the live
|
|
257
|
+
* arrays the entity's `Save()` walks; replacing them would leave `Save()` writing the old order.
|
|
258
|
+
*/
|
|
259
|
+
reorder(live, ordered) {
|
|
260
|
+
live.length = 0;
|
|
261
|
+
live.push(...ordered);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
SaveDealOperation = __decorate([
|
|
265
|
+
RegisterClass(BaseRemotableOperation, 'Sales.SaveDeal')
|
|
266
|
+
], SaveDealOperation);
|
|
267
|
+
export { SaveDealOperation };
|
|
268
|
+
//# sourceMappingURL=SaveDealOperation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaveDealOperation.js","sourceRoot":"","sources":["../src/SaveDealOperation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `SequenceService` — calls the DB-level atomic numbering sproc from TypeScript, so
|
|
3
|
+
* `DealEntityServer` can assign `DealNumber` before `super.Save()` commits the row.
|
|
4
|
+
*
|
|
5
|
+
* WHY THE COUNTER IS IN SQL AND THIS IS THE ONLY SQL-SHAPED THING HERE. `spAssignNextDealNumber` needs
|
|
6
|
+
* `HOLDLOCK, UPDLOCK` read-modify-write semantics that have no application-level equivalent — two deals
|
|
7
|
+
* created in the same instant must not take the same number, and no amount of careful TypeScript can
|
|
8
|
+
* promise that. Everything else about numbering (when to assign, what to do when it fails) is here.
|
|
9
|
+
*
|
|
10
|
+
* PROVIDER: INJECTED AND REQUIRED, with no global fallback. The caller is the entity server, which
|
|
11
|
+
* passes its own `ProviderToUse`, so the sproc call rides the SAME connection context — and therefore
|
|
12
|
+
* the same transaction — as the save it is numbering. A global provider would take the number on a
|
|
13
|
+
* different connection, and a rolled-back save would then leave the number consumed. Copied deliberately
|
|
14
|
+
* from accounting's `SequenceService`, including that constraint.
|
|
15
|
+
*
|
|
16
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
17
|
+
*/
|
|
18
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
19
|
+
/**
|
|
20
|
+
* This app's schema, in ONE place in this package.
|
|
21
|
+
*
|
|
22
|
+
* Exported because `DealEntityServer` needs it too, for the targeted `UPDATE` that maintains the
|
|
23
|
+
* `Deal.Amount` cache. A second copy of the literal is a second thing to change when the schema is
|
|
24
|
+
* renamed for the PostgreSQL conversion, and the one that gets missed is the one nobody tested.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SALES_SCHEMA = "__mj_BizAppsSales";
|
|
27
|
+
/**
|
|
28
|
+
* Atomically increments the singleton deal counter and returns the formatted `DEAL-{seq:000000}`.
|
|
29
|
+
*
|
|
30
|
+
* Singleton rather than per-company, matching contracts' `ContractSequence` and orders'
|
|
31
|
+
* `OrderSequence` — see the migration comment on `DealSequence` for why accounting's per-company,
|
|
32
|
+
* per-fiscal-year scope is the exception and not the pattern.
|
|
33
|
+
*
|
|
34
|
+
* @throws when the sproc returns nothing, rather than letting a null number reach the row. A deal with
|
|
35
|
+
* no number is recoverable; a deal that silently stored `null` where a number was expected is
|
|
36
|
+
* the kind of thing found weeks later by a report that skips it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getNextDealNumber(contextUser: UserInfo, provider: IMetadataProvider): Promise<string>;
|
|
39
|
+
//# sourceMappingURL=SequenceService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SequenceService.d.ts","sourceRoot":"","sources":["../src/SequenceService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGnE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB3G"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This app's schema, in ONE place in this package.
|
|
3
|
+
*
|
|
4
|
+
* Exported because `DealEntityServer` needs it too, for the targeted `UPDATE` that maintains the
|
|
5
|
+
* `Deal.Amount` cache. A second copy of the literal is a second thing to change when the schema is
|
|
6
|
+
* renamed for the PostgreSQL conversion, and the one that gets missed is the one nobody tested.
|
|
7
|
+
*/
|
|
8
|
+
export const SALES_SCHEMA = '__mj_BizAppsSales';
|
|
9
|
+
/**
|
|
10
|
+
* Atomically increments the singleton deal counter and returns the formatted `DEAL-{seq:000000}`.
|
|
11
|
+
*
|
|
12
|
+
* Singleton rather than per-company, matching contracts' `ContractSequence` and orders'
|
|
13
|
+
* `OrderSequence` — see the migration comment on `DealSequence` for why accounting's per-company,
|
|
14
|
+
* per-fiscal-year scope is the exception and not the pattern.
|
|
15
|
+
*
|
|
16
|
+
* @throws when the sproc returns nothing, rather than letting a null number reach the row. A deal with
|
|
17
|
+
* no number is recoverable; a deal that silently stored `null` where a number was expected is
|
|
18
|
+
* the kind of thing found weeks later by a report that skips it.
|
|
19
|
+
*/
|
|
20
|
+
export async function getNextDealNumber(contextUser, provider) {
|
|
21
|
+
const sqlProvider = getSqlServerProvider(provider);
|
|
22
|
+
const sql = `
|
|
23
|
+
DECLARE @dealNumber NVARCHAR(50);
|
|
24
|
+
EXEC ${SALES_SCHEMA}.spAssignNextDealNumber
|
|
25
|
+
@DealNumber = @dealNumber OUTPUT;
|
|
26
|
+
SELECT @dealNumber AS DealNumber;
|
|
27
|
+
`;
|
|
28
|
+
// ExecuteSQL binds an OBJECT of parameters BY NAME. An array is treated as positional (p0), which
|
|
29
|
+
// would not match a named @-param — accounting's service records learning this the hard way, and the
|
|
30
|
+
// note is kept here because the sproc taking no inputs makes it easy to reintroduce later.
|
|
31
|
+
const rows = await sqlProvider.ExecuteSQL(sql, {}, { isMutation: true, description: 'spAssignNextDealNumber' }, contextUser);
|
|
32
|
+
const value = rows?.[0]?.DealNumber;
|
|
33
|
+
if (!value || typeof value !== 'string') {
|
|
34
|
+
throw new Error('SequenceService.getNextDealNumber: spAssignNextDealNumber returned no value');
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function getSqlServerProvider(provider) {
|
|
39
|
+
if (!provider) {
|
|
40
|
+
throw new Error('SequenceService: an IMetadataProvider must be injected — there is no global fallback');
|
|
41
|
+
}
|
|
42
|
+
return provider;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=SequenceService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SequenceService.js","sourceRoot":"","sources":["../src/SequenceService.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,WAAqB,EAAE,QAA2B;IACtF,MAAM,WAAW,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG;;eAED,YAAY;;;KAGtB,CAAC;IACF,kGAAkG;IAClG,qGAAqG;IACrG,2FAA2F;IAC3F,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,UAAU,CACrC,GAAG,EACH,EAAE,EACF,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,wBAAwB,EAAE,EAC3D,WAAW,CACd,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;IACpC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,oBAAoB,CAAC,QAA2B;IACrD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,sFAAsF,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,QAAiC,CAAC;AAC7C,CAAC"}
|