In Credith, every sale is recorded as a factura (bill) generated through a single transactionalDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/RoyGeova07/Credith/llms.txt
Use this file to discover all available pages before exploring further.
POST /api/bills request. The server validates the cashier, the active CAI (government-authorized invoice number), store inventory, and — for installment sales — the client’s existing payment plan, all inside a single database transaction. A successful call creates the bill record, one or more bill_details line items, deducts inventory from stores_inventories, increments the CAI range counter, and creates a bill_payment_plan with individual monthly_payments if the sale is on credit.
Prerequisites and Sale Flow
Ensure an active CAI with a valid range exists
Each store must have exactly one active CAI (If the range is exhausted the server returns
isActive = true in cd.cais) with an active cai_range whose currentNumber has not yet reached maxRange. The bill controller derives the billNumberFinal string from the store number, machine number, CAI document type, and the next sequential bill number:406 El rango de CAI se ha agotado.Ensure the cashier user is associated with a checkout machine
The
userId in the request body must belong to a user with a checkoutMachineId set. Without an assigned checkout machine the server returns 404 Usuario no tiene asignado una caja de facturacion. The machine’s machineNumber is stamped directly on the bill record for audit purposes.Ensure products are in stock in the store's inventory
Every item in
details must appear in cd.stores_inventories for the given storeId with inStock >= quantity. The server checks each line item and throws 406 if stock is insufficient. A successful transaction deducts the sold quantity from inStock atomically.Identify or create the client (for installment sales)
For
paymentType: "INSTALLMENT" the customer.clientId field is required and must reference a record in cd.clients. The controller checks whether the client already has an active payment plan (status of PENDING or OVERDUE) — if so it returns 400 El cliente ya tiene un plan de pago activo. For cash sales, customer only needs a customerName string; no clientId is required.Request Body Reference
| Field | Type | Required | Notes |
|---|---|---|---|
userId | UUID | ✅ | Must match a user with an assigned checkout machine and store |
storeId | UUID | ✅ | Must match the user’s own store_id; server validates this |
limitDate | date string | ✅ | Invoice due date (YYYY-MM-DD) |
paymentType | "CASH" | "INSTALLMENT" | ✅ | Determines which payment plan is created |
discountPercentage | integer | — | Bill-level discount percentage |
discountAmount | number | — | Bill-level discount amount (Lempiras) |
exonerated | number | — | Exonerated tax amount |
exempt | number | — | Exempt amount |
details | array | ✅ | At least one item required |
details[].productId | UUID | ✅ | Must be a valid UUID |
details[].productName | string | ✅ | Informational; stamped on validation errors |
details[].quantity | integer | ✅ | Units to sell; subtracted from inStock |
details[].sellPrice | number | ✅ | Price per unit at time of sale |
details[].discountPercentage | number | — | Line-item discount percentage |
details[].discountAmount | number | — | Line-item discount amount |
details[].total | number | ✅ | Must equal quantity × sellPrice × (1 - discountPercentage/100) |
customer.customerName | string | ✅ | Printed on the bill |
customer.customerPhone | string | — | Printed on the bill |
customer.customerAddress | string | — | Printed on the bill |
customer.clientId | UUID | ✅ (INSTALLMENT only) | Links the payment plan to a client record |
paymentData.payment | number | — | Initial payment or down payment amount |
paymentData.startingDate | date string | ✅ (INSTALLMENT) | First installment reference date |
paymentData.monthsToPay | integer | ✅ (INSTALLMENT) | Number of monthly installments |
paymentData.paymentDay | integer | ✅ (INSTALLMENT) | Day of month each installment falls due |
paymentData.interestRate | number | — | Monthly interest rate (default 0) |
ISV (Honduran sales tax) at 15% is automatically computed by the server as
discountedSubtotal × 0.15 and stored in isv15Amount. The final total the client is charged equals discountedSubtotal + isv15Amount. You do not need to pre-calculate tax in your request.Example: CASH Sale
201 response returns the full bill object including billId, billNumberFinal, total, isv15Amount, and the embedded plan payment record.
Example: INSTALLMENT Sale
totalToPay (bill total after taxes, minus the down payment payment), creates a bill_payment_plans record with status: "PENDING", and bulk-inserts 6 monthly_payments rows — one per month starting from startingDate, each due on day 15. The last installment absorbs any rounding difference.
What Happens Inside the Transaction
EveryPOST /api/bills call runs inside a single Sequelize managed transaction, which means all of the following succeed or none of them persist:
Bill record created
A
cd.bills row is inserted with the denormalized company, cashier, and customer data snapshotted at sale time.Inventory deducted
Each
cd.stores_inventories row for the sold products has its inStock column decremented by the sold quantity.CAI counter incremented
The active
cai_range.currentNumber is incremented under a row-level LOCK.UPDATE to prevent duplicate bill numbers under concurrent load.Payment plan created
A
bill_payment_plans row is created (CASH = immediately PAYED, INSTALLMENT = PENDING) along with individual monthly_payments rows.