HOCO B2B
SRSAPI referenceBack-office endpoints

Finance

Back-office finance operations — invoices and their cancellation, payments recorded or declared and their verification, allocations, credit accounts with exposure and limit history, and the V2 overdue and accounting exports.

These operations follow the financial commitments of resellers (cahier §4.7): invoices generated from orders, payments by cash on delivery, transfer, CCP, BaridiMob or credit, partial payments and their allocation to invoices, the credit ceiling with used and available credit, and blocking (FIN-01 to FIN-05). Screens: invoices (BO-44), payments and declarations (BO-45), credit accounts (BO-46), the finance dashboard (BO-08) and, in V2, overdue reminders and accounting exports (BO-47, BO-48). Invoice and payment statuses are defined on Lifecycles › Invoice and Payment.

Credit notes are V1 since the 17 Oct 2026 decisions (FIN-08): they are issued automatically by a return reception (admin.returns.receive) and by an after-sales resolution CREDIT_NOTE (admin.tickets.resolve); they appear on invoices and in credit exposure here. Cancelling an invoice remains a cancellation (number kept, never reused).

Who uses it. The Responsable finance (cahier §3: « Paiements, crédit, factures, échéances et rapports financiers »); the Super administrateur; the Direction reads. Access to financial data is strictly limited (cahier §4.7, ACL-04): nothing on this page is returned without finance.view.

PermissionUnlocks on this page
finance.viewInvoices, payments, credit accounts (read); V2 overdue list
finance.createRecording a payment
finance.updateChanging the allocations of a confirmed payment
finance.validateVerifying a reseller declaration; cancelling an invoice
finance.administerCredit limit, payment terms, blocking and policy
finance.exportV2 accounting exports (list exports of invoices and payments use admin.exports.create)

Settings used (settings keys): billing.invoiceOn (default SHIPPED), credit.overLimitPolicy (default BLOCK), credit.defaultLimit (0), credit.defaultTermDays (30), payments.enabledMethods.

Credit exposure (used by checkout, order controls, the finance dashboard and this page):

TermFormula
used (exposure, BR-33)Σ balance of the reseller's UNPAID, PARTIALLY_PAID and OVERDUE invoices (whatever the payment method) + Σ totalTtc of their CREDIT orders not yet invoiced (SUBMITTED, PENDING, VALIDATED, PREPARING, READY, and later statuses while uninvoiced) − confirmed payments and credit notes not yet allocated. Declarations PENDING_VERIFICATION do not reduce it
availablemax(0, limit − used)
ControlApplies only to orders paid by CREDIT: orderTotal > available → refused at checkout (BLOCK) or order PENDING with CREDIT_OVER_LIMIT (REQUIRE_APPROVAL); a blocked account refuses every CREDIT order

Audit. UPDATE/STATUS_CHANGE on invoices (cancellation with reason), CREATE on payments, VALIDATE/REJECT on verifications, UPDATE on allocations (before/after), UPDATE on credit accounts with a CreditLimitChange row for every changed field.

Conventions: lists return Page<T> = { items, total, page, pageSize } (responses); Totals, FileRef, ResellerSummary, UserSummary are shared schemas; money in centimes; dates YYYY-MM-DD in Africa/Algiers; editable records carry version; a missing reason answers 422 REASON_REQUIRED; an Idempotency-Key is recommended on admin.payments.record. 401, 429 and 500 apply everywhere and are not repeated.

MethodPathOperationAccessAppsRelease
GET/admin/invoicesadmin.invoices.list
Invoices with status, reseller, dates filters
finance.viewBOV1
GET/admin/invoices/{invoiceId}admin.invoices.get
Invoice with allocations
finance.viewBOV1
POST/admin/invoices/{invoiceId}/canceladmin.invoices.cancel
Cancel an invoice (V2: issues a credit note)
finance.validateBOV1
GET/admin/paymentsadmin.payments.list
Payments and declarations to verify
finance.viewBOV1
POST/admin/paymentsadmin.payments.record
Record a payment (cash on delivery, transfer…) with allocations
finance.createBOV1
POST/admin/payments/{paymentId}/verificationadmin.payments.verify
Confirm or reject a reseller declaration
finance.validateBOV1
PUT/admin/payments/{paymentId}/allocationsadmin.payments.allocate
Allocate a payment across invoices
finance.updateBOV1
GET/admin/credit-accountsadmin.credit.list
Credit accounts with live exposure (filters: policy, blocked, over limit, overdue, wilaya, q)
finance.viewBOV1
GET/admin/credit-accounts/{resellerId}admin.credit.get
Credit account with exposure
finance.viewBOV1
PATCH/admin/credit-accounts/{resellerId}admin.credit.update
Set limit, terms, block/unblock (history kept)
finance.administerBOV1
GET/admin/finance/overdueadmin.finance.overdue
Overdue invoices and reminder history (V2)
finance.viewBOV2
POST/admin/finance/exportsadmin.finance.export
Accounting export (V2)
finance.exportBOV2

