@mj-biz-apps/contracts-ng 0.4.2 → 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.
- package/dist/lib/data/entity-names.d.ts +25 -0
- package/dist/lib/data/entity-names.d.ts.map +1 -1
- package/dist/lib/data/entity-names.js +25 -0
- package/dist/lib/data/entity-names.js.map +1 -1
- package/dist/lib/data/task-filters.d.ts +66 -0
- package/dist/lib/data/task-filters.d.ts.map +1 -0
- package/dist/lib/data/task-filters.js +124 -0
- package/dist/lib/data/task-filters.js.map +1 -0
- package/dist/lib/form-panels/contract-form.panels.d.ts +80 -1
- package/dist/lib/form-panels/contract-form.panels.d.ts.map +1 -1
- package/dist/lib/form-panels/contract-form.panels.js +150 -10
- package/dist/lib/form-panels/contract-form.panels.js.map +1 -1
- package/dist/lib/form-panels/contract.panels.d.ts +67 -3
- package/dist/lib/form-panels/contract.panels.d.ts.map +1 -1
- package/dist/lib/form-panels/contract.panels.js +55 -2
- package/dist/lib/form-panels/contract.panels.js.map +1 -1
- package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.js +25 -11
- package/dist/lib/generated/Entities/mjBizAppsContractsContractType/mjbizappscontractscontracttype.form.component.js.map +1 -1
- package/dist/lib/pages/contract-grid.page.d.ts +67 -0
- package/dist/lib/pages/contract-grid.page.d.ts.map +1 -1
- package/dist/lib/pages/contract-grid.page.js +147 -5
- package/dist/lib/pages/contract-grid.page.js.map +1 -1
- package/dist/lib/pages/contracts-dashboard.page.d.ts +132 -52
- package/dist/lib/pages/contracts-dashboard.page.d.ts.map +1 -1
- package/dist/lib/pages/contracts-dashboard.page.js +418 -425
- package/dist/lib/pages/contracts-dashboard.page.js.map +1 -1
- package/dist/lib/sections/contracts-sections.component.d.ts +25 -3
- package/dist/lib/sections/contracts-sections.component.d.ts.map +1 -1
- package/dist/lib/sections/contracts-sections.component.js +56 -9
- package/dist/lib/sections/contracts-sections.component.js.map +1 -1
- package/package.json +3 -2
|
@@ -42,5 +42,30 @@ export declare const MJC_FOREIGN_ENTITIES: {
|
|
|
42
42
|
* picker does not render — a supported state, not a failure.
|
|
43
43
|
*/
|
|
44
44
|
readonly Deal: "MJ_BizApps_Sales: Deals";
|
|
45
|
+
/**
|
|
46
|
+
* bizapps-tasks. Read for the "To process" tile and the "Has open task" pill — the finance flow
|
|
47
|
+
* (S-US2 / C-US2) is task-driven, which is why `mj-app.json` declares tasks a real dependency
|
|
48
|
+
* rather than an optional read.
|
|
49
|
+
*
|
|
50
|
+
* ⚠ These belong in `@mj-biz-apps/tasks-entities` and are here only because that package exports
|
|
51
|
+
* generated entity subclasses and nothing else — the names live as private consts scattered
|
|
52
|
+
* across four repos today. When tasks exports them, delete these three and import instead.
|
|
53
|
+
*/
|
|
54
|
+
readonly Task: "MJ_BizApps_Tasks: Tasks";
|
|
55
|
+
readonly TaskLink: "MJ_BizApps_Tasks: Task Links";
|
|
56
|
+
readonly TaskType: "MJ_BizApps_Tasks: Task Types";
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Stored queries this app owns, addressed by name the way entities are.
|
|
60
|
+
*
|
|
61
|
+
* `RunQuery` resolves by NAME plus category path, so these strings carry the same typo hazard the
|
|
62
|
+
* entity names do — wrong by one character and the tile renders an em dash at runtime with nothing
|
|
63
|
+
* failing to compile. The rows live in `metadata/queries/`; the category in `metadata/query-categories/`.
|
|
64
|
+
*/
|
|
65
|
+
export declare const MJC_QUERIES: {
|
|
66
|
+
/** Distinct customers with an in-force modified agreement, and how many contracts that spans. */
|
|
67
|
+
readonly SpecialTerms: "Contracts Special Terms";
|
|
45
68
|
};
|
|
69
|
+
/** The category every query above hangs under — `RunQuery`'s `CategoryPath`. */
|
|
70
|
+
export declare const MJC_QUERY_CATEGORY = "Contracts";
|
|
46
71
|
//# sourceMappingURL=entity-names.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-names.d.ts","sourceRoot":"","sources":["../../../src/lib/data/entity-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kCAAkC;AAClC,eAAO,MAAM,YAAY;;;;;;;CAOf,CAAC;AAEX,iGAAiG;AACjG,eAAO,MAAM,oBAAoB;;;IAG7B,sFAAsF;;IAEtF,4GAA4G;;;;IAI5G;;;;;;;;;;;OAWG;;
|
|
1
|
+
{"version":3,"file":"entity-names.d.ts","sourceRoot":"","sources":["../../../src/lib/data/entity-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kCAAkC;AAClC,eAAO,MAAM,YAAY;;;;;;;CAOf,CAAC;AAEX,iGAAiG;AACjG,eAAO,MAAM,oBAAoB;;;IAG7B,sFAAsF;;IAEtF,4GAA4G;;;;IAI5G;;;;;;;;;;;OAWG;;IAEH;;;;;;;;OAQG;;;;CAIG,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;IACpB,iGAAiG;;CAE3F,CAAC;AAEX,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,cAAc,CAAC"}
|
|
@@ -42,5 +42,30 @@ export const MJC_FOREIGN_ENTITIES = {
|
|
|
42
42
|
* picker does not render — a supported state, not a failure.
|
|
43
43
|
*/
|
|
44
44
|
Deal: 'MJ_BizApps_Sales: Deals',
|
|
45
|
+
/**
|
|
46
|
+
* bizapps-tasks. Read for the "To process" tile and the "Has open task" pill — the finance flow
|
|
47
|
+
* (S-US2 / C-US2) is task-driven, which is why `mj-app.json` declares tasks a real dependency
|
|
48
|
+
* rather than an optional read.
|
|
49
|
+
*
|
|
50
|
+
* ⚠ These belong in `@mj-biz-apps/tasks-entities` and are here only because that package exports
|
|
51
|
+
* generated entity subclasses and nothing else — the names live as private consts scattered
|
|
52
|
+
* across four repos today. When tasks exports them, delete these three and import instead.
|
|
53
|
+
*/
|
|
54
|
+
Task: 'MJ_BizApps_Tasks: Tasks',
|
|
55
|
+
TaskLink: 'MJ_BizApps_Tasks: Task Links',
|
|
56
|
+
TaskType: 'MJ_BizApps_Tasks: Task Types',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Stored queries this app owns, addressed by name the way entities are.
|
|
60
|
+
*
|
|
61
|
+
* `RunQuery` resolves by NAME plus category path, so these strings carry the same typo hazard the
|
|
62
|
+
* entity names do — wrong by one character and the tile renders an em dash at runtime with nothing
|
|
63
|
+
* failing to compile. The rows live in `metadata/queries/`; the category in `metadata/query-categories/`.
|
|
64
|
+
*/
|
|
65
|
+
export const MJC_QUERIES = {
|
|
66
|
+
/** Distinct customers with an in-force modified agreement, and how many contracts that spans. */
|
|
67
|
+
SpecialTerms: 'Contracts Special Terms',
|
|
45
68
|
};
|
|
69
|
+
/** The category every query above hangs under — `RunQuery`'s `CategoryPath`. */
|
|
70
|
+
export const MJC_QUERY_CATEGORY = 'Contracts';
|
|
46
71
|
//# sourceMappingURL=entity-names.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-names.js","sourceRoot":"","sources":["../../../src/lib/data/entity-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kCAAkC;AAClC,MAAM,CAAC,MAAM,YAAY,GAAG;IACxB,QAAQ,EAAE,iCAAiC;IAC3C,YAAY,EAAE,sCAAsC;IACpD,gBAAgB,EAAE,0CAA0C;IAC5D,oBAAoB,EAAE,+CAA+C;IACrE,yBAAyB,EAAE,oDAAoD;IAC/E,4BAA4B,EAAE,uDAAuD;CAC/E,CAAC;AAEX,iGAAiG;AACjG,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAChC,YAAY,EAAE,kCAAkC;IAChD,MAAM,EAAE,2BAA2B;IACnC,sFAAsF;IACtF,OAAO,EAAE,eAAe;IACxB,4GAA4G;IAC5G,IAAI,EAAE,WAAW;IACjB,YAAY,EAAE,qBAAqB;IACnC,oBAAoB,EAAE,8BAA8B;IACpD;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,yBAAyB;
|
|
1
|
+
{"version":3,"file":"entity-names.js","sourceRoot":"","sources":["../../../src/lib/data/entity-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kCAAkC;AAClC,MAAM,CAAC,MAAM,YAAY,GAAG;IACxB,QAAQ,EAAE,iCAAiC;IAC3C,YAAY,EAAE,sCAAsC;IACpD,gBAAgB,EAAE,0CAA0C;IAC5D,oBAAoB,EAAE,+CAA+C;IACrE,yBAAyB,EAAE,oDAAoD;IAC/E,4BAA4B,EAAE,uDAAuD;CAC/E,CAAC;AAEX,iGAAiG;AACjG,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAChC,YAAY,EAAE,kCAAkC;IAChD,MAAM,EAAE,2BAA2B;IACnC,sFAAsF;IACtF,OAAO,EAAE,eAAe;IACxB,4GAA4G;IAC5G,IAAI,EAAE,WAAW;IACjB,YAAY,EAAE,qBAAqB;IACnC,oBAAoB,EAAE,8BAA8B;IACpD;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,yBAAyB;IAC/B;;;;;;;;OAQG;IACH,IAAI,EAAE,yBAAyB;IAC/B,QAAQ,EAAE,8BAA8B;IACxC,QAAQ,EAAE,8BAA8B;CAClC,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACvB,iGAAiG;IACjG,YAAY,EAAE,yBAAyB;CACjC,CAAC;AAEX,gFAAgF;AAChF,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The one definition of "this contract has an open processing task".
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS A FILE AND NOT TWO INLINE STRINGS. The dashboard's "To process" tile and the All
|
|
5
|
+
* Contracts "Has open task" pill must count the same rows — the tile's entire promise is that
|
|
6
|
+
* clicking it lands on the list it just counted. Written twice they agree until one is edited, and
|
|
7
|
+
* the failure is silent: a tile saying 7 over a list showing 5 reads as a stale page rather than a
|
|
8
|
+
* bug. One exported builder, two callers.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a correlated subquery rather than three reads
|
|
11
|
+
*
|
|
12
|
+
* Tasks live in ANOTHER APP's schema, and `Task Links` carries `EntityID` / `RecordID` while the
|
|
13
|
+
* status and due date live on `Tasks`. There is no single view to filter, so the alternatives are a
|
|
14
|
+
* SQL `EXISTS` inside `ExtraFilter`, or reading task ids into memory and passing `ID IN (…)`. The
|
|
15
|
+
* second is not a worklist filter — an `IN` list has to be capped, and a capped filter quietly hides
|
|
16
|
+
* work, which is the one thing a work queue must not do.
|
|
17
|
+
*
|
|
18
|
+
* `ExtraFilter` is raw SQL appended to the base view's WHERE, so the subquery is legal. MJ screens
|
|
19
|
+
* it with a keyword denylist (`DatabaseProviderBase.ValidateUserProvidedSQLClause`) covering
|
|
20
|
+
* `insert / update / delete / exec / drop / -- / union / xp_ / ; / waitfor` — `SELECT`, `EXISTS` and
|
|
21
|
+
* `JOIN` are not on it. (The stricter validator that DOES block `EXISTS` governs aggregate
|
|
22
|
+
* expressions, not filters.) The base view is selected without an alias, so `vwContracts.ID` is a
|
|
23
|
+
* legal correlated reference.
|
|
24
|
+
*
|
|
25
|
+
* ## ⚠ Two consequences worth knowing before this pattern spreads
|
|
26
|
+
*
|
|
27
|
+
* **It bypasses MJ's permission and RLS model for Tasks.** MJ applies entity permissions and row
|
|
28
|
+
* filters to the entity being *read* — here, Contracts. Nothing checks the reader against Tasks, so
|
|
29
|
+
* a user with no access to Tasks still gets a count shaped by them. That is an acceptable trade for
|
|
30
|
+
* a COUNT of the reader's own contracts, and it is the reason not to extend this to reading task
|
|
31
|
+
* rows: the moment task content reaches the screen, it must go through a Tasks read.
|
|
32
|
+
*
|
|
33
|
+
* **Nothing here is hardcoded except the type code.** Schema and view names come from MJ's metadata
|
|
34
|
+
* rather than string literals, so an app that is not installed produces `null` (and a dashboard tile
|
|
35
|
+
* that renders "—") instead of SQL naming a schema that does not exist.
|
|
36
|
+
*
|
|
37
|
+
* @module @mj-biz-apps/contracts-ng
|
|
38
|
+
*/
|
|
39
|
+
import { type IMetadataProvider } from '@memberjunction/core';
|
|
40
|
+
/**
|
|
41
|
+
* The task type sales raises on Close-Won for finance to work through.
|
|
42
|
+
*
|
|
43
|
+
* Addressed by CODE, not by name or id: the row is seeded by bizapps-sales
|
|
44
|
+
* (`metadata/task-types/.task-types.json`) and `CloseWonTaskService` writes this exact code, so the
|
|
45
|
+
* code is the contract between the two apps. A display name is content and can be edited; an id
|
|
46
|
+
* differs per database.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MJC_TASK_CODE_CONTRACT_PROCESSING = "CONTRACT_PROCESSING";
|
|
49
|
+
/** The two filter fragments the dashboard and the pill share. */
|
|
50
|
+
export interface MJCOpenTaskFilters {
|
|
51
|
+
/** Contracts with at least one open Contract Processing task. */
|
|
52
|
+
HasOpenTask: string;
|
|
53
|
+
/** The same, narrowed to tasks whose due date has passed. */
|
|
54
|
+
HasOverdueTask: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the shared fragments, or `null` when they cannot be built.
|
|
58
|
+
*
|
|
59
|
+
* `null` is returned — rather than a filter that matches nothing — when bizapps-tasks is not
|
|
60
|
+
* installed, or when the CONTRACT_PROCESSING type row is missing. The distinction matters at the
|
|
61
|
+
* call site: a filter matching nothing renders a confident `0`, while `null` lets the caller render
|
|
62
|
+
* "—". "We cannot tell" and "there is no work" are different answers, and only one of them is safe
|
|
63
|
+
* to show a person whose job is working the queue.
|
|
64
|
+
*/
|
|
65
|
+
export declare function BuildOpenTaskFilters(provider?: IMetadataProvider | null): Promise<MJCOpenTaskFilters | null>;
|
|
66
|
+
//# sourceMappingURL=task-filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-filters.d.ts","sourceRoot":"","sources":["../../../src/lib/data/task-filters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAY,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAIxE;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,wBAAwB,CAAC;AAKvE,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IAC/B,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACtC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACpC,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAqCpC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The one definition of "this contract has an open processing task".
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS IS A FILE AND NOT TWO INLINE STRINGS. The dashboard's "To process" tile and the All
|
|
5
|
+
* Contracts "Has open task" pill must count the same rows — the tile's entire promise is that
|
|
6
|
+
* clicking it lands on the list it just counted. Written twice they agree until one is edited, and
|
|
7
|
+
* the failure is silent: a tile saying 7 over a list showing 5 reads as a stale page rather than a
|
|
8
|
+
* bug. One exported builder, two callers.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a correlated subquery rather than three reads
|
|
11
|
+
*
|
|
12
|
+
* Tasks live in ANOTHER APP's schema, and `Task Links` carries `EntityID` / `RecordID` while the
|
|
13
|
+
* status and due date live on `Tasks`. There is no single view to filter, so the alternatives are a
|
|
14
|
+
* SQL `EXISTS` inside `ExtraFilter`, or reading task ids into memory and passing `ID IN (…)`. The
|
|
15
|
+
* second is not a worklist filter — an `IN` list has to be capped, and a capped filter quietly hides
|
|
16
|
+
* work, which is the one thing a work queue must not do.
|
|
17
|
+
*
|
|
18
|
+
* `ExtraFilter` is raw SQL appended to the base view's WHERE, so the subquery is legal. MJ screens
|
|
19
|
+
* it with a keyword denylist (`DatabaseProviderBase.ValidateUserProvidedSQLClause`) covering
|
|
20
|
+
* `insert / update / delete / exec / drop / -- / union / xp_ / ; / waitfor` — `SELECT`, `EXISTS` and
|
|
21
|
+
* `JOIN` are not on it. (The stricter validator that DOES block `EXISTS` governs aggregate
|
|
22
|
+
* expressions, not filters.) The base view is selected without an alias, so `vwContracts.ID` is a
|
|
23
|
+
* legal correlated reference.
|
|
24
|
+
*
|
|
25
|
+
* ## ⚠ Two consequences worth knowing before this pattern spreads
|
|
26
|
+
*
|
|
27
|
+
* **It bypasses MJ's permission and RLS model for Tasks.** MJ applies entity permissions and row
|
|
28
|
+
* filters to the entity being *read* — here, Contracts. Nothing checks the reader against Tasks, so
|
|
29
|
+
* a user with no access to Tasks still gets a count shaped by them. That is an acceptable trade for
|
|
30
|
+
* a COUNT of the reader's own contracts, and it is the reason not to extend this to reading task
|
|
31
|
+
* rows: the moment task content reaches the screen, it must go through a Tasks read.
|
|
32
|
+
*
|
|
33
|
+
* **Nothing here is hardcoded except the type code.** Schema and view names come from MJ's metadata
|
|
34
|
+
* rather than string literals, so an app that is not installed produces `null` (and a dashboard tile
|
|
35
|
+
* that renders "—") instead of SQL naming a schema that does not exist.
|
|
36
|
+
*
|
|
37
|
+
* @module @mj-biz-apps/contracts-ng
|
|
38
|
+
*/
|
|
39
|
+
import { Metadata } from '@memberjunction/core';
|
|
40
|
+
import { MJC_ENTITIES, MJC_FOREIGN_ENTITIES } from './entity-names';
|
|
41
|
+
import { ScopedRunView } from './provider';
|
|
42
|
+
/**
|
|
43
|
+
* The task type sales raises on Close-Won for finance to work through.
|
|
44
|
+
*
|
|
45
|
+
* Addressed by CODE, not by name or id: the row is seeded by bizapps-sales
|
|
46
|
+
* (`metadata/task-types/.task-types.json`) and `CloseWonTaskService` writes this exact code, so the
|
|
47
|
+
* code is the contract between the two apps. A display name is content and can be edited; an id
|
|
48
|
+
* differs per database.
|
|
49
|
+
*/
|
|
50
|
+
export const MJC_TASK_CODE_CONTRACT_PROCESSING = 'CONTRACT_PROCESSING';
|
|
51
|
+
/** Statuses that mean the task is done with, in the Tasks app's own vocabulary. */
|
|
52
|
+
const CLOSED_TASK_STATUSES = ['Completed', 'Cancelled'];
|
|
53
|
+
/**
|
|
54
|
+
* Build the shared fragments, or `null` when they cannot be built.
|
|
55
|
+
*
|
|
56
|
+
* `null` is returned — rather than a filter that matches nothing — when bizapps-tasks is not
|
|
57
|
+
* installed, or when the CONTRACT_PROCESSING type row is missing. The distinction matters at the
|
|
58
|
+
* call site: a filter matching nothing renders a confident `0`, while `null` lets the caller render
|
|
59
|
+
* "—". "We cannot tell" and "there is no work" are different answers, and only one of them is safe
|
|
60
|
+
* to show a person whose job is working the queue.
|
|
61
|
+
*/
|
|
62
|
+
export async function BuildOpenTaskFilters(provider) {
|
|
63
|
+
const entities = (provider ?? Metadata.Provider)?.Entities ?? [];
|
|
64
|
+
const contracts = entities.find((e) => e.Name === MJC_ENTITIES.Contract);
|
|
65
|
+
const taskLinks = entities.find((e) => e.Name === MJC_FOREIGN_ENTITIES.TaskLink);
|
|
66
|
+
const tasks = entities.find((e) => e.Name === MJC_FOREIGN_ENTITIES.Task);
|
|
67
|
+
// Any of these missing means the Tasks app is not on this host. Not an error — contracts is
|
|
68
|
+
// installable without it, and the tile simply has nothing to say.
|
|
69
|
+
if (!contracts?.ID || !taskLinks?.BaseView || !tasks?.BaseView)
|
|
70
|
+
return null;
|
|
71
|
+
const typeID = await resolveContractProcessingTypeID(provider);
|
|
72
|
+
if (!typeID)
|
|
73
|
+
return null;
|
|
74
|
+
const linkView = qualified(taskLinks.SchemaName, taskLinks.BaseView);
|
|
75
|
+
const taskView = qualified(tasks.SchemaName, tasks.BaseView);
|
|
76
|
+
const contractView = qualified(contracts.SchemaName, contracts.BaseView);
|
|
77
|
+
const closed = CLOSED_TASK_STATUSES.map((s) => `'${s}'`).join(',');
|
|
78
|
+
/**
|
|
79
|
+
* `RecordID` is nvarchar — it holds a primary key of any shape, since a link can point at any
|
|
80
|
+
* entity — so the contract's uniqueidentifier is CAST rather than compared directly. Without the
|
|
81
|
+
* cast SQL Server applies its own conversion and the comparison becomes non-sargable at best.
|
|
82
|
+
*/
|
|
83
|
+
const exists = (extra) => `EXISTS (SELECT 1 FROM ${linkView} tl ` +
|
|
84
|
+
`INNER JOIN ${taskView} t ON t.ID = tl.TaskID ` +
|
|
85
|
+
`WHERE tl.RecordID = CAST(${contractView}.ID AS nvarchar(255)) ` +
|
|
86
|
+
`AND tl.EntityID = '${contracts.ID}' ` +
|
|
87
|
+
`AND t.TypeID = '${typeID}' ` +
|
|
88
|
+
`AND t.Status NOT IN (${closed})${extra})`;
|
|
89
|
+
return {
|
|
90
|
+
HasOpenTask: exists(''),
|
|
91
|
+
// Date, not datetime: "overdue" is a whole-day judgement, and comparing against the current
|
|
92
|
+
// instant would call a task due later today overdue all morning.
|
|
93
|
+
HasOverdueTask: exists(' AND t.DueAt < CAST(GETUTCDATE() AS date)'),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Code → id, once.
|
|
98
|
+
*
|
|
99
|
+
* The Tasks base view exposes the type's NAME but not its CODE, so the id cannot be reached by
|
|
100
|
+
* filtering Tasks directly — it takes a read against Task Types. Comparing on the name instead would
|
|
101
|
+
* make an editable label load-bearing.
|
|
102
|
+
*/
|
|
103
|
+
async function resolveContractProcessingTypeID(provider) {
|
|
104
|
+
try {
|
|
105
|
+
const result = await ScopedRunView(provider).RunView({
|
|
106
|
+
EntityName: MJC_FOREIGN_ENTITIES.TaskType,
|
|
107
|
+
ExtraFilter: `Code = '${MJC_TASK_CODE_CONTRACT_PROCESSING}'`,
|
|
108
|
+
ResultType: 'simple',
|
|
109
|
+
Fields: ['ID'],
|
|
110
|
+
MaxRows: 1,
|
|
111
|
+
});
|
|
112
|
+
return result?.Success ? (result.Results?.[0]?.ID ?? null) : null;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// The entity exists but could not be read — same answer as "not installed" from here, and the
|
|
116
|
+
// caller renders "—" either way.
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** `[schema].[view]`, with the schema omitted when metadata does not carry one. */
|
|
121
|
+
function qualified(schema, view) {
|
|
122
|
+
return schema ? `[${schema}].[${view}]` : `[${view}]`;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=task-filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-filters.js","sourceRoot":"","sources":["../../../src/lib/data/task-filters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Contract form body — Overview plus organized rail sections.
|
|
3
|
+
*
|
|
4
|
+
* Replaces leftover generated field dumps (Contract Details, Stakeholders, Provenance,
|
|
5
|
+
* Lifecycle, Notes) with:
|
|
6
|
+
* Overview (exec briefing) · Agreement · Parties · Dates · Renewal · Notes
|
|
7
|
+
* Modifications · Lineage · Re-papering · Provenance
|
|
8
|
+
*
|
|
9
|
+
* Dates / Renewal / Lineage / Re-papering already live in sibling files.
|
|
10
|
+
* Files on a contract are MJ's stock attachments (form toolbar), not a custom Documents section.
|
|
11
|
+
* `contributionKey` equals each panel's `SectionKey`. Overview is the only
|
|
12
|
+
* `inclusion: 'Primary'` so it leads; the rest sort into the related band by sortKey.
|
|
13
|
+
*
|
|
14
|
+
* @module @mj-biz-apps/contracts-ng
|
|
15
|
+
*/
|
|
16
|
+
import { type OnInit } from '@angular/core';
|
|
1
17
|
import { BaseFormPanel } from '@memberjunction/ng-base-forms';
|
|
2
18
|
import type { AfterDataLoadEventArgs } from '@memberjunction/ng-entity-viewer';
|
|
3
19
|
import { ContractEntity, type ContractState, type DealOption } from '@mj-biz-apps/contracts-entities';
|
|
@@ -57,8 +73,71 @@ export declare class MJCContractOverviewPanel extends BaseFormPanel<ContractEnti
|
|
|
57
73
|
static ɵfac: i0.ɵɵFactoryDeclaration<MJCContractOverviewPanel, never>;
|
|
58
74
|
static ɵcmp: i0.ɵɵComponentDeclaration<MJCContractOverviewPanel, "mjc-contract-overview-panel", never, {}, {}, never, never, true, never>;
|
|
59
75
|
}
|
|
60
|
-
export declare class MJCContractAgreementPanel extends BaseFormPanel<ContractEntity> {
|
|
76
|
+
export declare class MJCContractAgreementPanel extends BaseFormPanel<ContractEntity> implements OnInit {
|
|
77
|
+
private readonly cdr;
|
|
61
78
|
readonly Fields: ContractFieldSpec[];
|
|
79
|
+
/**
|
|
80
|
+
* The template ID this panel last put in the field — how it tells its own default from a choice.
|
|
81
|
+
*
|
|
82
|
+
* Session-local on purpose: it is never read from the record, because the record cannot say who
|
|
83
|
+
* wrote a value into it. See `MayWriteDefaultTemplate`, which owns what it means.
|
|
84
|
+
*/
|
|
85
|
+
private lastDefaultedID;
|
|
86
|
+
/**
|
|
87
|
+
* A contract that arrives with its type already set — Explorer's `NewRecordValues`, or a New
|
|
88
|
+
* clicked from a type-scoped list — gets its template here, before the form is ever drawn.
|
|
89
|
+
*
|
|
90
|
+
* The commoner path is a person choosing the type by hand, which never reaches `ngOnInit` and is
|
|
91
|
+
* why `OnFieldValueChange` exists too. Both call the same method; neither is sufficient alone.
|
|
92
|
+
*/
|
|
93
|
+
ngOnInit(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Re-decide the template whenever the CONTRACT TYPE changes (golive #218).
|
|
96
|
+
*
|
|
97
|
+
* `mj-form-field` emits this from its value setter, so it fires for a person's edit and NOT for
|
|
98
|
+
* the programmatic assignment below — writing `Record.ContractTemplateID` directly bypasses the
|
|
99
|
+
* setter. That asymmetry is what keeps this from re-entering itself.
|
|
100
|
+
*/
|
|
101
|
+
OnFieldValueChange(event: {
|
|
102
|
+
FieldName: string;
|
|
103
|
+
}): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Put the current standard-terms version in the field, if we are allowed to.
|
|
106
|
+
*
|
|
107
|
+
* THE CHEAP HALF OF THE RULE RUNS FIRST. `RecordWantsDefaultTemplate` needs no database, and it
|
|
108
|
+
* is false for every saved contract — so opening an existing agreement costs no extra query at
|
|
109
|
+
* all, and the full rule is still evaluated in one place, on `ShouldDefaultTemplate`, before
|
|
110
|
+
* anything is written.
|
|
111
|
+
*/
|
|
112
|
+
private defaultTemplate;
|
|
113
|
+
/**
|
|
114
|
+
* Does the contract's current type say its terms live in a template — `TemplateRequired`?
|
|
115
|
+
*
|
|
116
|
+
* READ FROM THE FLAG, NEVER FROM THE TYPE'S NAME. The columns on `ContractType` exist precisely
|
|
117
|
+
* so no code branches on a name someone can rename; `ContractEntityServer` reads the same flag
|
|
118
|
+
* to decide whether to REQUIRE the template this defaults.
|
|
119
|
+
*
|
|
120
|
+
* THREE-WAY, AND THE THIRD CASE IS LOAD-BEARING. `false` now means "this type does not want a
|
|
121
|
+
* template", which WITHDRAWS one this panel defaulted — so a read that failed must not be folded
|
|
122
|
+
* into it. It was `boolean` while defaulting was the only outcome, where swallowing a failure as
|
|
123
|
+
* `false` merely declined to act; under the withdrawal it would quietly take away a default on
|
|
124
|
+
* the strength of a query that never answered.
|
|
125
|
+
*
|
|
126
|
+
* · `true` — the type requires a template
|
|
127
|
+
* · `false` — the type does not, or there is no type at all: both known answers
|
|
128
|
+
* · `null` — could not tell, and the caller does nothing in either direction
|
|
129
|
+
*/
|
|
130
|
+
private typeRequiresTemplate;
|
|
131
|
+
/**
|
|
132
|
+
* The newest Published, usable template — the one a new contract should start on.
|
|
133
|
+
*
|
|
134
|
+
* A FAILED OR EMPTY READ IS SILENT, and deliberately so. This is a convenience: the field stays
|
|
135
|
+
* empty, the picker still works, and the server still refuses a save with no template when the
|
|
136
|
+
* type demands one — which is the message the user needs and the one they would already get.
|
|
137
|
+
* Announcing "could not default a template" on a form nobody has filled in yet reports our own
|
|
138
|
+
* plumbing as the user's problem.
|
|
139
|
+
*/
|
|
140
|
+
private currentTemplateID;
|
|
62
141
|
static ɵfac: i0.ɵɵFactoryDeclaration<MJCContractAgreementPanel, never>;
|
|
63
142
|
static ɵcmp: i0.ɵɵComponentDeclaration<MJCContractAgreementPanel, "mjc-contract-agreement-panel", never, {}, {}, never, never, true, never>;
|
|
64
143
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-form.panels.d.ts","sourceRoot":"","sources":["../../../src/lib/form-panels/contract-form.panels.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract-form.panels.d.ts","sourceRoot":"","sources":["../../../src/lib/form-panels/contract-form.panels.ts"],"names":[],"mappings":"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"}
|