@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.
Files changed (106) hide show
  1. package/LICENSE +183 -0
  2. package/dist/CloseDealOperation.d.ts +176 -0
  3. package/dist/CloseDealOperation.d.ts.map +1 -0
  4. package/dist/CloseDealOperation.js +1107 -0
  5. package/dist/CloseDealOperation.js.map +1 -0
  6. package/dist/CloseWonTaskService.d.ts +249 -0
  7. package/dist/CloseWonTaskService.d.ts.map +1 -0
  8. package/dist/CloseWonTaskService.js +460 -0
  9. package/dist/CloseWonTaskService.js.map +1 -0
  10. package/dist/DealEntityServer.d.ts +625 -0
  11. package/dist/DealEntityServer.d.ts.map +1 -0
  12. package/dist/DealEntityServer.js +1529 -0
  13. package/dist/DealEntityServer.js.map +1 -0
  14. package/dist/LiveContractsSeam.d.ts +212 -0
  15. package/dist/LiveContractsSeam.d.ts.map +1 -0
  16. package/dist/LiveContractsSeam.js +362 -0
  17. package/dist/LiveContractsSeam.js.map +1 -0
  18. package/dist/LiveOrdersSeam.d.ts +68 -0
  19. package/dist/LiveOrdersSeam.d.ts.map +1 -0
  20. package/dist/LiveOrdersSeam.js +80 -0
  21. package/dist/LiveOrdersSeam.js.map +1 -0
  22. package/dist/SaveDealOperation.d.ts +45 -0
  23. package/dist/SaveDealOperation.d.ts.map +1 -0
  24. package/dist/SaveDealOperation.js +268 -0
  25. package/dist/SaveDealOperation.js.map +1 -0
  26. package/dist/SequenceService.d.ts +39 -0
  27. package/dist/SequenceService.d.ts.map +1 -0
  28. package/dist/SequenceService.js +44 -0
  29. package/dist/SequenceService.js.map +1 -0
  30. package/dist/activities/ActivityIngestService.d.ts +111 -0
  31. package/dist/activities/ActivityIngestService.d.ts.map +1 -0
  32. package/dist/activities/ActivityIngestService.js +485 -0
  33. package/dist/activities/ActivityIngestService.js.map +1 -0
  34. package/dist/activities/ActivityReader.d.ts +68 -0
  35. package/dist/activities/ActivityReader.d.ts.map +1 -0
  36. package/dist/activities/ActivityReader.js +147 -0
  37. package/dist/activities/ActivityReader.js.map +1 -0
  38. package/dist/activities/ActivitySource.d.ts +142 -0
  39. package/dist/activities/ActivitySource.d.ts.map +1 -0
  40. package/dist/activities/ActivitySource.js +2 -0
  41. package/dist/activities/ActivitySource.js.map +1 -0
  42. package/dist/activities/ActivitySyncJob.d.ts +72 -0
  43. package/dist/activities/ActivitySyncJob.d.ts.map +1 -0
  44. package/dist/activities/ActivitySyncJob.js +146 -0
  45. package/dist/activities/ActivitySyncJob.js.map +1 -0
  46. package/dist/activities/ActivityWriterService.d.ts +119 -0
  47. package/dist/activities/ActivityWriterService.d.ts.map +1 -0
  48. package/dist/activities/ActivityWriterService.js +270 -0
  49. package/dist/activities/ActivityWriterService.js.map +1 -0
  50. package/dist/activities/DealMatcher.d.ts +58 -0
  51. package/dist/activities/DealMatcher.d.ts.map +1 -0
  52. package/dist/activities/DealMatcher.js +109 -0
  53. package/dist/activities/DealMatcher.js.map +1 -0
  54. package/dist/activities/FixtureActivitySource.d.ts +37 -0
  55. package/dist/activities/FixtureActivitySource.d.ts.map +1 -0
  56. package/dist/activities/FixtureActivitySource.js +58 -0
  57. package/dist/activities/FixtureActivitySource.js.map +1 -0
  58. package/dist/activities/GraphMessageMapper.d.ts +47 -0
  59. package/dist/activities/GraphMessageMapper.d.ts.map +1 -0
  60. package/dist/activities/GraphMessageMapper.js +141 -0
  61. package/dist/activities/GraphMessageMapper.js.map +1 -0
  62. package/dist/activities/ImportedGraphActivitySource.d.ts +16 -0
  63. package/dist/activities/ImportedGraphActivitySource.d.ts.map +1 -0
  64. package/dist/activities/ImportedGraphActivitySource.js +84 -0
  65. package/dist/activities/ImportedGraphActivitySource.js.map +1 -0
  66. package/dist/activities/MSGraphActivitySource.d.ts +67 -0
  67. package/dist/activities/MSGraphActivitySource.d.ts.map +1 -0
  68. package/dist/activities/MSGraphActivitySource.js +186 -0
  69. package/dist/activities/MSGraphActivitySource.js.map +1 -0
  70. package/dist/activities/MSGraphCalendarSource.d.ts +72 -0
  71. package/dist/activities/MSGraphCalendarSource.d.ts.map +1 -0
  72. package/dist/activities/MSGraphCalendarSource.js +225 -0
  73. package/dist/activities/MSGraphCalendarSource.js.map +1 -0
  74. package/dist/activities/RelevanceFilter.d.ts +101 -0
  75. package/dist/activities/RelevanceFilter.d.ts.map +1 -0
  76. package/dist/activities/RelevanceFilter.js +148 -0
  77. package/dist/activities/RelevanceFilter.js.map +1 -0
  78. package/dist/activities/activity-vocabulary.d.ts +103 -0
  79. package/dist/activities/activity-vocabulary.d.ts.map +1 -0
  80. package/dist/activities/activity-vocabulary.js +79 -0
  81. package/dist/activities/activity-vocabulary.js.map +1 -0
  82. package/dist/forecast/FixtureForecastSource.d.ts +22 -0
  83. package/dist/forecast/FixtureForecastSource.d.ts.map +1 -0
  84. package/dist/forecast/FixtureForecastSource.js +16 -0
  85. package/dist/forecast/FixtureForecastSource.js.map +1 -0
  86. package/dist/forecast/ForecastSnapshotJob.d.ts +78 -0
  87. package/dist/forecast/ForecastSnapshotJob.d.ts.map +1 -0
  88. package/dist/forecast/ForecastSnapshotJob.js +225 -0
  89. package/dist/forecast/ForecastSnapshotJob.js.map +1 -0
  90. package/dist/forecast/ForecastSource.d.ts +74 -0
  91. package/dist/forecast/ForecastSource.d.ts.map +1 -0
  92. package/dist/forecast/ForecastSource.js +25 -0
  93. package/dist/forecast/ForecastSource.js.map +1 -0
  94. package/dist/forecast/QueryForecastSource.d.ts +76 -0
  95. package/dist/forecast/QueryForecastSource.d.ts.map +1 -0
  96. package/dist/forecast/QueryForecastSource.js +164 -0
  97. package/dist/forecast/QueryForecastSource.js.map +1 -0
  98. package/dist/index.d.ts +117 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +125 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/orders-availability.d.ts +15 -0
  103. package/dist/orders-availability.d.ts.map +1 -0
  104. package/dist/orders-availability.js +47 -0
  105. package/dist/orders-availability.js.map +1 -0
  106. 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"}