Neary Integration
Neary is the first source system that ships financial events into
Moonlight via @tkgreg/moonlight-sdk. Moonlight is the sole writer to
QuickBooks Online for the Neary workspace; the legacy
Neary→QuickBooks path is being sunset (see
docs/MOONLIGHT_INTEGRATION.md
in neary-turbo).
Data Flow
Event Catalogue (v1)
The Neary bridge emits these outbound operations:
| Operation | Maps to | Notes |
|---|---|---|
Purchase.status='paid' | INCOME entry on the Neary business party | Implicit VAT split into PRODUCT + TAX entry_item rows. FX block carries USD→KHR rate from Invoice.exchangeRate. |
Transaction(refund/reversal) | EXPENSE entry, relation_type=REFUND/CORRECTION, relates_to_external_id set | Refund inserted before original sale fails fast — bridge re-enqueues until the parent exists. |
AffiliateEvent | EXPENSE (bonus/withdrawal/referral_bonus) or INCOME (claw_back) | entry.from_party / to_party use the affiliate party. |
Invoice.pdfUrl set | attachDocumentByUrl to the corresponding entry, type BASIS, status APPROVED | The invoice PDF is the official Cambodian tax document. |
Inbound Events Consumed
The Neary webhook handler (apps/api/src/routes/moonlight-webhook-routes.ts)
reacts to:
| Event | Neary action |
|---|---|
entry.review.approved (affiliate withdrawal) | Unlock the affiliate's pending balance, queue Telegram notification |
entry.review.rejected | Open finance Ticket with the rejection reason |
entry.review.changes_requested | Open finance Ticket with the requested change |
entry.document.requested | Open MOONLIGHT_DOCUMENT_REQUEST Ticket targeting the source aggregate |
entry.voided | Mark Purchase.metadata.moonlightVoided=true, alert finance |
accounting.sync.failed | Slack/Telegram alert in #finance-ops |
External-id Mapping
| Neary entity | external_id |
|---|---|
| Purchase | neary:purchase:{id} |
| Transaction (refund/reversal) | neary:tx:{id} |
| AffiliateEvent | neary:affiliate-event:{id} |
| User → CUSTOMER party | neary:user:{id} |
| Affiliate → EMPLOYEE party | neary:affiliate:{id} |
| Provider → VENDOR/BANK party | neary:provider:{id} |
| Neary entity (BUSINESS party) | neary:business:1 |
external_source is always "neary".
Project Routing
Neary SalesChannel.code (e.g. TG, FB) is forwarded as
project_code on the ingest payload. Moonlight resolves
(workspace_id, code) from the project table; if the code is unknown
the ingest is rejected with a clear error so the operator can create the
project before re-running.
Backfill
The Neary side ships three CLI scripts (run from the neary-turbo repo):
pnpm moonlight:backfill --phase=sales --from=2024-01-01 --to=2024-12-31
pnpm moonlight:backfill --phase=refunds --from=2024-01-01 --to=2024-12-31
pnpm moonlight:backfill --phase=affiliate --from=2024-01-01 --to=2024-12-31
pnpm moonlight:backfill --phase=documents --from=2024-01-01 --to=2024-12-31
Phases must run in order — refunds and documents reference parent
entries by external_id. Phases use partial_per_row=true, so a single
malformed row does not block the rest of the batch.
Reconciliation
pnpm moonlight:reconcile --from=2024-01-01 --to=2024-12-31 --verbose
Compares Neary purchase paid rows against Moonlight entries with
external_source='neary'. The script exits non-zero on any monthly
diff or any orphan id, suitable for a nightly cron.