Schemas

Invoice

interface Invoice {
  id: Uuid;
  number: string;                       // FAC-2026-000123
  status: InvoiceStatus;                // UNPAID, PARTIALLY_PAID, PAID, OVERDUE, CANCELLED
  order: { id: Uuid; number: string; paymentMethod: PaymentMethod };
  reseller: ResellerSummary & { customerType: CustomerType };
  issuedAt: Iso;
  dueDate: string;                      // YYYY-MM-DD = issue date + the account's termDays (CREDIT orders) or billing.dueDaysDefault (other methods, default 7) — BR-36
  totals: Totals;
  vatBreakdown: { vatRate: BasisPoints; baseHt: Money; vat: Money }[];
  paidAmount: Money;                    // Σ confirmed allocations
  creditedAmount: Money;                // Σ credit notes allocated to this invoice
  balance: Money;                       // totalTtc − paidAmount − creditedAmount
  daysOverdue: number;                  // 0 unless OVERDUE
  lines: { sku: string; label: string; quantity: number; unitPriceHt: Money; discountHt: Money; vatRate: BasisPoints; totalHt: Money; totalTtc: Money }[];
  allocations: { paymentId: Uuid; paymentNumber: string; method: PaymentMethod; amount: Money; allocatedAt: Iso }[];
  creditNotes: { id: Uuid; number: string; amountTtc: Money; allocatedAmount: Money; issuedAt: Iso; origin: "RETURN" | "AFTER_SALES"; file?: FileRef }[];
  legal: { sellerName: string; rc: string; nif: string; nis: string; ai: string; address: string };
  pdf?: FileRef;                        // signed URL, 5 min
  cancelledAt?: Iso;
  cancelReason?: string;
  version: number;
}

Payment

interface Payment {
  id: Uuid;
  number: string;                       // PAY-2026-000077
  status: PaymentStatus;                // PENDING_VERIFICATION, CONFIRMED, REJECTED
  source: "BACK_OFFICE" | "RESELLER_DECLARATION" | "DELIVERY_COLLECTION";   // PaymentSource
  reseller: ResellerSummary;
  method: PaymentMethod;
  amount: Money;
  paidOn: string;                       // YYYY-MM-DD, date of the transfer / deposit / collection
  reference?: string;                   // transfer reference, CCP receipt number…
  proof?: FileRef;                      // PAYMENT_PROOF
  order?: { id: Uuid; number: string }; // collections on delivery
  declaredAt?: Iso; declaredBy?: UserSummary;
  recordedBy?: UserSummary;
  verifiedAt?: Iso; verifiedBy?: UserSummary;
  rejection?: { reasonCode: string; reason?: string };
  allocations: { invoiceId: Uuid; invoiceNumber: string; amount: Money }[];
  unallocated: Money;                   // amount − Σ allocations (an advance once CONFIRMED)
  note?: string;
  createdAt: Iso;
  version: number;
}

CreditAccount

interface CreditAccount {
  resellerId: Uuid;
  reseller: ResellerSummary & { customerType: CustomerType };
  limit: Money;
  termsDays: number;                    // payment terms → invoice due date
  blocked: boolean;
  blockedReason?: string;
  policy: "INHERIT" | "BLOCK" | "REQUIRE_APPROVAL";   // CreditPolicy; INHERIT = credit.overLimitPolicy
  effectivePolicy: "BLOCK" | "REQUIRE_APPROVAL";
  used: Money;
  available: Money;
  exposure: { openInvoices: Money; uninvoicedCreditOrders: { count: number; amount: Money }; unallocatedCredit: Money };   // used = openInvoices + uninvoicedCreditOrders.amount − unallocatedCredit
  overdue: { amount: Money; invoices: number; oldestDaysOverdue: number };
  history: { field: "LIMIT" | "TERMS_DAYS" | "BLOCKED" | "POLICY"; before: number | boolean | string; after: number | boolean | string; reason: string; changedBy: UserSummary; changedAt: Iso }[];   // CreditLimitChange, last 20
  updatedAt: Iso;
  version: number;
}

