@autologix-engineering/shared 1.2.387 → 1.2.388
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/package.json +1 -1
- package/src/lib/models/src/ltr.payout.model.d.ts +20 -1
- package/src/lib/models/src/ltr.payout.model.js +1 -0
- package/src/lib/models/src/ltr.payout.model.js.map +1 -1
- package/src/lib/utilities/src/ba.payout.contract.selection.d.ts +117 -34
- package/src/lib/utilities/src/ba.payout.contract.selection.js +148 -5
- package/src/lib/utilities/src/ba.payout.contract.selection.js.map +1 -1
- package/src/lib/utilities/src/normalize.phone.number.d.ts +13 -2
- package/src/lib/utilities/src/normalize.phone.number.js +30 -3
- package/src/lib/utilities/src/normalize.phone.number.js.map +1 -1
package/package.json
CHANGED
|
@@ -17,7 +17,8 @@ export declare enum LTRPayoutField {
|
|
|
17
17
|
SignOffMethod = "signOffMethod",
|
|
18
18
|
SignOffTimestamp = "signOffTimestamp",
|
|
19
19
|
SignatureImageUrl = "signatureImageUrl",
|
|
20
|
-
LockedReason = "lockedReason"
|
|
20
|
+
LockedReason = "lockedReason",
|
|
21
|
+
DirectEntry = "directEntry"
|
|
21
22
|
}
|
|
22
23
|
export interface ILTRPayout {
|
|
23
24
|
[LTRPayoutField.LTRPayoutId]: string;
|
|
@@ -37,6 +38,24 @@ export interface ILTRPayout {
|
|
|
37
38
|
[LTRPayoutField.SignatureImageUrl]?: string;
|
|
38
39
|
/** Set only while `Status` is `LTRPayoutStatus.Locked`. */
|
|
39
40
|
[LTRPayoutField.LockedReason]?: LTRPayoutLockedReason;
|
|
41
|
+
/**
|
|
42
|
+
* These rows are one month typed as a summary, not a day-wise log.
|
|
43
|
+
* (AD-3279)
|
|
44
|
+
*
|
|
45
|
+
* Written on every submit, true or false, so the flag describes the rows the
|
|
46
|
+
* payout holds NOW rather than how the booking was once closed — a day-wise
|
|
47
|
+
* submit over a direct-entry month clears it.
|
|
48
|
+
*
|
|
49
|
+
* The booking's activity log already records that a closure happened and by
|
|
50
|
+
* what means, but a log row cannot be read by the screens that need this
|
|
51
|
+
* answer while they are drawing. Direct entry produces exactly one synthetic
|
|
52
|
+
* row, and without this flag the day-wise tab drew that row as though a
|
|
53
|
+
* driver had logged a single day, the chooser offered "use the driver app's
|
|
54
|
+
* logs" for a month no driver had touched, and the replace-the-driver's-
|
|
55
|
+
* record warning fired on a month that never had one. (QA run 3, items 10,
|
|
56
|
+
* 11 and 18)
|
|
57
|
+
*/
|
|
58
|
+
[LTRPayoutField.DirectEntry]?: boolean;
|
|
40
59
|
}
|
|
41
60
|
export interface ILTRBillingSummary {
|
|
42
61
|
lineItems: INumberedInvoiceLineItem[];
|
|
@@ -15,6 +15,7 @@ export var LTRPayoutField;
|
|
|
15
15
|
LTRPayoutField["SignOffTimestamp"] = "signOffTimestamp";
|
|
16
16
|
LTRPayoutField["SignatureImageUrl"] = "signatureImageUrl";
|
|
17
17
|
LTRPayoutField["LockedReason"] = "lockedReason";
|
|
18
|
+
LTRPayoutField["DirectEntry"] = "directEntry";
|
|
18
19
|
})(LTRPayoutField || (LTRPayoutField = {}));
|
|
19
20
|
export var LTRPayoutRowField;
|
|
20
21
|
(function (LTRPayoutRowField) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ltr.payout.model.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/models/src/ltr.payout.model.ts"],"names":[],"mappings":"AASA,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"ltr.payout.model.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/models/src/ltr.payout.model.ts"],"names":[],"mappings":"AASA,MAAM,CAAN,IAAY,cAiBX;AAjBD,WAAY,cAAc;IACzB,6CAA2B,CAAA;IAC3B,iDAA+B,CAAA;IAC/B,qCAAmB,CAAA;IACnB,yCAAuB,CAAA;IACvB,yCAAuB,CAAA;IACvB,mDAAiC,CAAA;IACjC,qDAAmC,CAAA;IACnC,uCAAqB,CAAA;IACrB,qCAAmB,CAAA;IACnB,mDAAiC,CAAA;IACjC,mCAAiB,CAAA;IACjB,iDAA+B,CAAA;IAC/B,uDAAqC,CAAA;IACrC,yDAAuC,CAAA;IACvC,+CAA6B,CAAA;IAC7B,6CAA2B,CAAA;AAC5B,CAAC,EAjBW,cAAc,KAAd,cAAc,QAiBzB;AAwDD,MAAM,CAAN,IAAY,iBA2FX;AA3FD,WAAY,iBAAiB;IAC5B,oCAAe,CAAA;IACf,iEAA4C,CAAA;IAC5C,6DAAwC,CAAA;IACxC,iDAA4B,CAAA;IAC5B,yCAAoB,CAAA;IACpB,qCAAgB,CAAA;IAChB,wCAAmB,CAAA;IACnB,kCAAa,CAAA;IACb,wCAAmB,CAAA;IACnB,sCAAiB,CAAA;IACjB,+CAA0B,CAAA;IAC1B,wCAAmB,CAAA;IACnB,2CAAsB,CAAA;IACtB,iDAA4B,CAAA;IAC5B,yDAAoC,CAAA;IACpC,iDAA4B,CAAA;IAC5B,0EAAqD,CAAA;IACrD,uEAAkD,CAAA;IAClD,2CAAsB,CAAA;IACtB,8FAAyE,CAAA;IACzE,6GAAwF,CAAA;IACxF,qDAAgC,CAAA;IAChC,mDAA8B,CAAA;IAC9B,uDAAkC,CAAA;IAClC,6CAAwB,CAAA;IACxB;;;;;;;OAOG;IACH,wDAAmC,CAAA;IACnC,4DAAuC,CAAA;IACvC,qDAAgC,CAAA;IAChC,gEAA2C,CAAA;IAC3C,oEAA+C,CAAA;IAC/C,yDAAoC,CAAA;IACpC,oEAA+C,CAAA;IAC/C,wEAAmD,CAAA;IACnD,+CAA0B,CAAA;IAC1B,iDAA4B,CAAA;IAC5B,uDAAkC,CAAA;IAClC,yDAAoC,CAAA;IACpC,qDAAgC,CAAA;IAChC,yDAAoC,CAAA;IACpC,sDAAiC,CAAA;IACjC,iDAA4B,CAAA;IAC5B,qEAAgD,CAAA;IAChD,uEAAkD,CAAA;IAClD,0DAAqC,CAAA;IACrC,0DAAqC,CAAA;IACrC,sDAAiC,CAAA;IACjC,sDAAiC,CAAA;IACjC,kDAA6B,CAAA;IAC7B,kDAA6B,CAAA;IAC7B,4DAAuC,CAAA;IACvC,4DAAuC,CAAA;IACvC,sDAAiC,CAAA;IACjC,wDAAmC,CAAA;IACnC,4CAAuB,CAAA;IACvB;;;;;;;;;OASG;IACH,kDAA6B,CAAA;IAC7B,4CAA4C;IAC5C,oDAA+B,CAAA;IAC/B,gDAA2B,CAAA;IAC3B,iEAA4C,CAAA;IAC5C,mEAA8C,CAAA;IAC9C;;;;;;;;;;OAUG;IACH,wCAAmB,CAAA;AACpB,CAAC,EA3FW,iBAAiB,KAAjB,iBAAiB,QA2F5B"}
|
|
@@ -1,38 +1,4 @@
|
|
|
1
1
|
import { IBAContract } from '../../models/src/index';
|
|
2
|
-
/**
|
|
3
|
-
* Which BA contract a BA Payout row belongs to — AD-3098 QA re-open.
|
|
4
|
-
*
|
|
5
|
-
* The BA Payout template and the upload validator disagreed about contracts,
|
|
6
|
-
* and the disagreement made a whole month's payout unuploadable:
|
|
7
|
-
*
|
|
8
|
-
* • The template emitted one row per contract overlapping the period with no
|
|
9
|
-
* status filter at all, so a Cancelled contract got a row.
|
|
10
|
-
* • The validator hashed contracts into `Map<contractCode, contract>` —
|
|
11
|
-
* last write wins. `baContractCode` is NOT unique: an older extend flow
|
|
12
|
-
* minted duplicate `E<n>` codes (AD-3227, AD-3097), and a contract that is
|
|
13
|
-
* cancelled and re-raised keeps its code. On staging, `1004E1` exists twice
|
|
14
|
-
* for BA1002 — one Cancelled, one Surrendered — so the validator compared
|
|
15
|
-
* the template's Cancelled row against the Surrendered record and reported
|
|
16
|
-
* "Contract Start Date mismatch. Expected: 01/12/2025, Found: 02/05/2026".
|
|
17
|
-
* Nothing the user could type would satisfy it.
|
|
18
|
-
*
|
|
19
|
-
* The rule here is the single answer both sides now use, so they cannot drift:
|
|
20
|
-
*
|
|
21
|
-
* 1. Cancelled contracts are not payable and are dropped outright. This is
|
|
22
|
-
* the same predicate `loadReportContracts` applies to the RM and SD
|
|
23
|
-
* schedules and `findBAContractForBooking` applies to bookings, so the
|
|
24
|
-
* payout, the schedules and the booking attribution agree about which
|
|
25
|
-
* contracts exist.
|
|
26
|
-
* 2. The same contract keyed in twice — same BA, same code, same vehicle —
|
|
27
|
-
* is ONE contract, collapsed to its most recently edited record. That is
|
|
28
|
-
* the tie-break `groupDuplicateContracts` already uses in the report
|
|
29
|
-
* builders. Without it the template would print the row twice and the
|
|
30
|
-
* payout would collect the RM twice.
|
|
31
|
-
* 3. The same code on DIFFERENT vehicles stays as separate contracts (BA1000
|
|
32
|
-
* has six rows coded '1000' on six vehicles). Those are resolved by the
|
|
33
|
-
* vehicle registration carried on the uploaded row, which is the column
|
|
34
|
-
* that actually distinguishes them.
|
|
35
|
-
*/
|
|
36
2
|
export declare const normalizeBAContractCode: (raw: unknown) => string;
|
|
37
3
|
/**
|
|
38
4
|
* Registrations are compared without spaces or punctuation: the template
|
|
@@ -77,3 +43,120 @@ export declare function selectBAPayoutContracts(contracts: IBAContract[] | undef
|
|
|
77
43
|
export declare function resolveBAPayoutContract(candidates: IBAContract[] | undefined | null, criteria?: {
|
|
78
44
|
vehicleRegistration?: unknown;
|
|
79
45
|
}): IBAContract | null;
|
|
46
|
+
/**
|
|
47
|
+
* Does this contract overlap the payout period?
|
|
48
|
+
*
|
|
49
|
+
* `periodStartIST` / `periodEndIST` are the period bounds already shifted into
|
|
50
|
+
* IST. A contract with no end date is open-ended and extends past the period.
|
|
51
|
+
* A contract with no *start* date cannot be judged and is kept, so that the
|
|
52
|
+
* template and the upload guard make the same call about it rather than one of
|
|
53
|
+
* them silently dropping it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isBAPayoutContractInPeriod(contract: IBAContract, periodStartIST: Date, periodEndIST: Date): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The BA contracts that get a row in one payout period's template — and,
|
|
58
|
+
* therefore, exactly the rows the upload's completeness guard may demand back.
|
|
59
|
+
*
|
|
60
|
+
* AD-3098 QA re-open, second round. PR #3951 taught the template generator to
|
|
61
|
+
* run its contracts through `selectBAPayoutContracts` (dropping Cancelled
|
|
62
|
+
* records) but left the "rows were deleted" guard in
|
|
63
|
+
* `process.business.associate.payout.upload.controller.ts` building its
|
|
64
|
+
* expected set straight from `ba.contractDetails` with no status filter. The
|
|
65
|
+
* template then omitted a Cancelled contract and the guard immediately
|
|
66
|
+
* rejected the file the application had just produced:
|
|
67
|
+
*
|
|
68
|
+
* "Some BA rows are missing from the uploaded Excel (it looks like rows were
|
|
69
|
+
* deleted). Please re-download the template ... Missing: BA1061
|
|
70
|
+
* (PARTHASARTHY YN) - 1022E2"
|
|
71
|
+
*
|
|
72
|
+
* Re-downloading omitted the row again, so there was no way through at all —
|
|
73
|
+
* 21 (context, period) combinations on staging were blocked outright.
|
|
74
|
+
*
|
|
75
|
+
* Both halves now call THIS function, which is the only place the question
|
|
76
|
+
* "which contracts does this period have?" is answered. Duplicating the
|
|
77
|
+
* status filter and the period filter is what let them drift; there is now
|
|
78
|
+
* nothing to keep in step by hand.
|
|
79
|
+
*
|
|
80
|
+
* Period bounds are already IST-shifted; pass null/undefined for either to
|
|
81
|
+
* select without a period filter.
|
|
82
|
+
*/
|
|
83
|
+
export declare function selectBAPayoutContractsForPeriod(contracts: IBAContract[] | undefined | null, periodStartIST?: Date | null, periodEndIST?: Date | null): IBAContract[];
|
|
84
|
+
/** A stored contract date as its IST calendar day, `YYYY-MM-DD`. */
|
|
85
|
+
export declare const istDayKey: (value: unknown) => string | null;
|
|
86
|
+
/**
|
|
87
|
+
* A date as it arrives from an uploaded payout sheet, as `YYYY-MM-DD`.
|
|
88
|
+
*
|
|
89
|
+
* The template writes `DD/MM/YYYY`, but a cell a user retyped — or one Excel
|
|
90
|
+
* re-serialised — routinely comes back as `YYYY-MM-DD`, with or without a time
|
|
91
|
+
* part. Both are accepted; anything else is `null` and simply does not match.
|
|
92
|
+
*/
|
|
93
|
+
export declare const parseUploadedDayKey: (raw: unknown) => string | null;
|
|
94
|
+
/**
|
|
95
|
+
* Does this uploaded row actually describe a CANCELLED record of its Contract
|
|
96
|
+
* ID, rather than the live record it was resolved to?
|
|
97
|
+
*
|
|
98
|
+
* AD-3098, Jahnavi's original report. `baContractCode` is not unique: on
|
|
99
|
+
* staging `1004E1` exists twice for BA1002 — Surrendered 2025-12-01 - 2026-05-01
|
|
100
|
+
* and Cancelled 2026-05-02 - 2027-03-02, on the same BA and the same vehicle.
|
|
101
|
+
* `resolveBAPayoutContract` deliberately drops the Cancelled record, so a row
|
|
102
|
+
* carrying the Cancelled record's dates was compared against the Surrendered
|
|
103
|
+
* sibling and the user was told
|
|
104
|
+
*
|
|
105
|
+
* "Contract Start Date mismatch. Expected: 01/12/2025, Found: 2026-05-02"
|
|
106
|
+
*
|
|
107
|
+
* which names a contract the row was never about and cannot be satisfied by
|
|
108
|
+
* any edit to the sheet. When the row's own Contract Start Date identifies a
|
|
109
|
+
* Cancelled record instead, the honest message is that the contract is
|
|
110
|
+
* cancelled.
|
|
111
|
+
*
|
|
112
|
+
* The Contract Start Date is the discriminator on purpose: it is the field
|
|
113
|
+
* that differs between records sharing a code, and the only one a hand-built
|
|
114
|
+
* row reliably carries verbatim. But a start date alone does NOT identify a
|
|
115
|
+
* contract, and this check used to rely on it alone — the bug AD-3098's review
|
|
116
|
+
* caught. `candidates` is whatever the caller keyed under the row's Contract
|
|
117
|
+
* ID, and the upload validator keys its map by contract code across the WHOLE
|
|
118
|
+
* context: every BA, every vehicle. So the check could fire on a Cancelled
|
|
119
|
+
* record belonging to some unrelated BA or vehicle that merely started on the
|
|
120
|
+
* same day.
|
|
121
|
+
*
|
|
122
|
+
* That matters because firing is destructive to the rest of the report: the
|
|
123
|
+
* caller then treats the row as having no contract at all and skips the checks
|
|
124
|
+
* for BA linkage, vehicle, city, garage, end date, closure date and working
|
|
125
|
+
* days. A row whose real problem is a wrong BA ID or a wrong vehicle would have
|
|
126
|
+
* been told only "this contract is cancelled" — true of some other BA's
|
|
127
|
+
* contract, and no help at all.
|
|
128
|
+
*
|
|
129
|
+
* So the row must identify the contract on all three parts of its identity —
|
|
130
|
+
* BA ID, Contract ID, vehicle — before a Cancelled record's date match is
|
|
131
|
+
* treated as conclusive. That triple is exactly `selectBAPayoutContracts`'
|
|
132
|
+
* collapse key (`baId|code|vaId`) and exactly what the template emits per row.
|
|
133
|
+
* The comparison is against the row's OWN BA ID and Vehicle columns, not the
|
|
134
|
+
* resolved record's: the whole point is that a row naming the wrong BA or the
|
|
135
|
+
* wrong vehicle must fall through to that error instead of being absorbed here.
|
|
136
|
+
*
|
|
137
|
+
* A row missing either column returns false and is reported as "Missing BA ID"
|
|
138
|
+
* / "Missing Vehicle" — messages the user can act on — rather than being
|
|
139
|
+
* guessed at.
|
|
140
|
+
*
|
|
141
|
+
* What is left is genuinely narrow: it fires only when the row's start date
|
|
142
|
+
* does NOT match the record it resolved to (so only on rows that were going to
|
|
143
|
+
* be reported anyway) and DOES match, exactly, a Cancelled record on the same
|
|
144
|
+
* BA and the same vehicle. It can still displace the date-mismatch messages for
|
|
145
|
+
* such a row, which is the intent; it can no longer displace anything else.
|
|
146
|
+
*/
|
|
147
|
+
export interface BAPayoutContractCandidate {
|
|
148
|
+
contract: IBAContract;
|
|
149
|
+
/**
|
|
150
|
+
* The BA ID as the sheet spells it (the BA code sequence, e.g. "BA1002").
|
|
151
|
+
* The contract entity carries only the BA's internal uuid, so the caller
|
|
152
|
+
* that built the code -> contracts map supplies the sheet-level id.
|
|
153
|
+
*/
|
|
154
|
+
baId?: unknown;
|
|
155
|
+
}
|
|
156
|
+
/** The three columns that identify which contract an uploaded row is about. */
|
|
157
|
+
export interface BAPayoutUploadedRowIdentity {
|
|
158
|
+
baId?: unknown;
|
|
159
|
+
vehicleRegistration?: unknown;
|
|
160
|
+
contractStartDate?: unknown;
|
|
161
|
+
}
|
|
162
|
+
export declare function uploadedRowNamesCancelledRecord(candidates: readonly BAPayoutContractCandidate[] | undefined | null, resolved: IBAContract | null | undefined, row: BAPayoutUploadedRowIdentity): boolean;
|
|
@@ -30,12 +30,27 @@ import { BAContractStatus } from '../../enums/src/index';
|
|
|
30
30
|
* the tie-break `groupDuplicateContracts` already uses in the report
|
|
31
31
|
* builders. Without it the template would print the row twice and the
|
|
32
32
|
* payout would collect the RM twice.
|
|
33
|
-
* 3. The same code on DIFFERENT vehicles stays as separate contracts
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* 3. The same code on DIFFERENT vehicles stays as separate contracts. Those
|
|
34
|
+
* are resolved by the vehicle registration carried on the uploaded row,
|
|
35
|
+
* which is the column that actually distinguishes them.
|
|
36
|
+
*
|
|
37
|
+
* On the scope of the identity: a code bucket here is per business context —
|
|
38
|
+
* the validator builds its map from one context's BAs. Code '1000' exists eight
|
|
39
|
+
* times on staging, but in eight different contexts, one BA and one vehicle
|
|
40
|
+
* each; within any single context no code is currently shared across two BAs or
|
|
41
|
+
* two vehicles (measured 2026-09-28, read-only). The reuse that DOES exist
|
|
42
|
+
* inside a context is AD-3416's: `generateExtendedContractCode()` re-mints the
|
|
43
|
+
* same `E<n>` code on the same BA and the same vehicle. So BA and vehicle are
|
|
44
|
+
* checked defensively, not because the collision is common — but a check that
|
|
45
|
+
* suppresses other checks must not be able to fire on a contract the row is not
|
|
46
|
+
* about. See `uploadedRowNamesCancelledRecord`.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* An identifier as a sheet spells it — trimmed and upper-cased. Contract codes
|
|
50
|
+
* and BA IDs are both short, human-typed keys and are compared the same way.
|
|
37
51
|
*/
|
|
38
|
-
|
|
52
|
+
const normalizeSheetIdentifier = (raw) => raw == null ? '' : String(raw).trim().toUpperCase();
|
|
53
|
+
export const normalizeBAContractCode = normalizeSheetIdentifier;
|
|
39
54
|
/**
|
|
40
55
|
* Registrations are compared without spaces or punctuation: the template
|
|
41
56
|
* writes the stored value verbatim, but a user who retypes a cell may enter
|
|
@@ -134,4 +149,132 @@ export function resolveBAPayoutContract(candidates, criteria) {
|
|
|
134
149
|
: eligible;
|
|
135
150
|
return pool.reduce((best, c) => (isNewerRecord(c, best) ? c : best));
|
|
136
151
|
}
|
|
152
|
+
/** IST is UTC+5:30. The BA payout period bounds are stored as the UTC instants
|
|
153
|
+
* of IST month boundaries, so every comparison here is done on the IST shift of
|
|
154
|
+
* both sides — the same arithmetic the payout template, the upload validator
|
|
155
|
+
* and the row calculator have always used. */
|
|
156
|
+
const IST_OFFSET_MS = 5.5 * 60 * 60 * 1000;
|
|
157
|
+
const toIST = (value) => {
|
|
158
|
+
if (value === null || value === undefined || value === '')
|
|
159
|
+
return null;
|
|
160
|
+
const ms = new Date(value).getTime();
|
|
161
|
+
return Number.isNaN(ms) ? null : new Date(ms + IST_OFFSET_MS);
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Does this contract overlap the payout period?
|
|
165
|
+
*
|
|
166
|
+
* `periodStartIST` / `periodEndIST` are the period bounds already shifted into
|
|
167
|
+
* IST. A contract with no end date is open-ended and extends past the period.
|
|
168
|
+
* A contract with no *start* date cannot be judged and is kept, so that the
|
|
169
|
+
* template and the upload guard make the same call about it rather than one of
|
|
170
|
+
* them silently dropping it.
|
|
171
|
+
*/
|
|
172
|
+
export function isBAPayoutContractInPeriod(contract, periodStartIST, periodEndIST) {
|
|
173
|
+
const startIST = toIST(contract?.[BAContractField.ContractStartDate]);
|
|
174
|
+
if (!startIST)
|
|
175
|
+
return true;
|
|
176
|
+
const endIST = toIST(contract[BAContractField.ContractEndDate]);
|
|
177
|
+
const surrenderIST = toIST(contract[BAContractField.SurrenderEffectiveDate]);
|
|
178
|
+
// Ended, or surrendered, before the period even started.
|
|
179
|
+
if (endIST && endIST < periodStartIST)
|
|
180
|
+
return false;
|
|
181
|
+
if (surrenderIST && surrenderIST < periodStartIST)
|
|
182
|
+
return false;
|
|
183
|
+
// Effective end: the surrender date when it lands inside the period,
|
|
184
|
+
// otherwise the contract end (null = open-ended, still matches).
|
|
185
|
+
const effectiveEnd = surrenderIST && surrenderIST <= periodEndIST ? surrenderIST : endIST;
|
|
186
|
+
return (startIST <= periodEndIST &&
|
|
187
|
+
(effectiveEnd === null || effectiveEnd >= periodStartIST));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The BA contracts that get a row in one payout period's template — and,
|
|
191
|
+
* therefore, exactly the rows the upload's completeness guard may demand back.
|
|
192
|
+
*
|
|
193
|
+
* AD-3098 QA re-open, second round. PR #3951 taught the template generator to
|
|
194
|
+
* run its contracts through `selectBAPayoutContracts` (dropping Cancelled
|
|
195
|
+
* records) but left the "rows were deleted" guard in
|
|
196
|
+
* `process.business.associate.payout.upload.controller.ts` building its
|
|
197
|
+
* expected set straight from `ba.contractDetails` with no status filter. The
|
|
198
|
+
* template then omitted a Cancelled contract and the guard immediately
|
|
199
|
+
* rejected the file the application had just produced:
|
|
200
|
+
*
|
|
201
|
+
* "Some BA rows are missing from the uploaded Excel (it looks like rows were
|
|
202
|
+
* deleted). Please re-download the template ... Missing: BA1061
|
|
203
|
+
* (PARTHASARTHY YN) - 1022E2"
|
|
204
|
+
*
|
|
205
|
+
* Re-downloading omitted the row again, so there was no way through at all —
|
|
206
|
+
* 21 (context, period) combinations on staging were blocked outright.
|
|
207
|
+
*
|
|
208
|
+
* Both halves now call THIS function, which is the only place the question
|
|
209
|
+
* "which contracts does this period have?" is answered. Duplicating the
|
|
210
|
+
* status filter and the period filter is what let them drift; there is now
|
|
211
|
+
* nothing to keep in step by hand.
|
|
212
|
+
*
|
|
213
|
+
* Period bounds are already IST-shifted; pass null/undefined for either to
|
|
214
|
+
* select without a period filter.
|
|
215
|
+
*/
|
|
216
|
+
export function selectBAPayoutContractsForPeriod(contracts, periodStartIST, periodEndIST) {
|
|
217
|
+
const eligible = selectBAPayoutContracts(contracts);
|
|
218
|
+
if (!periodStartIST || !periodEndIST)
|
|
219
|
+
return eligible;
|
|
220
|
+
return eligible.filter((contract) => isBAPayoutContractInPeriod(contract, periodStartIST, periodEndIST));
|
|
221
|
+
}
|
|
222
|
+
/** A stored contract date as its IST calendar day, `YYYY-MM-DD`. */
|
|
223
|
+
export const istDayKey = (value) => {
|
|
224
|
+
const ist = toIST(value);
|
|
225
|
+
if (!ist)
|
|
226
|
+
return null;
|
|
227
|
+
const y = ist.getUTCFullYear();
|
|
228
|
+
const m = String(ist.getUTCMonth() + 1).padStart(2, '0');
|
|
229
|
+
const d = String(ist.getUTCDate()).padStart(2, '0');
|
|
230
|
+
return `${y}-${m}-${d}`;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* A date as it arrives from an uploaded payout sheet, as `YYYY-MM-DD`.
|
|
234
|
+
*
|
|
235
|
+
* The template writes `DD/MM/YYYY`, but a cell a user retyped — or one Excel
|
|
236
|
+
* re-serialised — routinely comes back as `YYYY-MM-DD`, with or without a time
|
|
237
|
+
* part. Both are accepted; anything else is `null` and simply does not match.
|
|
238
|
+
*/
|
|
239
|
+
export const parseUploadedDayKey = (raw) => {
|
|
240
|
+
if (raw === null || raw === undefined)
|
|
241
|
+
return null;
|
|
242
|
+
if (raw instanceof Date)
|
|
243
|
+
return istDayKey(raw);
|
|
244
|
+
const text = String(raw).trim();
|
|
245
|
+
if (!text)
|
|
246
|
+
return null;
|
|
247
|
+
const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T\s].*)?$/.exec(text);
|
|
248
|
+
if (iso) {
|
|
249
|
+
return `${iso[1]}-${iso[2].padStart(2, '0')}-${iso[3].padStart(2, '0')}`;
|
|
250
|
+
}
|
|
251
|
+
const dmy = /^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/.exec(text);
|
|
252
|
+
if (dmy) {
|
|
253
|
+
return `${dmy[3]}-${dmy[2].padStart(2, '0')}-${dmy[1].padStart(2, '0')}`;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
};
|
|
257
|
+
export function uploadedRowNamesCancelledRecord(candidates, resolved, row) {
|
|
258
|
+
if (!candidates?.length || !resolved)
|
|
259
|
+
return false;
|
|
260
|
+
const rowStart = parseUploadedDayKey(row?.contractStartDate);
|
|
261
|
+
if (!rowStart)
|
|
262
|
+
return false;
|
|
263
|
+
// Only rows the resolved record already contradicts.
|
|
264
|
+
const resolvedStart = istDayKey(resolved[BAContractField.ContractStartDate]);
|
|
265
|
+
if (resolvedStart === null || resolvedStart === rowStart)
|
|
266
|
+
return false;
|
|
267
|
+
// The row has to say WHICH contract it means. Without both identity
|
|
268
|
+
// columns there is nothing to scope against, and guessing is how this
|
|
269
|
+
// check would suppress the very errors that name the missing column.
|
|
270
|
+
const rowBaId = normalizeSheetIdentifier(row?.baId);
|
|
271
|
+
const rowRegistration = normalizeVehicleRegistration(row?.vehicleRegistration);
|
|
272
|
+
if (!rowBaId || !rowRegistration)
|
|
273
|
+
return false;
|
|
274
|
+
return candidates.some(({ contract, baId }) => !!contract &&
|
|
275
|
+
!isBAPayoutEligibleContract(contract) &&
|
|
276
|
+
normalizeSheetIdentifier(baId) === rowBaId &&
|
|
277
|
+
registrationOf(contract) === rowRegistration &&
|
|
278
|
+
istDayKey(contract[BAContractField.ContractStartDate]) === rowStart);
|
|
279
|
+
}
|
|
137
280
|
//# sourceMappingURL=ba.payout.contract.selection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ba.payout.contract.selection.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/utilities/src/ba.payout.contract.selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAe,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"ba.payout.contract.selection.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/utilities/src/ba.payout.contract.selection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Normalises a raw phone value to E.164 (
|
|
2
|
+
* Normalises a raw phone value to E.164 (`+<country><subscriber>`) format.
|
|
3
3
|
* Returns null for blank, non-numeric, or out-of-range inputs.
|
|
4
|
-
* Default country code is +91 (India)
|
|
4
|
+
* Default country code is +91 (India) — applied only to numbers that do *not*
|
|
5
|
+
* already carry one of their own.
|
|
5
6
|
*
|
|
6
7
|
* **Every number handed to the SMS gateway must come through here.** The
|
|
7
8
|
* gateway refuses anything that is not exactly 13 characters with `91` in
|
|
@@ -10,6 +11,16 @@
|
|
|
10
11
|
* as a failed SMS log, so the caller is told the message went out and nothing
|
|
11
12
|
* arrives.
|
|
12
13
|
*
|
|
14
|
+
* A leading `+` (or the `00` international trunk prefix) is the operator saying
|
|
15
|
+
* "this number already carries its own country code", and is honoured as such.
|
|
16
|
+
* It has to be: every digit-only rule below is length-based, and for a good
|
|
17
|
+
* many countries the country code plus the subscriber number comes to exactly
|
|
18
|
+
* ten digits — Singapore (+65), Australia (+61), Denmark (+45), Norway (+47),
|
|
19
|
+
* New Zealand (+64) among them. Dropping the `+` made `+65-83663164` look like
|
|
20
|
+
* a bare Indian ten-digit number, so it went out as `+916583663164`: a number
|
|
21
|
+
* that does not belong to the guest and may well belong to somebody else
|
|
22
|
+
* (ZD-71141).
|
|
23
|
+
*
|
|
13
24
|
* Lives in the shared package rather than beside the alert handlers because the
|
|
14
25
|
* gatekeeper's OTP paths need it too, and a second copy over there is how the
|
|
15
26
|
* two would drift apart on which shapes they accept.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Normalises a raw phone value to E.164 (
|
|
2
|
+
* Normalises a raw phone value to E.164 (`+<country><subscriber>`) format.
|
|
3
3
|
* Returns null for blank, non-numeric, or out-of-range inputs.
|
|
4
|
-
* Default country code is +91 (India)
|
|
4
|
+
* Default country code is +91 (India) — applied only to numbers that do *not*
|
|
5
|
+
* already carry one of their own.
|
|
5
6
|
*
|
|
6
7
|
* **Every number handed to the SMS gateway must come through here.** The
|
|
7
8
|
* gateway refuses anything that is not exactly 13 characters with `91` in
|
|
@@ -10,10 +11,23 @@
|
|
|
10
11
|
* as a failed SMS log, so the caller is told the message went out and nothing
|
|
11
12
|
* arrives.
|
|
12
13
|
*
|
|
14
|
+
* A leading `+` (or the `00` international trunk prefix) is the operator saying
|
|
15
|
+
* "this number already carries its own country code", and is honoured as such.
|
|
16
|
+
* It has to be: every digit-only rule below is length-based, and for a good
|
|
17
|
+
* many countries the country code plus the subscriber number comes to exactly
|
|
18
|
+
* ten digits — Singapore (+65), Australia (+61), Denmark (+45), Norway (+47),
|
|
19
|
+
* New Zealand (+64) among them. Dropping the `+` made `+65-83663164` look like
|
|
20
|
+
* a bare Indian ten-digit number, so it went out as `+916583663164`: a number
|
|
21
|
+
* that does not belong to the guest and may well belong to somebody else
|
|
22
|
+
* (ZD-71141).
|
|
23
|
+
*
|
|
13
24
|
* Lives in the shared package rather than beside the alert handlers because the
|
|
14
25
|
* gatekeeper's OTP paths need it too, and a second copy over there is how the
|
|
15
26
|
* two would drift apart on which shapes they accept.
|
|
16
27
|
*/
|
|
28
|
+
/** Longest and shortest digit strings E.164 allows. */
|
|
29
|
+
const MIN_E164_DIGITS = 10;
|
|
30
|
+
const MAX_E164_DIGITS = 15;
|
|
17
31
|
export function normalizePhoneNumber(phone, countryCode = '+91') {
|
|
18
32
|
if (phone === null || phone === undefined) {
|
|
19
33
|
return null;
|
|
@@ -27,6 +41,19 @@ export function normalizePhoneNumber(phone, countryCode = '+91') {
|
|
|
27
41
|
if (!digits) {
|
|
28
42
|
return null;
|
|
29
43
|
}
|
|
44
|
+
// Brackets, spaces and hyphens are decoration; the `+` is not. Strip the
|
|
45
|
+
// decoration first so `(+91) 1234567890` is recognised as carrying a code.
|
|
46
|
+
const marker = raw.replace(/[^\d+]/g, '');
|
|
47
|
+
const carriesOwnCountryCode = marker.startsWith('+') || /^00[1-9]/.test(marker);
|
|
48
|
+
if (carriesOwnCountryCode) {
|
|
49
|
+
// `+` and `00` are alternative spellings of the same thing, and no
|
|
50
|
+
// country code starts with a zero, so any leading zeros are prefix.
|
|
51
|
+
const international = digits.replace(/^0+/, '');
|
|
52
|
+
return international.length >= MIN_E164_DIGITS &&
|
|
53
|
+
international.length <= MAX_E164_DIGITS
|
|
54
|
+
? `+${international}`
|
|
55
|
+
: null;
|
|
56
|
+
}
|
|
30
57
|
if (digits.length === 10) {
|
|
31
58
|
return `+${ccDigits}${digits}`;
|
|
32
59
|
}
|
|
@@ -37,7 +64,7 @@ export function normalizePhoneNumber(phone, countryCode = '+91') {
|
|
|
37
64
|
if (noLeadingZero.length === 10) {
|
|
38
65
|
return `+${ccDigits}${noLeadingZero}`;
|
|
39
66
|
}
|
|
40
|
-
if (digits.length >=
|
|
67
|
+
if (digits.length >= MIN_E164_DIGITS && digits.length <= MAX_E164_DIGITS) {
|
|
41
68
|
return `+${digits}`;
|
|
42
69
|
}
|
|
43
70
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize.phone.number.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/utilities/src/normalize.phone.number.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"normalize.phone.number.js","sourceRoot":"","sources":["../../../../../../../libs/shared/src/lib/utilities/src/normalize.phone.number.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,uDAAuD;AACvD,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,MAAM,UAAU,oBAAoB,CACnC,KAAyC,EACzC,cAAsB,KAAK;IAE3B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC;IACxD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACb,CAAC;IAED,yEAAyE;IACzE,2EAA2E;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC1C,MAAM,qBAAqB,GAC1B,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnD,IAAI,qBAAqB,EAAE,CAAC;QAC3B,mEAAmE;QACnE,oEAAoE;QACpE,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAChD,OAAO,aAAa,CAAC,MAAM,IAAI,eAAe;YAC7C,aAAa,CAAC,MAAM,IAAI,eAAe;YACvC,CAAC,CAAC,IAAI,aAAa,EAAE;YACrB,CAAC,CAAC,IAAI,CAAC;IACT,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC1B,OAAO,IAAI,QAAQ,GAAG,MAAM,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,MAAM,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,aAAa,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACjC,OAAO,IAAI,QAAQ,GAAG,aAAa,EAAE,CAAC;IACvC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,IAAI,eAAe,IAAI,MAAM,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;QAC1E,OAAO,IAAI,MAAM,EAAE,CAAC;IACrB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC"}
|