@mj-biz-apps/contracts-ng 0.6.0 → 0.7.1
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/business-day.d.ts +82 -0
- package/dist/lib/data/business-day.d.ts.map +1 -0
- package/dist/lib/data/business-day.js +87 -0
- package/dist/lib/data/business-day.js.map +1 -0
- package/dist/lib/data/task-filters.d.ts.map +1 -1
- package/dist/lib/data/task-filters.js +18 -3
- package/dist/lib/data/task-filters.js.map +1 -1
- package/dist/lib/form-panels/contract-dates.d.ts +39 -0
- package/dist/lib/form-panels/contract-dates.d.ts.map +1 -0
- package/dist/lib/form-panels/contract-dates.js +71 -0
- package/dist/lib/form-panels/contract-dates.js.map +1 -0
- package/dist/lib/form-panels/contract-form.panels.d.ts +2 -2
- package/dist/lib/form-panels/contract-form.panels.d.ts.map +1 -1
- package/dist/lib/form-panels/contract-form.panels.js +42 -28
- package/dist/lib/form-panels/contract-form.panels.js.map +1 -1
- package/dist/lib/form-panels/contract.panels.d.ts +30 -1
- package/dist/lib/form-panels/contract.panels.d.ts.map +1 -1
- package/dist/lib/form-panels/contract.panels.js +87 -70
- package/dist/lib/form-panels/contract.panels.js.map +1 -1
- package/dist/lib/pages/contract-grid.page.d.ts.map +1 -1
- package/dist/lib/pages/contract-grid.page.js +14 -6
- package/dist/lib/pages/contract-grid.page.js.map +1 -1
- package/dist/lib/pages/contracts-dashboard.page.d.ts.map +1 -1
- package/dist/lib/pages/contracts-dashboard.page.js +8 -4
- package/dist/lib/pages/contracts-dashboard.page.js.map +1 -1
- package/dist/lib/sections/contracts-sections.component.d.ts.map +1 -1
- package/dist/lib/sections/contracts-sections.component.js +10 -7
- package/dist/lib/sections/contracts-sections.component.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The notice-window predicates, and "today" for the one column the view cannot answer.
|
|
3
|
+
*
|
|
4
|
+
* ## What was wrong
|
|
5
|
+
*
|
|
6
|
+
* `V202609211200` moved every date comparison in `vwContracts` onto the BUSINESS day — `bt.Today`,
|
|
7
|
+
* from bizapps-common's `fnBusinessToday()` (bc-aidp-next-golive#168). Every OTHER consumer of
|
|
8
|
+
* "today" in this package stayed on `CAST(GETUTCDATE() AS date)`, the SERVER'S UTC calendar day,
|
|
9
|
+
* which is already tomorrow for the whole American evening.
|
|
10
|
+
*
|
|
11
|
+
* Before that migration both sides were wrong and AGREED, so nothing looked broken. Afterwards they
|
|
12
|
+
* disagree for exactly the window #168 is about: at 6 PM Central `vwContracts` still reports notice
|
|
13
|
+
* time left on a contract that the "Notice window open" pill, the dashboard tile and the left-nav
|
|
14
|
+
* badge have already dropped, because their `>=` compares the deadline against tomorrow. A row
|
|
15
|
+
* visible in one place and absent from another reads as a stale page rather than as a defect.
|
|
16
|
+
*
|
|
17
|
+
* (6 PM is the CST roll — Central is UTC-6 in winter, UTC-5 on CDT, so the window opens at 7 PM for
|
|
18
|
+
* the daylight half of the year. Every "from 6 PM Central" in this package and in `V202609211200`
|
|
19
|
+
* means the same thing: the whole American evening, whichever hour it starts.)
|
|
20
|
+
*
|
|
21
|
+
* ## Why these predicates carry no date at all
|
|
22
|
+
*
|
|
23
|
+
* The obvious repair — read the business day on the client and paste it in — makes the two sides
|
|
24
|
+
* agree only as long as two clocks and one cached engine setting agree. The view already publishes
|
|
25
|
+
* its own answer: `DaysUntilNoticeDeadline` is `DATEDIFF(day, bt.Today, RenewalNoticeDeadline)`,
|
|
26
|
+
* computed in the same row, from the same `bt.Today`, as the `DaysToEnd` and `State` the screen
|
|
27
|
+
* prints beside it. Filtering on that column cannot disagree with the view, at any hour, on any
|
|
28
|
+
* clock, whatever zone the instance books in — and the day is re-derived on every query, exactly as
|
|
29
|
+
* `GETUTCDATE()` was, rather than frozen when a component initialised.
|
|
30
|
+
*
|
|
31
|
+
* It is also already the house pattern in this very file's callers: the Renewals page's first pill
|
|
32
|
+
* has always read `DaysToEnd IS NOT NULL AND DaysToEnd BETWEEN 0 AND 120`. The notice pills were the
|
|
33
|
+
* outliers, and the deadline's own day-count column did not exist until V202609202354 added it.
|
|
34
|
+
*
|
|
35
|
+
* `DaysUntilNoticeDeadline` is NULL under exactly the conditions `RenewalNoticeDeadline` is (no end
|
|
36
|
+
* date, or no notice period recorded), and every comparison below drops a NULL — so the explicit
|
|
37
|
+
* `IS NOT NULL` these predicates used to carry is preserved, not lost.
|
|
38
|
+
*
|
|
39
|
+
* @module @mj-biz-apps/contracts-ng
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* We can still give renewal notice in time — the deadline is today or later.
|
|
43
|
+
*
|
|
44
|
+
* ONE definition for the "Notice window open" pill and the nav badge's lower bound, for the reason
|
|
45
|
+
* `task-filters.ts` states at length about open tasks: a badge or tile that counts rows the page then
|
|
46
|
+
* fails to show reads as a stale screen, not as a bug.
|
|
47
|
+
*/
|
|
48
|
+
export declare const NOTICE_WINDOW_OPEN = "DaysUntilNoticeDeadline >= 0";
|
|
49
|
+
/** The deadline to give notice has gone by — surfaced rather than hidden. */
|
|
50
|
+
export declare const NOTICE_WINDOW_PASSED = "DaysUntilNoticeDeadline < 0";
|
|
51
|
+
/**
|
|
52
|
+
* The notice deadline falls within the next `days` days, today included.
|
|
53
|
+
*
|
|
54
|
+
* `BETWEEN 0 AND n` rather than a pair of dates: both ends are then offsets from the one `bt.Today`
|
|
55
|
+
* the row was computed with, so the window cannot straddle two different notions of today.
|
|
56
|
+
*/
|
|
57
|
+
export declare function NoticeWindowWithin(days: number): string;
|
|
58
|
+
/**
|
|
59
|
+
* Today on the BUSINESS calendar, as a scalar subquery for an `ExtraFilter`.
|
|
60
|
+
*
|
|
61
|
+
* FOR A COLUMN NO VIEW OF OURS DERIVES A DAY-COUNT FOR — in practice `Tasks.DueAt`, which lives in
|
|
62
|
+
* another app's view and is reached through a correlated `EXISTS` (see `task-filters.ts`). Prefer a
|
|
63
|
+
* derived day-count column wherever one exists; this is the fallback, not the default.
|
|
64
|
+
*
|
|
65
|
+
* IN SQL RATHER THAN ON THE CLIENT, deliberately, and it is the same choice as above for the same
|
|
66
|
+
* reason. `BusinessTimeZoneEngine.Instance.Today()` would read the day in the browser — from a cached
|
|
67
|
+
* engine that falls open to UTC until something calls `Config()`, which is the precise failure this
|
|
68
|
+
* is fixing, silently reintroduced; and the value would be fixed at the moment the filter string was
|
|
69
|
+
* built rather than at the moment the query runs. Evaluated here, "today" comes from the same
|
|
70
|
+
* function, the same configuration row and the same clock the view uses.
|
|
71
|
+
*
|
|
72
|
+
* `[__mj_BizAppsCommon]` is named literally because the schema is fixed by bizapps-common's own
|
|
73
|
+
* migration, and it is already a hard requirement of this app: `V202609211200` refuses to apply
|
|
74
|
+
* without this function, with a message saying so.
|
|
75
|
+
*
|
|
76
|
+
* An `ExtraFilter` is raw SQL appended to the base view's WHERE and MJ screens it with a keyword
|
|
77
|
+
* denylist (`insert / update / delete / exec / drop / -- / union / xp_ / ; / waitfor`). A scalar
|
|
78
|
+
* subquery trips none of it — this package already ships a whole correlated `EXISTS (SELECT 1 FROM …)`
|
|
79
|
+
* through the same seam.
|
|
80
|
+
*/
|
|
81
|
+
export declare const BUSINESS_TODAY_SQL = "(SELECT b.[Today] FROM [__mj_BizAppsCommon].[fnBusinessToday]() b)";
|
|
82
|
+
//# sourceMappingURL=business-day.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"business-day.d.ts","sourceRoot":"","sources":["../../../src/lib/data/business-day.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,iCAAiC,CAAC;AAEjE,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAElE;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,uEAAuE,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The notice-window predicates, and "today" for the one column the view cannot answer.
|
|
3
|
+
*
|
|
4
|
+
* ## What was wrong
|
|
5
|
+
*
|
|
6
|
+
* `V202609211200` moved every date comparison in `vwContracts` onto the BUSINESS day — `bt.Today`,
|
|
7
|
+
* from bizapps-common's `fnBusinessToday()` (bc-aidp-next-golive#168). Every OTHER consumer of
|
|
8
|
+
* "today" in this package stayed on `CAST(GETUTCDATE() AS date)`, the SERVER'S UTC calendar day,
|
|
9
|
+
* which is already tomorrow for the whole American evening.
|
|
10
|
+
*
|
|
11
|
+
* Before that migration both sides were wrong and AGREED, so nothing looked broken. Afterwards they
|
|
12
|
+
* disagree for exactly the window #168 is about: at 6 PM Central `vwContracts` still reports notice
|
|
13
|
+
* time left on a contract that the "Notice window open" pill, the dashboard tile and the left-nav
|
|
14
|
+
* badge have already dropped, because their `>=` compares the deadline against tomorrow. A row
|
|
15
|
+
* visible in one place and absent from another reads as a stale page rather than as a defect.
|
|
16
|
+
*
|
|
17
|
+
* (6 PM is the CST roll — Central is UTC-6 in winter, UTC-5 on CDT, so the window opens at 7 PM for
|
|
18
|
+
* the daylight half of the year. Every "from 6 PM Central" in this package and in `V202609211200`
|
|
19
|
+
* means the same thing: the whole American evening, whichever hour it starts.)
|
|
20
|
+
*
|
|
21
|
+
* ## Why these predicates carry no date at all
|
|
22
|
+
*
|
|
23
|
+
* The obvious repair — read the business day on the client and paste it in — makes the two sides
|
|
24
|
+
* agree only as long as two clocks and one cached engine setting agree. The view already publishes
|
|
25
|
+
* its own answer: `DaysUntilNoticeDeadline` is `DATEDIFF(day, bt.Today, RenewalNoticeDeadline)`,
|
|
26
|
+
* computed in the same row, from the same `bt.Today`, as the `DaysToEnd` and `State` the screen
|
|
27
|
+
* prints beside it. Filtering on that column cannot disagree with the view, at any hour, on any
|
|
28
|
+
* clock, whatever zone the instance books in — and the day is re-derived on every query, exactly as
|
|
29
|
+
* `GETUTCDATE()` was, rather than frozen when a component initialised.
|
|
30
|
+
*
|
|
31
|
+
* It is also already the house pattern in this very file's callers: the Renewals page's first pill
|
|
32
|
+
* has always read `DaysToEnd IS NOT NULL AND DaysToEnd BETWEEN 0 AND 120`. The notice pills were the
|
|
33
|
+
* outliers, and the deadline's own day-count column did not exist until V202609202354 added it.
|
|
34
|
+
*
|
|
35
|
+
* `DaysUntilNoticeDeadline` is NULL under exactly the conditions `RenewalNoticeDeadline` is (no end
|
|
36
|
+
* date, or no notice period recorded), and every comparison below drops a NULL — so the explicit
|
|
37
|
+
* `IS NOT NULL` these predicates used to carry is preserved, not lost.
|
|
38
|
+
*
|
|
39
|
+
* @module @mj-biz-apps/contracts-ng
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* We can still give renewal notice in time — the deadline is today or later.
|
|
43
|
+
*
|
|
44
|
+
* ONE definition for the "Notice window open" pill and the nav badge's lower bound, for the reason
|
|
45
|
+
* `task-filters.ts` states at length about open tasks: a badge or tile that counts rows the page then
|
|
46
|
+
* fails to show reads as a stale screen, not as a bug.
|
|
47
|
+
*/
|
|
48
|
+
export const NOTICE_WINDOW_OPEN = 'DaysUntilNoticeDeadline >= 0';
|
|
49
|
+
/** The deadline to give notice has gone by — surfaced rather than hidden. */
|
|
50
|
+
export const NOTICE_WINDOW_PASSED = 'DaysUntilNoticeDeadline < 0';
|
|
51
|
+
/**
|
|
52
|
+
* The notice deadline falls within the next `days` days, today included.
|
|
53
|
+
*
|
|
54
|
+
* `BETWEEN 0 AND n` rather than a pair of dates: both ends are then offsets from the one `bt.Today`
|
|
55
|
+
* the row was computed with, so the window cannot straddle two different notions of today.
|
|
56
|
+
*/
|
|
57
|
+
export function NoticeWindowWithin(days) {
|
|
58
|
+
if (!Number.isInteger(days) || days < 0) {
|
|
59
|
+
throw new RangeError(`NoticeWindowWithin expects a whole number of days >= 0, got ${String(days)}`);
|
|
60
|
+
}
|
|
61
|
+
return `DaysUntilNoticeDeadline BETWEEN 0 AND ${days}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Today on the BUSINESS calendar, as a scalar subquery for an `ExtraFilter`.
|
|
65
|
+
*
|
|
66
|
+
* FOR A COLUMN NO VIEW OF OURS DERIVES A DAY-COUNT FOR — in practice `Tasks.DueAt`, which lives in
|
|
67
|
+
* another app's view and is reached through a correlated `EXISTS` (see `task-filters.ts`). Prefer a
|
|
68
|
+
* derived day-count column wherever one exists; this is the fallback, not the default.
|
|
69
|
+
*
|
|
70
|
+
* IN SQL RATHER THAN ON THE CLIENT, deliberately, and it is the same choice as above for the same
|
|
71
|
+
* reason. `BusinessTimeZoneEngine.Instance.Today()` would read the day in the browser — from a cached
|
|
72
|
+
* engine that falls open to UTC until something calls `Config()`, which is the precise failure this
|
|
73
|
+
* is fixing, silently reintroduced; and the value would be fixed at the moment the filter string was
|
|
74
|
+
* built rather than at the moment the query runs. Evaluated here, "today" comes from the same
|
|
75
|
+
* function, the same configuration row and the same clock the view uses.
|
|
76
|
+
*
|
|
77
|
+
* `[__mj_BizAppsCommon]` is named literally because the schema is fixed by bizapps-common's own
|
|
78
|
+
* migration, and it is already a hard requirement of this app: `V202609211200` refuses to apply
|
|
79
|
+
* without this function, with a message saying so.
|
|
80
|
+
*
|
|
81
|
+
* An `ExtraFilter` is raw SQL appended to the base view's WHERE and MJ screens it with a keyword
|
|
82
|
+
* denylist (`insert / update / delete / exec / drop / -- / union / xp_ / ; / waitfor`). A scalar
|
|
83
|
+
* subquery trips none of it — this package already ships a whole correlated `EXISTS (SELECT 1 FROM …)`
|
|
84
|
+
* through the same seam.
|
|
85
|
+
*/
|
|
86
|
+
export const BUSINESS_TODAY_SQL = '(SELECT b.[Today] FROM [__mj_BizAppsCommon].[fnBusinessToday]() b)';
|
|
87
|
+
//# sourceMappingURL=business-day.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"business-day.js","sourceRoot":"","sources":["../../../src/lib/data/business-day.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,8BAA8B,CAAC;AAEjE,6EAA6E;AAC7E,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAElE;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,UAAU,CAAC,+DAA+D,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,yCAAyC,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,oEAAoE,CAAC"}
|
|
@@ -1 +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;
|
|
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;AAKxE;;;;;;;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,CAmDpC"}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
import { Metadata } from '@memberjunction/core';
|
|
40
40
|
import { MJC_ENTITIES, MJC_FOREIGN_ENTITIES } from './entity-names';
|
|
41
41
|
import { ScopedRunView } from './provider';
|
|
42
|
+
import { BUSINESS_TODAY_SQL } from './business-day';
|
|
42
43
|
/**
|
|
43
44
|
* The task type sales raises on Close-Won for finance to work through.
|
|
44
45
|
*
|
|
@@ -88,9 +89,23 @@ export async function BuildOpenTaskFilters(provider) {
|
|
|
88
89
|
`AND t.Status NOT IN (${closed})${extra})`;
|
|
89
90
|
return {
|
|
90
91
|
HasOpenTask: exists(''),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
/*
|
|
93
|
+
* Date, not datetime: "overdue" is a whole-day judgement, and comparing against the current
|
|
94
|
+
* instant would call a task due later today overdue all morning.
|
|
95
|
+
*
|
|
96
|
+
* And the BUSINESS day, not the server's UTC day (bc-aidp-next-golive#168). `Task.DueAt` is
|
|
97
|
+
* written as UTC MIDNIGHT of a calendar day — bizapps-sales asserts it carries no time of
|
|
98
|
+
* day, precisely so "due today" cannot depend on the reader's zone — so this is a comparison
|
|
99
|
+
* between calendar days and the only open question is which day is today. On the UTC day
|
|
100
|
+
* every task due today read as overdue from 6 PM Central, in the same dashboard tile whose
|
|
101
|
+
* sibling counts come from `vwContracts` on `bt.Today`.
|
|
102
|
+
*
|
|
103
|
+
* `DueAt` is the one column here with no business-day day-count to filter on: it belongs to
|
|
104
|
+
* another app's view and is reached through the `EXISTS` above. So "today" is resolved in
|
|
105
|
+
* SQL, from the same function the view joins — see `BUSINESS_TODAY_SQL` for why not on the
|
|
106
|
+
* client.
|
|
107
|
+
*/
|
|
108
|
+
HasOverdueTask: exists(` AND t.DueAt < ${BUSINESS_TODAY_SQL}`),
|
|
94
109
|
};
|
|
95
110
|
}
|
|
96
111
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-filters.js","sourceRoot":"","sources":["../../../src/lib/data/task-filters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,QAAQ,EAA0B,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"task-filters.js","sourceRoot":"","sources":["../../../src/lib/data/task-filters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The four DATE columns the dates panel edits. */
|
|
2
|
+
export type ContractDateField = 'ExecutedDate' | 'EffectiveDate' | 'EndDate' | 'TerminatedDate';
|
|
3
|
+
/**
|
|
4
|
+
* The `yyyy-MM-dd` an `<input type="date">` needs, or '' when there is nothing readable to show.
|
|
5
|
+
*
|
|
6
|
+
* Never throws and never re-bases: an offset-bearing stored string is the day it was written as, not
|
|
7
|
+
* the day it converts to in the reader's zone.
|
|
8
|
+
*/
|
|
9
|
+
export declare function DateAsInput(value: Date | string | null | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* UTC midnight of the picked day — the shape a `DATE` column round-trips as — or null when the input
|
|
12
|
+
* is empty or unreadable. Unreadable clears rather than throwing: the panel writes straight onto the
|
|
13
|
+
* entity from an `ngModelChange`, and a `RangeError` there would surface as a dead form.
|
|
14
|
+
*/
|
|
15
|
+
export declare function InputAsDate(value: string): Date | null;
|
|
16
|
+
/**
|
|
17
|
+
* A stored calendar day as text for a read-only field, or an em dash when there is nothing to show.
|
|
18
|
+
*
|
|
19
|
+
* WHY NOT `| date: 'd MMM y'`. Angular's date pipe formats in the VIEWER'S LOCAL ZONE. A `DATE`
|
|
20
|
+
* column arrives at UTC midnight, so west of Greenwich the pipe renders the PREVIOUS day: the dates
|
|
21
|
+
* panel showed `2026-12-31` in the `<input type="date">` (which `DateAsInput` reads from UTC parts,
|
|
22
|
+
* correctly) and `30 Dec 2026` in the read-only `<div>` immediately after leaving edit mode. Same
|
|
23
|
+
* column, same record, two different days on one screen — and the read-only rendering is the one a
|
|
24
|
+
* person quotes into an email.
|
|
25
|
+
*
|
|
26
|
+
* `timeZone: 'UTC'` is the load-bearing option: `FromCalendarDay` re-anchors to UTC midnight, and
|
|
27
|
+
* without it the formatter would re-interpret that instant in the viewer's zone and slide the day
|
|
28
|
+
* back again one call later.
|
|
29
|
+
*
|
|
30
|
+
* A `DATETIMEOFFSET` is a different thing and must NOT come through here — an instant has no day
|
|
31
|
+
* independent of a zone and is still shown in the viewer's local time. This is for the four `DATE`
|
|
32
|
+
* columns and for `RenewalNoticeDeadline`, which is `DATE` arithmetic over two of them.
|
|
33
|
+
*
|
|
34
|
+
* The locale is the viewer's (`undefined`), matching `dateLabel` in `contract-form.panels.ts` — which
|
|
35
|
+
* this now backs, so the same date reads the same way in the hero, the Overview panel and the Dates
|
|
36
|
+
* tab. Only the ZONE is forced.
|
|
37
|
+
*/
|
|
38
|
+
export declare function DateAsText(value: Date | string | null | undefined): string;
|
|
39
|
+
//# sourceMappingURL=contract-dates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-dates.d.ts","sourceRoot":"","sources":["../../../src/lib/form-panels/contract-dates.ts"],"names":[],"mappings":"AAoBA,mDAAmD;AACnD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG,eAAe,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAEhG;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGtD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The boundary between the UI and the four DATE columns on Contract — reading, writing and RENDERING.
|
|
3
|
+
*
|
|
4
|
+
* `ExecutedDate`, `EffectiveDate`, `EndDate` and `TerminatedDate` are SQL `DATE` columns: calendar
|
|
5
|
+
* days with no time and no zone. The driver hands one back as a `Date` at UTC midnight, so it is
|
|
6
|
+
* read from its UTC parts and written back as UTC midnight — anything local-parts-shaped files the
|
|
7
|
+
* day into the previous one for every user west of Greenwich (bc-aidp-next-golive#168).
|
|
8
|
+
*
|
|
9
|
+
* These are free functions rather than methods on `MJCContractDatesPanel` for one reason: the panel
|
|
10
|
+
* is an Angular component and cannot be constructed outside a JIT/AOT host, so the rule that matters
|
|
11
|
+
* could not be tested without booting Angular. The panel delegates to them and keeps its method
|
|
12
|
+
* names and template bindings.
|
|
13
|
+
*
|
|
14
|
+
* `DateAsText` is here for the same reason and joins the same doctrine from the DISPLAY side. The
|
|
15
|
+
* panel read the stored day correctly into the `<input>` and then rendered the read-only view through
|
|
16
|
+
* Angular's `| date` pipe, which formats in the VIEWER'S zone — so one screen showed `31 Dec` in edit
|
|
17
|
+
* mode and `30 Dec` out of it. Four other panels rendered the same columns the same way.
|
|
18
|
+
*/
|
|
19
|
+
import { FromCalendarDay, ToCalendarDay, UTC_ZONE } from '@mj-biz-apps/common-entities';
|
|
20
|
+
/**
|
|
21
|
+
* The `yyyy-MM-dd` an `<input type="date">` needs, or '' when there is nothing readable to show.
|
|
22
|
+
*
|
|
23
|
+
* Never throws and never re-bases: an offset-bearing stored string is the day it was written as, not
|
|
24
|
+
* the day it converts to in the reader's zone.
|
|
25
|
+
*/
|
|
26
|
+
export function DateAsInput(value) {
|
|
27
|
+
return ToCalendarDay(value) ?? '';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* UTC midnight of the picked day — the shape a `DATE` column round-trips as — or null when the input
|
|
31
|
+
* is empty or unreadable. Unreadable clears rather than throwing: the panel writes straight onto the
|
|
32
|
+
* entity from an `ngModelChange`, and a `RangeError` there would surface as a dead form.
|
|
33
|
+
*/
|
|
34
|
+
export function InputAsDate(value) {
|
|
35
|
+
const day = ToCalendarDay(value);
|
|
36
|
+
return day === null ? null : FromCalendarDay(day);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A stored calendar day as text for a read-only field, or an em dash when there is nothing to show.
|
|
40
|
+
*
|
|
41
|
+
* WHY NOT `| date: 'd MMM y'`. Angular's date pipe formats in the VIEWER'S LOCAL ZONE. A `DATE`
|
|
42
|
+
* column arrives at UTC midnight, so west of Greenwich the pipe renders the PREVIOUS day: the dates
|
|
43
|
+
* panel showed `2026-12-31` in the `<input type="date">` (which `DateAsInput` reads from UTC parts,
|
|
44
|
+
* correctly) and `30 Dec 2026` in the read-only `<div>` immediately after leaving edit mode. Same
|
|
45
|
+
* column, same record, two different days on one screen — and the read-only rendering is the one a
|
|
46
|
+
* person quotes into an email.
|
|
47
|
+
*
|
|
48
|
+
* `timeZone: 'UTC'` is the load-bearing option: `FromCalendarDay` re-anchors to UTC midnight, and
|
|
49
|
+
* without it the formatter would re-interpret that instant in the viewer's zone and slide the day
|
|
50
|
+
* back again one call later.
|
|
51
|
+
*
|
|
52
|
+
* A `DATETIMEOFFSET` is a different thing and must NOT come through here — an instant has no day
|
|
53
|
+
* independent of a zone and is still shown in the viewer's local time. This is for the four `DATE`
|
|
54
|
+
* columns and for `RenewalNoticeDeadline`, which is `DATE` arithmetic over two of them.
|
|
55
|
+
*
|
|
56
|
+
* The locale is the viewer's (`undefined`), matching `dateLabel` in `contract-form.panels.ts` — which
|
|
57
|
+
* this now backs, so the same date reads the same way in the hero, the Overview panel and the Dates
|
|
58
|
+
* tab. Only the ZONE is forced.
|
|
59
|
+
*/
|
|
60
|
+
export function DateAsText(value) {
|
|
61
|
+
const day = ToCalendarDay(value);
|
|
62
|
+
if (day === null)
|
|
63
|
+
return '—';
|
|
64
|
+
return FromCalendarDay(day).toLocaleDateString(undefined, {
|
|
65
|
+
day: 'numeric',
|
|
66
|
+
month: 'short',
|
|
67
|
+
year: 'numeric',
|
|
68
|
+
timeZone: UTC_ZONE,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=contract-dates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-dates.js","sourceRoot":"","sources":["../../../src/lib/form-panels/contract-dates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAKxF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAuC;IAC/D,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACrC,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,KAAuC;IAC9D,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,GAAG,CAAC;IAC7B,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAAC,SAAS,EAAE;QACtD,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,QAAQ;KACrB,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -17,6 +17,7 @@ import { type OnInit } from '@angular/core';
|
|
|
17
17
|
import { BaseFormPanel } from '@memberjunction/ng-base-forms';
|
|
18
18
|
import type { AfterDataLoadEventArgs } from '@memberjunction/ng-entity-viewer';
|
|
19
19
|
import { ContractEntity, type ContractState, type DealOption } from '@mj-biz-apps/contracts-entities';
|
|
20
|
+
import { DateAsText } from './contract-dates';
|
|
20
21
|
import * as i0 from "@angular/core";
|
|
21
22
|
/**
|
|
22
23
|
* A deal option with its rendered label attached (golive #219).
|
|
@@ -45,9 +46,8 @@ interface ContractFieldSpec {
|
|
|
45
46
|
*/
|
|
46
47
|
readOnly?: boolean;
|
|
47
48
|
}
|
|
48
|
-
declare function dateLabel(d: Date | string | null | undefined): string;
|
|
49
49
|
export declare class MJCContractOverviewPanel extends BaseFormPanel<ContractEntity> {
|
|
50
|
-
dateLabel: typeof
|
|
50
|
+
dateLabel: typeof DateAsText;
|
|
51
51
|
get State(): ContractState;
|
|
52
52
|
get TypeName(): string;
|
|
53
53
|
get CustomerName(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-form.panels.d.ts","sourceRoot":"","sources":["../../../src/lib/form-panels/contract-form.panels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAKrG,OAAO,EAAE,aAAa,EAAmB,MAAM,+BAA+B,CAAC;AAE/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EACH,cAAc,EASd,KAAK,aAAa,EAClB,KAAK,UAAU,EAClB,MAAM,iCAAiC,CAAC;;
|
|
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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"}
|
|
@@ -28,6 +28,7 @@ import { BaseFormPanel, BaseFormsModule } from '@memberjunction/ng-base-forms';
|
|
|
28
28
|
import { MJAlertComponent, MJButtonDirective, MJComboboxComponent } from '@memberjunction/ng-ui-components';
|
|
29
29
|
import { CurrentTemplateFilter, CurrentTemplateOrderBy, DealIDClause, DealOptionLabel, DealSearchClause, MayWriteDefaultTemplate, ShouldDefaultTemplate, ShouldWithdrawDefaultTemplate, } from '@mj-biz-apps/contracts-entities';
|
|
30
30
|
import { MJC_ENTITIES, MJC_FOREIGN_ENTITIES } from '../data/entity-names';
|
|
31
|
+
import { DateAsText } from './contract-dates';
|
|
31
32
|
import * as i0 from "@angular/core";
|
|
32
33
|
import * as i1 from "@memberjunction/ng-base-forms";
|
|
33
34
|
import * as i2 from "@angular/forms";
|
|
@@ -307,11 +308,16 @@ const FIELD_STYLES = `
|
|
|
307
308
|
}
|
|
308
309
|
.mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }
|
|
309
310
|
`;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
311
|
+
/**
|
|
312
|
+
* One rendering of a stored calendar day, shared with the hero and the Dates tab.
|
|
313
|
+
*
|
|
314
|
+
* This function had the zone right — `timeZone: 'UTC'` on a UTC-midnight `DATE` is exactly the
|
|
315
|
+
* doctrine — and got the PARSE wrong in a narrower way: `new Date('2026-09-30T23:00:00-05:00')` is
|
|
316
|
+
* 1 October in UTC, so an offset-bearing stored string re-based onto the following day. `DateAsText`
|
|
317
|
+
* takes such a string as written. Kept as a name because the Overview template and three panels bind
|
|
318
|
+
* `dateLabel` directly.
|
|
319
|
+
*/
|
|
320
|
+
const dateLabel = DateAsText;
|
|
315
321
|
function endsInText(days) {
|
|
316
322
|
if (days == null)
|
|
317
323
|
return '';
|
|
@@ -384,7 +390,9 @@ let MJCContractOverviewPanel = class MJCContractOverviewPanel extends BaseFormPa
|
|
|
384
390
|
const d = this.Record?.RenewalNoticeDeadline;
|
|
385
391
|
if (!d)
|
|
386
392
|
return 'None';
|
|
387
|
-
const days =
|
|
393
|
+
const days = noticeDays(this.Record);
|
|
394
|
+
// The deadline is set but the derived count is absent — a row read without the wrapper view's
|
|
395
|
+
// columns. Print the date rather than guess a countdown from a day this panel does not know.
|
|
388
396
|
if (days == null)
|
|
389
397
|
return dateLabel(d);
|
|
390
398
|
if (days < 0)
|
|
@@ -397,7 +405,7 @@ let MJCContractOverviewPanel = class MJCContractOverviewPanel extends BaseFormPa
|
|
|
397
405
|
const d = this.Record?.RenewalNoticeDeadline;
|
|
398
406
|
if (!d)
|
|
399
407
|
return 'muted';
|
|
400
|
-
const days =
|
|
408
|
+
const days = noticeDays(this.Record);
|
|
401
409
|
if (days != null && days <= 30)
|
|
402
410
|
return 'warning';
|
|
403
411
|
return 'muted';
|
|
@@ -428,7 +436,7 @@ let MJCContractOverviewPanel = class MJCContractOverviewPanel extends BaseFormPa
|
|
|
428
436
|
else if (this.State === 'Active' && end != null && end <= 120) {
|
|
429
437
|
out.push(`${termEndsText(end)}.`);
|
|
430
438
|
}
|
|
431
|
-
const notice =
|
|
439
|
+
const notice = noticeDays(this.Record);
|
|
432
440
|
if (notice != null && notice < 0) {
|
|
433
441
|
out.push('Renewal notice deadline has already passed.');
|
|
434
442
|
}
|
|
@@ -448,7 +456,7 @@ let MJCContractOverviewPanel = class MJCContractOverviewPanel extends BaseFormPa
|
|
|
448
456
|
return 'Attach the executed agreement.';
|
|
449
457
|
if (this.Record?.IsInCancellationWindow)
|
|
450
458
|
return 'Cancellation window is open. Confirm whether the customer is renewing.';
|
|
451
|
-
const notice =
|
|
459
|
+
const notice = noticeDays(this.Record);
|
|
452
460
|
if (notice != null && notice <= 30) {
|
|
453
461
|
return `Send the renewal notice. Deadline ${dateLabel(this.Record?.RenewalNoticeDeadline)}.`;
|
|
454
462
|
}
|
|
@@ -759,19 +767,25 @@ export { MJCContractOverviewPanel };
|
|
|
759
767
|
</mj-collapsible-panel>
|
|
760
768
|
`, styles: ["\n .mjc-ov { display: flex; flex-direction: column; gap: var(--mj-space-4); padding: var(--mj-space-3) var(--mj-space-4) var(--mj-space-5); }\n .mjc-ov-health { display: flex; flex-direction: column; gap: var(--mj-space-2); }\n .mjc-ov-alert {\n display: flex; align-items: center; gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-3);\n background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text);\n border-radius: var(--mj-radius-md); font-size: var(--mj-text-sm); font-weight: 600;\n }\n .mjc-ov-ok {\n display: flex; align-items: center; gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-3);\n background: var(--mj-status-success-bg); color: var(--mj-status-success-text);\n border-radius: var(--mj-radius-md); font-size: var(--mj-text-sm); font-weight: 600;\n }\n .mjc-ov-strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--mj-space-3); }\n @media (max-width: 900px) { .mjc-ov-strip { grid-template-columns: 1fr 1fr; } }\n .mjc-ov-kpi {\n background: var(--mj-bg-page); border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md); padding: var(--mj-space-3) var(--mj-space-4);\n }\n .mjc-ov-kpi .l {\n font-size: var(--mj-text-xs); text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700;\n }\n .mjc-ov-kpi .v { font-size: 1.35rem; font-weight: 800; letter-spacing: -.02em; margin-top: 4px; line-height: 1.2; }\n .mjc-ov-kpi .s { color: var(--mj-text-muted); font-size: var(--mj-text-sm); margin-top: 2px; }\n .mjc-ov-kpi[data-tone='warning'] .v { color: var(--mj-status-warning-text); }\n .mjc-ov-kpi[data-tone='success'] .v { color: var(--mj-status-success-text); }\n .mjc-ov-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mj-space-3); }\n @media (max-width: 800px) { .mjc-ov-grid { grid-template-columns: 1fr; } }\n .mjc-ov-card {\n background: var(--mj-bg-page); border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md); padding: var(--mj-space-3) var(--mj-space-4);\n }\n .mjc-ov-card--wide { grid-column: 1 / -1; }\n .mjc-ov-card header {\n display: flex; align-items: center; gap: 8px;\n font-weight: 700; margin-bottom: var(--mj-space-3); color: var(--mj-text-primary);\n }\n .mjc-ov-card header i { color: var(--mj-brand-primary); width: 1.1rem; text-align: center; }\n .mjc-ov-facts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mj-space-3); }\n .mjc-ov-facts .l {\n font-size: var(--mj-text-xs); text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700;\n }\n .mjc-ov-facts .v { font-weight: 650; margin-top: 2px; }\n .mjc-ov-link {\n border: 0; padding: 0; background: transparent; color: var(--mj-text-link);\n cursor: pointer; font: inherit; font-weight: 650; text-align: left;\n }\n .mjc-ov-link:hover { text-decoration: underline; }\n .mjc-ov-next { margin: 0; font-size: 1.05rem; font-weight: 650; }\n .mjc-ov-empty { color: var(--mj-text-muted); margin: 0; }\n "] }]
|
|
761
769
|
}], null, null); })();
|
|
762
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractOverviewPanel, { className: "MJCContractOverviewPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
770
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractOverviewPanel, { className: "MJCContractOverviewPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 303 }); })();
|
|
771
|
+
/**
|
|
772
|
+
* Days until the renewal-notice deadline, TAKEN FROM THE VIEW (bc-aidp-next-golive#168).
|
|
773
|
+
*
|
|
774
|
+
* This used to be `daysUntil(Record.RenewalNoticeDeadline)`, a local re-derivation that read the
|
|
775
|
+
* stored day from UTC parts — right — and then took "today" from the SERVER'S UTC clock, which is
|
|
776
|
+
* already tomorrow for the whole American evening. `vwContracts` derives `DaysToEnd` and
|
|
777
|
+
* `DaysUntilNoticeDeadline` from `bt.Today`, the BUSINESS day, so from 6 PM Central `NoticeClock`,
|
|
778
|
+
* `NoticeTone`, `Health` and `NextMove` were one day ahead of the `DaysToEnd` printed beside them on
|
|
779
|
+
* the same card — and `NextMove`'s `notice <= 30` cutoff fired a day early.
|
|
780
|
+
*
|
|
781
|
+
* REPLACED RATHER THAN CORRECTED. Reading the business zone on the client would make the two agree
|
|
782
|
+
* only while a browser clock and a cached engine setting agreed with the database. The view already
|
|
783
|
+
* computed the answer, in the same row, from the same `bt.Today` as everything else the card prints;
|
|
784
|
+
* the second implementation was the defect, not its anchor. `DaysToEnd` was always read this way,
|
|
785
|
+
* three lines above, which is why the two disagreed at all.
|
|
786
|
+
*/
|
|
787
|
+
function noticeDays(record) {
|
|
788
|
+
return record?.DaysUntilNoticeDeadline ?? null;
|
|
775
789
|
}
|
|
776
790
|
/* ── Field sections ───────────────────────────────────────────────────────── */
|
|
777
791
|
let MJCContractAgreementPanel = class MJCContractAgreementPanel extends BaseFormPanel {
|
|
@@ -960,7 +974,7 @@ export { MJCContractAgreementPanel };
|
|
|
960
974
|
</mj-collapsible-panel>
|
|
961
975
|
`, styles: ["\n .mjc-fields-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--mj-space-4) var(--mj-space-5);\n padding: var(--mj-space-4) var(--mj-space-5);\n }\n @media (max-width: 720px) { .mjc-fields-grid { grid-template-columns: 1fr; } }\n .mjc-fg { min-width: 0; }\n .mjc-fg--span { grid-column: 1 / -1; }\n .mjc-fg .mj-forms-field {\n display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 0;\n }\n .mjc-fg .mj-forms-field-label {\n font-size: var(--mj-text-xs); font-weight: 700; letter-spacing: .06em;\n text-transform: uppercase; color: var(--mj-text-muted);\n }\n .mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }\n"] }]
|
|
962
976
|
}], null, null); })();
|
|
963
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractAgreementPanel, { className: "MJCContractAgreementPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
977
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractAgreementPanel, { className: "MJCContractAgreementPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 484 }); })();
|
|
964
978
|
let MJCContractPartiesPanel = class MJCContractPartiesPanel extends BaseFormPanel {
|
|
965
979
|
/**
|
|
966
980
|
* Record links on these fields emit `Navigate` from `mj-form-field`. That output must be
|
|
@@ -1013,7 +1027,7 @@ export { MJCContractPartiesPanel };
|
|
|
1013
1027
|
</mj-collapsible-panel>
|
|
1014
1028
|
`, styles: ["\n .mjc-fields-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--mj-space-4) var(--mj-space-5);\n padding: var(--mj-space-4) var(--mj-space-5);\n }\n @media (max-width: 720px) { .mjc-fields-grid { grid-template-columns: 1fr; } }\n .mjc-fg { min-width: 0; }\n .mjc-fg--span { grid-column: 1 / -1; }\n .mjc-fg .mj-forms-field {\n display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 0;\n }\n .mjc-fg .mj-forms-field-label {\n font-size: var(--mj-text-xs); font-weight: 700; letter-spacing: .06em;\n text-transform: uppercase; color: var(--mj-text-muted);\n }\n .mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }\n"] }]
|
|
1015
1029
|
}], null, null); })();
|
|
1016
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractPartiesPanel, { className: "MJCContractPartiesPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
1030
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractPartiesPanel, { className: "MJCContractPartiesPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 668 }); })();
|
|
1017
1031
|
let MJCContractNotesPanel = class MJCContractNotesPanel extends BaseFormPanel {
|
|
1018
1032
|
Fields = [
|
|
1019
1033
|
{ name: 'Notes', type: 'textarea', span: true },
|
|
@@ -1058,7 +1072,7 @@ export { MJCContractNotesPanel };
|
|
|
1058
1072
|
</mj-collapsible-panel>
|
|
1059
1073
|
`, styles: ["\n .mjc-fields-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--mj-space-4) var(--mj-space-5);\n padding: var(--mj-space-4) var(--mj-space-5);\n }\n @media (max-width: 720px) { .mjc-fields-grid { grid-template-columns: 1fr; } }\n .mjc-fg { min-width: 0; }\n .mjc-fg--span { grid-column: 1 / -1; }\n .mjc-fg .mj-forms-field {\n display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 0;\n }\n .mjc-fg .mj-forms-field-label {\n font-size: var(--mj-text-xs); font-weight: 700; letter-spacing: .06em;\n text-transform: uppercase; color: var(--mj-text-muted);\n }\n .mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }\n"] }]
|
|
1060
1074
|
}], null, null); })();
|
|
1061
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractNotesPanel, { className: "MJCContractNotesPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
1075
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractNotesPanel, { className: "MJCContractNotesPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 712 }); })();
|
|
1062
1076
|
let MJCContractProvenanceFieldsPanel = class MJCContractProvenanceFieldsPanel extends BaseFormPanel {
|
|
1063
1077
|
cdr = inject(ChangeDetectorRef);
|
|
1064
1078
|
/**
|
|
@@ -1395,7 +1409,7 @@ export { MJCContractProvenanceFieldsPanel };
|
|
|
1395
1409
|
</mj-collapsible-panel>
|
|
1396
1410
|
`, styles: ["\n .mjc-fields-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--mj-space-4) var(--mj-space-5);\n padding: var(--mj-space-4) var(--mj-space-5);\n }\n @media (max-width: 720px) { .mjc-fields-grid { grid-template-columns: 1fr; } }\n .mjc-fg { min-width: 0; }\n .mjc-fg--span { grid-column: 1 / -1; }\n .mjc-fg .mj-forms-field {\n display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 0;\n }\n .mjc-fg .mj-forms-field-label {\n font-size: var(--mj-text-xs); font-weight: 700; letter-spacing: .06em;\n text-transform: uppercase; color: var(--mj-text-muted);\n }\n .mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }\n"] }]
|
|
1397
1411
|
}], null, null); })();
|
|
1398
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractProvenanceFieldsPanel, { className: "MJCContractProvenanceFieldsPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
1412
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractProvenanceFieldsPanel, { className: "MJCContractProvenanceFieldsPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 811 }); })();
|
|
1399
1413
|
let MJCContractLifecyclePanel = class MJCContractLifecyclePanel extends BaseFormPanel {
|
|
1400
1414
|
Fields = [
|
|
1401
1415
|
{ name: 'ParentContractID', type: 'textbox', link: 'Record' },
|
|
@@ -1441,7 +1455,7 @@ export { MJCContractLifecyclePanel };
|
|
|
1441
1455
|
</mj-collapsible-panel>
|
|
1442
1456
|
`, styles: ["\n .mjc-fields-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--mj-space-4) var(--mj-space-5);\n padding: var(--mj-space-4) var(--mj-space-5);\n }\n @media (max-width: 720px) { .mjc-fields-grid { grid-template-columns: 1fr; } }\n .mjc-fg { min-width: 0; }\n .mjc-fg--span { grid-column: 1 / -1; }\n .mjc-fg .mj-forms-field {\n display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 0;\n }\n .mjc-fg .mj-forms-field-label {\n font-size: var(--mj-text-xs); font-weight: 700; letter-spacing: .06em;\n text-transform: uppercase; color: var(--mj-text-muted);\n }\n .mjc-fg .mj-forms-field--editing:hover { margin: 0; padding: 0; }\n"] }]
|
|
1443
1457
|
}], null, null); })();
|
|
1444
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractLifecyclePanel, { className: "MJCContractLifecyclePanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
1458
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractLifecyclePanel, { className: "MJCContractLifecyclePanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 1078 }); })();
|
|
1445
1459
|
let MJCContractModificationsPanel = class MJCContractModificationsPanel extends BaseFormPanel {
|
|
1446
1460
|
Entity = MJC_ENTITIES.ContractTemplateModification;
|
|
1447
1461
|
OnDataLoad(event) {
|
|
@@ -1493,5 +1507,5 @@ export { MJCContractModificationsPanel };
|
|
|
1493
1507
|
`,
|
|
1494
1508
|
}]
|
|
1495
1509
|
}], null, null); })();
|
|
1496
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractModificationsPanel, { className: "MJCContractModificationsPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber:
|
|
1510
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJCContractModificationsPanel, { className: "MJCContractModificationsPanel", filePath: "lib/form-panels/contract-form.panels.ts", lineNumber: 1114 }); })();
|
|
1497
1511
|
//# sourceMappingURL=contract-form.panels.js.map
|