Invoices

GET /admin/invoices

admin.invoices.list · Permission finance.view · Apps BO · Release V1 · Requirements FIN-01

Purpose. Invoices with status, reseller and date filters.

Request

QueryTypeValues / defaultNotes
statusInvoiceStatus[]repeatable
resellerIdUuidReseller 360° tab
orderIdUuid
wilayaCode / delegateIdstring[] / Uuid[]repeatableReseller's wilaya / assigned delegate
paymentMethodPaymentMethod[]repeatableOf the order
dateFieldstringissuedAt (default) | dueDate
from / tostring (date)
minBalanceMoney
qstringmin 2 charsInvoice or order number, store name, reseller number
sortstringissuedAt (default), dueDate, balance, totalTtc, number
order / page / pageSizedesc · 1 · 25 (max 200)

Response 200 OK

{
  "items": [
    {
      "id": "01930c2d-7e8f-7a9b-8c0d-1e2f3a4b5c70", "number": "FAC-2026-000123", "status": "PARTIALLY_PAID",
      "order": { "id": "0193016e-4b2a-7c11-9d8e-7f6a5b4c3d2e", "number": "CMD-2026-000158", "paymentMethod": "CREDIT" },
      "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE", "customerType": "DEMI_GROS" },
      "issuedAt": "2026-11-05T09:30:00Z", "dueDate": "2026-12-05",
      "totalTtc": 3074960, "paidAmount": 2000000, "creditedAmount": 0, "balance": 1074960, "daysOverdue": 0
    }
  ],
  "total": 1184,
  "page": 1,
  "pageSize": 25,
  "totals": { "totalTtc": 3074960, "balance": 1074960 }
}

totals sums the filtered set (not only the page).

Errors

StatusCodeWhen
400VALIDATION_FAILEDUnknown status or sort field
403PERMISSION_DENIEDCaller lacks finance.view

Rules & side effects

  1. Read-only. Invoices are created by the order state machine at billing.invoiceOn (default SHIPPED); this page never creates one.

Used by BO-44 · BO-11 · BO-08

GET /admin/invoices/{invoiceId}

admin.invoices.get · Permission finance.view · Apps BO · Release V1 · Requirements FIN-01

Purpose. An invoice with its lines, VAT breakdown, legal mentions, allocations, credit notes and PDF.

Response 200 OK — Invoice

{
  "id": "01930c2d-7e8f-7a9b-8c0d-1e2f3a4b5c70",
  "number": "FAC-2026-000123",
  "status": "PARTIALLY_PAID",
  "order": { "id": "0193016e-4b2a-7c11-9d8e-7f6a5b4c3d2e", "number": "CMD-2026-000158", "paymentMethod": "CREDIT" },
  "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE", "customerType": "DEMI_GROS" },
  "issuedAt": "2026-11-05T09:30:00Z",
  "dueDate": "2026-12-05",
  "totals": { "subtotalHt": 2710000, "discountTotal": 126000, "deliveryFee": 0, "vatTotal": 490960, "totalTtc": 3074960, "currency": "DZD" },
  "vatBreakdown": [{ "vatRate": 1900, "baseHt": 2584000, "vat": 490960 }],
  "paidAmount": 2000000,
  "creditedAmount": 0,
  "balance": 1074960,
  "daysOverdue": 0,
  "lines": [
    { "sku": "HC-X14-CC-1M-BK", "label": "HOCO Câble USB-C vers USB-C 60 W 1 m — Noir · 1 m", "quantity": 20, "unitPriceHt": 35000, "discountHt": 0, "vatRate": 1900, "totalHt": 700000, "totalTtc": 833000 },
    { "sku": "AF-A29-50W-WH", "label": "ACEFAST Chargeur GaN 50 W 2 ports — Blanc", "quantity": 6, "unitPriceHt": 210000, "discountHt": 126000, "vatRate": 1900, "totalHt": 1134000, "totalTtc": 1349460 },
    { "sku": "BF-BH20-BK", "label": "BOROFONE Support voiture magnétique — Noir", "quantity": 15, "unitPriceHt": 50000, "discountHt": 0, "vatRate": 1900, "totalHt": 750000, "totalTtc": 892500 }
  ],
  "allocations": [{ "paymentId": "01930d3e-8f90-7a1b-9c2d-3e4f5a6b7c81", "paymentNumber": "PAY-2026-000077", "method": "BANK_TRANSFER", "amount": 2000000, "allocatedAt": "2026-11-12T10:04:00Z" }],
  "creditNotes": [],
  "legal": { "sellerName": "HOCO Algérie SARL", "rc": "16/00-0987654 B 18", "nif": "001816098765432", "nis": "098716010098765", "ai": "16012098765", "address": "Zone d'activités, Rouiba, Alger" },
  "pdf": { "id": "01930d60-2c3d-7e4f-8a5b-6c7d8e9f0a1b", "url": "https://files.hoco.dz/s/fac-2026-000123.pdf?sig=…", "mimeType": "application/pdf", "size": 61207, "purpose": "GENERATED_DOCUMENT" },
  "version": 3
}

