@arcforgelabs/flow 2026.10.53-2 → 2026.10.53-3
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/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcforgelabs/flow",
|
|
3
|
-
"version": "2026.10.53-
|
|
3
|
+
"version": "2026.10.53-3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@arcforgelabs/flow",
|
|
9
|
-
"version": "2026.10.53-
|
|
9
|
+
"version": "2026.10.53-3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@clerk/backend": "3.25.0",
|
package/package.json
CHANGED
package/server/core/reconcile.js
CHANGED
|
@@ -124,6 +124,20 @@ export async function reconcileDays(db, organisationId, { integrations, from, to
|
|
|
124
124
|
and e.kind in ('observed', 'source_changed') and e.actor <> ${HISTORY_ACTOR})`,
|
|
125
125
|
));
|
|
126
126
|
|
|
127
|
+
// A billable feed cannot prove absence. Check the remaining ledger refs
|
|
128
|
+
// against the source copy without billing filters. Older adapters retain
|
|
129
|
+
// the feed comparison until they declare workJobPresence.
|
|
130
|
+
const present = new Set(work.keys());
|
|
131
|
+
for (const adapter of workAdapters) {
|
|
132
|
+
if (!adapter.capabilities.workJobPresence) continue;
|
|
133
|
+
const jobRefs = [...new Set(obligations
|
|
134
|
+
.filter((o) => o.sourceSystem === adapter.id && !present.has(`${adapter.id}:${o.sourceJobRef}`))
|
|
135
|
+
.map((o) => o.sourceJobRef))];
|
|
136
|
+
for (let i = 0; i < jobRefs.length; i += FIND_BATCH) {
|
|
137
|
+
const { presentRefs } = await adapter.capabilities.workJobPresence({ organisationId, jobRefs: jobRefs.slice(i, i + FIND_BATCH) });
|
|
138
|
+
for (const ref of presentRefs) present.add(`${adapter.id}:${ref}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
127
141
|
const reported = new Set(workAdapters.map((a) => a.id));
|
|
128
142
|
const inLedger = new Set();
|
|
129
143
|
for (const o of obligations) {
|
|
@@ -139,7 +153,7 @@ export async function reconcileDays(db, organisationId, { integrations, from, to
|
|
|
139
153
|
if (reported.has(o.sourceSystem)) {
|
|
140
154
|
const key = `${o.sourceSystem}:${o.sourceJobRef}`;
|
|
141
155
|
inLedger.add(key);
|
|
142
|
-
if (!
|
|
156
|
+
if (!present.has(key)) entry.differences.missingInWork.push({ id: o.id, cents: o.totalCents });
|
|
143
157
|
}
|
|
144
158
|
}
|
|
145
159
|
for (const [key, job] of work) {
|
|
@@ -24,6 +24,7 @@ export const CAPABILITIES = Object.freeze([
|
|
|
24
24
|
'pullPayments', // payments since a cursor, matched to invoices and bills
|
|
25
25
|
'checkInvoice', // does the accounting system hold an invoice for a billable job? (the ledger's liveCheck)
|
|
26
26
|
'billableJobs', // billable jobs by day from the work system's copy in Flow, for the daily reconcile; never a live call
|
|
27
|
+
'workJobPresence', // { organisationId, jobRefs } -> { presentRefs }: requested jobs still present in the work copy, without status, date or line filters; deleted jobs are absent; never a live call
|
|
27
28
|
'jobReferenceFields', // { patient }: the fields a job gives the invoice Reference (server/core/references.js)
|
|
28
29
|
'jobLink', // { url, exact } or null: the link to one job in the work system's own web app, for "Open in" on a job's problem; never a live call
|
|
29
30
|
'listWork', // the work system's jobs from its copy in Flow, open and lately finished, for the Jobs board; a job may carry `pendingMove`
|
|
@@ -4,7 +4,7 @@ import { createSeazonaClient } from './client.js';
|
|
|
4
4
|
import { listClients, listProducts, listWork, readBillableWork } from './feed.js';
|
|
5
5
|
import { historyJobIssues, historySummary, readHistoryWork } from './history/index.js';
|
|
6
6
|
import { readSeazonaSyncHealth, runSeazonaSync } from './lanes.js';
|
|
7
|
-
import { readBillableJobs } from './reconcile.js';
|
|
7
|
+
import { readBillableJobs, readWorkJobPresence } from './reconcile.js';
|
|
8
8
|
import { SEAZONA_CALL_GUARD } from './governor.js';
|
|
9
9
|
import { createSeazonaRouter } from './routes.js';
|
|
10
10
|
import {
|
|
@@ -200,6 +200,10 @@ export function createSeazonaAdapter({ env = null, fetchImpl = null, now = Date.
|
|
|
200
200
|
return readBillableJobs(current().db, { from, to });
|
|
201
201
|
},
|
|
202
202
|
|
|
203
|
+
async workJobPresence({ jobRefs }) {
|
|
204
|
+
return readWorkJobPresence(current().db, { jobRefs });
|
|
205
|
+
},
|
|
206
|
+
|
|
203
207
|
// Seazona clients by id and company name, for the identity map. Reads
|
|
204
208
|
// Flow's copy only.
|
|
205
209
|
async listCustomers() {
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import { and, inArray, isNull } from 'drizzle-orm';
|
|
1
2
|
import { readBillableWork } from './feed.js';
|
|
3
|
+
import { seazonaOrders } from './schema.js';
|
|
4
|
+
|
|
5
|
+
// Presence is not billing eligibility. Keep a job that returned to an open
|
|
6
|
+
// status, lost its lines, or changed its date separate from a missing job.
|
|
7
|
+
export async function readWorkJobPresence(db, { jobRefs }) {
|
|
8
|
+
if (!jobRefs.length) return { presentRefs: [] };
|
|
9
|
+
const rows = await db.select({ ref: seazonaOrders.id }).from(seazonaOrders).where(and(
|
|
10
|
+
inArray(seazonaOrders.id, jobRefs), isNull(seazonaOrders.deletedInSourceAt),
|
|
11
|
+
));
|
|
12
|
+
return { presentRefs: rows.map((row) => row.ref) };
|
|
13
|
+
}
|
|
2
14
|
|
|
3
15
|
// The billable jobs in Flow's copy of Seazona, by day, behind the port's
|
|
4
16
|
// `billableJobs` (the daily reconcile, server/core/reconcile.js). It walks the
|