e-arveldaja-mcp 0.36.0 → 0.37.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/.claude/commands/classify-unmatched.md +10 -1
- package/.claude/commands/import-wise.md +1 -1
- package/.claude/commands/reconcile-bank.md +3 -3
- package/CHANGELOG.md +16 -0
- package/dist/banking/reconciliation/executor.d.ts.map +1 -1
- package/dist/banking/reconciliation/executor.js +138 -32
- package/dist/banking/reconciliation/executor.js.map +1 -1
- package/dist/banking/reconciliation/presenter.d.ts +2 -1
- package/dist/banking/reconciliation/presenter.d.ts.map +1 -1
- package/dist/banking/reconciliation/presenter.js +36 -14
- package/dist/banking/reconciliation/presenter.js.map +1 -1
- package/dist/banking/reconciliation/projection.d.ts +2 -2
- package/dist/banking/reconciliation/projection.d.ts.map +1 -1
- package/dist/banking/reconciliation/projection.js +24 -14
- package/dist/banking/reconciliation/projection.js.map +1 -1
- package/dist/banking/reconciliation/types.d.ts +25 -4
- package/dist/banking/reconciliation/types.d.ts.map +1 -1
- package/dist/camt/executor.d.ts +15 -1
- package/dist/camt/executor.d.ts.map +1 -1
- package/dist/camt/executor.js +33 -19
- package/dist/camt/executor.js.map +1 -1
- package/dist/documents/operations.d.ts.map +1 -1
- package/dist/documents/operations.js +72 -2
- package/dist/documents/operations.js.map +1 -1
- package/dist/documents/types.d.ts +14 -3
- package/dist/documents/types.d.ts.map +1 -1
- package/dist/guided/process-accounting-document.d.ts.map +1 -1
- package/dist/guided/process-accounting-document.js +3 -1
- package/dist/guided/process-accounting-document.js.map +1 -1
- package/dist/guided/run-accounting-report.d.ts +3 -1
- package/dist/guided/run-accounting-report.d.ts.map +1 -1
- package/dist/guided/run-accounting-report.js +10 -2
- package/dist/guided/run-accounting-report.js.map +1 -1
- package/dist/receipts/classification-operations.d.ts +8 -2
- package/dist/receipts/classification-operations.d.ts.map +1 -1
- package/dist/receipts/classification-operations.js +246 -22
- package/dist/receipts/classification-operations.js.map +1 -1
- package/dist/receipts/classification-presenter.d.ts.map +1 -1
- package/dist/receipts/classification-presenter.js +42 -7
- package/dist/receipts/classification-presenter.js.map +1 -1
- package/dist/receipts/counterparty-booking-history.d.ts +13 -0
- package/dist/receipts/counterparty-booking-history.d.ts.map +1 -1
- package/dist/receipts/counterparty-booking-history.js +68 -24
- package/dist/receipts/counterparty-booking-history.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/create-server.js +5 -0
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/register-domain-tools.d.ts +2 -0
- package/dist/server/register-domain-tools.d.ts.map +1 -1
- package/dist/server/register-domain-tools.js +2 -2
- package/dist/server/register-domain-tools.js.map +1 -1
- package/dist/tools/accounting-inbox-autopilot-service.d.ts.map +1 -1
- package/dist/tools/accounting-inbox-autopilot-service.js +3 -1
- package/dist/tools/accounting-inbox-autopilot-service.js.map +1 -1
- package/dist/tools/accounting-inbox.js +1 -1
- package/dist/tools/accounting-inbox.js.map +1 -1
- package/dist/tools/bank-reconciliation-plan.d.ts +2 -0
- package/dist/tools/bank-reconciliation-plan.d.ts.map +1 -1
- package/dist/tools/bank-reconciliation-plan.js +4 -0
- package/dist/tools/bank-reconciliation-plan.js.map +1 -1
- package/dist/tools/bank-reconciliation.d.ts.map +1 -1
- package/dist/tools/bank-reconciliation.js +13 -9
- package/dist/tools/bank-reconciliation.js.map +1 -1
- package/dist/tools/receipt-extraction.d.ts +2 -1
- package/dist/tools/receipt-extraction.d.ts.map +1 -1
- package/dist/tools/receipt-extraction.js.map +1 -1
- package/dist/tools/receipt-inbox.d.ts +13 -0
- package/dist/tools/receipt-inbox.d.ts.map +1 -1
- package/dist/tools/receipt-inbox.js +22 -11
- package/dist/tools/receipt-inbox.js.map +1 -1
- package/dist/wise/executor.d.ts.map +1 -1
- package/dist/wise/executor.js +43 -3
- package/dist/wise/executor.js.map +1 -1
- package/dist/wise/presenter.d.ts +4 -1
- package/dist/wise/presenter.d.ts.map +1 -1
- package/dist/wise/presenter.js +18 -2
- package/dist/wise/presenter.js.map +1 -1
- package/dist/wise/projection.d.ts +9 -1
- package/dist/wise/projection.d.ts.map +1 -1
- package/dist/wise/projection.js +16 -1
- package/dist/wise/projection.js.map +1 -1
- package/dist/wise/types.d.ts +16 -0
- package/dist/wise/types.d.ts.map +1 -1
- package/dist/wise/types.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/workflows/classify-unmatched.md +2 -1
- package/workflows/import-wise.md +1 -1
- package/workflows/reconcile-bank.md +3 -3
|
@@ -76,7 +76,15 @@ Show the compact `result.summary`: `counts` (unmatched transactions, groups, aut
|
|
|
76
76
|
|
|
77
77
|
### Step 2: Explain what can be applied and choose the groups
|
|
78
78
|
|
|
79
|
-
Rows that may pay an open invoice never reach the groups: a possible invoice match, a residual on a partially paid purchase invoice, or a document-reference candidate (exact amount plus the purchase invoice's number or `Ostukorvi/tellimuse nr:` order/cart number in the description).
|
|
79
|
+
Rows that may pay an open invoice never reach the groups: a possible invoice match, a residual on a partially paid purchase invoice, or a document-reference candidate (exact amount plus the purchase invoice's number or `Ostukorvi/tellimuse nr:` order/cart number in the description). They are listed, never dropped: <!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:standard -->
|
|
80
|
+
Capability condition for `standard`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `list_account_dimensions`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
81
|
+
|
|
82
|
+
`result.invoice_matched` gives each row's transaction id, amount, date, the invoice (`invoice_type`, `invoice_id`, `invoice_number`) and what matched (`matched_by`, `matched_reference` or `match_reasons`)
|
|
83
|
+
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:standard --><!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:guided -->
|
|
84
|
+
Capability condition for `guided`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `classify_bank_transactions`, and none of these is advertised: `list_account_dimensions`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
85
|
+
|
|
86
|
+
the `invoice_matched` count and warning in `result.summary` name each row and its invoice
|
|
87
|
+
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:guided -->. The invoice already exists, so booking such a row here would double the expense: reconcile it with the **Reconcile Bank** workflow (`reconcile_bank_transactions` `mode: "suggest"` lists it), and drop any such transaction ID a user asks to add. Apply re-checks and skips it ("matches an open purchase invoice; skipped — reconcile instead").
|
|
80
88
|
|
|
81
89
|
Rows the ledger check took out of the groups are never applied here; report each with its `next_action`:
|
|
82
90
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:standard -->
|
|
@@ -95,6 +103,7 @@ Capability condition for `guided`: inspect the connected MCP server's advertised
|
|
|
95
103
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:guided -->
|
|
96
104
|
|
|
97
105
|
- `apply_mode="purchase_invoice"` groups are auto-bookable
|
|
106
|
+
- `apply_mode="account_distribution"` groups are auto-bookable from booking history: every earlier confirmed row of this counterparty (at least two, matched by its client, IBAN or legal form) went straight to one account and dimension with no invoice. Apply confirms each row to that account with its own amount, setting the row's client first when it has none. Show the proposed account, dimension and the evidence (`suggested_booking.transaction_history.history_total` and `last_journal_ids`, or the `history_distribution` warning) on the approval card. Apply re-derives the booking from the fresh ledger, so a history that changed since the dry run is `plan_drift`
|
|
98
107
|
- review-only categories are reported back as skipped
|
|
99
108
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:guided -->
|
|
100
109
|
Capability condition for `guided`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `classify_bank_transactions`, and none of these is advertised: `list_account_dimensions`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
@@ -110,7 +110,7 @@ The compact preview returns a `summary`: `summary.counts` (CSV rows, eligible, f
|
|
|
110
110
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:standard -->
|
|
111
111
|
Capability condition for `standard`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `import_wise_transactions`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
112
112
|
|
|
113
|
-
The dry run returns top-level `plan_handle` and `approved_command_digest`, a `summary` (`total_csv_rows`, `eligible`, `filtered_out`, `created` = would-create, `skipped`, `error_count`, `inter_account_total`, `needs_review`, `blocking_review_count` = the reviews that block approval; every review but `wise_transfer_ownership_unverified` blocks), `ownership_reviews` (transfers that could not be auto-verified as own-account transfers; never dropped), `transfer_counterpart_unresolved` (only when transfer rows are held: `message`, `unresolved_row_count`, `conflict_row_count`, `candidates[]` with `accounts_dimensions_id` + `label`; the per-row reviews are in `ownership_reviews` with `counterpart_candidate_dimension_ids` and, for a conflict, `evidence_transaction_ids`), `inter_account_reconciliation`, advisory `invoice_currency_fixes`, and `skipped_details`: skipped rows grouped by reason, each group with `count` and up to 5 `sample_ids`; for already-booked rows the ledger id is replaced in the reason by `journal (see records)` / `transaction (see records)` and the group's `records` lists every row's `wise_id` + `record_id`. In a dry run, `execution.skipped` itemises only skips that are not already-booked rows (unsupported directions, dependency skips); `execution.skipped_omitted_already_booked` counts the rest, which are in `skipped_details`. Transfers already booked by a journal are not commands: the dry run lists them in `execution.already_journalized_rows` (`wise_id`, `existing_journal_id`, date, amount, currency, direction; count in `execution.already_journalized_count`, of which `execution.already_journalized_auto_matched_count` are legacy transfers matched automatically). `execution.commands` and `command_count` cover only commands that write. An executed run keeps every write command and every skipped row itemised; the already-journalized re-check results are in `inter_account_reconciliation.details`, after the confirmed transfers.
|
|
113
|
+
The dry run returns top-level `plan_handle` and `approved_command_digest`, a `summary` (`total_csv_rows`, `eligible`, `filtered_out`, `created` = would-create, `skipped`, `error_count`, `inter_account_total`, `needs_review`, `blocking_review_count` = the reviews that block approval; every review but `wise_transfer_ownership_unverified` blocks), `ownership_reviews` (transfers that could not be auto-verified as own-account transfers; never dropped), `transfer_counterpart_unresolved` (only when transfer rows are held: `message`, `unresolved_row_count`, `conflict_row_count`, `candidates[]` with `accounts_dimensions_id` + `label`; the per-row reviews are in `ownership_reviews` with `counterpart_candidate_dimension_ids` and, for a conflict, `evidence_transaction_ids`), `inter_account_reconciliation`, advisory `invoice_currency_fixes`, and `skipped_details`: skipped rows grouped by reason, each group with `count` and up to 5 `sample_ids`; for already-booked rows the ledger id is replaced in the reason by `journal (see records)` / `transaction (see records)` and the group's `records` lists every row's `wise_id` + `record_id`. In a dry run, `execution.skipped` itemises only skips that are not already-booked rows (unsupported directions, dependency skips); `execution.skipped_omitted_already_booked` counts the rest, which are in `skipped_details`. Transfers already booked by a journal are not commands: the dry run lists them in `execution.already_journalized_rows` (`wise_id`, `existing_journal_id`, date, amount, currency, direction; count in `execution.already_journalized_count`, of which `execution.already_journalized_auto_matched_count` are legacy transfers matched automatically). `execution.commands` and `command_count` cover only commands that write. An executed run keeps every write command and every skipped row itemised; the already-journalized re-check results are in `inter_account_reconciliation.details`, after the confirmed transfers. A row booked as an ordinary payment (not an own transfer) carries the client whose normalized name equals its counterparty name, ignoring case (CAMT's exact-name rule; a partial name match is not used): `clients_id`, `client_match`, also in its `create_payload`; a row several clients match exactly has `client_ambiguous` and a `client_note`, is created without a client and needs one (`clients_id`) before it can be confirmed.
|
|
114
114
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:standard -->
|
|
115
115
|
|
|
116
116
|
Show main transactions and fee rows that would be created, exact duplicate / skip reasons, whether fees will be auto-confirmed to the chosen dimension, inter-account transfer confirmations or skips, and advisory invoice FX corrections (reported only — the import never changes confirmed purchase invoices).
|
|
@@ -80,7 +80,7 @@ Capability condition for `standard`: inspect the connected MCP server's advertis
|
|
|
80
80
|
Capability condition for `guided`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `reconcile_bank_transactions`, and none of these is advertised: `confirm_transaction`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
81
81
|
|
|
82
82
|
`document_reference_candidate` warnings
|
|
83
|
-
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:guided -->):
|
|
83
|
+
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:guided -->): a match whose bank description carries the exact-amount purchase invoice's number or its `Ostukorvi/tellimuse nr:` order/cart number gets the `document_reference` reason and ranks above amount/name-only candidates, capped at confidence 85, so it is never auto-confirmed — ask before confirming it. A document-reference invoice that another candidate outranked is listed in this group. They are NOT unmatched rows and are never auto-confirmed: show each one (transaction, invoice, matched reference) and ask the owner whether it is that invoice's payment.<!-- E_ARVELDAJA_CAPABILITY_CONDITION_START:standard -->
|
|
84
84
|
Capability condition for `standard`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `confirm_transaction`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
85
85
|
|
|
86
86
|
If approved, call `confirm_transaction` with `distributions: [{ related_table: "purchase_invoices", related_id: <invoice.id>, amount: <open balance> }]` (no `distribution` key is provided; check a partially paid invoice's open balance first).
|
|
@@ -258,7 +258,7 @@ Review the results:
|
|
|
258
258
|
Capability condition for `standard`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `confirm_transaction`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
259
259
|
|
|
260
260
|
- Treat `result.execution.summary` as the canonical source for counts, and use `result.pairs`, `result.one_sided`, `result.already_handled`, and `result.ambiguous_pairs` for the detailed breakdown.
|
|
261
|
-
- `already_handled`: a journal already books the transfer. Rows WITHOUT `mirror_proposal` are only listed for the owner: they are not proven mirrors, so never offer to delete them. Propose deleting a row only when it carries `mirror_proposal` (it is then the sole candidate): show its `evidence`, `journal_id` and `counterpart_transaction_id`, and delete it through its `next_action` (`delete_transaction`) only after the owner explicitly approves that row
|
|
261
|
+
- `already_handled`: a journal already books the transfer. Rows WITHOUT `mirror_proposal` are only listed for the owner: they are not proven mirrors, so never offer to delete them. Propose deleting a row only when it carries `mirror_proposal` (it is then the sole candidate): show its `evidence`, `journal_id` and `counterpart_transaction_id`, and delete it through its `next_action` (`delete_transaction`) only after the owner explicitly approves that row, or, once the owner approves all of them, re-run the dry run with `delete_proposed_mirrors: true` to put their deletions in the plan
|
|
262
262
|
- `one_sided`: would confirm against the other bank account
|
|
263
263
|
- `pairs`: would confirm the outgoing side and delete the duplicate incoming `PROJECT` (draft/unconfirmed) row (`incoming_action: "would_delete_duplicate"`)
|
|
264
264
|
- `result.execution.errors`: any confirmation failures or other blocking issues
|
|
@@ -267,7 +267,7 @@ Capability condition for `standard`: inspect the connected MCP server's advertis
|
|
|
267
267
|
Capability condition for `guided`: inspect the connected MCP server's advertised tool list before this section. Run this section only when every named tool is advertised: `reconcile_bank_transactions`, and none of these is advertised: `confirm_transaction`. Otherwise skip this section and continue with the surrounding workflow. Never call a missing tool to probe capability.
|
|
268
268
|
|
|
269
269
|
- `summary.counts` gives the pairs, one-sided confirms, already-handled rows (`duplicates`), proposed mirror deletions (`proposed_mirrors`), and transfers needing review; `summary.samples`, `summary.warnings`, and `summary.blockers` carry the detail. Warnings show at most 3 rows per category; when a category has more, a `<category>_more` warning reports how many are not shown, and when the response size limit trims further a `warnings_truncated` warning counts the dropped rows per code: tell the owner those counts and never present the shown rows as the complete list.
|
|
270
|
-
- Already-handled rows are only listed. A `mirror_proposal` warning names a row that is the sole proven mirror of a journal-booked transfer;
|
|
270
|
+
- Already-handled rows are only listed. A `mirror_proposal` warning names a row that is the sole proven mirror of a journal-booked transfer; to delete them, re-run the dry run with `delete_proposed_mirrors: true` (the summary's `next_action` says so when the plan is otherwise empty), review `proposed_mirror_deletes`, and execute with the same argument only after the owner's explicit approval; execute deletes only rows still matching the reviewed row and journal. Never offer to delete an already-handled row that has no mirror proposal.
|
|
271
271
|
- `summary.next_action` is the ready-to-send execute call (`mode: "execute_inter_account"` with the `plan_handle` and the dry run's `max_date_gap` / `target_accounts_dimensions_id`); run it only after approval.
|
|
272
272
|
<!-- E_ARVELDAJA_CAPABILITY_CONDITION_END:guided -->
|
|
273
273
|
- Ambiguous transfers (including `direction_unresolved`) are never auto-confirmed — present them for a decision.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.37.0] - 2026-10-30
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A bank payment that names an invoice's order or cart number is now suggested for that invoice, even when another invoice has the same amount.** Before, two same-amount ticket payments were both suggested for the older ticket and the right invoice was hidden. Details: reconcile suggest adds the match reason `document_reference` when the description carries an exact-amount purchase invoice's number or its notes order/cart reference, raising confidence by 30 and capping it at 85 (a document reference alone never reaches the 90 auto-confirm threshold; the auto-confirm path is unchanged); `document_reference_candidates` also lists such an invoice when a different invoice won the row. Suggest now re-reads the transaction and invoice lists instead of using the short list cache, so changes made elsewhere (web UI, another client) are seen — three extra list reads per suggest.
|
|
10
|
+
- **Wise payments to someone already in your client list now get that client, so they can be confirmed.** Before, a transfer to the owner imported as an ordinary payment had no client and confirming it failed with "buyer or supplier is missing". Details: the Wise import sets `clients_id` on payment rows and transfers treated as payments only on an exact (case-insensitive) name match, using the CAMT name rule (`selectClientByName`, shared); a partial name match leaves the client empty; several exact matches leave it empty with a note. The client is part of the reviewed import plan.
|
|
11
|
+
- **The Wise dry run now reports how many own-account transfers it will book.** Details: the dry-run summary `inter_account_total` counts the planned `inter_account` commands (it showed 0 before execute).
|
|
12
|
+
- **The guided profile can now delete the duplicate half of a transfer between your own accounts after you approve it.** Before, guided showed these rows but could not remove them. Details: `reconcile_bank_transactions` / `reconcile_inter_account_transfers` take opt-in `delete_proposed_mirrors: true` on the dry run; the deletions become plan commands run after all confirms, bound into the plan fingerprint (resend the flag at execute); before each delete the row, its confirmed counterpart and the booking journal are re-read, and any change stops that delete with nothing written.
|
|
13
|
+
- **The guided invoice booking can now book a ticket that names both the seller and the carrier, once you choose the supplier.** Before, a Tpilet ticket (seller T grupp, carrier Tulisilm) could only be booked on the standard profile. Details: on a booking-binding prepare, an explicit `supplier_client_id` whose registry code (digits) or VAT number equals one of the document's printed supplier identifiers turns `supplier_identifier_ambiguous` into the warning `supplier_identifier_acknowledged`, recorded in `booking_review`; the company's own identifiers never acknowledge, and the blocker stays when they cannot be read; create re-reads the client and refuses with `plan_drift` if its identifier changed.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Classification proposes the account a counterparty has always been booked to, and you can apply it with one approval.** Details: when at least two earlier confirmed payments of the same client (or IBAN / legal form — never a name alone), in the same direction, were all booked to one account and dimension with no invoice links, the group gets apply mode `account_distribution` with the evidence (count, journal ids); apply runs only through `dry_run_apply` → `execute_apply`, re-derives the booking from the fresh ledger (a change is `plan_drift`, nothing written), sets the client first and confirms with `related_sub_id`. Mixed history stays review-only and shows the split; own bank accounts, foreign currency, saved rules and statutory categories are excluded.
|
|
18
|
+
- **Classification now lists the payments it leaves out because they may pay an open invoice.** Details: new `invoice_matched` section (invoice, matched reference or score) counted in `total_partitioned_out`, a guided warning, and accounting-inbox follow-ups pointing to reconcile.
|
|
19
|
+
- **The guided accounting report can force a fresh read.** Details: `run_accounting_report` takes `fresh: true`, clearing the active connection's cached data before computing.
|
|
20
|
+
|
|
5
21
|
## [0.36.0] - 2026-10-30
|
|
6
22
|
|
|
7
23
|
### Fixed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/banking/reconciliation/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAqB7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/banking/reconciliation/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAqB7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAqE5E,OAAO,KAAK,EAEV,qBAAqB,EACrB,0BAA0B,EAC1B,iBAAiB,EAGjB,mBAAmB,EAGnB,qBAAqB,EACrB,0BAA0B,EAC1B,iBAAiB,EACjB,uBAAuB,EAEvB,mBAAmB,EAInB,yBAAyB,EAOzB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAkBpB,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAUlD,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,eAAe,CAAC,CAAC,IACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACvC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3D;;;;GAIG;AACH,qBAAa,kCAAmC,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,YAAY;IAA1C,YAAqB,OAAO,EAAE,YAAY,EAGzC;CACF;AAYD,wDAAwD;AACxD,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAcjD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,yBAAyB,CAAC,CAoPpC;AAoYD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,oBAAoB,EAAE,oBAAoB,EAC1C,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAI9B;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,oBAAoB,EAAE,oBAAoB,EAC1C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CA0BjD;AAID,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,iBAAiB,EACxB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAAC,uBAAuB,CAAC,CAw6BlC;AAuBD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,oBAAoB,EAAE,oBAAoB,EAC1C,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,mBAAmB,CAAC,CA8B9B;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,oBAAoB,EAAE,oBAAoB,EAC1C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAAC,CAwQjD"}
|
|
@@ -17,7 +17,7 @@ import { BookingGuard } from "../../booking-guard.js";
|
|
|
17
17
|
import { buildBankAccountLookups, normalizeReference } from "../../tools/inter-account-utils.js";
|
|
18
18
|
import { toUtcDay } from "../../date-utils.js";
|
|
19
19
|
import { annotateDistinctSourceReferences, findDuplicateBankPostings, resolveBankDimensionsSafe, transactionSourceReferenceIdentity, } from "../../bank-posting-duplicate-guard.js";
|
|
20
|
-
import { BANK_RECONCILIATION_PLAN_DOMAIN, buildReconciliationExecutionPlanInput, canonicalPlanJson, executeReconciliationCommands, reconClientUpdateCommandId, reconInvoiceConfirmCommandId, reconTransferConfirmCommandId, reconDeleteDuplicateCommandId, RECON_CONFIRM_INVOICE_CATEGORY, RECON_CONFIRM_TRANSFER_CATEGORY, RECON_DELETE_DUPLICATE_CATEGORY, RECON_UPDATE_CLIENT_CATEGORY, stripUndefinedDeep, } from "../../tools/bank-reconciliation-plan.js";
|
|
20
|
+
import { BANK_RECONCILIATION_PLAN_DOMAIN, buildReconciliationExecutionPlanInput, canonicalPlanJson, executeReconciliationCommands, reconClientUpdateCommandId, reconInvoiceConfirmCommandId, reconTransferConfirmCommandId, reconDeleteDuplicateCommandId, reconDeleteProposedMirrorCommandId, RECON_CONFIRM_INVOICE_CATEGORY, RECON_CONFIRM_TRANSFER_CATEGORY, RECON_DELETE_DUPLICATE_CATEGORY, RECON_DELETE_PROPOSED_MIRROR_CATEGORY, RECON_UPDATE_CLIENT_CATEGORY, stripUndefinedDeep, } from "../../tools/bank-reconciliation-plan.js";
|
|
21
21
|
import { buildInvoiceIndex, getIndexedCandidates } from "./invoice-index.js";
|
|
22
22
|
import { documentReferenceOnTransaction } from "./document-reference.js";
|
|
23
23
|
import { legacyTruncatedCamtMarkerHint } from "../../camt/duplicate-identity.js";
|
|
@@ -68,13 +68,28 @@ function validateInterAccountDateGap(maxDateGap) {
|
|
|
68
68
|
// === Suggest =================================================================
|
|
69
69
|
/** The suggestion path's default minimum confidence. */
|
|
70
70
|
export const SUGGEST_DEFAULT_MIN_CONFIDENCE = 50;
|
|
71
|
+
/**
|
|
72
|
+
* Suggest-only bonus for an exact-amount purchase invoice whose number or notes
|
|
73
|
+
* order / cart reference the bank description carries as a whole token.
|
|
74
|
+
* exact_amount (40) + 30 = 70 outranks every amount/name-only candidate
|
|
75
|
+
* (max 40 + 15 = 55). The cap 85 keeps it strictly below the 90 auto-confirm
|
|
76
|
+
* threshold (owner decision: a document reference alone never auto-confirms)
|
|
77
|
+
* and equals the Tier A cap. The exact-confirm path scores with matchScore
|
|
78
|
+
* alone, so ref_number_exact and >= 90 admission are untouched.
|
|
79
|
+
*/
|
|
80
|
+
const DOCUMENT_REFERENCE_POINTS = 30;
|
|
81
|
+
const DOCUMENT_REFERENCE_CONFIDENCE_CAP = 85;
|
|
71
82
|
export async function runSuggestMatches(api, input) {
|
|
72
83
|
const threshold = input.minConfidence ?? SUGGEST_DEFAULT_MIN_CONFIDENCE;
|
|
73
|
-
// A caller asking for a stricter threshold than the default gets no
|
|
74
|
-
// below-threshold document-reference group (okt 2026 finding 5).
|
|
75
|
-
const documentReferenceGroupEnabled = input.minConfidence === undefined || input.minConfidence <= SUGGEST_DEFAULT_MIN_CONFIDENCE;
|
|
76
84
|
const documentReferenceCandidates = [];
|
|
77
85
|
const block_on_duplicate = input.blockOnDuplicate;
|
|
86
|
+
// Read uncached: the 120 s list cache does not see invoices created or paid
|
|
87
|
+
// by another process (a second MCP server, the e-arveldaja UI), so two
|
|
88
|
+
// surfaces over the same books could report different matches (live
|
|
89
|
+
// 2026-10-30: an invoice confirmed 9 s earlier elsewhere was missing).
|
|
90
|
+
api.transactions.invalidateListCache();
|
|
91
|
+
api.saleInvoices.invalidateListCache();
|
|
92
|
+
api.purchaseInvoices.invalidateListCache();
|
|
78
93
|
// Get all unconfirmed transactions
|
|
79
94
|
const allTx = await api.transactions.listAll();
|
|
80
95
|
const unconfirmed = allTx.filter(isProjectTransaction);
|
|
@@ -104,6 +119,7 @@ export async function runSuggestMatches(api, input) {
|
|
|
104
119
|
let matchedDateTo;
|
|
105
120
|
for (const tx of unconfirmed) {
|
|
106
121
|
const candidates = [];
|
|
122
|
+
const documentReferenceByInvoiceId = new Map();
|
|
107
123
|
const { allowSaleInvoices, allowPurchaseInvoices } = getInvoiceMatchEligibility(tx);
|
|
108
124
|
if (allowSaleInvoices) {
|
|
109
125
|
for (const inv of getIndexedCandidates(saleIndex, tx.ref_number, tx.amount, tx.base_amount)) {
|
|
@@ -127,8 +143,19 @@ export async function runSuggestMatches(api, input) {
|
|
|
127
143
|
}
|
|
128
144
|
if (allowPurchaseInvoices) {
|
|
129
145
|
for (const inv of getIndexedCandidates(purchaseIndex, tx.ref_number, tx.amount, tx.base_amount)) {
|
|
130
|
-
const
|
|
146
|
+
const scored = matchScore(tx, inv, tx.amount);
|
|
147
|
+
const { reasons, partiallyPaidWarning } = scored;
|
|
148
|
+
let confidence = scored.confidence;
|
|
149
|
+
const matchedReference = reasons.includes("exact_amount")
|
|
150
|
+
? documentReferenceOnTransaction(tx.description, inv)
|
|
151
|
+
: undefined;
|
|
152
|
+
if (matchedReference !== undefined) {
|
|
153
|
+
reasons.push("document_reference");
|
|
154
|
+
confidence = Math.max(confidence, Math.min(confidence + DOCUMENT_REFERENCE_POINTS, DOCUMENT_REFERENCE_CONFIDENCE_CAP));
|
|
155
|
+
}
|
|
131
156
|
if (confidence >= threshold) {
|
|
157
|
+
if (matchedReference !== undefined)
|
|
158
|
+
documentReferenceByInvoiceId.set(inv.id, { inv, matchedReference });
|
|
132
159
|
candidates.push({
|
|
133
160
|
type: "purchase_invoice",
|
|
134
161
|
id: inv.id,
|
|
@@ -143,34 +170,36 @@ export async function runSuggestMatches(api, input) {
|
|
|
143
170
|
match_reasons: reasons,
|
|
144
171
|
});
|
|
145
172
|
}
|
|
146
|
-
else if (documentReferenceGroupEnabled && reasons.includes("exact_amount")) {
|
|
147
|
-
const matchedReference = documentReferenceOnTransaction(tx.description, inv);
|
|
148
|
-
if (matchedReference !== undefined) {
|
|
149
|
-
// RAW domain strings — the presenter wraps the free-text fields.
|
|
150
|
-
documentReferenceCandidates.push({
|
|
151
|
-
transaction_id: tx.id,
|
|
152
|
-
date: tx.date,
|
|
153
|
-
amount: tx.amount,
|
|
154
|
-
description: tx.description ?? undefined,
|
|
155
|
-
bank_account_name: tx.bank_account_name ?? undefined,
|
|
156
|
-
invoice: {
|
|
157
|
-
type: "purchase_invoice",
|
|
158
|
-
id: inv.id,
|
|
159
|
-
number: inv.number,
|
|
160
|
-
client_name: inv.client_name,
|
|
161
|
-
gross_price: inv.gross_price ?? 0,
|
|
162
|
-
payment_status: inv.payment_status ?? "NOT_PAID",
|
|
163
|
-
},
|
|
164
|
-
matched_reference: matchedReference,
|
|
165
|
-
match_reasons: [...reasons, "document_reference"],
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
if (candidates.length > 0) {
|
|
172
176
|
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
173
177
|
const bestMatch = candidates[0];
|
|
178
|
+
// A document-reference invoice that did not win this row is listed
|
|
179
|
+
// separately so it is not hidden behind the chosen best match.
|
|
180
|
+
for (const candidate of candidates.slice(1)) {
|
|
181
|
+
const docRef = candidate.type === "purchase_invoice" ? documentReferenceByInvoiceId.get(candidate.id) : undefined;
|
|
182
|
+
if (docRef === undefined)
|
|
183
|
+
continue;
|
|
184
|
+
// RAW domain strings — the presenter wraps the free-text fields.
|
|
185
|
+
documentReferenceCandidates.push({
|
|
186
|
+
transaction_id: tx.id,
|
|
187
|
+
date: tx.date,
|
|
188
|
+
amount: tx.amount,
|
|
189
|
+
description: tx.description ?? undefined,
|
|
190
|
+
bank_account_name: tx.bank_account_name ?? undefined,
|
|
191
|
+
invoice: {
|
|
192
|
+
type: "purchase_invoice",
|
|
193
|
+
id: docRef.inv.id,
|
|
194
|
+
number: docRef.inv.number,
|
|
195
|
+
client_name: docRef.inv.client_name,
|
|
196
|
+
gross_price: docRef.inv.gross_price ?? 0,
|
|
197
|
+
payment_status: docRef.inv.payment_status ?? "NOT_PAID",
|
|
198
|
+
},
|
|
199
|
+
matched_reference: docRef.matchedReference,
|
|
200
|
+
match_reasons: candidate.match_reasons,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
174
203
|
const crossCurrency = (bestMatch.match_reasons.includes("exact_base_amount") ||
|
|
175
204
|
bestMatch.match_reasons.includes("cross_currency_conflict")) &&
|
|
176
205
|
!bestMatch.match_reasons.includes("exact_amount");
|
|
@@ -718,9 +747,12 @@ export async function runInterAccountMatching(api, input, opts) {
|
|
|
718
747
|
const errors = [];
|
|
719
748
|
const consumedTxIds = new Set();
|
|
720
749
|
const blockedOneSidedTxIds = new Set();
|
|
750
|
+
const deleteProposedMirrors = input.deleteProposedMirrors === true;
|
|
751
|
+
const mirrorDeleteActions = [];
|
|
721
752
|
function finish(companyClientsId) {
|
|
722
|
-
const planCommandProjections = buildInterAccountPlanCommandProjections(confirmActions, companyClientsId);
|
|
723
|
-
const normalizedArgs = stripUndefinedDeep({ max_date_gap: maxGap, target_accounts_dimensions_id
|
|
753
|
+
const planCommandProjections = buildInterAccountPlanCommandProjections(confirmActions, companyClientsId, mirrorDeleteActions);
|
|
754
|
+
const normalizedArgs = stripUndefinedDeep({ max_date_gap: maxGap, target_accounts_dimensions_id,
|
|
755
|
+
...(deleteProposedMirrors ? { delete_proposed_mirrors: true } : {}) });
|
|
724
756
|
const fingerprint = interAccountFingerprint({ normalizedArgs, planCommandProjections, skippedAlreadyHandled,
|
|
725
757
|
ambiguousPairs, ambiguousRefless, crossCurrencyPairs });
|
|
726
758
|
const companyLegalName = (invoiceInfo.invoice_company_name ?? "").trim();
|
|
@@ -728,7 +760,8 @@ export async function runInterAccountMatching(api, input, opts) {
|
|
|
728
760
|
? ["Company name has no legal form; own-transfer detection relies on IBANs only."] : [];
|
|
729
761
|
return { totalUnconfirmed: unconfirmed.length, invoiceInfo, dimensionToIban, dimensionToTitle,
|
|
730
762
|
dimensionToAccountsId, matchedPairs, matchedOneSided, ambiguousPairs, skippedAlreadyHandled,
|
|
731
|
-
ambiguousRefless, crossCurrencyPairs, errors, confirmActions,
|
|
763
|
+
ambiguousRefless, crossCurrencyPairs, errors, confirmActions,
|
|
764
|
+
...(deleteProposedMirrors ? { mirrorDeleteActions } : {}), companyClientsId, normalizedArgs,
|
|
732
765
|
fingerprint, ...(warnings.length > 0 ? { warnings } : {}) };
|
|
733
766
|
}
|
|
734
767
|
// With no pending bank rows there is nothing to pair, deduplicate or confirm.
|
|
@@ -1495,6 +1528,18 @@ export async function runInterAccountMatching(api, input, opts) {
|
|
|
1495
1528
|
row.mirror_proposal = proposal;
|
|
1496
1529
|
row.next_action = { tool: "delete_transaction", args: { id: row.transaction_id }, approval_required: true };
|
|
1497
1530
|
row.reason = `Already journalized from the other account side: journal #${proposal.journal_id} books confirmed transaction ${proposal.counterpart_transaction_id}, and this PROJECT row is its likely mirror. Verify against the bank statements, then delete it with delete_transaction only after the owner explicitly approves; reconcile never deletes it automatically.`;
|
|
1531
|
+
if (deleteProposedMirrors) {
|
|
1532
|
+
// Opt-in: the reviewed plan carries this deletion; execute re-verifies it.
|
|
1533
|
+
mirrorDeleteActions.push({
|
|
1534
|
+
transactionId: row.transaction_id,
|
|
1535
|
+
journalId: proposal.journal_id,
|
|
1536
|
+
counterpartTransactionId: proposal.counterpart_transaction_id,
|
|
1537
|
+
targetDimensionId: row.target_dimension_id,
|
|
1538
|
+
material: snapshotBankTransaction(allTx.get(row.transaction_id)),
|
|
1539
|
+
});
|
|
1540
|
+
row.reason = `Already journalized from the other account side: journal #${proposal.journal_id} books confirmed transaction ${proposal.counterpart_transaction_id}, and this PROJECT row is its likely mirror. delete_proposed_mirrors put its deletion in this plan: verify against the bank statements before approving execute.`;
|
|
1541
|
+
delete row.next_action;
|
|
1542
|
+
}
|
|
1498
1543
|
}
|
|
1499
1544
|
const companyClientsId = await resolveCompanyClientsId();
|
|
1500
1545
|
for (const action of confirmActions) {
|
|
@@ -1535,7 +1580,7 @@ export async function prepareInterAccount(api, runtimeSafetyContext, input) {
|
|
|
1535
1580
|
const match = await runInterAccountMatching(api, input);
|
|
1536
1581
|
if (input.mintPlanHandles === false)
|
|
1537
1582
|
return { match };
|
|
1538
|
-
const planCommandProjections = buildInterAccountPlanCommandProjections(match.confirmActions, match.companyClientsId);
|
|
1583
|
+
const planCommandProjections = buildInterAccountPlanCommandProjections(match.confirmActions, match.companyClientsId, match.mirrorDeleteActions);
|
|
1539
1584
|
const planHandle = runtimeSafetyContext.planStore.issue(BANK_RECONCILIATION_PLAN_DOMAIN, buildReconciliationExecutionPlanInput({
|
|
1540
1585
|
normalizedArgs: match.normalizedArgs,
|
|
1541
1586
|
sourceIdentities: [],
|
|
@@ -1550,6 +1595,7 @@ export async function prepareInterAccount(api, runtimeSafetyContext, input) {
|
|
|
1550
1595
|
total_unconfirmed: match.totalUnconfirmed,
|
|
1551
1596
|
matched_pairs: match.matchedPairs.length,
|
|
1552
1597
|
matched_one_sided: match.matchedOneSided.length,
|
|
1598
|
+
...(match.mirrorDeleteActions !== undefined ? { proposed_mirror_deletes: match.mirrorDeleteActions.length } : {}),
|
|
1553
1599
|
},
|
|
1554
1600
|
totals: {},
|
|
1555
1601
|
exclusions: [...match.skippedAlreadyHandled, ...match.ambiguousRefless, ...match.crossCurrencyPairs].map(row => stripUndefinedDeep(row)),
|
|
@@ -1780,8 +1826,68 @@ export async function executeInterAccount(api, runtimeSafetyContext, input) {
|
|
|
1780
1826
|
});
|
|
1781
1827
|
}
|
|
1782
1828
|
}
|
|
1829
|
+
for (const action of match.mirrorDeleteActions ?? []) {
|
|
1830
|
+
// Every read gates the delete: the row must still be the reviewed PROJECT
|
|
1831
|
+
// row, and the journal must still book its confirmed counterpart.
|
|
1832
|
+
const verifyMirror = async () => {
|
|
1833
|
+
api.transactions.invalidateListCache();
|
|
1834
|
+
const row = await api.transactions.get(action.transactionId);
|
|
1835
|
+
if (!row || !isProjectTransaction(row))
|
|
1836
|
+
return "mirror_not_project";
|
|
1837
|
+
if (!compareBankTransactionMaterial(action.material, row).ok)
|
|
1838
|
+
return "mirror_material_drift";
|
|
1839
|
+
const counterpart = await api.transactions.get(action.counterpartTransactionId);
|
|
1840
|
+
if (!counterpart || counterpart.is_deleted || counterpart.status !== "CONFIRMED"
|
|
1841
|
+
|| counterpart.accounts_dimensions_id !== action.targetDimensionId
|
|
1842
|
+
|| !sameAmountToTheCent(counterpart.amount, action.material.amount ?? Number.NaN))
|
|
1843
|
+
return "mirror_counterpart_drift";
|
|
1844
|
+
api.journals.invalidateListCache();
|
|
1845
|
+
const journal = (await api.journals.listAllWithPostings()).find(j => j.id === action.journalId);
|
|
1846
|
+
if (!journal || journal.is_deleted || journal.registered !== true || journal.operation_type !== "TRANSACTION"
|
|
1847
|
+
|| journal.operations_id !== action.counterpartTransactionId)
|
|
1848
|
+
return "mirror_journal_drift";
|
|
1849
|
+
return undefined;
|
|
1850
|
+
};
|
|
1851
|
+
commands.push({
|
|
1852
|
+
id: reconDeleteProposedMirrorCommandId(action.transactionId),
|
|
1853
|
+
category: RECON_DELETE_PROPOSED_MIRROR_CATEGORY,
|
|
1854
|
+
prepare: async () => {
|
|
1855
|
+
const drift = await verifyMirror();
|
|
1856
|
+
if (drift)
|
|
1857
|
+
return { outcome: "drift", error_code: drift };
|
|
1858
|
+
// The journal read may await another actor: make the row the final read.
|
|
1859
|
+
api.transactions.invalidateListCache();
|
|
1860
|
+
const row = await api.transactions.get(action.transactionId);
|
|
1861
|
+
if (!row || !isProjectTransaction(row) || !compareBankTransactionMaterial(action.material, row).ok) {
|
|
1862
|
+
return { outcome: "drift", error_code: "mirror_material_drift" };
|
|
1863
|
+
}
|
|
1864
|
+
return { outcome: "ready" };
|
|
1865
|
+
},
|
|
1866
|
+
mutate: async () => {
|
|
1867
|
+
try {
|
|
1868
|
+
await api.transactions.delete(action.transactionId);
|
|
1869
|
+
logAudit({
|
|
1870
|
+
tool: "reconcile_inter_account_transfers", action: "DELETED", entity_type: "transaction",
|
|
1871
|
+
entity_id: action.transactionId,
|
|
1872
|
+
summary: `Deleted proposed mirror row ${action.transactionId} of confirmed transaction ${action.counterpartTransactionId} (journal ${action.journalId})`,
|
|
1873
|
+
details: { journal_id: action.journalId, counterpart_transaction_id: action.counterpartTransactionId, delete_proposed_mirrors: true },
|
|
1874
|
+
});
|
|
1875
|
+
return { outcome: "completed", known_objects: [{ entity_type: "transaction", entity_id: action.transactionId, outcome: "deleted" }] };
|
|
1876
|
+
}
|
|
1877
|
+
catch (err) {
|
|
1878
|
+
if (err instanceof MutationIndeterminateError)
|
|
1879
|
+
return { outcome: "indeterminate", error_code: "mutation_outcome_unknown" };
|
|
1880
|
+
return { outcome: "failed", error_code: "delete_failed", mutation_occurred: false };
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1783
1885
|
const executionReport = await executeReconciliationCommands(commands);
|
|
1784
1886
|
const completedIds = new Set(executionReport.command_partitions.completed.map(item => item.command_id));
|
|
1887
|
+
for (const row of match.skippedAlreadyHandled) {
|
|
1888
|
+
if (completedIds.has(reconDeleteProposedMirrorCommandId(row.transaction_id)))
|
|
1889
|
+
row.mirror_deleted = true;
|
|
1890
|
+
}
|
|
1785
1891
|
for (const pair of match.matchedPairs) {
|
|
1786
1892
|
const action = match.confirmActions.find(action => action.confirmedTxId === pair.outgoing_transaction_id);
|
|
1787
1893
|
if (action?.registrationJournalId != null)
|