Errors

StatusCodeWhen
403PERMISSION_DENIEDCaller lacks finance.view
404NOT_FOUNDUnknown invoice

Rules & side effects

  1. Read-only. Seller legal identifiers come from the company settings; the values above are placeholders.

Used by BO-44 · BO-39

POST /admin/invoices/{invoiceId}/cancel

admin.invoices.cancel · Permission finance.validate · Apps BO · Release V1 · Requirements FIN-01 · FIN-08

Purpose. Cancels an invoice that must not stand (wrong data, cancelled order). The number is kept and never reused; a credit note for the full amount is issued in V1 (BR-39).

Request

interface CancelInvoiceBody { reason: string; reissue?: boolean; version: number }   // reissue: issue a corrected invoice at once when the order is still invoiceable
{ "reason": "Adresse de facturation erronée — facture réémise.", "reissue": true, "version": 3 }

Response 200 OK — the cancelled Invoice (status CANCELLED, cancelledAt, cancelReason) plus "creditNote": { "id": "01931c50-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "number": "AV-2026-000006", "amountTtc": 3074960 } and, when reissue is true, "reissued": { "id": "01930e20-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "number": "FAC-2026-000131" }.

Errors

StatusCodeWhen
403PERMISSION_DENIEDCaller lacks finance.validate
404NOT_FOUNDUnknown invoice
409INVOICE_HAS_PAYMENTSConfirmed allocations or allocated credit notes exist — deallocate first
409INVOICE_ALREADY_CANCELLEDStatus CANCELLED
409VERSION_CONFLICTStale version
422REASON_REQUIREDEmpty reason

Rules & side effects

  1. UNPAID / OVERDUE → CANCELLED; a CreditNote (AV- number, full amount, origin INVOICE_CANCELLATION) is issued and linked to the invoice; the credit exposure moves back to the order if it is still invoiceable (or disappears when the order is cancelled or returned). reissue issues a new FAC- invoice from the order's current snapshot.
  2. The PDF is regenerated with the mention « ANNULÉE ». Audit STATUS_CHANGE with the reason.
  3. Notifications: the reseller is notified through the credit note (invoice.issued template family; no dedicated event in the notifications matrix); the reissued invoice sends invoice.issued.

Used by BO-44

Payments

GET /admin/payments

admin.payments.list · Permission finance.view · Apps BO · Release V1 · Requirements FIN-03

Purpose. Payments and reseller declarations; the default tab is « À vérifier » (PENDING_VERIFICATION).

Request

QueryTypeValues / defaultNotes
statusPaymentStatus[]repeatable
sourcePaymentSource[]BACK_OFFICE, RESELLER_DECLARATION, DELIVERY_COLLECTION
methodPaymentMethod[]repeatable
resellerIdUuid
unallocatedbooleanConfirmed payments with an unallocated remainder
from / tostring (date)On paidOn
qstringmin 2 charsPayment number, reference, store name, invoice number
sortstringcreatedAt (default), paidOn, amount
order / page / pageSizedesc · 1 · 25 (max 200)

Response 200 OK

