Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ency07/B2B-import/llms.txt

Use this file to discover all available pages before exploring further.

The Quotes module handles the creation and lifecycle management of commercial proposals in B2B Import ERP. Quotes are always linked to a client and optionally to an open Requirement. They support multi-line item composition with per-line discounts and taxes, automatic header total recalculation, PDF export with tenant branding, and a full versioning system when a quote is rejected and needs to be revised. Once approved, a quote unlocks the transition of its linked Requirement to OT_GENERADA (Work Order Generated).

Quote Lifecycle

Quote states are enforced by the validate_quote_state_transitions trigger. States marked as final cannot be changed once reached.
BORRADOR

   ├──► CANCELADA  (final — only from BORRADOR)


EN_REVISION ──── triggers automatic approval routing


ENVIADA

   ├──► APROBADA   (final)
   ├──► RECHAZADA  (final — requires reject_reason ≥ 10 chars)
   └──► VENCIDA
StateDescription
BORRADORDraft; items can be freely added or modified
EN_REVISIONSubmitted for internal review; triggers the approval workflow
ENVIADASent to the client; awaiting client decision
APROBADAApproved — final state; unlocks work order creation
RECHAZADARejected — final state; a new version can be created
VENCIDAExpired; typically set when a new version supersedes this one
CANCELADACancelled — final state; only allowed from BORRADOR
APROBADA, RECHAZADA, and CANCELADA are terminal states. The trigger raises an exception if any further status change is attempted on a quote in one of these states.

QuoteRow Interface

The following interface is exported from src/app/actions/quotes.ts:
interface QuoteRow {
  id: string;
  quote_code: string;            // Auto-generated: "COT-000001"
  client_id: string;
  requirement_id: string | null;
  assigned_user_id: string | null;
  valid_until: string;           // ISO date string
  subtotal: number;              // Auto-calculated from line items
  total_amount: number;          // subtotal - discount_amount + tax_amount
  status:
    | "BORRADOR"
    | "EN_REVISION"
    | "ENVIADA"
    | "APROBADA"
    | "RECHAZADA"
    | "VENCIDA";
  created_at: string;
  // Joined relation
  client: { legal_name: string } | null;
}
quote_code is auto-generated as COT-XXXXXX by the handle_quote_versioning trigger using tenant_sequences. The code is immutable after creation.

Quote Items

Line items are stored in the quote_items table and linked to a quote via quote_id. The trg_calculate_quote_item_totals trigger computes line amounts automatically on every insert or update:
subtotal_linea = quantity × unit_price
tax_amount     = (subtotal_linea - discount_amount) × tax_percent / 100
line_total     = subtotal_linea - discount_amount + tax_amount
After any change to quote_items, trg_update_quote_totals recalculates the quote header:
quotes.subtotal      = SUM(quote_items.line_total)
quotes.total_amount  = subtotal - discount_amount + tax_amount
FieldTypeDescription
quote_iduuidFK to quotes.id
item_orderintegerDisplay order of the line
item_typeenumMATERIAL · SERVICIO · EQUIPO · MANO_OBRA · OTRO
descriptiontextLine item description (required)
quantitynumeric(18,4)Quantity > 0
unitvarchar(50)Unit of measure (e.g. UNIDAD, ML, HR)
unit_pricenumeric(18,2)Unit price ≥ 0
discount_amountnumeric(18,2)Per-line monetary discount
tax_percentnumeric(5,2)Tax rate as a percentage (e.g. 19.00 for 19% IVA)
line_totalnumeric(18,2)Auto-calculated net line total

Server Actions

getQuotes

getQuotes(tenantCode?)Returns all non-deleted quotes for the resolved tenant with joined client.legal_name, ordered newest first.
const quotes = await getQuotes("acme");
// Returns QuoteRow[]

createQuote

createQuote(tenantCode, quoteData)Creates a new quote in BORRADOR status. quote_code and version are set by the database trigger. Provide requirementId to link the quote to an existing requirement.
const quote = await createQuote("acme", {
  clientId: "uuid-client",
  requirementId: "uuid-req",  // optional
  validUntil: "2026-09-30",
});

getQuoteItems

getQuoteItems(quoteId)Returns all line items for a specific quote ordered by item_order ascending.
const items = await getQuoteItems("uuid-quote");

addQuoteItem

addQuoteItem(tenantCode, itemData)Inserts a line item. The trigger immediately calculates tax_amount and line_total, then recalculates the quote header totals.
await addQuoteItem("acme", {
  quoteId: "uuid-quote",
  description: "Extractor centrífugo 3HP",
  itemType: "EQUIPO",
  quantity: 2,
  unitPrice: 1850000,
  discountAmount: 0,
  taxPercent: 19,
  itemOrder: 1,
});

PDF Export

Quotes can be exported to PDF using jsPDF with full tenant branding applied in the document header (logo, tenant name, contact information) and footer (terms, validity date, signature area). The export is triggered from the quote detail page and generates a downloadable PDF client-side.
A quote submitted for approval (EN_REVISION) must have at least one line item. The state transition trigger raises an exception if quote_items is empty when attempting to move from BORRADOR to EN_REVISION.

Versioning

When a quote is rejected (RECHAZADA), a new version can be created with the same quote_code but an incremented version number. The handle_quote_versioning trigger:
  1. Detects that a quote_code already exists in the tenant.
  2. Sets NEW.version = max_version + 1.
  3. Marks the previous ENVIADA or EN_REVISION version as VENCIDA automatically.
This preserves the complete negotiation history under one quote code while making it clear which version is the active proposal.

Approval Flow

When a quote transitions to EN_REVISION, the route_quote_approvals trigger fires automatically and:
  1. Looks up an active approval_rules rule matching the quote’s total_amount range and entity_type = 'QUOTE'.
  2. If a matching rule exists with a flow_id, it creates an approval_requests record and instantiates approval_request_steps for each step in the flow.
  3. If no rule is found, the quote is automatically escalated to the GERENTE_GENERAL role.
  4. If the rule has flow_id = NULL, the quote is auto-approved immediately.
While a quote has a PENDIENTE or EN_PROCESO approval request, its items and header are locked. Any attempt to modify the quote raises an exception from check_quote_approval_lock.
See the Approvals module for the full approval workflow and multi-step configuration.

RBAC — Role-Based Access

PermissionRoles
quotes.createADMIN_EMPRESA, GERENTE_GENERAL, DIRECTOR_COMERCIAL, EJECUTIVO_COMERCIAL
quotes.submit (→ EN_REVISION, ENVIADA)EJECUTIVO_COMERCIAL, DIRECTOR_COMERCIAL
quotes.approve (→ APROBADA, RECHAZADA)GERENTE_GENERAL, DIRECTOR_COMERCIAL
quotes.viewADMIN_EMPRESA, GERENTE_GENERAL, DIRECTOR_COMERCIAL, EJECUTIVO_COMERCIAL, DIRECTOR_OPERACIONES, AUDITOR, CLIENTE
quotes.cancelEJECUTIVO_COMERCIAL, DIRECTOR_COMERCIAL, GERENTE_GENERAL

Dashboard

Navigate to the Quotes dashboard for your tenant:
/dashboard/quotes?tenant=<code>

Build docs developers (and LLMs) love