@mj-biz-apps/contracts-entities 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -29,5 +29,6 @@ export * from './value-list-validation.js';
29
29
  export * from './contract-state.js';
30
30
  /** Which contracts the Supersedes picker may offer, and how it names them (#28 item 4). */
31
31
  export * from './supersede-candidates.js';
32
+ export * from './source-record-candidates.js';
32
33
  export * from './executed-agreement.js';
33
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAc,+BAA+B,CAAC;AAI9C,cAAc,kBAAkB,CAAC;AAIjC,cAAc,sCAAsC,CAAC;AAIrD,cAAc,mCAAmC,CAAC;AAIlD,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAIvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAMxC,cAAc,kBAAkB,CAAC;AAEjC,2FAA2F;AAC3F,cAAc,wBAAwB,CAAC;AAIvC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAc,+BAA+B,CAAC;AAI9C,cAAc,kBAAkB,CAAC;AAIjC,cAAc,sCAAsC,CAAC;AAIrD,cAAc,mCAAmC,CAAC;AAIlD,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAIvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAMxC,cAAc,kBAAkB,CAAC;AAEjC,2FAA2F;AAC3F,cAAc,wBAAwB,CAAC;AAIvC,cAAc,4BAA4B,CAAC;AAI3C,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -43,6 +43,9 @@ export * from './value-list-validation.js';
43
43
  export * from './contract-state.js';
44
44
  /** Which contracts the Supersedes picker may offer, and how it names them (#28 item 4). */
45
45
  export * from './supersede-candidates.js';
46
+ /* Which DEALS the Source-record picker may offer, and how it names them (golive #219). Name-only:
47
+ * sales depends on this app, so the link stays polymorphic and is resolved from metadata at runtime. */
48
+ export * from './source-record-candidates.js';
46
49
  /* The file category that clears IsAwaitingDocument (golive #203 item 16 / #213). One spelling, shared by
47
50
  * the view, the migration seed and the form panel that marks a file. */
48
51
  export * from './executed-agreement.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAc,+BAA+B,CAAC;AAE9C;kFACkF;AAClF,cAAc,kBAAkB,CAAC;AAEjC;qGACqG;AACrG,cAAc,sCAAsC,CAAC;AAErD;wEACwE;AACxE,cAAc,mCAAmC,CAAC;AAElD;wGACwG;AACxG,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAEvC;2FAC2F;AAC3F,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC;;;wCAGwC;AACxC,cAAc,kBAAkB,CAAC;AAEjC,2FAA2F;AAC3F,cAAc,wBAAwB,CAAC;AAEvC;wEACwE;AACxE,cAAc,sBAAsB,CAAC;AAErC;;;;oFAIoF"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,cAAc,+BAA+B,CAAC;AAE9C;kFACkF;AAClF,cAAc,kBAAkB,CAAC;AAEjC;qGACqG;AACrG,cAAc,sCAAsC,CAAC;AAErD;wEACwE;AACxE,cAAc,mCAAmC,CAAC;AAElD;wGACwG;AACxG,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AAEvC;2FAC2F;AAC3F,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC;;;wCAGwC;AACxC,cAAc,kBAAkB,CAAC;AAEjC,2FAA2F;AAC3F,cAAc,wBAAwB,CAAC;AAEvC;wGACwG;AACxG,cAAc,4BAA4B,CAAC;AAE3C;wEACwE;AACxE,cAAc,sBAAsB,CAAC;AAErC;;;;oFAIoF"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Which deals the Source-record picker may offer, and how it names them (golive #219).
