@mj-biz-apps/contracts-entities 0.4.1 → 0.5.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.
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { ValidationResult } from '@memberjunction/core';
23
23
  import { mjBizAppsContractsContractEntity } from './generated/entity_subclasses.js';
24
+ import { type ContractRenewalField } from './renewal-defaults.js';
24
25
  /**
25
26
  * Do two contracts sit at the SAME LEVEL of the tree — i.e. may one supersede the other?
26
27
  *
@@ -207,6 +208,49 @@ export declare class ContractEntity extends mjBizAppsContractsContractEntity {
207
208
  * neither does. Mirrors orders' `Confirm()`, which is likewise an intent-setter, not a persister.
208
209
  */
209
210
  Supersede(successor: ContractEntity): void;
211
+ /**
212
+ * Record that the USER set one of the four renewal fields, so seeding will never overwrite it.
213
+ *
214
+ * Called by the form panel that owns those fields, on every write it makes. It has to be an
215
+ * explicit signal rather than something derived from the entity's dirty state: `AutoRenew` starts
216
+ * at No on a new contract, so a user who deliberately answers No leaves the field holding the
217
+ * value it already had, and no amount of comparing against `OldValue` can tell that apart from a
218
+ * field nobody has looked at.
219
+ */
220
+ MarkRenewalFieldEdited(field: ContractRenewalField): void;
221
+ /**
222
+ * Copy the selected Contract Type's renewal defaults onto this contract.
223
+ *
224
+ * UNSAVED RECORDS ONLY, and this is the guard that makes the feature safe rather than merely
225
+ * convenient. On a saved contract every renewal value was typed by somebody reading the paper —
226
+ * including the blanks, which mean "the agreement says nothing" — so a type change on an existing
227
+ * record must not touch them. The story is about filling in a new contract, and the blast radius
228
+ * is kept to exactly that.
229
+ *
230
+ * Returns whether anything was written, so a caller can skip a redundant change-detection pass.
231
+ * Safe to call repeatedly with the same type: the computed seed is empty when the fields already
232
+ * hold their targets.
233
+ */
234
+ SeedRenewalDefaultsFromType(): Promise<boolean>;
235
+ /** The four fields as they stand right now. */
236
+ private currentRenewalValues;
237
+ /**
238
+ * The selected type's four default columns, or null when no type is selected — which restores the
239
+ * pristine values rather than leaving the departing type's numbers behind.
240
+ *
241
+ * One `RunView` rather than an entity load: four scalars are wanted and none of the type's rules
242
+ * are, and this runs on a keystroke-adjacent path (picking from a dropdown). The UUID is shape-
243
+ * checked before it reaches `ExtraFilter` for the same reason `ContractEntityServer` checks the
244
+ * one it walks the lineage with — this string is concatenated into SQL.
245
+ */
246
+ private loadRenewalDefaults;
247
+ /**
248
+ * Per-instance and deliberately NOT persisted: both of these describe an editing session, not the
249
+ * contract. A fresh instance of a saved record starts with neither, which is right — seeding does
250
+ * not run on saved records at all.
251
+ */
252
+ private readonly renewalFieldsUserEdited;
253
+ private renewalFieldsPristine;
210
254
  }
