Skip to content

Normalized Bank Transaction Model ​

Plane task: MHPAY-2

This document defines the normalized bank transaction model used by bank statement adapters in MyHoreca Pay.

The goal of the model is to convert different Indonesian bank statement formats into one internal representation that downstream modules can use for matching, allocation, audit, reconciliation, and operator review.

Source of truth ​

The current backend model is:

text
backend/src/MyHoreca.Pay.Domain/BankStatements/NormalizedBankTransaction.cs

Current fields:

csharp
public sealed class NormalizedBankTransaction
{
    public Guid Id { get; set; }
    public Guid ImportId { get; set; }
    public string SourceBank { get; set; } = string.Empty;
    public string SourceFormat { get; set; } = string.Empty;
    public string? AccountNumber { get; set; }
    public string? AccountName { get; set; }
    public string Currency { get; set; } = "IDR";
    public DateOnly TransactionDate { get; set; }
    public DateOnly? ValueDate { get; set; }
    public decimal Amount { get; set; }
    public TransactionDirection Direction { get; set; }
    public string RawDescription { get; set; } = string.Empty;
    public string? CounterpartyRawName { get; set; }
    public string? BankReference { get; set; }
    public string? CustomerReference { get; set; }
    public string? PaymentPurpose { get; set; }
    public decimal? BalanceAfter { get; set; }
    public Dictionary<string, string> RawFields { get; set; } = new();
}

Model purpose ​

A NormalizedBankTransaction is the canonical transaction record produced by any bank adapter after parsing a bank-specific source file.

It should answer these questions:

  • Which import did this transaction come from?
  • Which bank and source format produced it?
  • Which account does it belong to?
  • What date did the transaction happen?
  • Is it a debit or a credit?
  • What is the amount and currency?
  • What did the bank say in the description/purpose/reference fields?
  • Who appears to be the counterparty?
  • What raw source values were used to derive the normalized fields?

The model is intentionally close to bank data. It must not contain Syrve-specific allocation decisions. Matching and allocation happen later.

Field definitions ​

FieldRequiredDescriptionAdapter rule
IdYesInternal transaction ID.Generate on backend using UUID v7 only. Do not generate persistent IDs in React.
ImportIdYesParent bank statement import ID.Must be copied from the active import context.
SourceBankYesBank code, for example DEMO, PERMATA, BCA, etc.Must match adapter descriptor bank code.
SourceFormatYesSource file format code, for example CSV, TXT, MT940.Must match adapter descriptor format code.
AccountNumberOptionalBank account number from the statement.Fill when available. Preserve bank formatting only if normalization would lose meaning.
AccountNameOptionalAccount holder/name from the statement.Fill when available.
CurrencyYesCurrency code. Default is IDR.Use ISO-like code where possible. For Indonesian statements usually IDR.
TransactionDateYesMain transaction date.Must be parsed from source. If missing, adapter should fail or mark row invalid.
ValueDateOptionalValue/effective date if the bank provides one.If not available, leave null or copy transaction date only when the source format explicitly implies same date.
AmountYesAbsolute transaction amount.Store positive decimal amount. Direction determines debit/credit.
DirectionYesDebit or Credit.Must be derived from source sign, column, CR/DB marker, or transaction type.
RawDescriptionYesOriginal or near-original bank description.Preserve human-readable text. Do not over-normalize.
CounterpartyRawNameOptionalCounterparty name as extracted from bank data.Fill when a plausible counterparty can be extracted. Leave null if not reliable.
BankReferenceOptionalBank-generated reference/transaction ID.Prefer stable bank reference useful for duplicate detection.
CustomerReferenceOptionalCustomer/user/reference number provided by payer/payee.Use invoice number, customer reference, virtual account, or transfer reference when source separates it.
PaymentPurposeOptionalPayment purpose/narrative.Use when bank provides purpose separate from raw description.
BalanceAfterOptionalAccount balance after transaction.Fill if statement provides running balance. Useful for quality checks.
RawFieldsYesOriginal source fields and diagnostic metadata.Preserve enough source data for audit, debugging, and operator explanation.

Amount and direction rules ​

The normalized model stores amount and direction separately.

Rules:

  • Amount should be positive.
  • Direction = Credit means money came into the account.
  • Direction = Debit means money left the account.
  • Negative source amounts should be converted to positive Amount plus the correct Direction.
  • CR/DB markers should be mapped explicitly.
  • Separate debit/credit columns should be mapped by whichever column contains a value.
  • If both debit and credit columns contain values in one row, the adapter should raise a row-level error.
  • If neither debit nor credit can be determined, the adapter should skip the row with an explainable error.

Date rules ​

Adapters must be explicit about date parsing.

For every real bank adapter, document:

  • accepted date formats;
  • whether time is present;
  • whether transaction date and value date are separate;
  • timezone assumptions if timestamps are present;
  • what happens when a date is missing or invalid.

