# Kapitano keeps its books in Zoho Books as an agent, fed by the store

Kapitano has Zoho One (Enterprise) and keeps its accounting in Zoho Books. The Books organization is
`kapitano` (id 935558326, `.com` data centre, base currency SYP). On 2026-11-07 it was still empty, with
a chart of accounts built for a merchant that buys and resells (Sales + Cost of goods).

The store has to post into it permanently, with no room for error. Later it also has to feed CRM, Desk,
SalesIQ, campaigns and Analytics.

Decided with the user on 2026-11-07, in two question rounds:

- **Sellers' products: Kapitano is an agent (net).** Revenue is the commission and the shipping. A
  seller's share is money held for them, never revenue.
- **In-house products are Kapitano's own (gross).** They are sales revenue with a cost of goods sold, and
  inventory tracked in Books. The store does not record a supplier shop for them, and the user confirmed
  they are bought outright.
- **One sales invoice per checkout** (Combined Order), numbered `KAP-<combined order id>`, with a line per
  product. It is created **when the checkout is delivered** (`combined_delivery_status()`), so anything
  cancelled before delivery never reaches the books.
- **Money in SYP at the order's rate** (`convert_order_price()`, `exchange_rate_at()`), never today's.
  Seller documents are in the seller's currency, with that same rate stated explicitly.
- **A posted document is never edited.** Every later change is posted as a difference document (a credit
  note or a vendor credit).
- **The sync starts at the beginning of a period**, with one opening entry the store computes. No history
  is imported.
- **Clean the existing Books organization and rebuild the chart of accounts** for the agent model,
  rather than creating a new organization. Inventory, Expense and Commerce are joined to it.
- **The store is the only source of truth, and links the Zoho records itself.** Every Zoho record carries
  the store's key (`kapitano_user_id`, `kapitano_shop_id`, `kapitano_stock_id`, `KAP-<id>`) as a unique
  custom field, and the store keeps the Zoho ids per app in one table.
  - **CRM is the hub for the customer apps.** The store sends every customer to CRM only; Zoho's own
    integrations carry them to Desk, SalesIQ, campaigns and Analytics.
  - **Books is fed by the store directly**: the money, the customer of each invoice and the sellers as
    vendors.
- **The wallet is out of scope.** It is off in the store (`wallet_system = 0`).

The posting matrix, a worked example and the new chart of accounts are in
`docs/codebase/features/zoho-accounting-brief.md`, written in Arabic for the accountant, who signs it
before any code is built.

## Considered Options

- **Gross for everything** (the existing chart of accounts). Rejected by the user. Sellers' goods are never
  ours (even those held in the company warehouse), and treating them as sales would overstate revenue
  many times over.
- **Daily summary journals instead of one invoice per checkout.** Rejected. Totals could not be traced back
  to an order, and the reconciliation would get weaker.

  At about 35 checkouts a day, one invoice per checkout costs roughly 200 API calls a day, against
  Books' 10,000 a day and 100 a minute.
- **Post at placement or at confirmation.** Rejected. Many orders are cancelled, and every cancellation
  would need a void.
- **Import the history since the store opened.** Rejected. Old orders follow older rules (stock, coupon
  split, no frozen seller cost before 2026-10-06), so a clean period with opening balances is safer.
- **CRM as the only door into Zoho, Books included** (the user's first idea). Kept for the customer apps,
  not for Books:
  - The store must call Books for the money anyway.
  - Zoho's CRM→Books integration brings new records every two hours.
  - It detects duplicates by name only (CRM Account Name against Books Display Name), so it would merge
    different customers who share a name.
  - It would add a link outside our monitoring on the most critical path.
- **Zoho's native CRM↔Books customer sync for linking customers.** Rejected for the same name-matching
  reason. If CRM needs to show a customer's invoices, link the records by the store key through the API
  (to be tested in Phase 0), or show them with a widget that reads the store.
- **Zoho MCP as the sync path.** Rejected. MCP is for development, inspection and spot checks; the sync runs
  server to server over REST and OAuth.
- **Zoho Inventory for sellers' stock.** Not now. Sellers' goods are consignment, and their stock and the
  company warehouse already live in the store. Inventory tracking in Books is on for in-house items only.

## Consequences

- **A seller's share passes through one clearing liability**, "مبيعات التجار تحت التسوية" (2150):
  - The invoice credits it with the selling price.
  - The seller's bill debits it with the share (`commission_histories`).
  - The commission entry moves the rest to commission income.
  - Its balance per completed order is 0, and the daily audit checks this.
- **The seller's payable in Books equals `shops.admin_to_pay`, per seller and in their currency.** That is
  the strongest reconciliation we have. Because bills follow `commission_histories`, the store's balance
  rules (cancel, return, un-cancel) reach Books unchanged.
- **The coupon is Kapitano's** (the Discount account), as it is in the store.
- **Returns become credit notes and vendor credits.** Refunds stay manual, because the store does not
  return money.
- **In-house items are inventory items in Books.** Books computes their cost of goods sold. Their purchases
  are vendor bills the accountant enters, and a daily report compares their quantities with the store's.
- **Delivery staff remittances, the eCash 1% fee and refunds** are not recorded by the store. They stay
  manual entries in Books.
- **The integration follows the Kapitano Logistic pattern**: an outbox, a links table, fixed idempotency
  keys, retries, parking for a person, a reconcile safety net, and a daily audit that stops sending on any
  money mismatch. Map: `docs/codebase/features/zoho-integration.md`.
- **CRM's home currency must be SYP before any data enters it.** CRM must hold the customer's phone in one
  normalized form, because only about 31% of customers have an e-mail address, and Desk and campaigns
  match mostly by e-mail.
