Skip to main content

Sales Order Process & Viber Send

The Blu Coffee sales pipeline runs on Odoo's standard sale.order model, extended with the operational guardrails the team needs day-to-day:

  • A required Mode of Payment so every quote already carries the receivable account it'll post against.
  • Validator-only Confirm — the green button is replaced with a custom action that gates discount-bearing quotes behind a special-pricing approver group.
  • SLA tracking — region-aware target days and a live On Time / At Risk / Delayed badge on every confirmed order.
  • Send to Viber — fire the quote as a Viber chat message with a "View Quotation" button, in addition to email.
  • Force Close — when a sale won't be invoiced, an authorized user closes the SO with a recorded reason instead of cancelling.
  • Order tracking on the lists — every order shows how much was delivered, how much was collected, and its invoice, BIR SI, BIR CR and trip ticket numbers, all searchable from the search box.
  • Pick-only addresses — the Customer, Invoice Address and Delivery Address fields only pick existing records, and the two address fields only offer the customer's own addresses.

For the whole journey from client profile to collection, see Order-to-Cash.

The plumbing lives in two places:

ModuleProvides
bc17/blu_crm_sales/Mode of Payment, custom Confirm, Force Close wizard, Delivery / Collection Status, Invoice / SI / CR No. columns, list-view tweaks, custom TikTok print, sale-order-line On-Hand + Total Weight columns
common/sla_sales_tracking/SLA fields, working-day math, region-based target lookup
common/blu_8x8/ (blu_viber_sale_order.py)Send to Viber button on the SO form

Lifecycle​

Cancel is non-destructive (no inventory or accounting impact) and only available before invoicing. Force Close is the safety valve for confirmed orders that have already moved inventory but won't be invoiced — see Force Close below.

Quotation list​