Examples of likely Indonesian bank formats:

  • dd/MM/yyyy
  • dd-MM-yyyy
  • yyyy-MM-dd
  • dd MMM yyyy
  • localized month names if present in exports

For MVP adapters, prefer strict parsing with clear errors over guessing.

Currency rules ​

Default currency is IDR, but adapters should not blindly assume IDR when the file contains a currency column.

Rules:

  • Use source currency when provided.
  • Normalize common variants such as Rp, IDR, RUPIAH to IDR.
  • If a multi-currency file is detected, preserve the row currency.
  • If currency is missing and the bank account/export is known to be IDR-only, use IDR and record this assumption in RawFields or adapter notes.

Description and purpose rules ​

RawDescription should preserve the bank's original narrative as much as possible.

PaymentPurpose may be a cleaned or specific purpose field when source data supports it.

Do not remove important tokens such as:

  • invoice numbers;
  • virtual account numbers;
  • payer names;
  • bank references;
  • transfer channel markers;
  • free-text notes.

For matching, later modules can derive normalized search keys. The bank adapter should preserve source truth first.

Counterparty extraction rules ​

CounterpartyRawName is important for matching, but adapters must not invent it.

Rules:

  • Fill it when the source has a clear counterparty column.
  • Extract it from description only if the bank format has a reliable pattern.
  • If extraction is uncertain, leave it null and add a warning.
  • Preserve original casing/spelling where possible.
  • Do not map to Syrve counterparty here; that belongs to counterparty matching.

Reference fields ​

Use reference fields carefully because they are important for duplicate detection and audit.

BankReference should contain a stable bank-side transaction reference when available.

CustomerReference should contain payer/customer/invoice/virtual account reference when source separates it from bank reference.

If the source has only one reference-like field, prefer:

  1. BankReference for bank transaction ID / trace number / journal number.
  2. CustomerReference for invoice/customer/payment reference.
  3. PaymentPurpose or RawDescription for free-text narrative.

RawFields requirements ​

RawFields is mandatory for traceability.

Each adapter should include at least:

  • source line number or row number;
  • original raw line when practical;
  • original column names and values;
  • parser assumptions when useful;
  • detection-specific metadata when useful.

For CSV-like files, recommended keys:

text
LineNumber
RawLine
Column:<source column name>

For fixed-width or MT940-like formats, recommended keys:

text
StatementLine
RawSegment
Tag
SequenceNumber

Do not store secrets or credentials in RawFields.

Quality and warning expectations ​

The normalized transaction model itself does not hold warnings/errors. Warnings and errors are returned through parse result / quality report structures.

Adapters should emit warnings when:

  • counterparty was not extracted;
  • optional reference is missing;
  • value date is missing but transaction date exists;
  • currency was assumed;
  • description was empty;
  • balance was unavailable for balance verification.

Adapters should emit errors or skip row when:

  • transaction date is invalid;
  • amount is invalid;
  • direction cannot be determined;
  • mandatory columns are missing;
  • row structure is broken;
  • both debit and credit are populated in mutually exclusive columns.

Operator messages must be clear enough to explain what happened without reading code.

Duplicate detection fields ​

The strongest duplicate detection fields are expected to be:

  • SourceBank;
  • AccountNumber;
  • TransactionDate;
  • Amount;
  • Direction;
  • Currency;
  • BankReference;
  • CustomerReference;
  • normalized description/purpose;
  • normalized counterparty name.

Adapters should preserve the fields needed for duplicate checks even when a bank format is inconsistent.

What adapters must not do ​

Bank adapters must not:

  • create Syrve allocations;
  • decide which Syrve document should be paid;
  • call Syrve/iiko APIs;
  • silently drop invalid rows without warnings/errors;
  • invent counterparties when extraction is uncertain;
  • generate backend persistent IDs outside the backend UUID v7 generator;
  • hide source values needed for audit.

Example: demo CSV mapping ​

Demo CSV source:

csv
Date,Direction,Amount,Currency,Description,Counterparty,Reference
2026-03-02,Credit,100000.00,IDR,Payment invoice 123,PT DEMO SUPPLIER,REF001

Normalized mapping:

Source columnNormalized field
DateTransactionDate, ValueDate
DirectionDirection
AmountAmount
CurrencyCurrency
DescriptionRawDescription, PaymentPurpose
CounterpartyCounterpartyRawName
ReferenceBankReference
raw line/line numberRawFields

Acceptance rules for new bank adapters ​

A new adapter should be accepted only when:

  • it fills all required normalized fields;
  • it preserves raw source data in RawFields;
  • it has deterministic format detection;
  • it has unit tests with sanitized fixtures;
  • it documents date, amount, direction, reference, and counterparty rules;
  • it returns useful quality warnings/errors;
  • it does not break the shared import pipeline.
  • MHPAY-1 — bank import pipeline review.
  • MHPAY-3 — create full bank-statement-parsing.md architecture document.
  • MHPAY-4 — implement first real Indonesian bank CSV adapter.
  • MHPAY-5 — expose supported banks and formats.