{
  "items": [
    {
      "id": "01930d4f-9a01-7b2c-8d3e-4f5a6b7c8d92", "number": "PAY-2026-000079", "status": "PENDING_VERIFICATION", "source": "RESELLER_DECLARATION",
      "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE" },
      "method": "CCP", "amount": 1074960, "paidOn": "2026-11-20", "reference": "CCP 0012345678 clé 45 — reçu n° 88231",
      "proof": { "id": "01930d4f-0b12-7c3d-9e4f-5a6b7c8d9e03", "url": "https://files.hoco.dz/s/pay79-proof.jpg?sig=…", "thumbnailUrl": "https://files.hoco.dz/s/pay79-proof-thumb.jpg?sig=…", "mimeType": "image/jpeg", "size": 734221, "purpose": "PAYMENT_PROOF" },
      "declaredAt": "2026-11-20T15:41:00Z", "declaredBy": { "id": "0192f4b8-7001-7a02-8b03-0c0d0e0f1011", "displayName": "Karim Benali" },
      "allocations": [], "unallocated": 1074960, "createdAt": "2026-11-20T15:41:00Z", "version": 1
    }
  ],
  "total": 6,
  "page": 1,
  "pageSize": 25
}

Errors

StatusCodeWhen
400VALIDATION_FAILEDUnknown enum value
403PERMISSION_DENIEDCaller lacks finance.view

Rules & side effects

  1. Read-only. New declarations and cash collections arrive live with payment.declared.

Used by BO-45 · BO-11 · BO-08

POST /admin/payments

admin.payments.record · Permission finance.create · Apps BO · Release V1 · Requirements FIN-03 · FIN-04

Purpose. Records a payment finance has seen (transfer on the bank statement, cash brought to the depot…), confirmed at once, with its allocation to invoices.

Request

interface RecordPaymentBody {
  resellerId: Uuid;
  method: PaymentMethod;             // must be enabled (payments.enabledMethods); CREDIT is not a payment method here
  amount: Money;                     // > 0
  paidOn: string;                    // YYYY-MM-DD, not in the future
  reference?: string;
  proofFileId?: Uuid;                // PAYMENT_PROOF
  allocations?: { invoiceId: Uuid; amount: Money }[];   // omitted + autoAllocate → oldest due first
  autoAllocate?: boolean;            // default true when allocations is omitted
  note?: string;
}
{
  "resellerId": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a",
  "method": "BANK_TRANSFER",
  "amount": 2000000,
  "paidOn": "2026-11-12",
  "reference": "VIR BNA 2026-11-12 / 004512",
  "allocations": [{ "invoiceId": "01930c2d-7e8f-7a9b-8c0d-1e2f3a4b5c70", "amount": 2000000 }]
}

Response 201 Created — the Payment: number PAY-2026-000077, status CONFIRMED, source BACK_OFFICE, recordedBy the caller, allocations as sent, unallocated 0, version 1.

Errors

StatusCodeWhen
400VALIDATION_FAILEDAmount ≤ 0, future paidOn, CREDIT method, duplicate invoice in allocations
403PERMISSION_DENIEDCaller lacks finance.create
404NOT_FOUNDUnknown reseller, invoice or file
422ALLOCATION_EXCEEDS_PAYMENTΣ allocations > amount
422ALLOCATION_EXCEEDS_BALANCEAn allocation exceeds the invoice balance — details: { invoiceId, balance }
422ALLOCATION_RESELLER_MISMATCHAn invoice belongs to another reseller
422INVOICE_CANCELLEDAllocation to a cancelled invoice
422PAYMENT_METHOD_DISABLEDMethod not in payments.enabledMethods

Rules & side effects

  1. PAY- number; status CONFIRMED; allocations applied in the same transaction; invoice statuses recomputed (here UNPAID → PARTIALLY_PAID, balance 1 074 960); an unallocated remainder is an advance that lowers credit exposure.
  2. Credit exposure recomputed; orders PENDING for CREDIT_OVER_LIMIT are re-checked and move to SUBMITTED when the credit now suffices (they still wait for confirmation).
  3. Audit CREATE on Payment with allocations. Timeline Interaction PAYMENT on the reseller.
  4. Notifications: payment.confirmed → reseller (push + in-app) with the amount and the invoices settled.

Used by BO-45 · BO-44

POST /admin/payments/{paymentId}/verification

admin.payments.verify · Permission finance.validate · Apps BO · Release V1 · Requirements FIN-03

Purpose. Confirms or rejects a reseller's declaration (transfer, CCP, BaridiMob with proof) or a cash collection recorded at delivery.

Request