Sales → Orders → Quotations opens the quotation list, grouped by Branch (the order's warehouse) and then Salesperson. Remove the Branch > Salesperson facet in the search box to see a flat list. The SLA columns come from sla_sales_tracking; the delivery, collection and document-number columns are described in Tracking an order from the list:

Quotation list

Once confirmed, Sales → Orders → Orders shows the live SO list, grouped the same way. The SLA Status column is shown by default; Expected Delivery and Days Remaining are optional columns you can toggle on:

Sales Order list

Tracking an order from the list​

You can follow an order from the list without opening it. These columns are on the Quotations, Orders and SQ & SO Dates List screens. They are all shown by default, and you can hide any of them with the column toggle (⇌) at the right end of the header row.

ColumnWhat it tells you
Invoice StatusStandard Odoo: To Invoice, Fully Invoiced, Upselling, Nothing to Invoice
Delivery StatusHow much of the order has gone out: Not Delivered (red), Partially Delivered (orange), Fully Delivered (green). Nothing to Deliver (grey) means a confirmed order with nothing to ship, such as a service-only order. Quotations leave it blank.
Collection StatusFrom the order's posted invoices: No Invoice, Not Collected, In Payment (a payment is recorded but not yet matched to the invoice), Partially Collected, Fully Collected, Reversed
Invoice No.Odoo invoice number(s), e.g. INV-2026-09-1241. Drafts and cancelled invoices are left out.
SI No.The BIR Sales Invoice number Accounting keys on the invoice (Official Invoice #). Blank until it is encoded.
CR No.The BIR Collection Receipt number keyed on the payment (Customer Receipt) of every payment applied to the order's invoices. Blank until such a payment is matched to the invoice.
Trip TicketThe trip(s) that carried the order's deliveries. See Trip Ticket.
Actually DeliveredHidden by default. The date the client received the goods, from the post-validated trip ticket.

When an order was invoiced, collected or delivered in more than one go, the number columns list every number, separated by commas. One CR can show on several orders when one receipt paid several invoices.

Collected but not delivered

An order can be Fully Collected while still Not Delivered. That means the client has paid and the goods have not gone out. Use the Collected but Undelivered filter to list these orders.

A blank CR on a Fully Collected order

Credit notes and bank-statement matches carry no CR number. If an order is Fully Collected and the CR No. is blank, the payment was usually posted without its Customer Receipt number. Ask Accounting to add it on the payment, and the order picks it up.

Filters and grouping​

The Filters menu has a block for collection (Not Collected, In Payment (unreconciled), Partially Collected, Fully Collected, No Invoice) and one for delivery (Not Delivered, Partially Delivered, Fully Delivered, Collected but Undelivered). Group By adds Branch, Delivery Status and Collection Status.

Searching by invoice, SI or CR number​

Type or paste an order number, an INV- number, an SI number or a CR number in the search box and press Enter. The default search, labelled Order / Invoice / SI / CR, checks all of them, plus the customer reference and the customer. To search one kind of number only, pick its line from the dropdown:

Search by invoice, SI or CR number

The form at a glance​

Open any draft and you'll see the customised header:

Draft quotation form

ElementWhere it comes fromNotes
Send to Viber buttoncommon/blu_8x8Posts the quote as a Viber buttoned message to partner_id.phone. See Send to Viber below.
Send by Email buttonStandard OdooExposed regardless of permission group (recent change) — recipient is partner_id.email.
Confirm buttonbc17/blu_crm_sales (rts_action_confirm)Replaces Odoo's default. Visible only to validator + ecomm groups. If any line has discount > 0, an extra special-pricing group check fires.
Cancel buttonStandard, gated with a confirmation dialog ("Click OK button to confirm Cancel action")
Mode of Payment fieldbc17/blu_crm_sales (account_id)Required. Domain limited to receivable / cash accounts flagged sales_mop_account=True.
Payment Terms fieldStandard, but marked required="1" by bc17
Customer fieldbc17/blu_base + bc17/blu_crm_salesPick only. You cannot create or open a contact from here, and branch addresses and contact persons are not offered. See Customers & Addresses.
Invoice Address / Delivery Addressbc17/blu_crm_salesPick only. They offer only the selected customer and its own addresses. Empty until a customer is chosen.
Warehouse (Other Info tab)bc17/blu_crm_salesFilled in from your default warehouse. The order's delivery (DR) is taken from this warehouse's stock when the order is confirmed.
Order line On-Hand columnbc17/blu_crm_sales (qty_available)Live stock display for the line's product, related to product.qty_available.
Order line Total Weight columnbc17/blu_crm_sales (total_weight)Computed = product_uom_qty × product.weight.

Only the Customer's own addresses can be picked as Invoice or Delivery Address. If the branch you are delivering to is not in the list, add it to the customer first, in Contacts → the customer → Contacts & Addresses (how):

Address pickers offer only the customer's own addresses

The DR comes from the order's warehouse

Since 10/08/2026, confirming an order takes its delivery (DR) from the stock of the order's own warehouse. Before that, the DR was moved to the confirming user's default warehouse, so a Mandaluyong user confirming a Pampanga order made the DR deduct Mandaluyong stock. Check the Warehouse on the order before confirming. The only exception is e-Comm: an e-Comm user still ships from their e-Comm stock.

Mode of Payment​

Each quote must declare where the receivable will post. The picker is restricted to accounts tagged sales_mop_account=True so reps can't accidentally select an arbitrary GL account:

  • 112101 ACCOUNTS RECEIVABLE — default cash receivable
  • 112105 ACCOUNTS RECEIVABLE - TIKTOK
  • 112106 ACCOUNTS RECEIVABLE - GCASH
  • 112108 ACCOUNTS RECEIVABLE - PAYPAL
  • 112109 ACCOUNTS RECEIVABLE - XENDIT
  • 112110 ACCOUNTS RECEIVABLE - SHOPEE
  • …and the other channel receivables (PayMaya, Lazada, Web, and the PNB / BDO / Global Payments credit-card accounts)

The field is tracking=True so changes are logged in chatter.

SLA tracking​

Every confirmed SO gets a Service Level Agreement group inside the form, comparing the order date against the customer's region delivery target:

Confirmed SO with SLA section

Fields​

FieldComputed from
Regionpartner_id.region_id.region_name (related, stored)
Target Dayspartner_id.region_id.delivery_time (working days), with fallbacks: NCR (region code 130000000) → 4 days; everywhere else → 12 days
Expected Deliverydate_order + target_days, skipping weekends and entries in the sla.holiday model
Actual DeliveryDate the last outgoing picking went to state done
SLA StatusOne of: On Time, At Risk, Delayed, Delivered On Time, Delivered Late, N/A
Days RemainingWorking days from today to Expected Delivery (negative = overdue)
Variance (Days)Working days between Actual and Expected (positive = early, negative = late)

Status logic​

Search filters​

The Quotations / Orders search bar gets five state filters and two group-bys courtesy of sla_sales_tracking:

FilterWhat it shows
On TimeConfirmed SOs still within their SLA window with >2 days to spare
At RiskConfirmed SOs with ≤2 working days remaining
DelayedConfirmed SOs past their Expected Delivery with no completed picking
Delivered On TimeClosed orders that hit the window
Delivered LateClosed orders that missed it

Plus group-bys for SLA Status and Region — useful for weekly sales-ops reviews.

SLA is region-driven

The numbers come from res.region.delivery_time. To change the target for a whole region, update it there — every existing SO for partners in that region recomputes on its next save. There's no need to touch SOs one-by-one.

Current settings: NCR / Region III / CAR / Region VII = 3 working days, all other regions = 5 working days.

For the full configuration walkthrough — region SLA editing, holiday management, customer-region prerequisites, and the hardcoded fallbacks — see Inventory → Delivery Orders SLA → Configuration.

…and on every Delivery Order​

The same SLA badge now appears on outgoing pickings in Inventory → Operations → Transfers, so warehouse and dispatch staff can spot at-risk deliveries without opening the underlying SO. The picking-side fields are stored related copies of sale_id.sla_*, so there's only one source of truth.

Delivery Orders list with SLA Status badge

See Inventory → Delivery Orders SLA for the column toggles, filters, and dispatcher workflow.

Send to Viber​

The Send to Viber button lets a salesperson push the quote straight to the customer's Viber number. It reuses the 8x8 messaging sub-account already configured for marketing campaigns — there's no separate setup.

What it does​

  1. Reads partner_id.phone, validates and sanitises to E.164 (+639xxxxxxxxx).
  2. Generates the standard Odoo customer-portal preview URL for the quote (action_preview_sale_order()['url']).
  3. POSTs a Viber buttons payload to https://chatapps.8x8.com/api/v1/subaccounts/<messaging_sub_account>/messages:
    • Greeting personalised with the customer's name.
    • Salesperson's signature with their own phone number.
    • View Quotation#: <SO name> button → portal preview URL.
  4. On 200 OK, logs the response UMID as a blu.viber.campaign.partners row (so delivery webhooks update its state alongside marketing-campaign messages) and posts a chatter line.

What the customer receives​

The customer's journey is two taps — Viber message arrives on their phone, they tap the button, and they're on Odoo's standard public portal where they can review, accept, sign, or reject:

Viber quotation message
1. Viber message arrives, branded from Blu Coffee, signed by the salesperson
Quotation portal preview
2. Tap View Quotation# → Odoo portal with Accept & Sign / Feedback / Reject

The portal page is fully self-serve — the customer can:

  • Accept & Sign electronically (top-left on mobile, bottom-centre on desktop) to confirm the quotation. The SO state flips to Sale Order on Odoo's side immediately.
  • View Details to drill into product lines or download the PDF.
  • Feedback to add a comment that lands in the SO's chatter.
  • Reject to decline (the SO state moves to Cancelled).

They don't need an Odoo login — the portal URL carries the access token from the chat-button payload.

What the SO records​

Right after the click, the SO's chatter logs the send so the sales manager can see at a glance that the quote went out:

Chatter after Send to Viber

The chatter entry includes the Viber Message ID (UMID) — same identifier the marketing-campaign delivery webhook uses. Once 8x8 fires the corresponding outbound_message_status_changed event, the matching blu.viber.campaign.partners row's state updates to queued → sent → delivered → read.

Validation hard-fails the send

If partner_id.phone is missing or unparseable, the action raises <Customer> has no valid Viber/Phone Number and the SO state is unchanged. No partial send happens — clean up the contact first, then retry. Tip: Viber numbers go in the Phone field, not Mobile.

Confirm — and the special-pricing gate​

The standard green Confirm button is replaced with rts_action_confirm(). The custom action does three things:

  1. Special-pricing check — if any line has discount > 0, the user must be in bc17.blu_sales_order_special_pricing_validators_group, otherwise it raises "You are not allowed to confirm quotations with special pricing."
  2. Confirm visibility — the button itself is only rendered for users in blu_sales_order_validators_group, the special-pricing validators, or the ecomm admin/user groups. Rank-and-file salespeople can build quotes but not confirm them.
  3. Warehouse routing — after action_confirm(), every open delivery (draft / waiting / ready) is sourced from the stock location of the order's warehouse. If the confirming user's default warehouse is an e-Comm warehouse, the e-Comm stock is used instead. If the location changes, any stock already reserved is released and reserved again from the new location.

A pre-confirm dialog ("Click OK button to confirm your action") fires on every press to prevent fat-finger commits.

Sales orders cannot be deleted by anyone except Blu Coffee Administrators, so the order numbering has no gaps. Cancel a quotation instead of deleting it.

Force Close — when a sale won't be invoiced​

Sometimes inventory has shipped and a final invoice still won't happen — a swap, a goodwill write-off, a discontinued customer. Cancelling isn't right (the moves happened), so the team uses Force Close:

Force Close wizard

The Close this Order button (with the ⚠️ icon) is only visible when invoice_status == 'to invoice' — i.e. there are lines still expecting an invoice. Clicking it opens a wizard that requires a reason before proceeding.

When confirmed, the action:

  • Sets invoice_status = 'no' on the SO and on every line whose status was 'to invoice'.
  • Stores the reason on the SO (force_close_so_reason).
  • Stamps force_close = True, the closer's user_id (force_close_uid), and the timestamp (force_close_date).
  • Posts a chatter line: "Forced closing of Sales Order: <reason>".

After that, the SO disappears from invoicing queues. The reason and closer are stored in tracking fields, so an auditor can find them on any closed order.

SQ & SO Dates List​

bc17/blu_crm_sales adds a sales reporting list at Sales → Reporting → SQ & SO Dates List that returns a read-only, tightly-columned view of every confirmed sale (state = sale). It shows confirmed orders only and cannot be used to create, edit or delete — it is designed for reviewing the dates pipeline and for exporting to Excel.

SQ &amp; SO Dates List

It surfaces:

  • Create date, order date, commitment date, expected date
  • Order number, customer, salesperson, team
  • Invoice status with colour badges (Fully Invoiced / To Invoice / Upselling)
  • Delivery Status, Collection Status, Invoice No., SI No., CR No. and Trip Ticket (see Tracking an order from the list)
  • Tags, untaxed / tax / total amounts (hidden by default)

Branch on the Sales Report​

Blu Reports → Sales → Sales Report (Vatable) lists invoice lines grouped by Branch, the warehouse of the sales order behind the line.

Sales Report (Vatable) grouped by Branch

Two fixes in 10/2026 stopped lines landing in a blank (false) Branch group:

  • Lines added by hand on the invoice (10/02/2026). A line typed onto the invoice, rather than brought over from the order, has no link to an order line. It now takes the branch of the order behind the rest of the invoice, or of the order named in the invoice's Source Document.
  • The first line of a new invoice (10/09/2026). The first line, and the only line of a one-line invoice, used to come out blank until someone refreshed it.
Still seeing a blank Branch?

Lines posted before the fix can still be in the false group. Select them in the report and run Action → Refresh Branches.

Reference — code & data map​

ConcernFile
sale.order extension (account_id, force_close, rts_action_confirm, default warehouse, collection / delivery status, invoice / SI / CR numbers, TikTok print)bc17/blu_crm_sales/models/sale_order.py
Top-level-only Customer pickerbc17/blu_base/models/partner_id_domain.py
Delete block (Blu Coffee Administrators only)bc17/blu_base/models/blu_no_delete.py
Branch on Sales Report linesbc17/blu_reports/models/account_move_line.py
sale.order.line extension (qty_available, total_weight)bc17/blu_crm_sales/models/sale_order_line.py
Force close wizardbc17/blu_crm_sales/wizard/force_close_so_wizard.py
SO form patches (Mode of Payment, custom Confirm, Close this Order button, address pickers), list columns, filters, searchbc17/blu_crm_sales/views/sale_order_view.xml
SQ & SO Dates List + custom list viewbc17/blu_crm_sales/views/quotation_order_view.xml
SLA fields + working-day calculationscommon/sla_sales_tracking/models/sale_order.py
SLA list/form/search view patchescommon/sla_sales_tracking/views/sale_order_views.xml
SLA on stock.picking (related fields)common/sla_sales_tracking/models/stock_picking.py
Delivery-Orders tree + search inheritcommon/sla_sales_tracking/views/stock_picking_views.xml
Holiday lookup for working-day mathcommon/sla_sales_tracking/models/sla_holiday.py
Send to Viber actioncommon/blu_8x8/models/blu_viber_sale_order.py
Send to Viber button injectioncommon/blu_8x8/views/blu_viber_sale_order_views.xml

Groups used​

GroupEffect
bc17.blu_sales_order_validators_groupSees Confirm button, Close this Order button.
bc17.blu_sales_order_special_pricing_validators_groupSame as above + can confirm quotes with discounted lines.
bc17.blu_sales_invoicing_groupSees Create Invoice + Create Invoice Percentage buttons.
bc17.blu_ecomm_admin_group / bc17.blu_ecomm_user_groupCan confirm + invoice ecomm orders.
bc17.blu_admin_group (Blu Coffee Administrators)The only users who can delete a sales order.

Custom sale.order fields​

FieldTypeNotes
account_idMany2one (account.account)Mode of Payment, required, tracked
force_closeBooleanTrue after Force Close wizard fires
force_close_so_reasonTextReason captured in wizard
force_close_dateDatetimeWhen it was closed
force_close_uidMany2one (res.users)Who closed it
is_ecomm_saleBooleanSet by ecomm flow on create
collection_statusSelection (stored)no_invoice/not_paid/in_payment/partial/paid/reversed, from posted invoices
delivery_statusSelection (stored)sale_stock's pending/started/partial/full, plus nothing for confirmed orders with nothing to ship
invoice_noChar (stored)Odoo invoice numbers, comma-separated
si_noChar (stored)BIR SI numbers (account.move.si_number), comma-separated
cr_noChar (stored)BIR CR numbers (account.payment.cr_number) of reconciled payments, comma-separated
trip_ticket_no / actual_delivery_dateChar / Date (stored)From the deliveries' trip tickets
sla_target_daysIntegerRegion-based
sla_expected_delivery_dateDateComputed
sla_actual_delivery_dateDateFrom done outgoing pickings
sla_statusSelectionon_time/at_risk/delayed/delivered_on_time/delivered_late/not_applicable
sla_days_remainingIntegerWorking days
sla_days_varianceIntegerWorking days; positive = early
sla_region_nameChar (related)From partner's region