UNPKG

@accounter/server

Version:
891 lines (821 loc) 30.8 kB
import { describe, expect, it } from 'vitest'; import type { IGetChargesByIdsResult } from '../../../charges/types.js'; import type { IGetReferenceMergeCandidatesResult } from '../../types.js'; import { buildMergeChargesByTransactionReferencePlan } from '../merge-charges-by-reference.helper.js'; function buildCharge( id: string, overrides: Partial<IGetChargesByIdsResult> = {}, ): IGetChargesByIdsResult { return { id, accountant_status: 'PENDING', created_at: new Date('2026-01-01T00:00:00.000Z'), documents_optional_flag: false, invoice_payment_currency_diff: null, is_property: false, optional_vat: false, owner_id: 'owner-1', tax_category_id: null, type: 'COMMON', updated_at: new Date('2026-01-01T00:00:00.000Z'), user_description: null, ...overrides, }; } function buildCandidate( id: string, chargeId: string, reference: string, eventDate: string, overrides: Partial<IGetReferenceMergeCandidatesResult> = {}, ): IGetReferenceMergeCandidatesResult { return { id, account_id: 'account-1', amount: '10.00', business_id: null, charge_id: chargeId, counter_account: null, created_at: new Date('2026-01-01T00:00:00.000Z'), currency: 'ILS', currency_rate: '1', current_balance: '0', debit_date: null, debit_date_override: null, debit_timestamp: null, event_date: new Date(eventDate), is_fee: false, origin_key: 'origin-key', origin_user_description: null, owner_id: 'owner-1', source_description: null, source_id: `source-${id}`, source_origin: 'POALIM_ILS', source_reference: reference, updated_at: new Date('2026-01-01T00:00:00.000Z'), ...overrides, }; } function buildChargeById(charges: IGetChargesByIdsResult[]) { return new Map(charges.map(charge => [charge.id, charge])); } describe('buildMergeChargesByTransactionReferencePlan', () => { it('builds separate plans for disconnected candidate clusters', () => { const charges = [ buildCharge('charge-a', { created_at: new Date('2026-01-01T00:00:00.000Z') }), buildCharge('charge-b', { created_at: new Date('2026-01-02T00:00:00.000Z') }), buildCharge('charge-c', { created_at: new Date('2026-01-03T00:00:00.000Z') }), buildCharge('charge-d', { created_at: new Date('2026-01-04T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-a', 'charge-a', 'REF-1', '2026-01-01T10:00:00.000Z'), buildCandidate('tx-b', 'charge-b', 'REF-1', '2026-01-01T14:00:00.000Z'), buildCandidate('tx-c', 'charge-c', 'REF-1', '2026-03-15T10:00:00.000Z'), buildCandidate('tx-d', 'charge-d', 'REF-1', '2026-03-15T14:00:00.000Z'), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-1', baseChargeId: 'charge-a', chargeIdsToMerge: ['charge-b'], }, { reference: 'REF-1', baseChargeId: 'charge-c', chargeIdsToMerge: ['charge-d'], }, ]); }); it('matches candidates by wide date window when descriptions contain each other', () => { const charges = [ buildCharge('charge-a', { created_at: new Date('2026-01-01T00:00:00.000Z') }), buildCharge('charge-b', { created_at: new Date('2026-01-02T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-a', 'charge-a', 'REF-2', '2026-01-01T10:00:00.000Z', { source_description: 'payment for vendor abc', }), buildCandidate('tx-b', 'charge-b', 'REF-2', '2026-01-20T10:00:00.000Z', { source_description: 'vendor abc', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-2', baseChargeId: 'charge-a', chargeIdsToMerge: ['charge-b'], }, ]); }); it('prefers a non-fee charge as the merge base', () => { const charges = [ buildCharge('charge-fee', { created_at: new Date('2026-01-01T00:00:00.000Z'), }), buildCharge('charge-main', { created_at: new Date('2026-01-02T00:00:00.000Z'), }), ]; const candidates = [ buildCandidate('tx-fee', 'charge-fee', 'REF-3', '2026-01-01T10:00:00.000Z', { is_fee: true, }), buildCandidate('tx-main', 'charge-main', 'REF-3', '2026-01-01T12:00:00.000Z', { is_fee: false, }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-3', baseChargeId: 'charge-main', chargeIdsToMerge: ['charge-fee'], }, ]); }); it('keeps the first assignment when a target charge appears in multiple plans', () => { const charges = [ buildCharge('charge-main-1', { created_at: new Date('2026-01-01T00:00:00.000Z') }), buildCharge('charge-main-2', { created_at: new Date('2026-01-03T00:00:00.000Z') }), buildCharge('charge-target', { created_at: new Date('2026-01-02T00:00:00.000Z'), user_description: 'unlinked from charge', }), ]; const candidates = [ buildCandidate('tx-1', 'charge-main-1', 'REF-4', '2026-01-01T10:00:00.000Z'), buildCandidate('tx-2', 'charge-target', 'REF-4', '2026-01-01T11:00:00.000Z', { is_fee: true, }), buildCandidate('tx-3', 'charge-main-2', 'REF-5', '2026-01-02T10:00:00.000Z'), buildCandidate('tx-4', 'charge-target', 'REF-5', '2026-01-02T11:00:00.000Z', { is_fee: true, }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.plans).toEqual([ { reference: 'REF-4', baseChargeId: 'charge-main-1', chargeIdsToMerge: ['charge-target'], }, ]); expect(result.errors).toHaveLength(1); expect(result.errors[0]).toContain('charge-target'); }); it('consolidates transitive plans into a single merge base', () => { const charges = [ buildCharge('charge-a', { created_at: new Date('2026-01-01T00:00:00.000Z') }), buildCharge('charge-b', { created_at: new Date('2026-01-02T00:00:00.000Z') }), buildCharge('charge-c', { created_at: new Date('2026-01-03T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-a-ref-a', 'charge-a', 'REF-A', '2026-01-01T10:00:00.000Z', { amount: '500.00', }), buildCandidate('tx-b-ref-a', 'charge-b', 'REF-A', '2026-01-01T11:00:00.000Z', { amount: '-10.00', is_fee: true, }), buildCandidate('tx-b-ref-b', 'charge-b', 'REF-B', '2026-01-02T10:00:00.000Z', { amount: '650.00', }), buildCandidate('tx-c-ref-b', 'charge-c', 'REF-B', '2026-01-02T11:00:00.000Z', { amount: '-12.00', is_fee: true, }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-A', baseChargeId: 'charge-a', chargeIdsToMerge: ['charge-b', 'charge-c'], }, ]); }); it('does not merge recurring monthly payments with identical details', () => { const charges = [ buildCharge('charge-june', { created_at: new Date('2026-06-01T00:00:00.000Z') }), buildCharge('charge-july', { created_at: new Date('2026-07-01T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-june', 'charge-june', 'REF-6', '2026-06-01T10:00:00.000Z', { amount: '-2500.00', source_description: 'standing order recurring service payment', }), buildCandidate('tx-july', 'charge-july', 'REF-6', '2026-07-01T10:00:00.000Z', { amount: '-2500.00', source_description: 'standing order recurring service payment', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge distinct same-reference income payments with different amounts', () => { const charges = [ buildCharge('charge-income-1', { created_at: new Date('2026-04-19T00:00:00.000Z') }), buildCharge('charge-income-2', { created_at: new Date('2026-04-25T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-income-1', 'charge-income-1', 'REF-7', '2026-04-19T10:00:00.000Z', { currency: 'EUR', amount: '1000.00', source_description: 'incoming transfer from customer alpha', }), buildCandidate('tx-income-2', 'charge-income-2', 'REF-7', '2026-04-25T10:00:00.000Z', { currency: 'EUR', amount: '1100.00', source_description: 'incoming transfer from customer alpha', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('merges same-day principal and interest records under one reference', () => { const charges = [ buildCharge('charge-interest', { created_at: new Date('2026-07-06T00:00:00.000Z') }), buildCharge('charge-principal', { created_at: new Date('2026-07-06T00:00:01.000Z') }), ]; const candidates = [ buildCandidate('tx-interest', 'charge-interest', 'REF-8', '2026-07-06T10:00:00.000Z', { amount: '0.02', source_description: 'deposit interest credit', }), buildCandidate('tx-principal', 'charge-principal', 'REF-8', '2026-07-06T10:00:00.000Z', { amount: '395.04', source_description: 'deposit withdrawal principal', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-8', baseChargeId: 'charge-interest', chargeIdsToMerge: ['charge-principal'], }, ]); }); it('does not merge same-reference same-day income transfers from different clients', () => { const charges = [ buildCharge('charge-client-a', { created_at: new Date('2026-08-15T00:00:00.000Z') }), buildCharge('charge-client-b', { created_at: new Date('2026-08-16T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-client-a', 'charge-client-a', 'REF-10', '2026-08-15T10:00:00.000Z', { currency: 'USD', amount: '1492.50', source_description: 'incoming transfer client alpha', }), buildCandidate('tx-client-b', 'charge-client-b', 'REF-10', '2026-08-16T10:00:00.000Z', { currency: 'USD', amount: '3292.50', source_description: 'incoming transfer client beta', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge three monthly same-reference payments from the same client', () => { const charges = [ buildCharge('charge-month-1', { created_at: new Date('2026-01-06T00:00:00.000Z') }), buildCharge('charge-month-2', { created_at: new Date('2026-02-04T00:00:00.000Z') }), buildCharge('charge-month-3', { created_at: new Date('2026-03-04T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-month-1', 'charge-month-1', 'REF-11', '2026-01-06T10:00:00.000Z', { currency: 'ILS', amount: '8985.50', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), buildCandidate('tx-month-2', 'charge-month-2', 'REF-11', '2026-02-04T10:00:00.000Z', { currency: 'ILS', amount: '8795.75', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), buildCandidate('tx-month-3', 'charge-month-3', 'REF-11', '2026-03-04T10:00:00.000Z', { currency: 'ILS', amount: '8859.00', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge repeated same-reference payments in shorter cadence', () => { const charges = [ buildCharge('charge-repeat-1', { created_at: new Date('2026-10-02T00:00:00.000Z') }), buildCharge('charge-repeat-2', { created_at: new Date('2026-10-28T00:00:00.000Z') }), buildCharge('charge-repeat-3', { created_at: new Date('2026-11-12T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-repeat-1', 'charge-repeat-1', 'REF-12', '2026-10-02T10:00:00.000Z', { currency: 'ILS', amount: '9147.75', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), buildCandidate('tx-repeat-2', 'charge-repeat-2', 'REF-12', '2026-10-28T10:00:00.000Z', { currency: 'ILS', amount: '9233.50', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), buildCandidate('tx-repeat-3', 'charge-repeat-3', 'REF-12', '2026-11-12T10:00:00.000Z', { currency: 'ILS', amount: '9146.50', source_description: 'rtgs incoming payment customer omega', counter_account: '912-799-705101', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge repeated client transfer with distinct swift fee references', () => { const charges = [ buildCharge('charge-swift-1', { created_at: new Date('2026-02-17T00:00:00.000Z') }), buildCharge('charge-swift-2', { created_at: new Date('2026-02-09T00:00:00.000Z') }), ]; const candidates = [ buildCandidate('tx-swift-fee-1', 'charge-swift-1', 'SWIFT-REF-1', '2026-02-17T10:00:00.000Z', { currency: 'USD', amount: '-25.00', is_fee: true, source_description: 'swift fee vendor invoice 40430', counter_account: 'SWIFT', }), buildCandidate('tx-transfer-1', 'charge-swift-1', 'REF-13', '2026-02-17T10:00:00.000Z', { currency: 'USD', amount: '2575.00', source_description: 'incoming transfer vendor alpha', }), buildCandidate('tx-transfer-2', 'charge-swift-2', 'REF-13', '2026-02-09T10:00:00.000Z', { currency: 'USD', amount: '2575.00', source_description: 'incoming transfer vendor alpha', }), buildCandidate('tx-swift-fee-2', 'charge-swift-2', 'SWIFT-REF-2', '2026-02-09T10:00:00.000Z', { currency: 'USD', amount: '-25.00', is_fee: true, source_description: 'swift fee vendor invoice 40443', counter_account: 'SWIFT', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge repeated conversion charges with the same bank reference', () => { const charges = [ buildCharge('charge-conversion-1', { created_at: new Date('2026-07-17T00:00:00.000Z'), type: 'CONVERSION', user_description: 'USD to EUR conversion', }), buildCharge('charge-conversion-2', { created_at: new Date('2026-06-29T00:00:00.000Z'), type: 'CONVERSION', user_description: 'USD to EUR conversion', }), ]; const candidates = [ buildCandidate('tx-conversion-1-usd', 'charge-conversion-1', 'CONV-10086', '2026-07-15T10:00:00.000Z', { currency: 'USD', amount: '-115945.91', source_description: 'conversion', }), buildCandidate('tx-conversion-1-eur', 'charge-conversion-1', 'CONV-10086', '2026-07-15T10:00:00.000Z', { currency: 'EUR', amount: '99000.00', source_description: 'conversion', }), buildCandidate('tx-conversion-2-eur', 'charge-conversion-2', 'CONV-10086', '2026-06-26T10:00:00.000Z', { currency: 'EUR', amount: '101000.00', source_description: 'conversion', }), buildCandidate('tx-conversion-2-usd', 'charge-conversion-2', 'CONV-10086', '2026-06-26T10:00:00.000Z', { currency: 'USD', amount: '-118935.56', source_description: 'conversion', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge foreign-securities charges across cycles by shared fee reference', () => { const charges = [ buildCharge('charge-securities-a', { created_at: new Date('2026-03-04T00:00:00.000Z'), type: 'FOREIGN_SECURITIES', }), buildCharge('charge-securities-b', { created_at: new Date('2026-02-05T00:00:00.000Z'), type: 'FOREIGN_SECURITIES', }), ]; const candidates = [ buildCandidate('tx-securities-payment-a', 'charge-securities-a', 'SEC-REF-A', '2026-03-04T10:00:00.000Z', { currency: 'USD', amount: '2178.66', source_description: 'securities settlement payment batch', }), buildCandidate('tx-securities-fee-a', 'charge-securities-a', 'SEC-FEE-REF', '2026-03-04T10:00:00.000Z', { currency: 'ILS', amount: '-33.48', is_fee: true, source_description: 'securities settlement payment fee', }), buildCandidate('tx-securities-payment-b', 'charge-securities-b', 'SEC-REF-B', '2026-02-05T10:00:00.000Z', { currency: 'USD', amount: '2640.09', source_description: 'securities settlement payment batch', }), buildCandidate('tx-securities-fee-b', 'charge-securities-b', 'SEC-FEE-REF', '2026-02-05T10:00:00.000Z', { currency: 'ILS', amount: '-40.74', is_fee: true, source_description: 'securities settlement payment fee', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('merges foreign-securities fee charge when main description contains padded fee reference', () => { const charges = [ buildCharge('charge-securities-main', { created_at: new Date('2026-07-08T00:00:00.000Z'), type: 'FOREIGN_SECURITIES', }), buildCharge('charge-securities-fee', { created_at: new Date('2026-07-08T00:00:01.000Z'), type: 'FOREIGN_SECURITIES', }), ]; const candidates = [ buildCandidate('tx-securities-fee', 'charge-securities-fee', '79915674', '2026-07-07T10:00:00.000Z', { currency: 'ILS', amount: '-88.80', is_fee: true, source_origin: 'POALIM', source_description: 'fsec payment fee', }), buildCandidate('tx-securities-main', 'charge-securities-main', '3901767', '2026-07-07T10:00:00.000Z', { currency: 'USD', amount: '4521.61', source_origin: 'POALIM', source_description: 'securities settlement 0079915674', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: '79915674', baseChargeId: 'charge-securities-main', chargeIdsToMerge: ['charge-securities-fee'], }, ]); }); it('merges foreign-securities fee charge when main description contains padded fee reference and charges types are not defined', () => { const charges = [ buildCharge('charge-securities-main', { created_at: new Date('2026-07-08T00:00:00.000Z'), }), buildCharge('charge-securities-fee', { created_at: new Date('2026-07-08T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-securities-fee', 'charge-securities-fee', '79915674', '2026-07-07T10:00:00.000Z', { currency: 'ILS', amount: '-88.80', is_fee: true, source_origin: 'POALIM', source_description: 'ני"עז עמ קניה FSEC BUY FEE', }), buildCandidate('tx-securities-main', 'charge-securities-main', '3901767', '2026-07-07T10:00:00.000Z', { currency: 'USD', amount: '4521.61', source_origin: 'POALIM', source_description: 'ניע"ז תשלומים 0079915674', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: '79915674', baseChargeId: 'charge-securities-main', chargeIdsToMerge: ['charge-securities-fee'], }, ]); }); it('merges foreign-securities fee with padded reference alias from main transaction description', () => { const charges = [ buildCharge('charge-foreign-main', { created_at: new Date('2026-08-10T00:00:00.000Z'), }), buildCharge('charge-foreign-fee', { created_at: new Date('2026-08-10T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-foreign-fee', 'charge-foreign-fee', '12345678', '2026-08-09T10:00:00.000Z', { currency: 'ILS', amount: '-41.20', is_fee: true, source_origin: 'POALIM', source_description: 'ניעז עמ תשלום fsec pymnt fee', }), buildCandidate('tx-foreign-main', 'charge-foreign-main', '7654321', '2026-08-09T10:00:00.000Z', { currency: 'USD', amount: '3120.55', source_origin: 'POALIM', source_description: 'ניע"ז תשלומים 0012345678', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: '12345678', baseChargeId: 'charge-foreign-main', chargeIdsToMerge: ['charge-foreign-fee'], }, ]); }); it('merges foreign-securities fee when main transaction contains zero-padded fee reference', () => { const charges = [ buildCharge('charge-fsec-main', { created_at: new Date('2026-09-03T00:00:00.000Z'), }), buildCharge('charge-fsec-fee', { created_at: new Date('2026-09-03T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-fsec-fee', 'charge-fsec-fee', '88997766', '2026-09-02T10:00:00.000Z', { currency: 'ILS', amount: '-37.45', is_fee: true, source_origin: 'POALIM', source_description: 'ניעז עמ תשלום fsec pymnt fee', }), buildCandidate('tx-fsec-main', 'charge-fsec-main', '2233445', '2026-09-02T10:00:00.000Z', { currency: 'USD', amount: '4270.00', source_origin: 'POALIM', source_description: 'ניע"ז תשלומים 0088997766', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: '88997766', baseChargeId: 'charge-fsec-main', chargeIdsToMerge: ['charge-fsec-fee'], }, ]); }); it('does not merge foreign-securities alias pair when source origin is not POALIM', () => { const charges = [ buildCharge('charge-origin-main', { created_at: new Date('2026-10-03T00:00:00.000Z'), }), buildCharge('charge-origin-fee', { created_at: new Date('2026-10-03T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-origin-fee', 'charge-origin-fee', '90112233', '2026-10-02T10:00:00.000Z', { currency: 'ILS', amount: '-18.00', is_fee: true, source_origin: 'POALIM_ILS', source_description: 'ניעז עמ תשלום fsec pymnt fee', }), buildCandidate('tx-origin-main', 'charge-origin-main', '3322110', '2026-10-02T10:00:00.000Z', { currency: 'USD', amount: '1810.00', source_origin: 'POALIM_ILS', source_description: 'ניע"ז תשלומים 0090112233', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge foreign-securities alias pair across different dates', () => { const charges = [ buildCharge('charge-date-main', { created_at: new Date('2026-11-03T00:00:00.000Z'), }), buildCharge('charge-date-fee', { created_at: new Date('2026-11-03T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-date-fee', 'charge-date-fee', '77123456', '2026-11-01T10:00:00.000Z', { currency: 'ILS', amount: '-21.00', is_fee: true, source_origin: 'POALIM', source_description: 'ניעז עמ תשלום fsec pymnt fee', }), buildCandidate('tx-date-main', 'charge-date-main', '4411002', '2026-11-03T10:00:00.000Z', { currency: 'USD', amount: '2205.50', source_origin: 'POALIM', source_description: 'ניע"ז תשלומים 0077123456', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('does not merge foreign-securities alias pair when non-fee references differ', () => { const charges = [ buildCharge('charge-action-a', { created_at: new Date('2026-12-03T00:00:00.000Z'), }), buildCharge('charge-action-b', { created_at: new Date('2026-12-03T00:00:01.000Z'), }), ]; const candidates = [ buildCandidate('tx-action-a-main', 'charge-action-a', '3900138', '2026-12-02T10:00:00.000Z', { currency: 'USD', amount: '78.55', source_origin: 'POALIM', source_description: 'ניע"ז תשלומים 0077448736', }), buildCandidate('tx-action-a-fee', 'charge-action-a', '77448736', '2026-12-02T10:00:00.000Z', { currency: 'ILS', amount: '-9.10', is_fee: true, source_origin: 'POALIM', source_description: 'ניעז עמ תשלום fsec pymnt fee', }), buildCandidate('tx-action-b-fee', 'charge-action-b', '1501588', '2026-12-02T10:00:00.000Z', { currency: 'USD', amount: '-75.38', is_fee: true, source_origin: 'POALIM', source_description: 'ניע"ז עמ קניה 0077448736', }), buildCandidate('tx-action-b-main', 'charge-action-b', '1501588', '2026-12-02T10:00:00.000Z', { currency: 'USD', amount: '-75379.79', source_origin: 'POALIM', source_description: 'ניע"ז קניה 0077448736', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([]); }); it('keeps conversion pair separate and merges transfer with related fees', () => { const charges = [ buildCharge('charge-conversion', { created_at: new Date('2026-03-02T00:00:00.000Z'), type: 'CONVERSION', }), buildCharge('charge-transfer', { created_at: new Date('2026-03-02T00:00:01.000Z'), type: null, }), buildCharge('charge-fee-a', { created_at: new Date('2026-03-02T00:00:02.000Z'), type: null, }), buildCharge('charge-fee-b', { created_at: new Date('2026-03-05T00:00:02.000Z'), type: null, }), ]; const candidates = [ buildCandidate('tx-conversion-base', 'charge-conversion', 'REF-9', '2026-03-02T10:00:00.000Z', { currency: 'EUR', amount: '1000.00', source_description: 'conversion batch 26', }), buildCandidate('tx-conversion-quote', 'charge-conversion', 'REF-9', '2026-03-02T10:00:00.000Z', { currency: 'USD', amount: '-1180.00', source_description: 'conversion batch 26', }), buildCandidate('tx-transfer', 'charge-transfer', 'REF-9', '2026-03-02T10:00:00.000Z', { currency: 'EUR', amount: '-1000.00', source_description: 'fx transfer batch 26', }), buildCandidate('tx-fee-a', 'charge-fee-a', 'REF-9', '2026-03-02T10:00:00.000Z', { currency: 'ILS', amount: '-2.00', is_fee: true, source_description: 'fx transfer commission batch 26', }), buildCandidate('tx-fee-b', 'charge-fee-b', 'REF-9', '2026-03-05T10:00:00.000Z', { currency: 'EUR', amount: '-10.00', is_fee: true, source_description: 'bank fee for transfer batch 26', }), ]; const result = buildMergeChargesByTransactionReferencePlan({ candidates, chargeById: buildChargeById(charges), }); expect(result.errors).toEqual([]); expect(result.plans).toEqual([ { reference: 'REF-9', baseChargeId: 'charge-transfer', chargeIdsToMerge: ['charge-fee-a', 'charge-fee-b'], }, ]); }); });