interface VerifyPaymentBody {
  decision: "CONFIRM" | "REJECT";
  amount?: Money;                    // CONFIRM: the amount actually received if it differs from the declaration (≤ declared)
  allocations?: { invoiceId: Uuid; amount: Money }[];   // CONFIRM; default: oldest due invoices first
  reasonCode?: string;               // REJECT: PAYMENT_REJECT_REASON (PROOF_UNREADABLE, AMOUNT_MISMATCH, NOT_RECEIVED, DUPLICATE, OTHER…)
  reason?: string;                   // REJECT: required for OTHER; shown to the reseller
  note?: string;
  version: number;
}
{ "decision": "CONFIRM", "allocations": [{ "invoiceId": "01930c2d-7e8f-7a9b-8c0d-1e2f3a4b5c70", "amount": 1074960 }], "version": 1 }

Response 200 OK — the Payment with status CONFIRMED, verifiedAt, verifiedBy, allocations; invoice FAC-2026-000123 becomes PAID (balance 0).

Errors

StatusCodeWhen
400VALIDATION_FAILEDreasonCode on a confirmation, amount above the declared amount
403PERMISSION_DENIEDCaller lacks finance.validate
404NOT_FOUNDUnknown payment or invoice
409PAYMENT_NOT_PENDINGStatus is not PENDING_VERIFICATION
409VERSION_CONFLICTStale version
422REASON_REQUIREDRejection without reasonCode, or OTHER without reason
422ALLOCATION_EXCEEDS_PAYMENT · ALLOCATION_EXCEEDS_BALANCE · ALLOCATION_RESELLER_MISMATCH · INVOICE_CANCELLEDAs for recording

Rules & side effects

  1. PENDING_VERIFICATION → CONFIRMED (allocations applied, invoices recomputed, exposure lowered — the declaration did not change available credit before) or → REJECTED (proof kept).
  2. Audit VALIDATE or REJECT; timeline PAYMENT entry on confirmation.
  3. Notifications: payment.confirmed or payment.rejected (with the reason) → reseller (push + in-app).

Used by BO-45

PUT /admin/payments/{paymentId}/allocations

admin.payments.allocate · Permission finance.update · Apps BO · Release V1 · Requirements FIN-04

Purpose. Replaces the allocation of a confirmed payment across invoices (reconciliation, correcting a mistake — cahier §4.7 « Paiements partiels et rapprochement »).

Request

interface AllocatePaymentBody {
  allocations: { invoiceId: Uuid; amount: Money }[];   // the full new set (empty = everything unallocated)
  reason: string;                                      // required: a confirmed payment is never edited silently
  version: number;
}
{
  "allocations": [
    { "invoiceId": "01930c2e-1a2b-7c3d-9e4f-5a6b7c8d9e01", "amount": 1023400 },
    { "invoiceId": "01930c2d-7e8f-7a9b-8c0d-1e2f3a4b5c70", "amount": 976600 }
  ],
  "reason": "Le client précise que le virement couvre d'abord FAC-2026-000122.",
  "version": 1
}

Response 200 OK — the Payment with its new allocations and unallocated, plus affectedInvoices: [{ id, number, status, balance }].

Errors

StatusCodeWhen
403PERMISSION_DENIEDCaller lacks finance.update
404NOT_FOUNDUnknown payment or invoice
409PAYMENT_NOT_CONFIRMEDPayment PENDING_VERIFICATION or REJECTED
409VERSION_CONFLICTStale version
422REASON_REQUIREDEmpty reason
422ALLOCATION_EXCEEDS_PAYMENT · ALLOCATION_EXCEEDS_BALANCE · ALLOCATION_RESELLER_MISMATCH · INVOICE_CANCELLEDBalance is checked after releasing this payment's current allocations

Rules & side effects

  1. All-or-nothing; invoices statuses recomputed (a PAID invoice can go back to PARTIALLY_PAID, UNPAID or OVERDUE); credit exposure recomputed.
  2. Audit UPDATE on Payment with the before/after allocations and the reason. No notification to the reseller.

Used by BO-45 · BO-44

Credit accounts

GET /admin/credit-accounts

admin.credit.list · Permission finance.view · Apps BO · Release V1 · Requirements FIN-05 · Added in contract 1.1.0 (SRV-41)

Purpose. The table of BO-46: every credit account with its live exposure — the resellers with an account row (limit set by finance) and every approved customer (default limit credit.defaultLimit, version: 0 until a first change).

Request