3
+ *
4
+ * C-US1 says a finance user may associate a manually created contract with an existing deal by
5
+ * looking it up. The link is the polymorphic pair `CreatingEntityID` / `CreatingRecordID`, which the
6
+ * Provenance panel has rendered read-only since contracts#28 item 18 — for a reason that still
7
+ * holds: hand-typing the two halves produced a corrupt pair (`CTR-000026` names
8
+ * `MJ: Explorer Navigation Items` and a record id that is not a UUID). A picker that sets both halves
9
+ * together is the way to satisfy the story WITHOUT reopening that defect, so the raw fields stay
10
+ * read-only and this is what feeds the control above them.
11
+ *
12
+ * WHY THESE LIVE HERE AND NOT ON THE PANEL. Same reason `supersede-candidates.ts` exists: anything
13
+ * declared inside an Angular component can only be asserted by standing up Angular's DI, so importing
14
+ * it from a unit test fails with "the injectable 'PlatformLocation' needs to be compiled using the JIT
15
+ * compiler". The panel owns the QUERY; these own the decisions inside it.
16
+ *
17
+ * ── WHY THIS FILE MENTIONS DEALS AND THE PACKAGE STILL DOES NOT DEPEND ON SALES ────────────────
18
+ *
19
+ * `bizapps-sales` depends on THIS app — it is sales' Close-Won seam that creates contracts — so a
20
+ * dependency pointing back would invert the graph, which is the reason the column is polymorphic
21
+ * rather than a `DealID` FK in the first place (the Contract entity's own description says so). The
22
+ * strings below are therefore a SOFT, name-only read resolved from provider metadata at runtime: no
23
+ * import, no manifest dependency, and an installation without sales simply finds no entity and shows
24
+ * no picker.
25
+ *
26
+ * @module @mj-biz-apps/contracts-entities
27
+ */
28
+ /** One row of the picker, as the panel reads it off `vwDeals`. */
29
+ export interface DealOption {
30
+ ID: string;
31
+ DealNumber: string | null;
32
+ Name: string;
33
+ /** The customer's name, joined onto the deal's base view as `Account`. */
34
+ Account: string | null;
35
+ }
36
+ /**
37
+ * The filter that decides WHICH deals the picker offers.
38
+ *
39
+ * Two modes, and the difference matters more than it looks:
40
+ *
41
+ * · **NOTHING TYPED — the customer's own deals.** This is the seeded list, and it is the case the
42
+ * story is really about: a finance user papering a contract for Acme wants Acme's deals, not a
43
+ * page of whatever sorted first. Issue #219 asks the lookup to "ideally prefer deals for the
44
+ * contract's customer organization"; seeding it this way makes the common case zero-typing.
45
+ * · **TEXT TYPED — search the whole book, by deal or by customer.** Once a person types they are
46
+ * looking for something the seeded list did not contain, so staying scoped to one customer would
47
+ * silently answer a different question than the one asked. #219 states the search as "by name and
48
+ * customer", which is `Name`, `DealNumber` and the joined `Account`.
49
+ *
50
+ * AN ABSENT CUSTOMER WITH NO SEARCH TEXT OFFERS NOTHING, NOT EVERYTHING — the same direction, and the
51
+ * same reasoning, as `SameCustomerClause`. `CustomerOrganizationID` is NOT NULL on Contract, so a
52
+ * missing value never means "this contract has no customer"; it means the record could not be read.
53
+ * Opening the picker to every deal in the database at exactly that moment is the failure worth
54
+ * refusing, and a person who genuinely wants the whole book can type.
55
+ *
56
+ * The CUSTOMER LINK IS DIRECT, which is easy to disbelieve and is verified: sales' `SalesAccount`
57
+ * shares its primary key with `__mj_BizAppsCommon.Organization` (`FK_SalesAccount_Organization` is on
58
+ * `ID` itself), so a deal's `AccountID` IS an organization id and compares to the contract's
59
+ * `CustomerOrganizationID` with no bridge table.
60
+ */
61
+ export declare function DealSearchClause(customerOrganizationID: string | null | undefined, searchText?: string | null): string;
62
+ /**
63
+ * A clause matching ONE deal by id — the currently linked one.
64
+ *
65
+ * The seeded list is the customer's deals, and the deal a contract is already pointed at need not be
66
+ * among them: Close Won writes the pair from sales, where the deal's account and the contract's
67
+ * customer are two records a person maintains separately, and a re-pointed contract may name a deal
68
+ * for a related entity entirely. Without this the picker renders BLANK beside a contract that plainly
69
+ * has a source, which reads as "no deal" — the exact confusion #219 is about.
70
+ *
71
+ * ORed into the seeded filter rather than fetched separately so the list arrives in one read and the
72
+ * selected option can never flicker in after the dropdown is already open.
73
+ */
74
+ export declare function DealIDClause(dealID: string | null | undefined): string;
75
+ /**
76
+ * The option label: `<DealNumber> — <Name> (<Account>)`.
77
+ *
78
+ * Every part earns its place. The NUMBER is what a person quotes in an email; the NAME is what they
79
+ * recognise; the CUSTOMER disambiguates the renewals, which are frequently named identically across
80
+ * accounts ("2026 Renewal" is not one deal). The customer is also the reason the parenthetical is
81
+ * kept when searching widens past one account — without it the widened list is a column of
82
+ * indistinguishable names.
83
+ *
84
+ * Each part is TRIMMED before its emptiness test, because blank strings are common on imported rows
85
+ * and a label reading `— ( )` looks like broken software rather than a sparse record. A deal with no
86
+ * number loses the prefix rather than rendering a dangling dash, and one with no name falls back to
87
+ * the number so the option is never blank.
88
+ */
89
+ export declare function DealOptionLabel(deal: DealOption): string;
90
+ //# sourceMappingURL=source-record-candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-record-candidates.d.ts","sourceRoot":"","sources":["../src/source-record-candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,sBAAsB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAStH;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAItE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAOxD"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Which deals the Source-record picker may offer, and how it names them (golive #219).
3
+ *
4
+ * C-US1 says a finance user may associate a manually created contract with an existing deal by
5
+ * looking it up. The link is the polymorphic pair `CreatingEntityID` / `CreatingRecordID`, which the
6
+ * Provenance panel has rendered read-only since contracts#28 item 18 — for a reason that still
7
+ * holds: hand-typing the two halves produced a corrupt pair (`CTR-000026` names
8
+ * `MJ: Explorer Navigation Items` and a record id that is not a UUID). A picker that sets both halves
9
+ * together is the way to satisfy the story WITHOUT reopening that defect, so the raw fields stay
10
+ * read-only and this is what feeds the control above them.
11
+ *
12
+ * WHY THESE LIVE HERE AND NOT ON THE PANEL. Same reason `supersede-candidates.ts` exists: anything
13
+ * declared inside an Angular component can only be asserted by standing up Angular's DI, so importing
14
+ * it from a unit test fails with "the injectable 'PlatformLocation' needs to be compiled using the JIT
15
+ * compiler". The panel owns the QUERY; these own the decisions inside it.
16
+ *
17
+ * ── WHY THIS FILE MENTIONS DEALS AND THE PACKAGE STILL DOES NOT DEPEND ON SALES ────────────────
18
+ *
19
+ * `bizapps-sales` depends on THIS app — it is sales' Close-Won seam that creates contracts — so a
20
+ * dependency pointing back would invert the graph, which is the reason the column is polymorphic
21
+ * rather than a `DealID` FK in the first place (the Contract entity's own description says so). The
22
+ * strings below are therefore a SOFT, name-only read resolved from provider metadata at runtime: no
23
+ * import, no manifest dependency, and an installation without sales simply finds no entity and shows
24
+ * no picker.
25
+ *
26
+ * @module @mj-biz-apps/contracts-entities
27
+ */
28
+ /**
29
+ * Escape a value on its way into an `ExtraFilter` string literal.
30
+ *
31
+ * Doubling the quote is the whole of it for a UUID, which is what the customer id is by construction.
32
+ * The SEARCH text is different in kind — it is whatever a person typed — so it also needs the LIKE
33
+ * metacharacters neutralised; see {@link DealSearchClause}.
34
+ */
35
+ function quote(value) {
36
+ return value.replace(/'/g, "''");
37
+ }
38
+ /**
39
+ * Neutralise a user's text for a `LIKE` pattern.
40
+ *
41
+ * `%` and `_` are wildcards and `[` opens a character class, so a customer named `A_B` typed verbatim
42
+ * matches `AxB` too. T-SQL has no backslash escape by default; the portable form is to bracket each
43
+ * metacharacter, which means what it says in every collation and needs no `ESCAPE` clause.
44
+ */
45
+ function likeLiteral(value) {
46
+ return quote(value).replace(/[%_[]/g, (c) => `[${c}]`);
47
+ }
48
+ /**
49
+ * The filter that decides WHICH deals the picker offers.
50
+ *
51
+ * Two modes, and the difference matters more than it looks:
52
+ *
53
+ * · **NOTHING TYPED — the customer's own deals.** This is the seeded list, and it is the case the
54
+ * story is really about: a finance user papering a contract for Acme wants Acme's deals, not a
55
+ * page of whatever sorted first. Issue #219 asks the lookup to "ideally prefer deals for the
56
+ * contract's customer organization"; seeding it this way makes the common case zero-typing.
57
+ * · **TEXT TYPED — search the whole book, by deal or by customer.** Once a person types they are
58
+ * looking for something the seeded list did not contain, so staying scoped to one customer would
59
+ * silently answer a different question than the one asked. #219 states the search as "by name and
60
+ * customer", which is `Name`, `DealNumber` and the joined `Account`.
61
+ *
62
+ * AN ABSENT CUSTOMER WITH NO SEARCH TEXT OFFERS NOTHING, NOT EVERYTHING — the same direction, and the
63
+ * same reasoning, as `SameCustomerClause`. `CustomerOrganizationID` is NOT NULL on Contract, so a
64
+ * missing value never means "this contract has no customer"; it means the record could not be read.
65
+ * Opening the picker to every deal in the database at exactly that moment is the failure worth
66
+ * refusing, and a person who genuinely wants the whole book can type.
67
+ *
68
+ * The CUSTOMER LINK IS DIRECT, which is easy to disbelieve and is verified: sales' `SalesAccount`
69
+ * shares its primary key with `__mj_BizAppsCommon.Organization` (`FK_SalesAccount_Organization` is on
70
+ * `ID` itself), so a deal's `AccountID` IS an organization id and compares to the contract's
71
+ * `CustomerOrganizationID` with no bridge table.
72
+ */
73
+ export function DealSearchClause(customerOrganizationID, searchText) {
74
+ const search = (searchText ?? '').trim();
75
+ if (search) {
76
+ const like = `%${likeLiteral(search)}%`;
77
+ return `(Name LIKE '${like}' OR DealNumber LIKE '${like}' OR Account LIKE '${like}')`;
78
+ }
79
+ const customer = (customerOrganizationID ?? '').trim();
80
+ if (!customer)
81
+ return '1 = 0';
82
+ return `AccountID = '${quote(customer)}'`;
83
+ }
84
+ /**
85
+ * A clause matching ONE deal by id — the currently linked one.
86
+ *
87
+ * The seeded list is the customer's deals, and the deal a contract is already pointed at need not be
88
+ * among them: Close Won writes the pair from sales, where the deal's account and the contract's
89
+ * customer are two records a person maintains separately, and a re-pointed contract may name a deal
90
+ * for a related entity entirely. Without this the picker renders BLANK beside a contract that plainly
91
+ * has a source, which reads as "no deal" — the exact confusion #219 is about.
92
+ *
93
+ * ORed into the seeded filter rather than fetched separately so the list arrives in one read and the
94
+ * selected option can never flicker in after the dropdown is already open.
95
+ */
96
+ export function DealIDClause(dealID) {
97
+ const id = (dealID ?? '').trim();
98
+ if (!id)
99
+ return '1 = 0';
100
+ return `ID = '${quote(id)}'`;
101
+ }
102
+ /**
103
+ * The option label: `<DealNumber> — <Name> (<Account>)`.
104
+ *
105
+ * Every part earns its place. The NUMBER is what a person quotes in an email; the NAME is what they
106
+ * recognise; the CUSTOMER disambiguates the renewals, which are frequently named identically across
107
+ * accounts ("2026 Renewal" is not one deal). The customer is also the reason the parenthetical is
108
+ * kept when searching widens past one account — without it the widened list is a column of
109
+ * indistinguishable names.
110
+ *
111
+ * Each part is TRIMMED before its emptiness test, because blank strings are common on imported rows
112
+ * and a label reading `— ( )` looks like broken software rather than a sparse record. A deal with no
113
+ * number loses the prefix rather than rendering a dangling dash, and one with no name falls back to
114
+ * the number so the option is never blank.
115
+ */
116
+ export function DealOptionLabel(deal) {
117
+ const number = (deal.DealNumber ?? '').trim();
118
+ const name = (deal.Name ?? '').trim();
119
+ const account = (deal.Account ?? '').trim();
120
+ const head = number && name ? `${number} — ${name}` : name || number || 'Deal';
121
+ return account ? `${head} (${account})` : head;
122
+ }
123
+ //# sourceMappingURL=source-record-candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-record-candidates.js","sourceRoot":"","sources":["../src/source-record-candidates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,KAAa;IACxB,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAAC,KAAa;IAC9B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,sBAAiD,EAAE,UAA0B;IAC1G,MAAM,MAAM,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC;QACxC,OAAO,eAAe,IAAI,yBAAyB,IAAI,sBAAsB,IAAI,IAAI,CAAC;IAC1F,CAAC;IACD,MAAM,QAAQ,GAAG,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC;IAC9B,OAAO,gBAAgB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiC;IAC1D,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC;IACxB,OAAO,SAAS,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,IAAgB;IAC5C,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAE5C,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC;IAC/E,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mj-biz-apps/contracts-entities",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "Generated entity subclasses for the app schema. TODO(template): rename package + description.",
6
6
  "main": "dist/index.js",