@arcforgelabs/flow 2026.11.5 → 2026.11.6
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 +2 -2
- package/package.json +1 -1
- package/server/api/invoicing.js +5 -2
- package/server/core/automations.js +4 -1
- package/server/core/invoice-rules.js +64 -15
- package/server/core/invoicing-rules.js +50 -6
- package/server/core/job-board.js +3 -2
- package/server/core/ledger.js +10 -2
- package/server/core/pipeline.js +5 -2
- package/server/core/price-list.js +41 -12
- package/server/core/sending.js +3 -2
- package/server/db/migrations/0029_job_remake.sql +1 -0
- package/server/db/migrations/meta/0029_snapshot.json +5069 -0
- package/server/db/migrations/meta/_journal.json +7 -0
- package/server/db/schema.js +3 -0
- package/server/integrations/port.js +1 -0
- package/server/integrations/seazona/feed.js +1 -0
- package/server/integrations/seazona/history/index.js +5 -2
- package/server/integrations/seazona/lanes.js +3 -0
- package/server/integrations/seazona/ledger.js +1 -0
- package/server/integrations/seazona/map.js +2 -0
- package/server/integrations/seazona/migrations/0008_order_remake.sql +1 -0
- package/server/integrations/seazona/migrations/meta/0008_snapshot.json +1526 -0
- package/server/integrations/seazona/migrations/meta/_journal.json +7 -0
- package/server/integrations/seazona/schema.js +2 -0
- package/server/integrations/seazona/store.js +14 -0
- package/server/integrations/seazona/sweep.js +3 -0
- package/server/integrations/sentry/index.js +9 -4
- package/server/integrations/xero/catalogue.js +40 -0
- package/server/integrations/xero/drafts.js +16 -5
- package/server/integrations/xero/index.js +10 -0
- package/server/integrations/xero/lookup.js +11 -4
- package/server/integrations/xero/settings.js +5 -0
- package/server/ui/monitoring.js +63 -3
- package/server/ui/price-list.js +5 -2
- package/skills/flow/SKILL.md +31 -1
package/server/db/schema.js
CHANGED
|
@@ -253,6 +253,9 @@ export const billingObligations = pgTable('billing_obligations', {
|
|
|
253
253
|
// The work system's status for the job, as it reported it (for example
|
|
254
254
|
// "Shipped"). The draft rules read it.
|
|
255
255
|
sourceStatus: text('source_status'),
|
|
256
|
+
// The work system marks the job a remake (Seazona's remake flag): the
|
|
257
|
+
// invoice rule field `remake` reads it.
|
|
258
|
+
remake: boolean('remake').notNull().default(false),
|
|
256
259
|
// Flow's own stage: the job is ready to invoice, since when and by whom (a
|
|
257
260
|
// person, or an automation such as one that reads the work system status).
|
|
258
261
|
readyAt: timestamp('ready_at', { withTimezone: true }),
|
|
@@ -37,6 +37,7 @@ export const CAPABILITIES = Object.freeze([
|
|
|
37
37
|
'createCustomer', // { organisationId, contactId, actor }: create the system's customer from the Flow contact. Answers { outcome, contact }. Xero, when Create missing clients is on.
|
|
38
38
|
'findInvoices', // sales invoices in the accounting system's copy, for the matcher; never a live call
|
|
39
39
|
'readChanges', // { state: fresh | read | unavailable }: the accounting system's latest changes into its copy now, just before Flow drafts or sends (read before act)
|
|
40
|
+
'itemMatches', // { organisationId, codes } -> the Flow price codes that send as an item the system sells (Xero: linked item, else same code), from its copy; null while not connected or its item list was never read; never a live call
|
|
40
41
|
'listPriceItems', // the system's own price list from its copy in Flow, for the price list's links and check; never a live call
|
|
41
42
|
'health', // connection state for Settings
|
|
42
43
|
'alerts', // what only a person can clear (a refused sign-in, stopped calls), for the Integrations page; reads Flow's data only
|
|
@@ -77,6 +77,7 @@ function toFeedEntry({ order, invoice, client, invoiceOrderCount }) {
|
|
|
77
77
|
lines: order.lines,
|
|
78
78
|
subtotalCents: order.subtotalCents,
|
|
79
79
|
patientReference: order.patientReference,
|
|
80
|
+
remake: order.remake === true,
|
|
80
81
|
syncedAt: order.lastSyncedAt.toISOString(),
|
|
81
82
|
invoice: invoice ? {
|
|
82
83
|
invoiceNumber: invoice.invoiceNumber,
|
|
@@ -495,12 +495,13 @@ export async function readHistoryWork(db, { cursor = null, limit = 200 } = {}) {
|
|
|
495
495
|
}
|
|
496
496
|
const pageSize = Math.max(1, Math.min(1000, limit));
|
|
497
497
|
const changedAt = sql`to_char(${seazonaHistoryJobs.lastChangedAt} at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
|
|
498
|
-
const rows = await db.select({ job: seazonaHistoryJobs, changedAt })
|
|
498
|
+
const rows = await db.select({ job: seazonaHistoryJobs, changedAt, remake: seazonaOrders.remake })
|
|
499
499
|
.from(seazonaHistoryJobs)
|
|
500
|
+
.leftJoin(seazonaOrders, eq(seazonaOrders.id, seazonaHistoryJobs.mirrorOrderId))
|
|
500
501
|
.where(after ? sql`(${seazonaHistoryJobs.lastChangedAt}, ${seazonaHistoryJobs.jobKey}) > (${after.at}::timestamptz, ${after.key})` : undefined)
|
|
501
502
|
.orderBy(seazonaHistoryJobs.lastChangedAt, seazonaHistoryJobs.jobKey)
|
|
502
503
|
.limit(pageSize);
|
|
503
|
-
const items = rows.map(({ job }) => {
|
|
504
|
+
const items = rows.map(({ job, remake }) => {
|
|
504
505
|
const sourceJobRef = job.mirrorOrderId ?? job.caseRef ?? `history:${job.jobKey}`;
|
|
505
506
|
return {
|
|
506
507
|
source: 'seazona',
|
|
@@ -518,6 +519,8 @@ export async function readHistoryWork(db, { cursor = null, limit = 200 } = {}) {
|
|
|
518
519
|
shippedOn: job.shippedDate,
|
|
519
520
|
jobDate: job.jobDate ?? job.dueDate ?? job.shippedDate,
|
|
520
521
|
sourceStatus: job.status,
|
|
522
|
+
// The order's remake flag, from Flow's copy of the order.
|
|
523
|
+
remake: remake === true,
|
|
521
524
|
lines: (job.lines ?? []).map(ledgerLine),
|
|
522
525
|
// The CSV reports a total, but no invoice tax allocation.
|
|
523
526
|
sourceTotals: job.totalCents == null ? null : { totalCents: job.totalCents },
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
recordOrderFailure,
|
|
29
29
|
recordRecordFailure,
|
|
30
30
|
selectActiveOrders,
|
|
31
|
+
syncRemakeFlags,
|
|
31
32
|
touchSynced,
|
|
32
33
|
upsertRecords,
|
|
33
34
|
upsertReference,
|
|
@@ -507,6 +508,8 @@ const laneRuns = {
|
|
|
507
508
|
else failures.push(result.failure);
|
|
508
509
|
}
|
|
509
510
|
await touchSynced(db, 'orders', unchanged, { at });
|
|
511
|
+
// The remake flag is on the list row: kept with no detail read.
|
|
512
|
+
await syncRemakeFlags(db, new Map(unchanged.map((id) => [id, byId.get(id)?.isRemake === true])), { at });
|
|
510
513
|
summary.unchanged += unchanged.length;
|
|
511
514
|
const failureDetails = judgeReads('recent order reads', detailReads, failures);
|
|
512
515
|
const through = lastDone ?? (Number.isFinite(doneThrough) ? doneThrough : null);
|
|
@@ -60,6 +60,7 @@ export function toLedgerJob(item) {
|
|
|
60
60
|
jobDate: item.invoice?.invoiceDate ?? item.dueOn ?? item.orderedOn ?? null,
|
|
61
61
|
// The draft rules bill only the statuses the lab chose (for example Shipped).
|
|
62
62
|
sourceStatus: item.status ?? null,
|
|
63
|
+
remake: item.remake === true,
|
|
63
64
|
lines,
|
|
64
65
|
sourceTotals,
|
|
65
66
|
observedAt: item.syncedAt,
|
|
@@ -136,6 +136,8 @@ export function mapOrder(listItem = {}, detail = null, { keepPatientReference =
|
|
|
136
136
|
lines,
|
|
137
137
|
subtotalCents: lines.length ? sumCents(lines) : null,
|
|
138
138
|
patientReference: keepPatientReference ? textOrNull(merged.patient) : null,
|
|
139
|
+
// Seazona's own remake flag (the list and the order both carry it).
|
|
140
|
+
remake: merged.isRemake === true,
|
|
139
141
|
};
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "seazona_orders" ADD COLUMN IF NOT EXISTS "remake" boolean DEFAULT false NOT NULL;
|