QueryTypeValues / defaultNotes
page, pageSizeint1, 25 (max 200)Offset pagination
qstring≥ 2 charactersReseller number, store name, owner name or phone
policyCreditPolicy[]repeatable: INHERIT | BLOCK | REQUIRE_APPROVALThe account's own policy (INHERIT includes resellers without an account row)
blockedbooleanCredit blocked by finance
overLimitbooleanused above limit
hasOverduebooleanAt least one OVERDUE invoice
wilayaCodestring[]repeatableStore address
sortstringstoreName (default) | limit | used | available | overdue | updatedAtoverdue sorts by the overdue amount
orderasc | descasc

Response 200 OK — { items, total, page, pageSize }; each item is a CreditAccount without history, plus overLimit:

{
  "items": [
    {
      "resellerId": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a",
      "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE", "customerType": "DEMI_GROS" },
      "limit": 5000000, "termsDays": 30, "blocked": false, "policy": "INHERIT", "effectivePolicy": "BLOCK",
      "used": 6074600, "available": 0, "overLimit": true,
      "exposure": { "openInvoices": 2100000, "uninvoicedCreditOrders": { "count": 1, "amount": 3974600 }, "unallocatedCredit": 0 },
      "overdue": { "amount": 0, "invoices": 0, "oldestDaysOverdue": 0 },
      "updatedAt": "2026-10-02T09:00:00Z", "version": 4
    }
  ],
  "total": 1, "page": 1, "pageSize": 25
}

Errors

StatusCodeWhen
400VALIDATION_FAILEDUnknown sort, q shorter than 2 characters
403PERMISSION_DENIEDCaller lacks finance.view

Rules & side effects

  1. Read-only. The exposure is computed live (formula above), never stored: a filter on overLimit / hasOverdue or a sort other than storeName computes it for every matching account before paginating.

Used by BO-46

GET /admin/credit-accounts/{resellerId}

admin.credit.get · Permission finance.view · Apps BO · Release V1 · Requirements FIN-05

Purpose. A reseller's credit account with its exposure, overdue amounts and limit history.

Request

PathTypeNotes
resellerIdUuidEvery reseller has an account (limit credit.defaultLimit until set)

Response 200 OK — CreditAccount

{
  "resellerId": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a",
  "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE", "customerType": "DEMI_GROS" },
  "limit": 5000000,
  "termsDays": 30,
  "blocked": false,
  "policy": "INHERIT",
  "effectivePolicy": "BLOCK",
  "used": 6074600,
  "available": 0,
  "exposure": { "openInvoices": 2100000, "uninvoicedCreditOrders": { "count": 1, "amount": 3974600 }, "unallocatedCredit": 0 },
  "overdue": { "amount": 0, "invoices": 0, "oldestDaysOverdue": 0 },
  "history": [
    { "field": "LIMIT", "before": 3000000, "after": 5000000, "reason": "Historique de paiement régulier sur 4 mois.", "changedBy": { "id": "01929eaa-7b8c-7d9e-8fa0-1b2c3d4e5f60", "displayName": "Rachid Ouali" }, "changedAt": "2026-10-02T09:00:00Z" }
  ],
  "updatedAt": "2026-10-02T09:00:00Z",
  "version": 4
}

Errors

StatusCodeWhen
403PERMISSION_DENIEDCaller lacks finance.view
404NOT_FOUNDUnknown reseller

Rules & side effects

  1. Read-only; exposure is computed live with the formula above.

Used by BO-46 · BO-11 · BO-39

PATCH /admin/credit-accounts/{resellerId}

admin.credit.update · Permission finance.administer · Apps BO · Release V1 · Requirements FIN-05 · FIN-06

Purpose. Sets the credit limit, payment terms, policy, or blocks / unblocks the account — every change historised with its reason.

Request

interface UpdateCreditAccountBody {
  limit?: Money;                     // ≥ 0
  termsDays?: number;                // 0–180; applies to invoices issued from now on
  blocked?: boolean;
  policy?: "INHERIT" | "BLOCK" | "REQUIRE_APPROVAL";
  reason: string;                    // required, 3–500
  version: number;
}
{ "limit": 7000000, "reason": "Plafond relevé pour la commande CMD-2026-000158 (accord direction).", "version": 4 }

