@mj-biz-apps/sales-core-entities-server 5.1.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/LICENSE +183 -0
- package/dist/CloseDealOperation.d.ts +176 -0
- package/dist/CloseDealOperation.d.ts.map +1 -0
- package/dist/CloseDealOperation.js +1107 -0
- package/dist/CloseDealOperation.js.map +1 -0
- package/dist/CloseWonTaskService.d.ts +249 -0
- package/dist/CloseWonTaskService.d.ts.map +1 -0
- package/dist/CloseWonTaskService.js +460 -0
- package/dist/CloseWonTaskService.js.map +1 -0
- package/dist/DealEntityServer.d.ts +625 -0
- package/dist/DealEntityServer.d.ts.map +1 -0
- package/dist/DealEntityServer.js +1529 -0
- package/dist/DealEntityServer.js.map +1 -0
- package/dist/LiveContractsSeam.d.ts +212 -0
- package/dist/LiveContractsSeam.d.ts.map +1 -0
- package/dist/LiveContractsSeam.js +362 -0
- package/dist/LiveContractsSeam.js.map +1 -0
- package/dist/LiveOrdersSeam.d.ts +68 -0
- package/dist/LiveOrdersSeam.d.ts.map +1 -0
- package/dist/LiveOrdersSeam.js +80 -0
- package/dist/LiveOrdersSeam.js.map +1 -0
- package/dist/SaveDealOperation.d.ts +45 -0
- package/dist/SaveDealOperation.d.ts.map +1 -0
- package/dist/SaveDealOperation.js +268 -0
- package/dist/SaveDealOperation.js.map +1 -0
- package/dist/SequenceService.d.ts +39 -0
- package/dist/SequenceService.d.ts.map +1 -0
- package/dist/SequenceService.js +44 -0
- package/dist/SequenceService.js.map +1 -0
- package/dist/activities/ActivityIngestService.d.ts +111 -0
- package/dist/activities/ActivityIngestService.d.ts.map +1 -0
- package/dist/activities/ActivityIngestService.js +485 -0
- package/dist/activities/ActivityIngestService.js.map +1 -0
- package/dist/activities/ActivityReader.d.ts +68 -0
- package/dist/activities/ActivityReader.d.ts.map +1 -0
- package/dist/activities/ActivityReader.js +147 -0
- package/dist/activities/ActivityReader.js.map +1 -0
- package/dist/activities/ActivitySource.d.ts +142 -0
- package/dist/activities/ActivitySource.d.ts.map +1 -0
- package/dist/activities/ActivitySource.js +2 -0
- package/dist/activities/ActivitySource.js.map +1 -0
- package/dist/activities/ActivitySyncJob.d.ts +72 -0
- package/dist/activities/ActivitySyncJob.d.ts.map +1 -0
- package/dist/activities/ActivitySyncJob.js +146 -0
- package/dist/activities/ActivitySyncJob.js.map +1 -0
- package/dist/activities/ActivityWriterService.d.ts +119 -0
- package/dist/activities/ActivityWriterService.d.ts.map +1 -0
- package/dist/activities/ActivityWriterService.js +270 -0
- package/dist/activities/ActivityWriterService.js.map +1 -0
- package/dist/activities/DealMatcher.d.ts +58 -0
- package/dist/activities/DealMatcher.d.ts.map +1 -0
- package/dist/activities/DealMatcher.js +109 -0
- package/dist/activities/DealMatcher.js.map +1 -0
- package/dist/activities/FixtureActivitySource.d.ts +37 -0
- package/dist/activities/FixtureActivitySource.d.ts.map +1 -0
- package/dist/activities/FixtureActivitySource.js +58 -0
- package/dist/activities/FixtureActivitySource.js.map +1 -0
- package/dist/activities/GraphMessageMapper.d.ts +47 -0
- package/dist/activities/GraphMessageMapper.d.ts.map +1 -0
- package/dist/activities/GraphMessageMapper.js +141 -0
- package/dist/activities/GraphMessageMapper.js.map +1 -0
- package/dist/activities/ImportedGraphActivitySource.d.ts +16 -0
- package/dist/activities/ImportedGraphActivitySource.d.ts.map +1 -0
- package/dist/activities/ImportedGraphActivitySource.js +84 -0
- package/dist/activities/ImportedGraphActivitySource.js.map +1 -0
- package/dist/activities/MSGraphActivitySource.d.ts +67 -0
- package/dist/activities/MSGraphActivitySource.d.ts.map +1 -0
- package/dist/activities/MSGraphActivitySource.js +186 -0
- package/dist/activities/MSGraphActivitySource.js.map +1 -0
- package/dist/activities/MSGraphCalendarSource.d.ts +72 -0
- package/dist/activities/MSGraphCalendarSource.d.ts.map +1 -0
- package/dist/activities/MSGraphCalendarSource.js +225 -0
- package/dist/activities/MSGraphCalendarSource.js.map +1 -0
- package/dist/activities/RelevanceFilter.d.ts +101 -0
- package/dist/activities/RelevanceFilter.d.ts.map +1 -0
- package/dist/activities/RelevanceFilter.js +148 -0
- package/dist/activities/RelevanceFilter.js.map +1 -0
- package/dist/activities/activity-vocabulary.d.ts +103 -0
- package/dist/activities/activity-vocabulary.d.ts.map +1 -0
- package/dist/activities/activity-vocabulary.js +79 -0
- package/dist/activities/activity-vocabulary.js.map +1 -0
- package/dist/forecast/FixtureForecastSource.d.ts +22 -0
- package/dist/forecast/FixtureForecastSource.d.ts.map +1 -0
- package/dist/forecast/FixtureForecastSource.js +16 -0
- package/dist/forecast/FixtureForecastSource.js.map +1 -0
- package/dist/forecast/ForecastSnapshotJob.d.ts +78 -0
- package/dist/forecast/ForecastSnapshotJob.d.ts.map +1 -0
- package/dist/forecast/ForecastSnapshotJob.js +225 -0
- package/dist/forecast/ForecastSnapshotJob.js.map +1 -0
- package/dist/forecast/ForecastSource.d.ts +74 -0
- package/dist/forecast/ForecastSource.d.ts.map +1 -0
- package/dist/forecast/ForecastSource.js +25 -0
- package/dist/forecast/ForecastSource.js.map +1 -0
- package/dist/forecast/QueryForecastSource.d.ts +76 -0
- package/dist/forecast/QueryForecastSource.d.ts.map +1 -0
- package/dist/forecast/QueryForecastSource.js +164 -0
- package/dist/forecast/QueryForecastSource.js.map +1 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/orders-availability.d.ts +15 -0
- package/dist/orders-availability.d.ts.map +1 -0
- package/dist/orders-availability.js +47 -0
- package/dist/orders-availability.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The ingest run: source → filter → match → write, with the watermark and failure state.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is behind `IActivitySource`, so this whole file is exercised by the fixture source and
|
|
5
|
+
* needs no mailbox. Order is not negotiable and is enforced by the shape of the method:
|
|
6
|
+
*
|
|
7
|
+
* 1. `Fetch` — the source hands over normalized items
|
|
8
|
+
* 2. `Apply` — the relevance filter discards anything involving no known contact
|
|
9
|
+
* 3. `MatchOpenDeals` — resolve which deal, if any
|
|
10
|
+
* 4. `LogActivity` — write, idempotently
|
|
11
|
+
* 5. watermark — advance `LastSyncAt` only if the run got that far
|
|
12
|
+
*
|
|
13
|
+
* Step 2 is the privacy boundary. No model call may be inserted before it; see `RelevanceFilter`.
|
|
14
|
+
*
|
|
15
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
16
|
+
*/
|
|
17
|
+
import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
18
|
+
import { ActivityWriterService } from './ActivityWriterService.js';
|
|
19
|
+
import { DealMatcher } from './DealMatcher.js';
|
|
20
|
+
import { RelevanceFilter } from './RelevanceFilter.js';
|
|
21
|
+
import type { IActivitySource } from './ActivitySource.js';
|
|
22
|
+
export interface IngestRunResult {
|
|
23
|
+
Success: boolean;
|
|
24
|
+
Fetched: number;
|
|
25
|
+
/** Passed the relevance filter. */
|
|
26
|
+
Relevant: number;
|
|
27
|
+
/** Discarded because no participant was a known contact. Not a failure — the filter working. */
|
|
28
|
+
Irrelevant: number;
|
|
29
|
+
Written: number;
|
|
30
|
+
/** Matched an existing `(SourceSystem, ExternalID)`; nothing written. The idempotency count. */
|
|
31
|
+
Duplicates: number;
|
|
32
|
+
/** Relevant, but matched no open deal. Filed against the parties only. */
|
|
33
|
+
Unattributed: number;
|
|
34
|
+
/**
|
|
35
|
+
* Items this run FAILED on, as opposed to discarded.
|
|
36
|
+
*
|
|
37
|
+
* The distinction is the whole point and the code did not make it. A discarded item has been SEEN
|
|
38
|
+
* — the filter looked at it and said no — so the watermark may pass it. A FAILED item has not been
|
|
39
|
+
* seen to a conclusion, and because `GetMessages` has no date filter (D-17) anything the watermark
|
|
40
|
+
* passes can never be re-fetched. Advancing over a failure loses the item permanently.
|
|
41
|
+
*/
|
|
42
|
+
Failed: number;
|
|
43
|
+
WatermarkAdvancedTo: Date | null;
|
|
44
|
+
Issues: string[];
|
|
45
|
+
}
|
|
46
|
+
export declare class ActivityIngestService {
|
|
47
|
+
private readonly writer;
|
|
48
|
+
private readonly filter;
|
|
49
|
+
private readonly matcher;
|
|
50
|
+
/**
|
|
51
|
+
* The collaborators are injectable, defaulting to the real ones.
|
|
52
|
+
*
|
|
53
|
+
* ── WHY, AND IT IS NOT FOR TIDINESS ──
|
|
54
|
+
*
|
|
55
|
+
* A mutation pass found that nothing exercised the LOOKUP-FAILURE path: reverting
|
|
56
|
+
* `RelevanceFilter`'s `failed: true` to `false` killed no check, because the only failure any check
|
|
57
|
+
* could force was a failed WRITE. So the branch that holds the watermark when a contact-method read
|
|
58
|
+
* blips was untested — the exact branch that stops a transient error discarding a batch of real
|
|
59
|
+
* mail permanently.
|
|
60
|
+
*
|
|
61
|
+
* A read failure cannot be provoked from outside without breaking the database for everything else.
|
|
62
|
+
* Injecting a filter that reports one is the only way to reach it, and it costs three fields.
|
|
63
|
+
*/
|
|
64
|
+
constructor(writer?: ActivityWriterService, filter?: RelevanceFilter, matcher?: DealMatcher);
|
|
65
|
+
/**
|
|
66
|
+
* Runs one sync for one connection.
|
|
67
|
+
*
|
|
68
|
+
* ── WHY FAILURE LANDS ON THE CONNECTION ROW AND NOT IN A LOG ──
|
|
69
|
+
*
|
|
70
|
+
* `ActivitySyncConnection` has `Status` (Active/Paused/Error/Disabled) and `LastError` because a sync
|
|
71
|
+
* that has been failing for a week is an operational fact somebody has to see. A log line is seen by
|
|
72
|
+
* whoever happens to be tailing it. So a failed run sets `Status = 'Error'` with the reason, and a
|
|
73
|
+
* successful one clears both — which means the row answers "is this working" without anyone reading
|
|
74
|
+
* anything else.
|
|
75
|
+
*/
|
|
76
|
+
RunSync(connectionID: string, source: IActivitySource, provider: IMetadataProvider, contextUser: UserInfo, limit?: number): Promise<IngestRunResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Writes one relevant item — once per matched deal, or once against the parties when none matched.
|
|
79
|
+
*
|
|
80
|
+
* ── THE UNATTRIBUTED CASE IS THE POINT, NOT THE FALLBACK ──
|
|
81
|
+
*
|
|
82
|
+
* S-US10's third criterion is that items link to account and contact "even where no deal match
|
|
83
|
+
* exists". The writer needs a `DealID`, so an unattributed item cannot go through the same path —
|
|
84
|
+
* instead the parties are recorded as UNRESOLVED IDENTITIES on the deal-less side. That keeps the
|
|
85
|
+
* evidence without inventing a stub Person, which the common migration forbids.
|
|
86
|
+
*/
|
|
87
|
+
private fileItem;
|
|
88
|
+
/**
|
|
89
|
+
* Whether the stored rules allow this item.
|
|
90
|
+
*
|
|
91
|
+
* `Sequence` order, LAST MATCH WINS, default include. That is the ordinary firewall convention and
|
|
92
|
+
* the one a reader of `Sequence` + `Action` would expect — but nothing in the schema states it, so it
|
|
93
|
+
* is a choice recorded here rather than a fact read off the table. See D-21.
|
|
94
|
+
*
|
|
95
|
+
* `Filter` is NOT evaluated. It is a free-text column with no stated grammar, and inventing one here
|
|
96
|
+
* would mean every future consumer had to match an undocumented dialect.
|
|
97
|
+
*/
|
|
98
|
+
private rulesAllow;
|
|
99
|
+
private loadConnection;
|
|
100
|
+
private loadRules;
|
|
101
|
+
/** The key in `Settings` holding the calendar watermark. */
|
|
102
|
+
private static readonly CALENDAR_WATERMARK;
|
|
103
|
+
/** The watermark for one surface. Messages use the column; the calendar uses `Settings`. */
|
|
104
|
+
private watermarkFor;
|
|
105
|
+
private advanceWatermark;
|
|
106
|
+
private recordFailure;
|
|
107
|
+
/** A clean run clears the error, so `Status` reflects the LAST run rather than the worst one. */
|
|
108
|
+
private clearFailure;
|
|
109
|
+
private connectionEntity;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=ActivityIngestService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivityIngestService.d.ts","sourceRoot":"","sources":["../../src/activities/ActivityIngestService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAqB,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,qBAAqB,EAAmC,MAAM,4BAA4B,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAyB,MAAM,sBAAsB,CAAC;AAE9E,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,qBAAqB,CAAC;AAG3E,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAyBD,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC;;;;;;;;;;;;;OAaG;gBAEC,MAAM,GAAE,qBAAmD,EAC3D,MAAM,GAAE,eAAuC,EAC/C,OAAO,GAAE,WAA+B;IAO5C;;;;;;;;;;OAUG;IACU,OAAO,CAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,QAAQ,EACrB,KAAK,SAAM,GACZ,OAAO,CAAC,eAAe,CAAC;IAsL3B;;;;;;;;;OASG;YACW,QAAQ;IA4HtB;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;YAoBJ,cAAc;YAad,SAAS;IAavB,4DAA4D;IAC5D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAElE,4FAA4F;IAC5F,OAAO,CAAC,YAAY;YAiBN,gBAAgB;YAoChB,aAAa;IAiB3B,iGAAiG;YACnF,YAAY;YAmBZ,gBAAgB;CAWjC"}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The ingest run: source → filter → match → write, with the watermark and failure state.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is behind `IActivitySource`, so this whole file is exercised by the fixture source and
|
|
5
|
+
* needs no mailbox. Order is not negotiable and is enforced by the shape of the method:
|
|
6
|
+
*
|
|
7
|
+
* 1. `Fetch` — the source hands over normalized items
|
|
8
|
+
* 2. `Apply` — the relevance filter discards anything involving no known contact
|
|
9
|
+
* 3. `MatchOpenDeals` — resolve which deal, if any
|
|
10
|
+
* 4. `LogActivity` — write, idempotently
|
|
11
|
+
* 5. watermark — advance `LastSyncAt` only if the run got that far
|
|
12
|
+
*
|
|
13
|
+
* Step 2 is the privacy boundary. No model call may be inserted before it; see `RelevanceFilter`.
|
|
14
|
+
*
|
|
15
|
+
* @module @mj-biz-apps/sales-core-entities-server
|
|
16
|
+
*/
|
|
17
|
+
import { LogError, RunView } from '@memberjunction/core';
|
|
18
|
+
import { ActivityWriterService, escapeSql } from './ActivityWriterService.js';
|
|
19
|
+
import { DealMatcher } from './DealMatcher.js';
|
|
20
|
+
import { RelevanceFilter } from './RelevanceFilter.js';
|
|
21
|
+
import { E_ACTIVITY_SYNC_CONNECTION, E_ACTIVITY_SYNC_RULE } from '@mj-biz-apps/sales-entities';
|
|
22
|
+
export class ActivityIngestService {
|
|
23
|
+
/**
|
|
24
|
+
* The collaborators are injectable, defaulting to the real ones.
|
|
25
|
+
*
|
|
26
|
+
* ── WHY, AND IT IS NOT FOR TIDINESS ──
|
|
27
|
+
*
|
|
28
|
+
* A mutation pass found that nothing exercised the LOOKUP-FAILURE path: reverting
|
|
29
|
+
* `RelevanceFilter`'s `failed: true` to `false` killed no check, because the only failure any check
|
|
30
|
+
* could force was a failed WRITE. So the branch that holds the watermark when a contact-method read
|
|
31
|
+
* blips was untested — the exact branch that stops a transient error discarding a batch of real
|
|
32
|
+
* mail permanently.
|
|
33
|
+
*
|
|
34
|
+
* A read failure cannot be provoked from outside without breaking the database for everything else.
|
|
35
|
+
* Injecting a filter that reports one is the only way to reach it, and it costs three fields.
|
|
36
|
+
*/
|
|
37
|
+
constructor(writer = new ActivityWriterService(), filter = new RelevanceFilter(), matcher = new DealMatcher()) {
|
|
38
|
+
this.writer = writer;
|
|
39
|
+
this.filter = filter;
|
|
40
|
+
this.matcher = matcher;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs one sync for one connection.
|
|
44
|
+
*
|
|
45
|
+
* ── WHY FAILURE LANDS ON THE CONNECTION ROW AND NOT IN A LOG ──
|
|
46
|
+
*
|
|
47
|
+
* `ActivitySyncConnection` has `Status` (Active/Paused/Error/Disabled) and `LastError` because a sync
|
|
48
|
+
* that has been failing for a week is an operational fact somebody has to see. A log line is seen by
|
|
49
|
+
* whoever happens to be tailing it. So a failed run sets `Status = 'Error'` with the reason, and a
|
|
50
|
+
* successful one clears both — which means the row answers "is this working" without anyone reading
|
|
51
|
+
* anything else.
|
|
52
|
+
*/
|
|
53
|
+
async RunSync(connectionID, source, provider, contextUser, limit = 100) {
|
|
54
|
+
const result = {
|
|
55
|
+
Success: false,
|
|
56
|
+
Fetched: 0,
|
|
57
|
+
Relevant: 0,
|
|
58
|
+
Irrelevant: 0,
|
|
59
|
+
Written: 0,
|
|
60
|
+
Duplicates: 0,
|
|
61
|
+
Unattributed: 0,
|
|
62
|
+
Failed: 0,
|
|
63
|
+
WatermarkAdvancedTo: null,
|
|
64
|
+
Issues: [],
|
|
65
|
+
};
|
|
66
|
+
const connection = await this.loadConnection(connectionID, contextUser);
|
|
67
|
+
if (!connection) {
|
|
68
|
+
result.Issues.push(`No ActivitySyncConnection with ID '${connectionID}' exists.`);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* ONLY `Active` RUNS. Paused, Error and Disabled all mean "do not read this mailbox", and the
|
|
73
|
+
* distinction between them is for a human — an operator paused it, a run failed, an admin turned
|
|
74
|
+
* it off. Treating any of them as runnable would make pausing advisory.
|
|
75
|
+
*/
|
|
76
|
+
/*
|
|
77
|
+
* WHY THIS IS EXEMPT FROM THE VOCABULARY GATE, which correctly flagged it.
|
|
78
|
+
*
|
|
79
|
+
* `ActivitySyncConnection.Status` is a CHECK-constrained OPERATIONAL state in another app's
|
|
80
|
+
* schema, not domain vocabulary: there is no companion type table and no behaviour flag to read
|
|
81
|
+
* instead -- the column IS the state, and `recordFailure` below writes it. Renaming a value is a
|
|
82
|
+
* migration in bizapps-common, not somebody editing a lookup row, so the hazard the gate guards
|
|
83
|
+
* against -- a rename silently changing behaviour -- cannot arise here.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* `Error` IS RETRIED. `Paused` and `Disabled` are not.
|
|
87
|
+
*
|
|
88
|
+
* This gate used to refuse anything but `Active`, and `clearFailure` sat inside the try block
|
|
89
|
+
* the gate protected — so one Graph throttle latched the connection off permanently. Nothing
|
|
90
|
+
* would retry it, and the job then reported SUCCESS with NO_CONNECTIONS when the only mailbox
|
|
91
|
+
* had been disabled by a transient error. Recovering meant a manual UPDATE on another app's
|
|
92
|
+
* table.
|
|
93
|
+
*
|
|
94
|
+
* The distinction the gate was missing: `Paused` and `Disabled` are decisions a PERSON made and
|
|
95
|
+
* must be honoured, while `Error` is a record of the last run and says nothing about whether
|
|
96
|
+
* the next one will work. So `Error` retries, and a clean run clears it.
|
|
97
|
+
*/
|
|
98
|
+
const runnable = connection.Status === 'Active' || connection.Status === 'Error'; // vocabulary-grep-allow: operational state, not vocabulary
|
|
99
|
+
if (!runnable) {
|
|
100
|
+
result.Issues.push(`Connection '${connectionID}' is ${connection.Status}, not Active, so no sync was attempted.`);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
if (connection.Provider !== source.ProviderName) {
|
|
104
|
+
result.Issues.push(`Connection provider '${connection.Provider}' does not match the source '${source.ProviderName}'.`);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
if (!connection.Mailbox) {
|
|
108
|
+
result.Issues.push(`Connection '${connectionID}' names no mailbox.`);
|
|
109
|
+
await this.recordFailure(connectionID, 'No mailbox is configured.', provider, contextUser);
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
/**
|
|
114
|
+
* ONE WATERMARK PER SURFACE, and this is a correctness requirement rather than tidiness.
|
|
115
|
+
*
|
|
116
|
+
* A connection has two sources -- messages and calendar -- and they advance independently. A
|
|
117
|
+
* shared `LastSyncAt` would take the max of both, so a meeting older than the newest email
|
|
118
|
+
* would be considered already-seen and SKIPPED FOREVER: the watermark moved past it without
|
|
119
|
+
* the calendar source ever having been asked. Nothing would error and no row would be
|
|
120
|
+
* missing from anything anyone counts.
|
|
121
|
+
*
|
|
122
|
+
* `LastSyncAt` stays the MESSAGE watermark, so nothing about existing behaviour changes, and
|
|
123
|
+
* the calendar's lives in `Settings` -- an existing nullable JSON column on the connection.
|
|
124
|
+
* That avoids a migration in another app's schema for a field only this app reads.
|
|
125
|
+
*/
|
|
126
|
+
const since = this.watermarkFor(connection, source.Kind);
|
|
127
|
+
const batch = await source.Fetch({ Mailbox: connection.Mailbox, Since: since, Limit: limit });
|
|
128
|
+
result.Fetched = batch.Items.length;
|
|
129
|
+
result.Issues.push(...batch.Issues);
|
|
130
|
+
const rules = await this.loadRules(connectionID, contextUser);
|
|
131
|
+
const allowed = batch.Items.filter((item) => this.rulesAllow(item, rules));
|
|
132
|
+
if (allowed.length !== batch.Items.length) {
|
|
133
|
+
result.Issues.push(`${batch.Items.length - allowed.length} item(s) were excluded by ActivitySyncRule rows.`);
|
|
134
|
+
}
|
|
135
|
+
// ── THE PRIVACY BOUNDARY. Nothing above this line has looked at content for meaning. ──
|
|
136
|
+
const filtered = await this.filter.Apply(allowed, contextUser);
|
|
137
|
+
if (filtered.LookupFailed) {
|
|
138
|
+
/**
|
|
139
|
+
* A FAILED LOOKUP IS NOT AN IRRELEVANT BATCH. Counting these as failures is what holds
|
|
140
|
+
* the watermark: without it a database blip discarded the whole batch as personal mail
|
|
141
|
+
* and moved past it, and D-17 means it could never be re-fetched.
|
|
142
|
+
*/
|
|
143
|
+
result.Failed += allowed.length;
|
|
144
|
+
result.Issues.push('The contact-method lookup failed, so nothing could be judged relevant. The watermark '
|
|
145
|
+
+ 'was held and these items will be re-read.');
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
const relevant = filtered.Verdicts.filter((v) => v.IsRelevant);
|
|
149
|
+
result.Relevant = relevant.length;
|
|
150
|
+
result.Irrelevant = filtered.Verdicts.length - relevant.length;
|
|
151
|
+
for (const verdict of relevant) {
|
|
152
|
+
await this.fileItem(verdict, connection, source, provider, contextUser, result);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* THE WATERMARK ADVANCES ON A SUCCESSFUL RUN ONLY, and to the newest item the SOURCE reported
|
|
156
|
+
* rather than the newest one written. An item the filter discarded has still been seen, and
|
|
157
|
+
* not advancing past it would re-read and re-discard it forever.
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* THE WATERMARK ONLY MOVES IF NOTHING FAILED.
|
|
161
|
+
*
|
|
162
|
+
* Two ways a failure used to slip past it. `fileItem` records a failed write as an Issue
|
|
163
|
+
* and continues, and `LogActivity` returns `Success: false` rather than throwing — so the
|
|
164
|
+
* loop completed and `result.Success` was set true regardless. And `RelevanceFilter.lookup`
|
|
165
|
+
* and `DealMatcher` both return empty on a READ failure, which made a transient database
|
|
166
|
+
* blip look exactly like a batch of irrelevant mail.
|
|
167
|
+
*
|
|
168
|
+
* Either way the watermark advanced over items that were never written, and D-17 means they
|
|
169
|
+
* can never be re-fetched. Holding the watermark costs one re-read on the next run, which
|
|
170
|
+
* dedupe absorbs; advancing over a failure costs the item.
|
|
171
|
+
*/
|
|
172
|
+
if (batch.HighWatermark && result.Failed === 0) {
|
|
173
|
+
await this.advanceWatermark(connectionID, batch.HighWatermark, source.Kind, provider, contextUser, result);
|
|
174
|
+
}
|
|
175
|
+
if (result.Failed > 0) {
|
|
176
|
+
result.Issues.push(`${result.Failed} item(s) failed rather than being discarded, so the watermark was `
|
|
177
|
+
+ 'held. They will be re-read next run and dedupe will absorb anything that did land.');
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* A run that failed on individual items is NOT a success. It used to report one, which is
|
|
181
|
+
* what let the connection look healthy while losing mail.
|
|
182
|
+
*/
|
|
183
|
+
result.Success = result.Failed === 0;
|
|
184
|
+
/**
|
|
185
|
+
* ── AND THE FAILURE ROW IS ONLY CLEARED ON AN ACTUAL SUCCESS ────────────────────
|
|
186
|
+
*
|
|
187
|
+
* `clearFailure` ran BEFORE `Success` was computed, so it ran unconditionally: a run where
|
|
188
|
+
* EVERY write failed still reset the connection to `Active` with `LastError` blanked. The
|
|
189
|
+
* fix above made the RESULT honest and left the ROW lying, which is the half a human
|
|
190
|
+
* actually looks at.
|
|
191
|
+
*
|
|
192
|
+
* This file's own header says that row exists so a week of failure is visible. Clearing it
|
|
193
|
+
* on a failed run is the precise inverse — each failure erases the evidence of the last, so
|
|
194
|
+
* a connection failing every night forever looks healthy every morning.
|
|
195
|
+
*/
|
|
196
|
+
if (result.Success) {
|
|
197
|
+
await this.clearFailure(connectionID, provider, contextUser);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
LogError(`ActivityIngestService.RunSync failed for connection ${connectionID}: ${err}`);
|
|
203
|
+
result.Issues.push(`The sync failed: ${String(err)}`);
|
|
204
|
+
await this.recordFailure(connectionID, String(err), provider, contextUser);
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Writes one relevant item — once per matched deal, or once against the parties when none matched.
|
|
210
|
+
*
|
|
211
|
+
* ── THE UNATTRIBUTED CASE IS THE POINT, NOT THE FALLBACK ──
|
|
212
|
+
*
|
|
213
|
+
* S-US10's third criterion is that items link to account and contact "even where no deal match
|
|
214
|
+
* exists". The writer needs a `DealID`, so an unattributed item cannot go through the same path —
|
|
215
|
+
* instead the parties are recorded as UNRESOLVED IDENTITIES on the deal-less side. That keeps the
|
|
216
|
+
* evidence without inventing a stub Person, which the common migration forbids.
|
|
217
|
+
*/
|
|
218
|
+
async fileItem(verdict, connection, source, provider, contextUser, result) {
|
|
219
|
+
const item = verdict.Item;
|
|
220
|
+
const matched = await this.matcher.MatchOpenDeals(verdict.Matches, contextUser);
|
|
221
|
+
if (matched.ReadFailed) {
|
|
222
|
+
/**
|
|
223
|
+
* The deal read failed, so this item has NOT been considered. Reporting it as unattributed
|
|
224
|
+
* would claim it was looked at and matched nothing.
|
|
225
|
+
*/
|
|
226
|
+
result.Failed++;
|
|
227
|
+
result.Issues.push(`"${truncate(item.Subject)}" could not be matched to a deal — the read failed, so it was `
|
|
228
|
+
+ 'neither filed nor discarded.');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const deals = matched.Matches;
|
|
232
|
+
if (deals.length === 0) {
|
|
233
|
+
result.Unattributed++;
|
|
234
|
+
result.Issues.push(`"${truncate(item.Subject)}" involves a known contact but matches no open deal, so it was `
|
|
235
|
+
+ 'not filed. Its participants are known; a later matcher can attribute it.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
for (const match of deals) {
|
|
239
|
+
const written = await this.writer.LogActivity({
|
|
240
|
+
DealID: match.DealID,
|
|
241
|
+
TypeCode: item.TypeCode,
|
|
242
|
+
Title: item.Subject,
|
|
243
|
+
Description: item.Body,
|
|
244
|
+
StartedAt: item.StartedAt,
|
|
245
|
+
EndedAt: item.EndedAt,
|
|
246
|
+
Location: item.Location,
|
|
247
|
+
Direction: item.Direction,
|
|
248
|
+
/**
|
|
249
|
+
* D-25, DECIDED: a cancelled meeting is `Cancelled`, not `Completed`.
|
|
250
|
+
*
|
|
251
|
+
* A meeting that did not happen is not a completed activity, and anyone reading a
|
|
252
|
+
* timeline would be misled by one that says it was. `CK_Activity_Status` already
|
|
253
|
+
* allows `'Cancelled'`, so this needs no new vocabulary -- the value was there and
|
|
254
|
+
* the earlier version simply was not using it.
|
|
255
|
+
*
|
|
256
|
+
* `Outcome` is deliberately left NULL. The nearest seeded outcome is `NoShow`, and
|
|
257
|
+
* that is a DIFFERENT fact: a no-show is a meeting that went ahead and somebody
|
|
258
|
+
* failed to attend. Reusing it for a meeting called off in advance would put a
|
|
259
|
+
* false claim in a column reports read.
|
|
260
|
+
*/
|
|
261
|
+
Status: item.Cancelled ? 'Cancelled' : 'Completed',
|
|
262
|
+
/**
|
|
263
|
+
* `Integration`, never `Manual` — and only when the source actually contacted a
|
|
264
|
+
* remote. A fixture run writes `System`, so a database can never be read as holding
|
|
265
|
+
* real ingested mail when it holds test data.
|
|
266
|
+
*/
|
|
267
|
+
Source: source.IsLive ? 'Integration' : 'System',
|
|
268
|
+
SourceSystem: source.ProviderName,
|
|
269
|
+
ExternalID: item.ExternalID,
|
|
270
|
+
ExternalThreadID: item.ExternalThreadID,
|
|
271
|
+
ActivitySyncConnectionID: connection.ID,
|
|
272
|
+
Details: safeStringify(item.Raw),
|
|
273
|
+
/**
|
|
274
|
+
* The deal's own parties are NOT added. Attaching the deal's contact to a message they
|
|
275
|
+
* were not on would be a fabricated fact — the participants below are the ones who
|
|
276
|
+
* were actually on it.
|
|
277
|
+
*/
|
|
278
|
+
IncludeDealParties: false,
|
|
279
|
+
Parties: verdict.Matches.flatMap((known) => {
|
|
280
|
+
const role = roleFor(item, known.Address);
|
|
281
|
+
const parties = [];
|
|
282
|
+
if (known.PersonID) {
|
|
283
|
+
parties.push({ Kind: 'Person', RecordID: known.PersonID, Role: role });
|
|
284
|
+
}
|
|
285
|
+
if (known.OrganizationID) {
|
|
286
|
+
parties.push({
|
|
287
|
+
Kind: 'Organization',
|
|
288
|
+
RecordID: known.OrganizationID,
|
|
289
|
+
Role: 'Participant',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return parties;
|
|
293
|
+
}),
|
|
294
|
+
/** Everyone on the message we do not know, kept as identities for a later matcher. */
|
|
295
|
+
UnresolvedParties: verdict.Unmatched.map((address) => ({
|
|
296
|
+
Kind: 'Email',
|
|
297
|
+
Value: address,
|
|
298
|
+
Role: roleFor(item, address),
|
|
299
|
+
})),
|
|
300
|
+
}, provider, contextUser);
|
|
301
|
+
if (written.AlreadyPresent) {
|
|
302
|
+
result.Duplicates++;
|
|
303
|
+
}
|
|
304
|
+
else if (written.Success) {
|
|
305
|
+
result.Written++;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
/**
|
|
309
|
+
* COUNTED AS A FAILURE, not just reported. This is one of the two paths that used to
|
|
310
|
+
* let the watermark advance over an item that was never written.
|
|
311
|
+
*/
|
|
312
|
+
result.Failed++;
|
|
313
|
+
result.Issues.push(`"${truncate(item.Subject)}" could not be filed: ${written.Issues.join(' | ')}`);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* ONE ROW, MANY DEALS — so only the FIRST deal writes it. `(SourceSystem, ExternalID)` is
|
|
317
|
+
* unique, so a second deal would hit the idempotency short-circuit and count as a duplicate
|
|
318
|
+
* rather than gaining a second `Regarding` link. Linking one activity to several deals needs a
|
|
319
|
+
* writer that appends links to an existing row, which is a real gap: see D-20.
|
|
320
|
+
*/
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Whether the stored rules allow this item.
|
|
326
|
+
*
|
|
327
|
+
* `Sequence` order, LAST MATCH WINS, default include. That is the ordinary firewall convention and
|
|
328
|
+
* the one a reader of `Sequence` + `Action` would expect — but nothing in the schema states it, so it
|
|
329
|
+
* is a choice recorded here rather than a fact read off the table. See D-21.
|
|
330
|
+
*
|
|
331
|
+
* `Filter` is NOT evaluated. It is a free-text column with no stated grammar, and inventing one here
|
|
332
|
+
* would mean every future consumer had to match an undocumented dialect.
|
|
333
|
+
*/
|
|
334
|
+
rulesAllow(item, rules) {
|
|
335
|
+
let allowed = true;
|
|
336
|
+
for (const rule of rules) {
|
|
337
|
+
if (!rule.IsEnabled) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (rule.Direction && rule.Direction !== item.Direction) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (rule.DateFrom && item.StartedAt < new Date(rule.DateFrom)) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (rule.DateTo && item.StartedAt > new Date(rule.DateTo)) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
allowed = rule.Action === 'Include';
|
|
350
|
+
}
|
|
351
|
+
return allowed;
|
|
352
|
+
}
|
|
353
|
+
async loadConnection(id, contextUser) {
|
|
354
|
+
const r = await new RunView().RunView({
|
|
355
|
+
EntityName: E_ACTIVITY_SYNC_CONNECTION,
|
|
356
|
+
ExtraFilter: `ID = '${escapeSql(id)}'`,
|
|
357
|
+
ResultType: 'simple',
|
|
358
|
+
Fields: ['ID', 'Provider', 'Status', 'Mailbox', 'LastSyncAt', 'Settings'],
|
|
359
|
+
}, contextUser);
|
|
360
|
+
return r.Success ? ((r.Results ?? [])[0] ?? null) : null;
|
|
361
|
+
}
|
|
362
|
+
async loadRules(connectionID, contextUser) {
|
|
363
|
+
const r = await new RunView().RunView({
|
|
364
|
+
EntityName: E_ACTIVITY_SYNC_RULE,
|
|
365
|
+
ExtraFilter: `ActivitySyncConnectionID = '${escapeSql(connectionID)}'`,
|
|
366
|
+
OrderBy: 'Sequence ASC',
|
|
367
|
+
ResultType: 'simple',
|
|
368
|
+
}, contextUser);
|
|
369
|
+
return r.Success ? (r.Results ?? []) : [];
|
|
370
|
+
}
|
|
371
|
+
/** The key in `Settings` holding the calendar watermark. */
|
|
372
|
+
static { this.CALENDAR_WATERMARK = 'CalendarLastSyncAt'; }
|
|
373
|
+
/** The watermark for one surface. Messages use the column; the calendar uses `Settings`. */
|
|
374
|
+
watermarkFor(connection, kind) {
|
|
375
|
+
if (kind === 'Message') {
|
|
376
|
+
return connection.LastSyncAt ? new Date(connection.LastSyncAt) : null;
|
|
377
|
+
}
|
|
378
|
+
const stored = readSettings(connection.Settings)[ActivityIngestService.CALENDAR_WATERMARK];
|
|
379
|
+
if (typeof stored !== 'string' || !stored) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const parsed = new Date(stored);
|
|
383
|
+
/**
|
|
384
|
+
* AN UNPARSEABLE WATERMARK IS TREATED AS ABSENT, not as an error. Hand-edited JSON is the likely
|
|
385
|
+
* cause, and the recovery -- re-read the window and let dedupe absorb it -- costs one extra fetch.
|
|
386
|
+
* Failing the run instead would leave the calendar permanently stuck on a typo.
|
|
387
|
+
*/
|
|
388
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
389
|
+
}
|
|
390
|
+
async advanceWatermark(connectionID, to, kind, provider, contextUser, result) {
|
|
391
|
+
const row = await this.connectionEntity(connectionID, provider, contextUser);
|
|
392
|
+
if (!row) {
|
|
393
|
+
result.Issues.push('The watermark could not be advanced — the connection row would not load.');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (kind === 'Message') {
|
|
397
|
+
row.LastSyncAt = to;
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
/**
|
|
401
|
+
* MERGED, NOT OVERWRITTEN. `Settings` is a shared JSON bag on another app's row -- anything
|
|
402
|
+
* else keeping state there must survive this write, and replacing the object would delete it
|
|
403
|
+
* silently.
|
|
404
|
+
*/
|
|
405
|
+
const settings = readSettings(row.Settings);
|
|
406
|
+
settings[ActivityIngestService.CALENDAR_WATERMARK] = to.toISOString();
|
|
407
|
+
row.Settings = JSON.stringify(settings);
|
|
408
|
+
}
|
|
409
|
+
if (await row.Save()) {
|
|
410
|
+
result.WatermarkAdvancedTo = to;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
result.Issues.push(`The watermark could not be advanced: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}. `
|
|
414
|
+
+ 'The next run will re-read this window; dedupe will absorb it.');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async recordFailure(connectionID, reason, provider, contextUser) {
|
|
418
|
+
const row = await this.connectionEntity(connectionID, provider, contextUser);
|
|
419
|
+
if (!row) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
row.Status = 'Error';
|
|
423
|
+
row.LastError = reason.slice(0, 4000);
|
|
424
|
+
if (!(await row.Save())) {
|
|
425
|
+
LogError(`Could not record the sync failure on connection ${connectionID}.`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/** A clean run clears the error, so `Status` reflects the LAST run rather than the worst one. */
|
|
429
|
+
async clearFailure(connectionID, provider, contextUser) {
|
|
430
|
+
const row = await this.connectionEntity(connectionID, provider, contextUser);
|
|
431
|
+
/**
|
|
432
|
+
* Clears on either signal, not just `LastError`. A connection left `Error` with a null message —
|
|
433
|
+
* possible if a previous version failed before writing one — would otherwise stay `Error`
|
|
434
|
+
* forever even though every run since has succeeded.
|
|
435
|
+
*/
|
|
436
|
+
if (!row || (!row.LastError && row.Status !== 'Error')) { // vocabulary-grep-allow: operational state, not vocabulary
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
row.LastError = null;
|
|
440
|
+
row.Status = 'Active';
|
|
441
|
+
await row.Save();
|
|
442
|
+
}
|
|
443
|
+
async connectionEntity(id, provider, contextUser) {
|
|
444
|
+
const row = await provider.GetEntityObject(E_ACTIVITY_SYNC_CONNECTION, contextUser);
|
|
445
|
+
return (await row.Load(id)) ? row : null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* `ActivitySyncConnection.Settings` parsed, or an empty bag.
|
|
450
|
+
*
|
|
451
|
+
* Never throws: the column is free-form JSON on a row this app does not own, so unparseable content is
|
|
452
|
+
* somebody else's problem to fix and not a reason to fail a sync.
|
|
453
|
+
*/
|
|
454
|
+
function readSettings(raw) {
|
|
455
|
+
if (!raw) {
|
|
456
|
+
return {};
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const parsed = JSON.parse(raw);
|
|
460
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
461
|
+
? parsed
|
|
462
|
+
: {};
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/** The role an address earned on this item, defaulting to `Participant` when it is not listed. */
|
|
469
|
+
function roleFor(item, address) {
|
|
470
|
+
const match = item.Participants.find((p) => p.Address.trim().toLowerCase() === address.trim().toLowerCase());
|
|
471
|
+
return match ? match.Role : 'Participant';
|
|
472
|
+
}
|
|
473
|
+
/** `Details` is nvarchar(max); a payload that will not stringify must not take the whole run down. */
|
|
474
|
+
function safeStringify(value) {
|
|
475
|
+
try {
|
|
476
|
+
return JSON.stringify(value);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function truncate(value, max = 60) {
|
|
483
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=ActivityIngestService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivityIngestService.js","sourceRoot":"","sources":["../../src/activities/ActivityIngestService.ts"],"names":[],"mappings":"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"}
|