@cfxlabsinc/b2b-services
    Preparing search index...

    Customer-scoped projection of a product_quote audit row. Mirrors the fields needed by both audit-trail readers (admin dashboards, customer receipts) and the quote() idempotency-replay path: every field needed to rebuild the original quote's QuoteOutput from the row alone is here.

    Internal numeric ids and FKs are dropped in favour of external IDs. fees is the per-receiver fee snapshot reconstructed from the tier- tagged columns (cfx_*, customer_*, provider_*). inputAmount is the discriminated union the caller originally passed (or null for fee-only quotes).

    type ProductQuote = {
        acceptedAt: Temporal.Instant | null;
        activationRuleVersionId: number | null;
        bankId: BankId | null;
        cashDepositRetailerId: string | null;
        createdAt: Temporal.Instant;
        customerId: string;
        data: PinnedMatcherCriteria;
        decimalPlaces: number;
        entity: { id: string; type: "IDENTITY" | "ORGANIZATION" };
        expiresAt: Temporal.Instant;
        feeRuleVersionId: number | null;
        fees: ProductFee[];
        id: string;
        idempotencyKey: string | null;
        inputAmount: { source: BigNumber } | { target: BigNumber } | null;
        inputBankId: BankId | null;
        limitRuleVersionId: number | null;
        minSettlementDate: Temporal.PlainDate | null;
        outcome: "OK" | "LIMIT_EXCEEDED" | "INACTIVE" | "LEGACY";
        productName: ProductName;
        providerFeeVisible: boolean | null;
        quantity: number;
        quote: QuoteShape | null;
        regionCode: string | null;
        routeId: string;
        routeVersionId: number;
        sourceCurrency: string | null;
        speed: Speed | null;
        targetCurrency: string | null;
        vendorVersionId: number;
        violation: ProductLimitViolation | null;
    }
    Index

    Properties

    acceptedAt: Temporal.Instant | null
    activationRuleVersionId: number | null
    bankId: BankId | null

    Resolved bankId — joined through product_route_version → vendor → bank. Reflects what the resolver actually picked. null when the route's vendor is non-bank (e.g. cfx).

    cashDepositRetailerId: string | null
    createdAt: Temporal.Instant
    customerId: string

    The pinned matcher criteria the resolver evaluated against.

    decimalPlaces: number
    entity: { id: string; type: "IDENTITY" | "ORGANIZATION" }
    expiresAt: Temporal.Instant
    feeRuleVersionId: number | null
    fees: ProductFee[]

    Reconstructed fee template — per-receiver line items.

    id: string
    idempotencyKey: string | null
    inputAmount: { source: BigNumber } | { target: BigNumber } | null
    inputBankId: BankId | null

    The caller's bankId hint, if any (narrows route selection). Audit row column; distinct from the resolved bankId above.

    limitRuleVersionId: number | null
    minSettlementDate: Temporal.PlainDate | null

    Earliest settle-by date. null on pre-migration rows (column was added 2026-05-11); service rebuild uses the current product config to recompute as a fallback.

    outcome: "OK" | "LIMIT_EXCEEDED" | "INACTIVE" | "LEGACY"
    productName: ProductName
    providerFeeVisible: boolean | null

    Snapshot of providerFeeVisible at quote time.

    quantity: number
    quote: QuoteShape | null

    Concrete amounts. null when the original input was amount-less.

    regionCode: string | null
    routeId: string
    routeVersionId: number

    Numeric FKs to the pinned version rows, exposed so callers that need deep version-row context (admin diff views, the quote() replay rebuild) can resolve them without re-querying the audit row.

    sourceCurrency: string | null
    speed: Speed | null
    targetCurrency: string | null
    vendorVersionId: number
    violation: ProductLimitViolation | null

    Limit-violation breakdown. Non-null iff outcome === 'LIMIT_EXCEEDED'.