Response 200 OK — the updated CreditAccount (limit 7 000 000, used 6 074 600, available 925 400 — and the credit control of CMD-2026-000158, which excludes the order itself, becomes OK: 7 000 000 − 2 100 000 = 4 900 000 ≥ 3 974 600; a new history entry; version 5) plus warnings — e.g. [{ "code": "LIMIT_BELOW_USED", "details": { "used": 6074600 } }] when a new limit is below used (allowed: CREDIT orders are then refused).

Errors

StatusCodeWhen
400VALIDATION_FAILEDNegative limit, terms out of range, empty body
403PERMISSION_DENIEDCaller lacks finance.administer
404NOT_FOUNDUnknown reseller
409VERSION_CONFLICTStale version
422REASON_REQUIREDEmpty reason

Rules & side effects

  1. One CreditLimitChange row per changed field (before, after, reason, actor); audit UPDATE. Detailed credit (automatic blocking on overdue, approval workflows) is V2 (FIN-06); in V1 blocking is manual.
  2. blocked: true refuses every CREDIT checkout and makes order controls BLOCKED; orders PENDING for CREDIT_OVER_LIMIT are re-checked after a limit increase.
  3. Notifications: credit.limit_changed → reseller (in-app) on a limit change; credit.blocked → reseller (push + in-app) and the assigned delegate when blocked. The reseller app's finance screen reflects the new available credit at once.

Used by BO-46 · BO-11

V2 — overdue and accounting

GET /admin/finance/overdue

admin.finance.overdue · Permission finance.view · Apps BO · Release V2 · Requirements FIN-07

V2

Contract reserved; not built in V1 (cahier §14.2). In V1 overdue invoices are read with admin.invoices.list ?status=OVERDUE.

Purpose. Overdue invoices grouped by reseller with aging buckets and the history of automatic reminders (PaymentReminder).

Request — filters agingBucket (1-30, 31-60, 61-90, 90+), resellerId, delegateId, wilayaCode, minAmount, reminderStatus; sort overdueAmount (default) | oldestDaysOverdue; page / pageSize.

Response 200 OK

{
  "items": [
    {
      "reseller": { "id": "0192f501-6b7c-7d8e-9f0a-1b2c3d4e5f60", "number": "REV-000188", "storeName": "Mobile Center", "ownerName": "Samir Haddad", "phone": "+213770998877", "wilayaCode": "16", "communeName": "Bir Mourad Raïs", "status": "SUSPENDED" },
      "overdueAmount": 1890000,
      "invoices": [{ "id": "01930a01-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "number": "FAC-2026-000088", "dueDate": "2026-09-30", "balance": 1890000, "daysOverdue": 36 }],
      "aging": { "1-30": 0, "31-60": 1890000, "61-90": 0, "90+": 0 },
      "reminders": [{ "at": "2026-10-07T08:00:00Z", "channel": "SMS", "level": 1 }, { "at": "2026-10-21T08:00:00Z", "channel": "WHATSAPP", "level": 2 }]
    }
  ],
  "total": 17,
  "page": 1,
  "pageSize": 25
}

Errors

StatusCodeWhen
403PERMISSION_DENIEDCaller lacks finance.view

Rules & side effects

  1. Read-only. V2 reminders are sent by a daily job per reminder rules (FIN-07).

Used by BO-47

POST /admin/finance/exports

admin.finance.export · Permission finance.export · Apps BO · Release V2 · Requirements FIN-09

V2

Contract reserved; not built in V1 (cahier §4.7 « Exports comptables », §11 ERP).

Purpose. Accounting exports for the client's accounting tool: sales journal, payments journal, credit notes and aged balance.

Request

interface FinanceExportBody {
  kind: "SALES_JOURNAL" | "PAYMENTS_JOURNAL" | "CREDIT_NOTES" | "AGED_BALANCE";
  from: string; to: string;          // AGED_BALANCE: `to` is the balance date
  format: "XLSX" | "CSV";
  layout?: "GENERIC";                // accounting-tool layouts added when the ERP is known (INT-06)
}
{ "kind": "SALES_JOURNAL", "from": "2027-01-01", "to": "2027-01-31", "format": "CSV" }

Response 202 Accepted — an ExportJob with "source": { "kind": "REPORT", "key": "finance.sales_journal" }.

Errors

StatusCodeWhen
400VALIDATION_FAILEDUnknown kind, to before from
403PERMISSION_DENIEDCaller lacks finance.export
422REPORT_RANGE_TOO_LARGEMore than 366 days

Rules & side effects

  1. Audit EXPORT; notification and SSE export.ready to the requester.

Used by BO-48

On this page