211
255
  /**
212
256
  * Anti-tree-shake anchor. Importing this module is what fires `@RegisterClass`; a production build
@@ -1 +1 @@
1
- {"version":3,"file":"ContractEntity.d.ts","sourceRoot":"","sources":["../src/ContractEntity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAwD,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9G,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AAEjF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAM5H;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,CAWpF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUtF;AAED,qBACa,cAAe,SAAQ,gCAAgC;IAChE;;;;;;;;;;;;;OAaG;IACa,QAAQ,IAAI,gBAAgB;IAwB5C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,wBAAwB;IAchC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,kCAAkC;IAkB1C;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,4BAA4B;IAmBpC;;;;OAIG;IACH,SAAS,CAAC,uBAAuB,UAAS;IAE1C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAIjC;;;;;;;;;;;OAWG;IACI,SAAS,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI;CAgBpD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
1
+ {"version":3,"file":"ContractEntity.d.ts","sourceRoot":"","sources":["../src/ContractEntity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAwD,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE9G,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAGH,KAAK,oBAAoB,EAG5B,MAAM,oBAAoB,CAAC;AAS5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAM5H;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EAAE,CAWpF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAUtF;AAED,qBACa,cAAe,SAAQ,gCAAgC;IAChE;;;;;;;;;;;;;OAaG;IACa,QAAQ,IAAI,gBAAgB;IAwB5C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,wBAAwB;IAchC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,kCAAkC;IAkB1C;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,4BAA4B;IAmBpC;;;;OAIG;IACH,SAAS,CAAC,uBAAuB,UAAS;IAE1C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAIjC;;;;;;;;;;;OAWG;IACI,SAAS,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI;IAqBjD;;;;;;;;OAQG;IACI,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAIhE;;;;;;;;;;;;OAYG;IACU,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC;IAsB5D,+CAA+C;IAC/C,OAAO,CAAC,oBAAoB;IAQ5B;;;;;;;;OAQG;YACW,mBAAmB;IAgBjC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAmC;IAC3E,OAAO,CAAC,qBAAqB,CAA4B;CAE5D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -28,6 +28,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
28
28
  import { BaseEntity, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
29
29
  import { RegisterClass } from '@memberjunction/global';
30
30
  import { mjBizAppsContractsContractEntity } from './generated/entity_subclasses.js';
31
+ import { ComputeRenewalSeed, RENEWAL_SEED_FIELDS, } from './renewal-defaults.js';
32
+ /**
33
+ * Guards an ID before it is interpolated into a filter. Mirrors `ContractEntityServer`'s own guard;
34
+ * both exist because MJ has no parameter binding on `ExtraFilter`, so the shape check IS the
35
+ * protection.
36
+ */
37
+ const UUID_SHAPE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
31
38
  /**
32
39
  * Do two contracts sit at the SAME LEVEL of the tree — i.e. may one supersede the other?
33
40
  *
@@ -126,6 +133,13 @@ let ContractEntity = class ContractEntity extends mjBizAppsContractsContractEnti
126
133
  * the entity has any business claiming a number was system-assigned.
127
134
  */
128
135
  this.NumberWasSystemAssigned = false;
136
+ /**
137
+ * Per-instance and deliberately NOT persisted: both of these describe an editing session, not the
138
+ * contract. A fresh instance of a saved record starts with neither, which is right — seeding does
139
+ * not run on saved records at all.
140
+ */
141
+ this.renewalFieldsUserEdited = new Set();
142
+ this.renewalFieldsPristine = null;
129
143
  }
130
144
  /**
131
145
  * `HasModifications` is MONOTONIC: it must be true when modification rows exist, and it is never
@@ -299,6 +313,83 @@ let ContractEntity = class ContractEntity extends mjBizAppsContractsContractEnti
299
313
  }
300
314
  this.SupersededByContractID = successor.ID;
301
315
  }
316
+ /* ────────────────────────────────────────────────────────────────────────
317
+ * Renewal-obligation defaults (golive #217 / C-US1). The decision is in
318
+ * `renewal-defaults.ts`; this is the half that reads the type row and writes.
319
+ * ──────────────────────────────────────────────────────────────────────── */
320
+ /**
321
+ * Record that the USER set one of the four renewal fields, so seeding will never overwrite it.
322
+ *
323
+ * Called by the form panel that owns those fields, on every write it makes. It has to be an
324
+ * explicit signal rather than something derived from the entity's dirty state: `AutoRenew` starts
325
+ * at No on a new contract, so a user who deliberately answers No leaves the field holding the
326
+ * value it already had, and no amount of comparing against `OldValue` can tell that apart from a
327
+ * field nobody has looked at.
328
+ */
329
+ MarkRenewalFieldEdited(field) {
330
+ this.renewalFieldsUserEdited.add(field);
331
+ }
332
+ /**
333
+ * Copy the selected Contract Type's renewal defaults onto this contract.
334
+ *
335
+ * UNSAVED RECORDS ONLY, and this is the guard that makes the feature safe rather than merely
336
+ * convenient. On a saved contract every renewal value was typed by somebody reading the paper —
337
+ * including the blanks, which mean "the agreement says nothing" — so a type change on an existing
338
+ * record must not touch them. The story is about filling in a new contract, and the blast radius
339
+ * is kept to exactly that.
340
+ *
341
+ * Returns whether anything was written, so a caller can skip a redundant change-detection pass.
342
+ * Safe to call repeatedly with the same type: the computed seed is empty when the fields already
343
+ * hold their targets.
344
+ */
345
+ async SeedRenewalDefaultsFromType() {
346
+ if (this.IsSaved)
347
+ return false;
348
+ // Captured ONCE, before the first seed ever writes: the values this record had of its own
349
+ // accord. Restored later for any field the newly-chosen type has no opinion about, so a
350
+ // default never outlives the type that supplied it.
351
+ this.renewalFieldsPristine ??= this.currentRenewalValues();
352
+ const seed = ComputeRenewalSeed({
353
+ defaults: await this.loadRenewalDefaults(),
354
+ current: this.currentRenewalValues(),
355
+ pristine: this.renewalFieldsPristine,
356
+ userEdited: this.renewalFieldsUserEdited,
357
+ });
358
+ const fields = Object.keys(seed);
359
+ for (const field of fields) {
360
+ this.Set(field, seed[field]);
361
+ }
362
+ return fields.length > 0;
363
+ }
364
+ /** The four fields as they stand right now. */
365
+ currentRenewalValues() {
366
+ const values = {};
367
+ for (const field of RENEWAL_SEED_FIELDS) {
368
+ values[field.ContractField] = this.Get(field.ContractField);
369
+ }
370
+ return values;
371
+ }
372
+ /**
373
+ * The selected type's four default columns, or null when no type is selected — which restores the
374
+ * pristine values rather than leaving the departing type's numbers behind.
375
+ *
376
+ * One `RunView` rather than an entity load: four scalars are wanted and none of the type's rules
377
+ * are, and this runs on a keystroke-adjacent path (picking from a dropdown). The UUID is shape-
378
+ * checked before it reaches `ExtraFilter` for the same reason `ContractEntityServer` checks the
379
+ * one it walks the lineage with — this string is concatenated into SQL.
380
+ */
381
+ async loadRenewalDefaults() {
382
+ const id = String(this.ContractTypeID ?? '').trim();
383
+ if (!UUID_SHAPE.test(id))
384
+ return null;
385
+ const result = await this.RunViewProviderToUse.RunView({
386
+ EntityName: 'MJ_BizApps_Contracts: Contract Types',
387
+ ExtraFilter: `ID = '${id}'`,
388
+ Fields: RENEWAL_SEED_FIELDS.map((f) => f.TypeField),
389
+ ResultType: 'simple',
390
+ }, this.ContextCurrentUser);
391
+ return result?.Results?.[0] ?? null;
392
+ }
302
393
  };
