@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,625 @@
1
+ /**
2
+ * @fileoverview `DealEntityServer` — the rules that need a database, on the one path every write takes.
3
+ *
4
+ * WHAT CHANGED, AND WHY THIS FILE GOT MUCH SMALLER. A deal is not one row, and until MJ v6 this class
5
+ * had to compose the tree itself: transient `Lines` / `PaymentSchedule` arrays, a deletion queue, a
6
+ * re-sequencer, and a `Save()` override that walked header → deletions → children inside an explicit
7
+ * `BeginTransaction` / `CommitTransaction`. All of that is now **Related Record Collections** —
8
+ * declared in metadata, generated onto `mjBizAppsSalesDealEntity`, and executed by MJ's
9
+ * `EntitySavePlan` inside one `EntityTransactionScope`. The framework does header-before-children and
10
+ * deletions-before-inserts because those orderings are inherent to the problem, not to this app.
11
+ *
12
+ * Deleting a hand-rolled implementation in favour of the framework's is only worth it when the
13
+ * framework's is at least as careful. It is: `RelatedRecordCollection.Load()` throws on a failed read
14
+ * AND refuses to load over unsaved children, which is the same "a failed load and an empty collection
15
+ * must never look alike" contract the hand-written `LoadLines` documented — plus a guard against a
16
+ * stray reload discarding work mid-edit, which the hand-written one did not have.
17
+ *
18
+ * WHAT REMAINS HERE IS EXACTLY WHAT NEEDS A DATABASE:
19
+ * - `DealNumber` — a gap-free counter taken from a locked row, inside the save's transaction.
20
+ * - The OWNER STAMP — `Deal.OwnerEmployeeID` derived from the `DealTeamMember` carrying the owner
21
+ * role, resolved from `DealRole.IsOwnerRole` rather than from any role name.
22
+ *
23
+ * WHAT THIS FILE MUST NEVER DO. Arithmetic on money. No summing lines into `Amount`, no applying
24
+ * `RequestedDiscountPct`, no checking that the payment schedule adds up. `Amount` is a cached answer
25
+ * from `Orders.PreviewOrder` carrying its own provenance; the signed figures on a line are
26
+ * transcriptions.
27
+ *
28
+ * The form-shaped rules are NOT here — they are on {@link DealEntity} in `sales-entities`, which this
29
+ * class extends, so they run in the browser as well as on the server. Extending `DealEntity` rather
30
+ * than the generated entity is load-bearing: extend the generated one and every rule in that file
31
+ * silently stops applying on the server.
32
+ *
33
+ * - The CLOSE LOCK (S4). Once the deal's status carries `DealStatusType.LocksDeal = 1`, the header
34
+ * (except Description / NextStep), its lines, its instalments and its team are immutable, and
35
+ * reopening goes through `Sales.ReopenDeal`, which records a reason.
36
+ *
37
+ * ── STILL TO COME (deliberately not stubbed) ────────────────────────────────────────────────────
38
+ * - `DealStageEvent` append on ORDINARY stage transition, stamping `AmountAtTransition` and
39
+ * `ProbabilityAtTransition`. `Sales.CloseDeal` already stamps the CLOSE transition; consolidating
40
+ * the two into a single writer here is D-BD2. It must be inside the graph's transaction, which
41
+ * since MJ `47ff71d68b` means a scope opened around `super.Save()` rather than a graph-node
42
+ * branch — see the note on {@link DealEntityServer.Save}.
43
+ *
44
+ * @module @mj-biz-apps/sales-core-entities-server
45
+ */
46
+ import { EntitySaveOptions } from '@memberjunction/core';
47
+ import { DealEntity } from '@mj-biz-apps/sales-entities';
48
+ /**
49
+ * The persisted state a deal held on the way OUT of a stage — what `DealStageEvent` records.
50
+ *
51
+ * A snapshot rather than a read at write time, because by then `super.Save()` has overwritten the
52
+ * values. See {@link DealEntityServer.planStageEvent}.
53
+ */
54
+ /**
55
+ * The kinds of governed transition a remote operation can declare. Both are transactional acts that own
56
+ * their own provenance — `Sales.CloseDeal` and `Sales.ReopenDeal`. An ordinary stage move declares
57
+ * nothing; it is detected, not announced.
58
+ */
59
+ export type DealTransitionKind = 'Close' | 'Reopen';
60
+ export declare class DealEntityServer extends DealEntity {
61
+ /**
62
+ * Async validation always runs on this path. The base class defaults to skipping it; a deal that
63
+ * carries a whole graph of children is exactly the case where you want it, and silently skipping
64
+ * validation on the one code path every write takes would defeat the point of putting rules here.
65
+ */
66
+ get DefaultSkipAsyncValidation(): boolean;
67
+ /**
68
+ * Prepares the deal, then hands the whole graph to the framework.
69
+ *
70
+ * ── THIS METHOD RUNS EXACTLY ONCE PER SAVE, COMPOSITE OR NOT. ──
71
+ *
72
+ * That is a guarantee of MJ's, not an accident of ours, and it is worth knowing why because it used
73
+ * to be false. When a deal has children, `BaseEntity.Save()` builds a save plan and routes to
74
+ * `saveGraph`, which opens the transaction and executes every node — **the root included** — through
75
+ * the private `BaseEntity.saveAsGraphNode`, which calls `_InnerSave` directly:
76
+ *
77
+ * SaveSelfOnly: (entity, opts) => entity.saveAsGraphNode(opts)
78
+ *
79
+ * `_InnerSave` is BELOW this override, so the graph never re-enters `Save()`. Preparation therefore
80
+ * happens once by construction, on the only call there is, and a childless deal (which never builds
81
+ * a plan at all) takes the same single path.
82
+ *
83
+ * ── DO NOT RE-ADD A GRAPH-NODE GUARD ──
84
+ *
85
+ * Until MJ `47ff71d68b` the graph executed the root by calling this record's public `Save()` again
86
+ * with `EntitySaveOptions.IsGraphNodeSave` set, so this method DID run twice and needed an early
87
+ * return to stay idempotent. MJ deleted that flag deliberately — *"so application code cannot skip
88
+ * companions by passing a public flag"* — and made the node path private. There is no successor
89
+ * property and none is wanted: the re-entrancy the guard defended against no longer happens.
90
+ *
91
+ * `EntitySaveOptions.SkipRelatedCollections` is NOT that successor. It suppresses the caller's own
92
+ * collections on the way in (orders' `OrderEntityServer` uses it to hold its lines back until the
93
+ * header has a key); it says nothing about who is calling. Reaching for it here would skip the
94
+ * deal's lines and team entirely.
95
+ *
96
+ * ── WHERE THE S4 STAGE-EVENT APPEND HAS TO GO (this changed too) ──
97
+ *
98
+ * `DealStageEvent` is append-only provenance, so a rolled-back save must not leave an event behind
99
+ * claiming a transition that did not happen — the append has to be inside the same transaction as
100
+ * the write. The old answer was "put it in the `IsGraphNodeSave` branch", because that branch ran
101
+ * inside the graph's transaction. **That branch is gone, and code placed here now runs BEFORE the
102
+ * graph opens anything.**
103
+ *
104
+ * The mechanism that still works is the one {@link DealEntityServer.saveWithinScope} uses: open a
105
+ * scope with `BeginEntityTransaction()`, do the work, then call `super.Save(options)` inside it. The
106
+ * graph's own scope JOINS an ambient transaction rather than opening a second one, so the append and
107
+ * every row in the graph commit or roll back together.
108
+ *
109
+ * **That append now exists** (D-BD2) and shares its scope with two other jobs. `saveWithinScope` is
110
+ * where the ordering between them is decided and is the only place this class opens a transaction.
111
+ */
112
+ Save(options?: EntitySaveOptions): Promise<boolean>;
113
+ private saveDeclared;
114
+ /**
115
+ * The snapshot an ordinary stage move owes the append-only log, or null when the deal did not move.
116
+ *
117
+ * ── THE PERSISTED VALUES, read before the save overwrites them ──
118
+ *
119
+ * `OldValue` is what is on disk, which is the only thing that makes a transition detectable — the
120
+ * same discipline the close lock uses when it asks whether the deal was ALREADY locked rather than
121
+ * whether it is about to be.
122
+ *
123
+ * ── THE STAMPS COME FROM THE PRIOR VALUES, NOT THE NEW ONES ──
124
+ *
125
+ * That is the whole point of the table. A board drag applies the target stage's probability default,
126
+ * so reading the current value here would record the number the deal acquired by ARRIVING rather
127
+ * than the one it held on the way out — and every velocity report built on it would be quietly
128
+ * wrong.
129
+ *
130
+ * A brand-new deal has no prior stage and owes no event: `priorStageID` is null, so it is not a
131
+ * MOVE. It is a birth, and `DealStageEvent` is a log of movement.
132
+ */
133
+ private planStageEvent;
134
+ /**
135
+ * The ONE place a save opens a transaction, and the one place the ordering is decided.
136
+ *
137
+ * Four jobs share this scope, and each has a reason to be where it is:
138
+ *
139
+ * 1. **The deal number**, BEFORE the header is written — `DealNumber` carries a filtered UNIQUE
140
+ * index, and assigning it afterwards would mean a second write to a row already visible. The
141
+ * transaction is what keeps the counter gap-free: `spAssignNextDealNumber` increments a locked
142
+ * row, so a save that then fails must roll the increment back.
143
+ * 2. **The order status**, BEFORE the header is written, because an unsaved order is INSERTED in the
144
+ * target status rather than created and then moved — one row, one state, no history of a status
145
+ * it was never really in.
146
+ * 3. **The stage event**, AFTER, because it is provenance for a move that has actually happened.
147
+ * An event for a save that then failed is worse than no event: it is a claim about history.
148
+ * 4. **The `Deal.Amount` cache**, LAST, because `OrderHeader.TotalGross` is trigger-maintained and
149
+ * only reflects this save's lines once they are written. Its trigger is the ORDER changing rather
150
+ * than the stage, which is why the caller decides separately whether it runs.
151
+ *
152
+ * **BOTH-OR-NEITHER, ACROSS ALL THREE.** A moved deal with no event is a hole in an append-only
153
+ * log. A numbered deal that does not exist consumes a number nobody can account for. And a stage
154
+ * change visible while the order it moved is not would be a worse outcome than either alone.
155
+ *
156
+ * `BeginEntityTransaction` JOINS an ambient transaction rather than opening a second, so a caller
157
+ * already in one — `Sales.CloseDeal`, or an integration check — gets a savepoint and everything
158
+ * still settles together.
159
+ *
160
+ * **A save with nothing to do here takes no transaction at all** (see the caller). The overwhelming
161
+ * majority of deal saves are edits to a field nobody is watching, and wrapping those in a scope
162
+ * would be a cost paid on every keystroke for a guarantee they do not need.
163
+ */
164
+ private saveWithinScope;
165
+ /**
166
+ * Writes the one `DealStageEvent` a move owes.
167
+ *
168
+ * APPEND-ONLY — never edited, never deleted. Note what is NOT here: no close, no routing, no
169
+ * downstream call. A stage change is a stage change; closing is `Sales.CloseDeal` and stays an
170
+ * explicit act, even when the stage a deal moves into is the one a pipeline calls Signed.
171
+ */
172
+ private appendStageEvent;
173
+ /**
174
+ * Gives a brand-new deal its embedded order, in Draft, on the deal's FIRST save.
175
+ *
176
+ * ── WHY THE ENTITY SERVER AND NOT THE WORKSPACE ──
177
+ *
178
+ * The workspace called `OrderID_EnsureObject()` from `AddLine()`, so a deal only got an order when
179
+ * somebody added a line THROUGH THE UI. An agent, an importer, a remote operation or a plain
180
+ * `BaseEntity.Save()` each produced a deal with no order at all -- and S-US4 puts the order at
181
+ * creation, not at first line. Same reasoning as the close lock below: a rule the UI enforces holds
182
+ * only until something that is not the UI writes.
183
+ *
184
+ * ── WHY FIRST SAVE AND NOT NewRecord() ──
185
+ *
186
+ * `OrderHeader.CompanyID` is NOT NULL, and a deal does not know its company until
187
+ * `stampCompanyFromPipeline()` has run: the caller supplies a PipelineID and the selling company is
188
+ * derived from it (D-4). Provisioning in `NewRecord()` would mean inventing a company or building an
189
+ * order that cannot be inserted. So it happens here, after the stamps, on the one call where
190
+ * `IsSaved` is still false.
191
+ *
192
+ * ── WHY THE GUARD IS `IsSaved`, AND NOT `IsSaved || OrderID` ──
193
+ *
194
+ * It was the second form, and that was a real bug rather than a stylistic choice. `Ensure()` assigns
195
+ * the new peer's key to `OrderID` immediately, so a caller that reached the order BEFORE saving --
196
+ * the workspace's `AddLine()`, an importer building a deal and its lines for one `Save()`, or any of
197
+ * the line checks in `save-deal` -- arrived here with `OrderID` already populated. The guard then
198
+ * returned early, the stamps below never ran, and the save died inside orders on
199
+ * `CompanyID cannot be null`: a NOT NULL complaint about a column two apps away from the mistake.
200
+ *
201
+ * Idempotent twice over all the same: `Ensure()` returns any existing peer, `IsSaved` means a re-save
202
+ * never re-provisions, and an order that is ALREADY PERSISTED is left untouched (below) so attaching
203
+ * an existing order stays a legitimate thing to do.
204
+ *
205
+ * ── WHAT SALES STATES, AND WHAT IT DOES NOT ──
206
+ *
207
+ * Company, type, and who to bill. NOT a status: orders' own `NewRecord()` defaults it to Draft, and
208
+ * restating that here would be sales asserting an order lifecycle it does not own. NOT an
209
+ * `OrderNumber`: the server subclass in orders mints it, which is what the diagnostic below is about.
210
+ *
211
+ * `BillToOrganizationID` and `BillToPersonID` are BOTH set, and unlike the contract case that is
212
+ * legal -- `OrderHeader` has no XOR across them, checked against `sys.check_constraints`. Do not
213
+ * carry the contract's exactly-one rule across by analogy; it is that table's rule, not a house style.
214
+ * `Deal.AccountID` is a `SalesAccount`, an IsA child of common's `Organization`, so its key IS an
215
+ * organization key; `PrimaryContactID` is a `SalesContact`, an IsA child of `Person`.
216
+ */
217
+ private provisionEmbeddedOrder;
218
+ /**
219
+ * Turns the one failure this provisioning can cause into a message that names its cause.
220
+ *
221
+ * `OrderNumber` is NOT NULL and is minted by the SERVER subclass in orders. If that class is not
222
+ * registered in the process, `ClassFactory` resolves the generated `OrderHeader` instead, nothing
223
+ * mints a number, and the insert dies on a NOT NULL violation raised inside the save graph. What
224
+ * reaches the caller is `Save failed for OrderID_Object (MJ_BizApps_Orders: Order Headers): Error
225
+ * executing SQL` -- which names the entity, not the reason, and reads like a database fault rather
226
+ * than a missing import.
227
+ *
228
+ * SALES MUST NOT FIX THAT BY DEPENDING ON ORDERS' SERVER PACKAGE. Neither `sales-server` nor
229
+ * `sales-core-entities-server` does, and neither should start: loading another app's server classes
230
+ * from inside this one is how two registrations for one key end up decided by import order. MJAPI
231
+ * owns that -- MJ declares orders' server packages itself, which is why the running app works. A
232
+ * bare node harness has to call `LoadBizAppsOrdersServer()` on its own.
233
+ *
234
+ * So this cannot repair anything. It exists to stop the next person debugging the wrong layer.
235
+ */
236
+ private explainOrderProvisioningFailure;
237
+ /**
238
+ * Warnings from the last `Save()` about what did NOT happen to the order, and why.
239
+ *
240
+ * A channel rather than a thrown error, because that is the whole ruling: *"The Deal stage is the
241
+ * salesperson's record of the sales process; it should never be held hostage by order-side rules"*
242
+ * (Andrew, D-OS1). So a refused order update is not a failure of the save — it is a fact about the
243
+ * save, and something has to be able to say it out loud.
244
+ *
245
+ * `Sales.ReopenDeal` and `Sales.CloseDeal` copy these into their `Issues` with
246
+ * `Severity: 'warning'`, which is how a caller sees them. A plain `deal.Save()` from the board's
247
+ * drag leaves them here and in the server log; see `DECISIONS-NEEDED.md` DN-12, because a warning
248
+ * only a server log carries is one a rep never reads.
249
+ */
250
+ private readonly _orderStatusWarnings;
251
+ get OrderStatusWarnings(): readonly string[];
252
+ /**
253
+ * Resolves what this save's stage change asks of the order. Reads only; opens nothing.
254
+ *
255
+ * ── WHAT COUNTS AS "CHANGING STAGE" ──
256
+ *
257
+ * `OldValue` versus the current value, the same mechanism {@link checkCloseLock} keys on. A save
258
+ * that does not touch `PipelineStageID` plans nothing, so an ordinary edit — a rename, a nudge to
259
+ * `NextStep` — never reaches the order. That matters more than it looks: an order that got
260
+ * re-Quoted on every save of its deal would be a write amplification nobody asked for, and on a
261
+ * Confirmed order it would be a refusal warning on every single save.
262
+ *
263
+ * A BRAND-NEW deal also counts as entering its first stage. `CanTransition` treats a null `from` as
264
+ * "may be created in any legal state", so a new deal on a stage naming `Quoted` gets an order
265
+ * created Quoted rather than created Draft and then moved — one row, one state, no history of a
266
+ * status it was never really in.
267
+ */
268
+ /**
269
+ * What the stage says this deal's probability and forecast category become — read BEFORE the
270
+ * transaction, like every other plan in this class.
271
+ *
272
+ * ── WHY THIS IS ON THE WRITE PATH AND NOT IN THE WORKSPACE ──────────────────────────────────
273
+ *
274
+ * It was in the workspace: `DealWorkspaceComponent.ApplyStageDefaults`, called from the stage picker.
275
+ * So a stage set by an agent, by the S6 HubSpot importer, by an Action or by any API caller got
276
+ * NEITHER value — the deal landed with whatever probability the caller happened to supply, or null,
277
+ * while the pipeline designer's answer sat unused in the stage row. Exactly the shape of the order
278
+ * provisioning that used to live in the workspace and now lives in `Save()`: a rule the UI enforces
279
+ * is a rule only the UI obeys.
280
+ *
281
+ * ── SAME TRIGGER AS THE OTHER THREE, DELIBERATELY A SEPARATE READ ───────────────────────────
282
+ *
283
+ * Keyed on `PipelineStageID` changing, like the order-status writer and the stage event. It reads the
284
+ * stage row a second time rather than widening `planStageOrderStatus`, and that is a considered
285
+ * trade: one extra read on a stage change — a rare event — against touching a method whose exact
286
+ * behaviour is pinned by `close-won-order.CO3`, `CO5` and three mutants. Cheap insurance beats a
287
+ * clever saving here.
288
+ *
289
+ * ── IT DOES NOT FIRE ON PROVISIONING, unlike the order-status writer ────────────────────────
290
+ *
291
+ * `planStageOrderStatus` also runs when an existing deal acquires an order (`_orderJustProvisioned`,
292
+ * SD25), because an order that has just come into existence has never been told anything. These
293
+ * fields are different: the deal has held them all along, and re-applying a stage default to a deal
294
+ * whose stage did not move would overwrite a number a rep tuned by hand.
295
+ */
296
+ private planStageDefaults;
297
+ /**
298
+ * Fills the forecast fields from the stage, WITHOUT overwriting a value this save set deliberately.
299
+ *
300
+ * ── THE RULE IS THE AMOUNT CACHE'S RULE ─────────────────────────────────────────────────────
301
+ *
302
+ * `Deal.Amount` distinguishes a computed figure from a stated one and never overwrites the stated
303
+ * one (SD22). The same distinction applies here and is read the same way: a field the caller TOUCHED
304
+ * in this save is theirs, and a field they did not touch is the stage's to fill.
305
+ *
306
+ * `Dirty` is what expresses that. It is true only when this save changed the value, so:
307
+ *
308
+ * · A rep dragging a card sets no probability -> not dirty -> the stage's number lands.
309
+ * · A rep who moves the stage AND types 85 -> dirty -> 85 stands, which is the point of the
310
+ * workspace comment promising both stay editable.
311
+ * · THERE IS NO UI COPY ANY MORE. The claim that stood here — that the workspace's own
312
+ * `ApplyStageDefaults` set both fields before saving, so they arrived dirty and were respected,
313
+ * so "the UI copy and this one cannot fight" — was WRONG, in the damaging direction. Arriving
314
+ * dirty is precisely what made this method respect a number the UI had ALREADY overwritten.
315
+ * Two copies did it, the workspace's and the board's, and BD6 stayed green because it drives the
316
+ * entity layer and never touches either. Both are deleted; this is the only writer.
317
+ * · An importer that supplies a historical probability keeps it; one that supplies none inherits
318
+ * the pipeline's design instead of landing on null.
319
+ */
320
+ /**
321
+ * Did the CALLER put this value here in this save, or is it mine to fill?
322
+ *
323
+ * ── TWO OF MJ'S SEMANTICS BITE ON CREATE, AND THE ANSWER DIFFERS BECAUSE OF THEM ─────────────
324
+ *
325
+ * On an UPDATE, `Dirty` is exactly the question: it is true when this save changed the value.
326
+ *
327
+ * On a CREATE it is useless. A first assignment does not mark a field dirty, and the first write
328
+ * BECOMES the `OldValue` — so `Dirty` is false for a value the caller definitely supplied, and
329
+ * `OldValue` is a value that was never on disk. Asking `Dirty` on a new record therefore says "not
330
+ * theirs" about everything the caller just set.
331
+ *
332
+ * `applyStageDefaults` already knew this and guarded with `creating = !this.IsSaved`. Two other
333
+ * places did not, and both were wrong in the same direction:
334
+ *
335
+ * · `planStageEvent` inferred a birth from `OldValue === null`. But a new deal whose stage is set
336
+ * TWICE before the first save — `NewDeal()` preselects a pipeline and its first stage, the rep
337
+ * then picks a different one — has a non-null `OldValue` holding a stage the deal was NEVER IN.
338
+ * It appended a transition event for it, into an append-only log. `BD1` asserts against exactly
339
+ * that and passes only because it assigns the stage once.
340
+ * · `ownerStampEditRefusal` asked `Dirty`, so an importer doing `NewRecord()` ->
341
+ * `OwnerEmployeeID = X` -> `Save()` slipped straight through the refusal and created a deal whose
342
+ * owner column and owner-role roster name different people — the state SD26 exists to forbid.
343
+ *
344
+ * So the question lives here now, once, and the callers ask it rather than each re-deriving it.
345
+ */
346
+ private callerSuppliedValue;
347
+ private applyStageDefaults;
348
+ /**
349
+ * The status a BRAND-NEW deal starts in when nothing else supplied one.
350
+ *
351
+ * ── WHY A DEAL WITH NO STATUS IS WORSE THAN A DEAL WITH A WRONG ONE ──────────────────────────────
352
+ *
353
+ * `Deal.DealStatusTypeID` is nullable with no column default, and until now nothing in the write path
354
+ * required it. A deal saved without touching Status landed NULL, and every measure that reads the
355
+ * status through `JOIN DealStatusType` silently did not count it — a tile that under-reports rather
356
+ * than one that errors. It also cost a misdiagnosis: `71-lost-and-reopen` failed with
357
+ * `Cannot read properties of undefined (reading 'IsLost')`, which reads as a bad field and was the
358
+ * JOIN dropping the row.
359
+ *
360
+ * ── NEVER DERIVED FROM THE STAGE, AND THE NUMBERS SAY WHY ────────────────────────────────────
361
+ *
362
+ * The obvious-looking fix — take the stage's status — closes deals by side effect. Measured on this
363
+ * host: `Booked` → Won, `Signed` → Won and `Lost` → Lost all carry `LocksDeal = 1`. A deal created
364
+ * in one of those stages would be born closed and locked, and a board drag would book revenue. That
365
+ * is why {@link planStageDefaults} gates its own stage-derived status on `LocksDeal`, and why this
366
+ * fallback asks the VOCABULARY what an opening status is instead of asking the stage.
367
+ *
368
+ * ── THE THIRD CONDITION IS NOT REDUNDANT ────────────────────────────────────────────────────
369
+ *
370
+ * `IsActive = 1 AND IsOpen = 1 AND LocksDeal = 0`. Today every `IsOpen = 1` row also has
371
+ * `LocksDeal = 0`, so the third term selects nothing extra — but that is a property of the seeded
372
+ * data, not an invariant the schema enforces. Nothing stops a tenant declaring an open status that
373
+ * also locks, and since the entire hazard here is closing a deal by side effect, the guard says so
374
+ * rather than relying on the coincidence holding.
375
+ *
376
+ * `DisplayRank` makes the choice deterministic when a tenant defines several opening statuses.
377
+ *
378
+ * ── AND IT LEAVES NULL RATHER THAN REFUSING ──────────────────────────────────────────────────
379
+ *
380
+ * If no active, open, non-locking status exists the vocabulary is misconfigured — but refusing the
381
+ * save would block ALL deal creation on that tenant, which is a worse failure than the gap being
382
+ * closed. Returning null preserves exactly today's behaviour for that case and nothing more.
383
+ */
384
+ private defaultOpeningStatusID;
385
+ private planStageOrderStatus;
386
+ /**
387
+ * Applies the plan to the deal's order, and NEVER lets a refusal reach the caller as a failure.
388
+ *
389
+ * ── WHY THE TRANSITION IS CHECKED BEFORE IT IS ATTEMPTED ──
390
+ *
391
+ * `CanTransition` is orders' own table of legal moves. Asking it first means the ordinary refusal —
392
+ * `Voided → Quoted` on a reopened lost deal, which S-US8 says WILL happen — costs no write and
393
+ * produces orders' own explanation. Attempting the save and reading the failure would also work,
394
+ * but it would leave the in-memory order holding a status the database refused, and that object is
395
+ * a participant in this deal's save graph.
396
+ *
397
+ * ── WHY THE SAVE STILL RUNS IN ITS OWN SCOPE ──
398
+ *
399
+ * A legal transition can still fail for reasons this app does not model — confirming books journal
400
+ * entries and needs a payer. `BeginEntityTransaction` joins the ambient transaction as a savepoint,
401
+ * so rolling this one back undoes the order write and leaves the deal's own transaction intact.
402
+ * Without that, one order-side failure would take the stage change down with it, which is exactly
403
+ * what D-OS1 forbids.
404
+ *
405
+ * ── THE UNSAVED ORDER ──
406
+ *
407
+ * A new deal's order has not been written yet, so there is nothing to transition and nothing to
408
+ * roll back: setting `Status` means it is INSERTED in that state by the graph a moment later.
409
+ */
410
+ private applyStageOrderStatus;
411
+ /**
412
+ * Re-establishes `Deal.Amount` from the order, as a CACHE with honest provenance.
413
+ *
414
+ * ── WHAT BROKE, AND WHY IT WAS INVISIBLE ──
415
+ *
416
+ * `Amount` was a cached `Orders.PreviewOrder` answer and `AmountSourceHash` fingerprinted the
417
+ * `DealLine` set it came from. `DealLine` is retired, so the fingerprint describes a table that does
418
+ * not exist and NOTHING repopulated the number. It did not go blank — it kept whatever it last held,
419
+ * which is the failure mode that survives review: a dashboard and a board reporting a pipeline
420
+ * figure with no relationship to the order underneath it.
421
+ *
422
+ * ── WHY `OrderHeader.TotalGross` AND NOT A SUM HERE ──
423
+ *
424
+ * Rule 1. Orders maintains `TotalGross` with its own rollup trigger (their D41 —
425
+ * trigger-maintained, deliberately not a computed column), so this reads ONE number that orders
426
+ * already stands behind. There is no multiplication, no addition and no rounding in this method, and
427
+ * there must never be: the moment sales sums `LineTotalGross` itself, the quote and the invoice can
428
+ * disagree.
429
+ *
430
+ * ── WHY IT IS CACHED ONTO THE DEAL ──
431
+ *
432
+ * The roster and the board read `Deal.Amount` as a column. Reading through the embedded order per
433
+ * row would turn one roster query into one-plus-N, on the hottest read in the app. Same argument as
434
+ * `OwnerEmployeeID`: a denormalised stamp exists so a list does not need a join.
435
+ *
436
+ * ── WHY IT RUNS LAST, INSIDE THE SCOPE ──
437
+ *
438
+ * `TotalGross` is trigger-maintained, so the value only reflects this save's lines AFTER
439
+ * `super.Save()` has written them. Reading earlier caches the PREVIOUS line set — the exact staleness
440
+ * this method exists to end. It writes with a targeted `UPDATE` rather than a second `Save()`: the
441
+ * row is already written in this transaction, and re-entering `Save()` would re-run the close lock,
442
+ * the stamps and the stage-event planner for a three-column cache refresh.
443
+ *
444
+ * ── THE PROVENANCE RULE, WHICH IS THE PART TO GET RIGHT ──
445
+ *
446
+ * `AmountIsComputed = 0` means a human typed this figure and is owed no explanation (L-2, the SIMPLE
447
+ * deal). Such an amount is NEVER overwritten. But the column defaults to 0, so "0" alone cannot mean
448
+ * hand-typed — a brand-new deal has never been touched by anyone. The distinction is whether an
449
+ * amount is actually THERE:
450
+ *
451
+ * | `AmountIsComputed` | `Amount` | what it means | what happens |
452
+ * |---|---|---|---|
453
+ * | 1 | anything | a cache | refreshed |
454
+ * | 0 | NULL | nobody has said | filled, and marked computed |
455
+ * | 0 | a number | **a human typed it** | **left alone, always** |
456
+ *
457
+ * And a `TotalGross` of NULL means the order has no priced lines, so there is no answer to cache —
458
+ * that leaves the amount alone too, rather than writing a zero that would read as "priced at nil".
459
+ */
460
+ private refreshAmountFromOrder;
461
+ /**
462
+ * Set only for the duration of {@link BeginReopen}, which is the ONE audited path through the lock.
463
+ *
464
+ * Deliberately NOT public: a caller that could set this could bypass the lock, which would make the
465
+ * lock advisory. `Sales.ReopenDeal` records a reason; nothing else may get through.
466
+ */
467
+ private _reopenInProgress;
468
+ /**
469
+ * True when THIS save built the deal's order, rather than finding one already there.
470
+ *
471
+ * It exists so the stage's `OrderStatusOnEntry` can be applied to an order that has just come into
472
+ * existence. The status writer otherwise keys on `PipelineStageID` CHANGING, which is right for a
473
+ * move and wrong for a birth: a deal that is already at Proposal when its order is provisioned
474
+ * never moved, so nothing asked the stage what the order should be, and the order stayed Draft
475
+ * while its stage plainly declared Quoted. Found by the story audit against seeded data
476
+ * (`DEAL-9003`), and it would have hit every deal the HubSpot import lands past Proposal.
477
+ */
478
+ private _orderJustProvisioned;
479
+ /**
480
+ * Whether the PERSISTED status locks this deal — set by {@link checkCloseLock} on every save.
481
+ *
482
+ * The lock used to answer one question ("may this edit proceed?") and nothing else, so the writers
483
+ * further down the save could not tell a frozen deal from a live one. `provisionEmbeddedOrder` in
484
+ * particular ran unconditionally, and Description is deliberately editable while locked — so editing
485
+ * the description of a closed legacy deal INSERTED an empty Draft order, rewrote `Deal.OrderID` on a
486
+ * frozen row, and let the stage writer stamp that order from the CLOSING stage (`Voided`, for a lost
487
+ * deal). Every one of those is a write to provenance that is supposed to be immutable.
488
+ *
489
+ * So the lock now reports what it found. Nothing else may recompute it: two places deciding whether a
490
+ * deal is locked is how they come to disagree.
491
+ */
492
+ /**
493
+ * ── ONE WRITER FOR THE STAGE LOG, AND CALLERS DECLARE INSTEAD OF WRITING ────────────────────────
494
+ *
495
+ * `PipelineStageID` had FOUR writers by the end of the last round — this class's event appender and
496
+ * defaults applier, plus `Sales.CloseDeal` and `Sales.ReopenDeal`, each hand-writing a
497
+ * `DealStageEvent` and then moving the stage. The sequence was never audited whole, and all three
498
+ * consequences were of the same kind:
499
+ *
500
+ * · **Two rows for one transition.** `stampClose` wrote its event, then set
501
+ * `deal.PipelineStageID = input.ClosingStageID`, and the save that followed saw a stage change and
502
+ * appended a SECOND row. `close-deal.CD4` could not see it because it never passes
503
+ * `ClosingStageID` — the only input that makes the stage move. Reopen had it too, identically.
504
+ * · **A self-transition.** `planStageEvent` compared stage ids case-sensitively while the two
505
+ * writers beside it lowercased first, so a caller that normalised its ids got an append-only row
506
+ * saying the deal moved from a stage to itself.
507
+ * · **A reopen losing its probability.** `ReopenDeal` passing a `StageID` silently invoked the
508
+ * DEFAULTS writer, which re-derived `Probability` from the arriving stage — after the reopen
509
+ * event had already stamped the old one. The stamp said 60 and the deal said 10.
510
+ *
511
+ * The fix is not a fourth special case behind a wider gate. A caller now DECLARES that this save is a
512
+ * governed transition and supplies the note that belongs on it; the entity remains the only thing that
513
+ * writes to `DealStageEvent`, and it reads the declaration to answer the two questions the callers
514
+ * were each answering for themselves:
515
+ *
516
+ * · **Is an event owed?** Yes if the stage moved — and also yes if a transition is declared, which
517
+ * is what makes a close that does NOT move the stage still leave provenance. That case was the
518
+ * reason `stampClose` wrote its own row in the first place.
519
+ * · **Do the stage's defaults apply?** No. A declared transition's probability is the caller's
520
+ * considered value — a close's final number, a reopen's hand-tuned one — and re-deriving it from
521
+ * the pipeline would overwrite the very figure the event just stamped.
522
+ *
523
+ * Declared per save and cleared when it finishes, like every other per-save field here: a declaration
524
+ * that outlived its save would silently suppress the defaults on the next unrelated edit.
525
+ */
526
+ DeclareTransition(kind: DealTransitionKind, note: string | null): void;
527
+ /** The id of the `DealStageEvent` the last save appended, or null if it owed none. */
528
+ get LastStageEventID(): string | null;
529
+ private _declaredTransition;
530
+ private _lastStageEventID;
531
+ private _lockedAtSave;
532
+ /**
533
+ * The fields that stay editable on a locked deal — read from the SHARED rule, not redeclared.
534
+ *
535
+ * It moved to `@mj-biz-apps/sales-entities` so the Explorer Deal form can apply the same list. A
536
+ * second copy here would drift, and the drift would only ever surface as a user typing into a field
537
+ * the server then refuses. See `close-lock.ts` for the reasoning; CD14 pins it to real behaviour.
538
+ */
539
+ private static readonly LOCK_EDITABLE_FIELDS;
540
+ /**
541
+ * Runs `body` with the close lock suppressed, and always restores it.
542
+ *
543
+ * The `finally` is the point: an exception inside the reopen must not leave the lock disabled for
544
+ * the rest of this entity's life, which would silently turn every later save into an unguarded one.
545
+ */
546
+ BeginReopen<T>(body: () => Promise<T>): Promise<T>;
547
+ /**
548
+ * Why this save is refused, or null if it is allowed.
549
+ *
550
+ * KEYED ON THE **PERSISTED** STATUS, via `OldValue` — not on the in-memory one. That is what makes
551
+ * the closing transition itself legal: a deal moving open → closed still has an OPEN status in the
552
+ * database, so it passes, and only saves made once the deal is ALREADY closed are refused. Reading
553
+ * the current value instead would make a deal impossible to close.
554
+ */
555
+ private checkCloseLock;
556
+ /**
557
+ * Whether a status carries `LocksDeal`.
558
+ *
559
+ * THE FLAG, never the name — a deployment may call its winning status "Signed", and this must keep
560
+ * working. It is also why the lock is a property of the STATUS TYPE rather than of the stage.
561
+ */
562
+ private statusLocksDeal;
563
+ /**
564
+ * Forces `Deal.CompanyID` to the pipeline's company, ignoring whatever the caller supplied.
565
+ *
566
+ * THE PIPELINE IS THE ONLY SOURCE THAT CAN BE RIGHT. A deal belongs to the company that owns the
567
+ * pipeline it sits in, and a CHECK constraint cannot express that — it cannot reach across the
568
+ * foreign key to compare the two. So it is resolved on the write path instead, on every save, for
569
+ * every caller.
570
+ *
571
+ * An explicitly supplied value is OVERWRITTEN rather than rejected. A client that gets this wrong is
572
+ * not making a choice the app should honour or argue with; it is stating something it has no standing
573
+ * to know, and the correct answer is available here.
574
+ *
575
+ * A missing pipeline is left alone: `Validate()` already refuses a deal without one, and raising a
576
+ * second, vaguer complaint here would just bury the specific message the user needs.
577
+ */
578
+ private stampCompanyFromPipeline;
579
+ /**
580
+ * Maintains `Deal.OwnerEmployeeID` from whichever team member holds the owner role.
581
+ *
582
+ * `SetOwner` — the intent-expressing half — is on {@link DealEntity}, because the workspace's owner
583
+ * picker has to express the same thing. What is server-only is this: keeping the DENORMALIZED stamp
584
+ * true. `DealTeamMember` is the source of truth; the stamp exists only so "my deals" and per-rep
585
+ * boards need no join, and nothing outside this hierarchy may write it.
586
+ *
587
+ * GUARDED ON THE COLLECTION BEING AUTHORITATIVE, which is the whole subtlety. A save that never
588
+ * touched the roster has an empty, never-loaded `Team` — and deriving "no owner" from that would
589
+ * silently CLEAR the stamp on every ordinary header edit. So the stamp is only rewritten when the
590
+ * collection was actually loaded, or actually appended to.
591
+ *
592
+ * Both conditions are needed because `Add()` and `Create()` do not mark a collection loaded: a new
593
+ * deal whose roster was composed in the browser arrives with `IsLoaded === false` and `Count > 0`.
594
+ */
595
+ /**
596
+ * Why a direct write to `OwnerEmployeeID` is refused, or null when this save is not doing that.
597
+ *
598
+ * ── THE HOLE THIS CLOSES, AND WHY IT WAS NOT OBVIOUS ────────────────────────────────────────
599
+ *
600
+ * {@link stampOwnerFromTeam} is guarded on the roster having participated in the save, and that
601
+ * guard is correct — without it, an ordinary header edit would derive "no owner" from an unloaded
602
+ * collection and CLEAR the stamp. But its consequence was two paths and a refusal on neither: a
603
+ * save carrying the roster silently DISCARDED a hand-set stamp, and a header-only save silently
604
+ * KEPT one. The second is the damaging half. It left the app able to hold a deal whose owner column
605
+ * and owner-role team row name different people, reached by a plain `BaseEntity.Save()` with no
606
+ * error and no warning — and the stamp exists precisely so per-rep rollups need no join, so a
607
+ * rollup could disagree with the roster it was meant to shortcut. Proven against the database by
608
+ * `scripts/audit-story-evidence.mjs` (E3) before this existed.
609
+ *
610
+ * ── WHY REFUSE RATHER THAN QUIETLY RE-DERIVE ────────────────────────────────────────────────
611
+ *
612
+ * Because the caller believed they were setting the owner. Correcting them in silence produces the
613
+ * same wrong outcome as before — the owner is not who they said — with no way to notice. The
614
+ * message names {@link DealEntity.SetOwner}, which is the operation they actually wanted.
615
+ *
616
+ * ── WHY `SetOwner` IS NOT CAUGHT BY THIS ────────────────────────────────────────────────────
617
+ *
618
+ * It calls `Team.Load()` first, so `IsLoaded` is true by the time it assigns the stamp: the roster
619
+ * IS part of that save, and the server re-derives the same value from it a moment later. The two
620
+ * conditions mirror `stampOwnerFromTeam`'s guard exactly, which is what keeps them from disagreeing.
621
+ */
622
+ private ownerStampEditRefusal;
623
+ private stampOwnerFromTeam;
624
+ }
625
+ //# sourceMappingURL=DealEntityServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DealEntityServer.d.ts","sourceRoot":"","sources":["../src/DealEntityServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,EAGH,iBAAiB,EAIpB,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,EAEH,UAAU,EAEb,MAAM,6BAA6B,CAAC;AA0DrC;;;;;GAKG;AACH;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAqBpD,qBACa,gBAAiB,SAAQ,UAAU;IAC5C;;;;OAIG;IACH,IAAoB,0BAA0B,IAAI,OAAO,CAExD;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CG;IACmB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;YAyB3D,YAAY;IAmL1B;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,cAAc;IAmDtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;YACW,eAAe;IAyI7B;;;;;;OAMG;YACW,gBAAgB;IAqD9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2CG;IACH,OAAO,CAAC,sBAAsB;IAmC9B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,+BAA+B;IAgBvC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAgB;IAErD,IAAW,mBAAmB,IAAI,SAAS,MAAM,EAAE,CAElD;IAED;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;YACW,iBAAiB;IA2F/B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,kBAAkB;IAmE1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;YACW,sBAAsB;YAoBtB,oBAAoB;IAmDlC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,qBAAqB;IA2DnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;YACW,sBAAsB;IA8HpC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB,CAAS;IAElC;;;;;;;;;OASG;IACH,OAAO,CAAC,qBAAqB,CAAS;IAEtC;;;;;;;;;;;;OAYG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACI,iBAAiB,CAAC,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7E,sFAAsF;IACtF,IAAW,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAE3C;IAED,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,aAAa,CAAS;IAE9B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAqC;IAEjF;;;;;OAKG;IACU,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAS/D;;;;;;;OAOG;YACW,cAAc;IAkD5B;;;;;OAKG;YACW,eAAe;IAuB7B;;;;;;;;;;;;;;OAcG;YACW,wBAAwB;IA8BtC;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,qBAAqB;YAef,kBAAkB;CASnC"}