@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;
        bankAccountId: string | 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;
        fees: ProductFee[];
        id: string;
        idempotencyKey: string | null;
        inputAmount: { source: BigNumber } | { target: BigNumber } | null;
        inputBankId: BankId | null;
        limitEvaluation: LimitEvaluation | null;
        minSettlementDate: Temporal.PlainDate | null;
        outcome: SelectProductQuote["outcome"];
        productCategory: Product["category"];
        productId: Product["id"];
        productMethod: Product["method"];
        productName: Product["name"];
        programId: string | null;
        providerFeeVisible: boolean | null;
        quantity: number;
        quote: QuoteShape | null;
        regionCode: string | null;
        routeId: string;
        sourceChain: SelectChain["key"] | null;
        sourceCurrency: string | null;
        speed: Speed | null;
        targetCurrency: string | null;
        vendorId: Vendor;
        violation: ProductLimitViolation | null;
    }
    Index
    acceptedAt: Temporal.Instant | null
    bankAccountId: string | null

    The pinned route version's target account and program (external ids), read through route_version_id; see ResolvedRoute.programId.

    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
    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.

    limitEvaluation: LimitEvaluation | null

    What the unconditional limit evaluation concluded, independent of the caller's bypassLimits. null on rows where no evaluation ran (INACTIVE, LEGACY, pre-migration). Differs from outcome exactly when a bypassed quote would have been blocked — which is the only way to see that from the audit trail.

    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: SelectProductQuote["outcome"]
    productCategory: Product["category"]
    productId: Product["id"]
    productMethod: Product["method"]
    productName: Product["name"]
    programId: string | null
    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
    sourceChain: SelectChain["key"] | null

    The b2b.chain.key the request named as its source chain.

    sourceCurrency: string | null
    speed: Speed | null
    targetCurrency: string | null
    vendorId: Vendor

    External id of the vendor the resolver picked (product_vendor.external_id). Never null — the vendor join is INNER.

    This is the dispatch key for consumers that must branch on which vendor won; bankId cannot serve that purpose, because it is null for every vendor that fronts banks rather than being one.

    violation: ProductLimitViolation | null

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