303
394
  ContractEntity = __decorate([
304
395
  RegisterClass(BaseEntity, 'MJ_BizApps_Contracts: Contracts')
@@ -1 +1 @@
1
- {"version":3,"file":"ContractEntity.js","sourceRoot":"","sources":["../src/ContractEntity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ContractEntity.js","sourceRoot":"","sources":["../src/ContractEntity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @fileoverview Which template a NEW contract starts with, and when we are allowed to put it there.
3
+ *
4
+ * golive #218. Story C-US1 says `ContractTemplateID` defaults to the current template and the user
5
+ * corrects it if the executed document cites an older version. Nothing defaulted it, so every new
6
+ * Order Form, Statement of Work and Payment Link opened with the field empty — and all three carry
7
+ * `TemplateRequired = 1`, so the save was refused until the user found the picker themselves.
8
+ *
9
+ * FOUR DECISIONS LIVE HERE, AND THE PANEL OWNS THE QUERY — the same split as
10
+ * `supersede-candidates.ts`, and for the same reason: anything declared inside an Angular component
11
+ * can only be asserted by standing up Angular's DI, so a rule stated in a panel is a rule no unit
12
+ * test reaches.
13
+ *
14
+ * · {@link CurrentTemplateFilter} — WHICH templates may be defaulted at all
15
+ * · {@link CurrentTemplateOrderBy} — which of them is "current"
16
+ * · {@link ShouldDefaultTemplate} — whether we may write over what is in the field right now
17
+ * · {@link ShouldWithdrawDefaultTemplate} — whether we must TAKE BACK what we wrote, once the
18
+ * contract type stops wanting a template at all
19
+ *
20
+ * SCOPING, THE ISSUE'S ONE OPEN DECISION: the newest published usable template wins, REGARDLESS of
21
+ * its template type. Not a simplification — `ContractType` carries no `ContractTemplateTypeID`, so
22
+ * there is nothing in the data to scope by, and of the two seeded template types only Master
23
+ * Agreement has templates behind it (Statement of Work is seeded deliberately unused, because the
24
+ * business does not version its SOW language). Scoping by type would need a schema change and would
25
+ * today narrow a one-element set to itself.
26
+ *
27
+ * @module @mj-biz-apps/contracts-entities
28
+ */
29
+ /**
30
+ * The templates a contract may be started on — an `ExtraFilter`, not a preference.
31
+ *
32
+ * Both halves restate what `ContractEntityServer.refuseUnusableTemplate()` enforces on save, and
33
+ * that duplication is the point: a default that offers something the server will refuse turns a
34
+ * convenience into a save the user cannot complete and did not cause. The server stays the floor.
35
+ *
36
+ * · `Status = 'Published'` — a draft's provisions can still be added to, edited and removed, so a
37
+ * contract pointing at one would have its standard terms change underneath it.
38
+ * · `IsUsable = 1` — derived in `vwContractTemplates`: the template records a `SourceURL` or has a
39
+ * file attached. A contract cannot incorporate terms nobody can open.
40
+ */
41
+ export declare const CurrentTemplateFilter = "Status = 'Published' AND IsUsable = 1";
42
+ /**
43
+ * Which published template is the CURRENT one: the most recently introduced.
44
+ *
45
+ * `IntroducedDate` is when a version started being offered — deliberately not named `EffectiveDate`,
46
+ * because a template becomes effective for a customer when THAT customer signs it. It is the column
47
+ * the business versions by, so it leads.
48
+ *
49
+ * IT IS NULLABLE, AND THE DIRECTION MATTERS. T-SQL sorts NULLs FIRST ascending, therefore LAST
50
+ * descending — so a template whose introduction date was never recorded loses to every template that
51
+ * has one, and only wins when it is the only candidate. That is the outcome we want and it is the
52
+ * one a switch to `ASC` silently inverts, which is why it is pinned by a test rather than by this
53
+ * sentence.
54
+ *
55
+ * `__mj_CreatedAt` breaks a same-date tie by which row was registered last. Two templates introduced
56
+ * on one day is a correction, not a schedule, and the later row is the correction.
57
+ */
58
+ export declare const CurrentTemplateOrderBy = "IntroducedDate DESC, __mj_CreatedAt DESC";
59
+ /** What the defaulting rule needs to know about the contract in front of it. */
60
+ export interface TemplateDefaultState {
61
+ /** `BaseEntity.IsSaved` — false only for a contract that has never been written. */
62
+ isSaved: boolean;
63
+ /** The value in the field right now. */
64
+ currentTemplateID: string | null | undefined;
65
+ /** The value THIS session last defaulted, or null if it has defaulted nothing yet. */
66
+ lastDefaultedID: string | null | undefined;
67
+ }
68
+ /**
69
+ * May this panel write the template field at all — the half of every rule that needs no database?
70
+ *
71
+ * Exported separately so the caller can ask it BEFORE reading the contract type, the only input the
72
+ * rules below need that costs a query. It is false for every saved contract, which is what keeps an
73
+ * ordinary form open free of one.
74
+ *
75
+ * NAMED FOR PERMISSION RATHER THAN FOR DEFAULTING, because it now gates two opposite writes — the
76
+ * default and its withdrawal — and both are allowed in exactly the same circumstances: the contract
77
+ * is unsaved, and the field is either empty or still holds our own default. It was
78
+ * `RecordWantsDefaultTemplate` while defaulting was the only thing it guarded.
79
+ *
80
+ * · **a SAVED contract is never touched.** Its template is a fact about an agreement that exists,
81
+ * and re-deciding it when someone opens the form would rewrite history on a record the user only
82
+ * came to read.
83
+ * · **an empty field is fair game** — the ordinary case the issue is about.
84
+ * · **a HAND-PICKED template is never touched.** A user who deliberately chose last year's version
85
+ * and then corrected the type must not silently get this year's back, and must not have theirs
86
+ * taken away either. We may only overwrite what we ourselves put there.
87
+ */
88
+ export declare function MayWriteDefaultTemplate(state: TemplateDefaultState): boolean;
89
+ /**
90
+ * Take back a template we defaulted, once the type stops wanting one (review of PR #51).
91
+ *
92
+ * THE BUG THIS EXISTS TO CLOSE. Picking Order Form fills the field in; changing the type to Change
93
+ * Order used to leave it filled, because the defaulting rule saw a type that needs no template and
94
+ * simply stopped — treating "there was never anything to do here" and "what I put here no longer
95
+ * belongs" as the same case. Nothing downstream caught it: `ContractEntityServer` refuses a MISSING
96
+ * template on a type that requires one and says nothing about a present one on a type that does not.
97
+ * The contract saved, claiming to incorporate a full set of standard terms nobody chose.
98
+ *
99
+ * A default leaves with the type that supplied it — the same principle the renewal-field defaults
100
+ * follow.
101
+ *
102
+ * ONLY OUR OWN DEFAULT IS WITHDRAWN. A template the user picked by hand stays, exactly as it stays
103
+ * through a default; `holdsOurDefault` is the single comparison both directions read.
104
+ *
105
+ * Clearing the type entirely withdraws it too, and that is deliberate: the caller reports a type it
106
+ * genuinely could not find as `templateRequired = false`, and a contract with no type has nothing
107
+ * asserting that those standard terms apply. A type it could not READ is a different answer, and the
108
+ * caller must not translate that into `false` — see the panel's `typeRequiresTemplate`.
109
+ */
110
+ export declare function ShouldWithdrawDefaultTemplate(state: TemplateDefaultState & {
111
+ templateRequired: boolean;
112
+ }): boolean;
113
+ /**
114
+ * The whole rule in the other direction: may we default this contract's template right now?
115
+ *
116
+ * `templateRequired` is `ContractType.TemplateRequired`, read from the type the contract currently
117
+ * names. It gates the default because the flag is what says a type's terms live in a template at
118
+ * all: Change Order is the one seeded type carrying `0`, and it carries no template of its own —
119
+ * a modification recorded on a change order cites a provision of a template at or above it in the
120
+ * tree. Defaulting one onto it would assert an incorporation nobody made.
121
+ *
122
+ * A contract with NO type yet cannot be defaulted either, and falls out of this naturally: the
123
+ * caller has no type to read `TemplateRequired` from, so it passes `false`.
124
+ */
125
+ export declare function ShouldDefaultTemplate(state: TemplateDefaultState & {
126
+ templateRequired: boolean;
127
+ }): boolean;
128
+ //# sourceMappingURL=default-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-template.d.ts","sourceRoot":"","sources":["../src/default-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,0CAA0C,CAAC;AAE7E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,6CAA6C,CAAC;AAEjF,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACjC,oFAAoF;IACpF,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC7C,sFAAsF;IACtF,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC9C;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAK5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAIlH;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,oBAAoB,GAAG;IAAE,gBAAgB,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAE1G"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @fileoverview Which template a NEW contract starts with, and when we are allowed to put it there.
3
+ *
4
+ * golive #218. Story C-US1 says `ContractTemplateID` defaults to the current template and the user
5
+ * corrects it if the executed document cites an older version. Nothing defaulted it, so every new
6
+ * Order Form, Statement of Work and Payment Link opened with the field empty — and all three carry
7
+ * `TemplateRequired = 1`, so the save was refused until the user found the picker themselves.
8
+ *
9
+ * FOUR DECISIONS LIVE HERE, AND THE PANEL OWNS THE QUERY — the same split as
10
+ * `supersede-candidates.ts`, and for the same reason: anything declared inside an Angular component
11
+ * can only be asserted by standing up Angular's DI, so a rule stated in a panel is a rule no unit
12
+ * test reaches.
13
+ *
14
+ * · {@link CurrentTemplateFilter} — WHICH templates may be defaulted at all
15
+ * · {@link CurrentTemplateOrderBy} — which of them is "current"
16
+ * · {@link ShouldDefaultTemplate} — whether we may write over what is in the field right now
17
+ * · {@link ShouldWithdrawDefaultTemplate} — whether we must TAKE BACK what we wrote, once the
18
+ * contract type stops wanting a template at all
19
+ *
20
+ * SCOPING, THE ISSUE'S ONE OPEN DECISION: the newest published usable template wins, REGARDLESS of
21
+ * its template type. Not a simplification — `ContractType` carries no `ContractTemplateTypeID`, so
22
+ * there is nothing in the data to scope by, and of the two seeded template types only Master
23
+ * Agreement has templates behind it (Statement of Work is seeded deliberately unused, because the
24
+ * business does not version its SOW language). Scoping by type would need a schema change and would
25
+ * today narrow a one-element set to itself.
26
+ *
27
+ * @module @mj-biz-apps/contracts-entities
28
+ */
29
+ /**
30
+ * The templates a contract may be started on — an `ExtraFilter`, not a preference.
31
+ *
32
+ * Both halves restate what `ContractEntityServer.refuseUnusableTemplate()` enforces on save, and
33
+ * that duplication is the point: a default that offers something the server will refuse turns a
34
+ * convenience into a save the user cannot complete and did not cause. The server stays the floor.
35
+ *
36
+ * · `Status = 'Published'` — a draft's provisions can still be added to, edited and removed, so a
37
+ * contract pointing at one would have its standard terms change underneath it.
38
+ * · `IsUsable = 1` — derived in `vwContractTemplates`: the template records a `SourceURL` or has a
39
+ * file attached. A contract cannot incorporate terms nobody can open.
40
+ */
41
+ export const CurrentTemplateFilter = `Status = 'Published' AND IsUsable = 1`;
42
+ /**
43
+ * Which published template is the CURRENT one: the most recently introduced.
44
+ *
45
+ * `IntroducedDate` is when a version started being offered — deliberately not named `EffectiveDate`,
46
+ * because a template becomes effective for a customer when THAT customer signs it. It is the column
47
+ * the business versions by, so it leads.
48
+ *
49
+ * IT IS NULLABLE, AND THE DIRECTION MATTERS. T-SQL sorts NULLs FIRST ascending, therefore LAST
50
+ * descending — so a template whose introduction date was never recorded loses to every template that
51
+ * has one, and only wins when it is the only candidate. That is the outcome we want and it is the
52
+ * one a switch to `ASC` silently inverts, which is why it is pinned by a test rather than by this
53
+ * sentence.
54
+ *
55
+ * `__mj_CreatedAt` breaks a same-date tie by which row was registered last. Two templates introduced
56
+ * on one day is a correction, not a schedule, and the later row is the correction.
57
+ */
58
+ export const CurrentTemplateOrderBy = `IntroducedDate DESC, __mj_CreatedAt DESC`;
59
+ /**
60
+ * Does the field hold exactly the value THIS panel put there?
61
+ *
62
+ * The shared notion underneath every rule in this file, and the reason it is factored out rather
63
+ * than written three times: "ours" is what separates a default we may revisit from a choice a person
64
+ * made, and a copy of that comparison that drifts turns one of those into the other.
65
+ *
66
+ * An EMPTY field is not ours — it is nobody's. That distinction is what keeps
67
+ * {@link ShouldWithdrawDefaultTemplate} from dirtying a record to write null over null.
68
+ *
69
+ * The comparison is case-insensitive because MJ hands UUIDs back in either casing depending on how
70
+ * the record was loaded (MJ's UUID_COMPARISON_GUIDE); a `===` here would read our own default as the
71
+ * user's choice. Compared by hand rather than through `UUIDsEqual` for the same reason
72
+ * `IsSameContractLevel` does: these free functions stay importable by a test with no MJ runtime
73
+ * standing behind them.
74
+ */
75
+ function holdsOurDefault(state) {
76
+ const current = (state.currentTemplateID ?? '').trim();
77
+ if (!current)
78
+ return false;
79
+ const defaulted = (state.lastDefaultedID ?? '').trim();
80
+ if (!defaulted)
81
+ return false;
82
+ return current.toLowerCase() === defaulted.toLowerCase();
83
+ }
84
+ /**
85
+ * May this panel write the template field at all — the half of every rule that needs no database?
86
+ *
87
+ * Exported separately so the caller can ask it BEFORE reading the contract type, the only input the
88
+ * rules below need that costs a query. It is false for every saved contract, which is what keeps an
89
+ * ordinary form open free of one.
90
+ *
91
+ * NAMED FOR PERMISSION RATHER THAN FOR DEFAULTING, because it now gates two opposite writes — the
92
+ * default and its withdrawal — and both are allowed in exactly the same circumstances: the contract
93
+ * is unsaved, and the field is either empty or still holds our own default. It was
94
+ * `RecordWantsDefaultTemplate` while defaulting was the only thing it guarded.
95
+ *
96
+ * · **a SAVED contract is never touched.** Its template is a fact about an agreement that exists,
97
+ * and re-deciding it when someone opens the form would rewrite history on a record the user only
98
+ * came to read.
99
+ * · **an empty field is fair game** — the ordinary case the issue is about.
100
+ * · **a HAND-PICKED template is never touched.** A user who deliberately chose last year's version
101
+ * and then corrected the type must not silently get this year's back, and must not have theirs
102
+ * taken away either. We may only overwrite what we ourselves put there.
103
+ */
104
+ export function MayWriteDefaultTemplate(state) {
105
+ if (state.isSaved)
106
+ return false;
107
+ const current = (state.currentTemplateID ?? '').trim();
108
+ if (!current)
109
+ return true;
110
+ return holdsOurDefault(state);
111
+ }
112
+ /**
113
+ * Take back a template we defaulted, once the type stops wanting one (review of PR #51).
114
+ *
115
+ * THE BUG THIS EXISTS TO CLOSE. Picking Order Form fills the field in; changing the type to Change
116
+ * Order used to leave it filled, because the defaulting rule saw a type that needs no template and
117
+ * simply stopped — treating "there was never anything to do here" and "what I put here no longer
118
+ * belongs" as the same case. Nothing downstream caught it: `ContractEntityServer` refuses a MISSING
119
+ * template on a type that requires one and says nothing about a present one on a type that does not.
120
+ * The contract saved, claiming to incorporate a full set of standard terms nobody chose.
121
+ *
122
+ * A default leaves with the type that supplied it — the same principle the renewal-field defaults
123
+ * follow.
124
+ *
125
+ * ONLY OUR OWN DEFAULT IS WITHDRAWN. A template the user picked by hand stays, exactly as it stays
126
+ * through a default; `holdsOurDefault` is the single comparison both directions read.
127
+ *
128
+ * Clearing the type entirely withdraws it too, and that is deliberate: the caller reports a type it
129
+ * genuinely could not find as `templateRequired = false`, and a contract with no type has nothing
130
+ * asserting that those standard terms apply. A type it could not READ is a different answer, and the
131
+ * caller must not translate that into `false` — see the panel's `typeRequiresTemplate`.
132
+ */
133
+ export function ShouldWithdrawDefaultTemplate(state) {
134
+ if (state.templateRequired)
135
+ return false;
136
+ if (state.isSaved)
137
+ return false;
138
+ return holdsOurDefault(state);
139
+ }
140
+ /**
141
+ * The whole rule in the other direction: may we default this contract's template right now?
142
+ *
143
+ * `templateRequired` is `ContractType.TemplateRequired`, read from the type the contract currently
144
+ * names. It gates the default because the flag is what says a type's terms live in a template at
145
+ * all: Change Order is the one seeded type carrying `0`, and it carries no template of its own —
146
+ * a modification recorded on a change order cites a provision of a template at or above it in the
147
+ * tree. Defaulting one onto it would assert an incorporation nobody made.
148
+ *
149
+ * A contract with NO type yet cannot be defaulted either, and falls out of this naturally: the
150
+ * caller has no type to read `TemplateRequired` from, so it passes `false`.
151
+ */
152
+ export function ShouldDefaultTemplate(state) {
153
+ return state.templateRequired && MayWriteDefaultTemplate(state);
154
+ }
155
+ //# sourceMappingURL=default-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-template.js","sourceRoot":"","sources":["../src/default-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,uCAAuC,CAAC;AAE7E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,0CAA0C,CAAC;AAYjF;;;;;;;;;;;;;;;GAeG;AACH,SAAS,eAAe,CAAC,KAA2B;IAChD,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE3B,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,OAAO,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,WAAW,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAA2B;IAC/D,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAChC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,6BAA6B,CAAC,KAA2D;IACrG,IAAI,KAAK,CAAC,gBAAgB;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAChC,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA2D;IAC7F,OAAO,KAAK,CAAC,gBAAgB,IAAI,uBAAuB,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC"}
@@ -158,6 +158,10 @@ export declare const mjBizAppsContractsContractTypeSchema: z.ZodObject<{
158
158
  TemplateRequired: z.ZodBoolean;
159
159
  __mj_CreatedAt: z.ZodDate;
160
160
  __mj_UpdatedAt: z.ZodDate;
161
+ DefaultAutoRenew: z.ZodNullable<z.ZodBoolean>;
162
+ DefaultRenewalNoticeDays: z.ZodNullable<z.ZodNumber>;
163
+ DefaultCancellationWindowDays: z.ZodNullable<z.ZodNumber>;
164
+ DefaultAnnualIncreasePercent: z.ZodNullable<z.ZodNumber>;
161
165
  }, "strip", z.ZodTypeAny, {
162
166
  ID?: string;
163
167
  __mj_CreatedAt?: Date;
@@ -169,6 +173,10 @@ export declare const mjBizAppsContractsContractTypeSchema: z.ZodObject<{
169
173
  MustBeRoot?: boolean;
170
174
  MustBeChild?: boolean;
171
175
  TemplateRequired?: boolean;
176
+ DefaultAutoRenew?: boolean;
177
+ DefaultRenewalNoticeDays?: number;
178
+ DefaultCancellationWindowDays?: number;
179
+ DefaultAnnualIncreasePercent?: number;
172
180
  }, {
173
181
  ID?: string;
174
182
  __mj_CreatedAt?: Date;
@@ -180,6 +188,10 @@ export declare const mjBizAppsContractsContractTypeSchema: z.ZodObject<{
180
188
  MustBeRoot?: boolean;
181
189
  MustBeChild?: boolean;
182
190
  TemplateRequired?: boolean;
191
+ DefaultAutoRenew?: boolean;
192
+ DefaultRenewalNoticeDays?: number;
193
+ DefaultCancellationWindowDays?: number;
194
+ DefaultAnnualIncreasePercent?: number;
183
195
  }>;
184
196
  export type mjBizAppsContractsContractTypeEntityType = z.infer<typeof mjBizAppsContractsContractTypeSchema>;
185
197
  /**
@@ -752,6 +764,9 @@ export declare class mjBizAppsContractsContractTypeEntity extends BaseEntity<mjB
752
764
  Load(ID: string, EntityRelationshipsToLoad?: string[]): Promise<boolean>;
753
765
  /**
754
766
  * Validate() method override for MJ_BizApps_Contracts: Contract Types entity. This is an auto-generated method that invokes the generated validators for this entity for the following fields:
767
+ * * DefaultAnnualIncreasePercent: The default annual increase percentage must be greater than or equal to 0% if it is specified.
768
+ * * DefaultCancellationWindowDays: The default cancellation window days, if specified, must be a non-negative number (0 or greater).
769
+ * * DefaultRenewalNoticeDays: The default renewal notice days, if specified, must be 0 or greater to ensure a valid notice period.
755
770
  * * Table-Level: An entity cannot be designated as both a root and a child at the same time.
756
771
  * @public
757
772
  * @method
@@ -759,6 +774,27 @@ export declare class mjBizAppsContractsContractTypeEntity extends BaseEntity<mjB
759
774
  */
760
775
  Validate(): ValidationResult;
761
776
  /**
777
+ * The default annual increase percentage must be greater than or equal to 0% if it is specified.
778
+ * @param result - the ValidationResult object to add any errors or warnings to
779
+ * @public
780
+ * @method
781
+ */
782
+ ValidateDefaultAnnualIncreasePercentGreaterThanOrEqualToZero(result: ValidationResult): void;
783
+ /**
784
+ * The default cancellation window days, if specified, must be a non-negative number (0 or greater).
785
+ * @param result - the ValidationResult object to add any errors or warnings to
786
+ * @public
787
+ * @method
788
+ */
789
+ ValidateDefaultCancellationWindowDaysMin(result: ValidationResult): void;
790
+ /**
791
+ * The default renewal notice days, if specified, must be 0 or greater to ensure a valid notice period.
792
+ * @param result - the ValidationResult object to add any errors or warnings to
793
+ * @public
794
+ * @method
795
+ */
796
+ ValidateDefaultRenewalNoticeDaysMin(result: ValidationResult): void;
797
+ /**
762
798
  * An entity cannot be designated as both a root and a child at the same time.
763
799
  * @param result - the ValidationResult object to add any errors or warnings to
764
800
  * @public
@@ -850,6 +886,38 @@ export declare class mjBizAppsContractsContractTypeEntity extends BaseEntity<mjB
850
886
  * * Default Value: getutcdate()
851
887
  */
852
888
  get __mj_UpdatedAt(): Date;
889
+ /**
890
+ * * Field Name: DefaultAutoRenew
891
+ * * Display Name: Default Auto Renew
892
+ * * SQL Data Type: bit
893
+ * * Description: Seeds Contract.AutoRenew when a new contract picks this type. NULL means the type has no opinion and the contract is left alone — which is why this is nullable where the contract's own column is not. Copied once, on an unsaved contract; never consulted afterwards and never enforced.
894
+ */
895
+ get DefaultAutoRenew(): boolean | null;
896
+ set DefaultAutoRenew(value: boolean | null);
897
+ /**
898
+ * * Field Name: DefaultRenewalNoticeDays
899
+ * * Display Name: Default Renewal Notice Days
900
+ * * SQL Data Type: int
901
+ * * Description: Seeds Contract.RenewalNoticeDays — the notice WE owe the customer before a renewal price change. NULL means no default. A starting point for whoever reads the paper, not a term of any agreement.
902
+ */
903
+ get DefaultRenewalNoticeDays(): number | null;
904
+ set DefaultRenewalNoticeDays(value: number | null);
905
+ /**
906
+ * * Field Name: DefaultCancellationWindowDays
907
+ * * Display Name: Default Cancellation Window Days
908
+ * * SQL Data Type: int
909
+ * * Description: Seeds Contract.CancellationWindowDays — the notice the CUSTOMER owes us to cancel. Deliberately a separate default from the renewal notice even where a type sets them equal: one obligation is ours and the other theirs, and a single default would hide that.
910
+ */
911
+ get DefaultCancellationWindowDays(): number | null;
912
+ set DefaultCancellationWindowDays(value: number | null);
913
+ /**
914
+ * * Field Name: DefaultAnnualIncreasePercent
915
+ * * Display Name: Default Annual Increase Percent
916
+ * * SQL Data Type: decimal(7, 4)
917
+ * * Description: Seeds Contract.AnnualIncreasePercent — the year-over-year uplift this kind of agreement usually carries. NULL means no default. Same precision as the column it seeds, so a legal default can never seed an unsaveable contract.
918
+ */
919
+ get DefaultAnnualIncreasePercent(): number | null;
920
+ set DefaultAnnualIncreasePercent(value: number | null);
853
921
  }
854
922
  /**
855
923
  * MJ_BizApps_Contracts: Contracts - strongly typed entity sub-class