MedBridge Sales Ops — Quote & Reconciliation SOP
Transferable operating procedure for MedBridge Sales Ops CRM tasks (B2B medical wholesale + implementation-services). Two task families: QUOTE (reconcile a customer RFQ/quote against the API → account-ready quote decision package) and RECONCILIATION (reconcile a won opportunity's invoices/payments/revenue-journals/events → finance-ready reconciliation). All business records come ONLY from the remote HTTP API. Money is USD 2-decimals; dates are ISO YYYY-MM-DD; use stable record IDs exactly as they appear.
0. API contract (memorize)
- Base URL: from
environment_access.md(API_BASE_URL). All GET. GET /api→ metadata (lists collections + endpoints).GET /api/<collection>→{collection, count, records[]}._collections:customers, products, rfqs, quotes, freight-quotes, policies, opportunities, invoices, payments, revenue-journals, events, vouchers.GET /api/<collection>?<key>=<value>→ filter (case-insensitive, matches nested keys, numeric tolerance 2dp, multiple filters AND together). Use this forinvoices?opportunity_id=...,payments?opportunity_id=...,freight-quotes?quote_id=...,revenue-journals?opportunity_id=....GET /api/<collection>/<id>→ single record. Detail-by-id works ONLY for:customers, products, rfqs, quotes, freight-quotes, opportunities. For invoices/payments/revenue-journals/events/vouchers/policies use listing or filter endpoints (no detail-by-id).GET /api/search?q=<text>→ substring search across ALL collections (≤100 hits, each tagged withcollection+id). Great for finding an opportunity's full object graph by customer/opportunity name.- Pipeline
python3 -m json.toolto read. Prefer writing a fetch-script that dumps all needed endpoints to one file, then Read it (avoids dropped output).
Critical API gotchas
revenue-journalsdoes NOT reliably filter bycustomer_id— filter byopportunity_id. (Filtering by customer_id can return empty.)- Trust the API over the prompt narrative when they conflict, BUT read the prompt carefully for WHICH entity (quote_id / rfq_id / opportunity_id / customer_id) to reconcile.
- Customer/quote/RFQ records contain
component_composition_distractors,prior_unit_price_usd, and "old route" / "Distractor route" freight entries placed to mislead — see pitfalls §5.
1. Universal workflow (every task)
- Read the prompt +
input/payloads/answer_template.jsonfully. The template is the contract — field names, nesting, and controlled enum values are graded. Templates vary across tasks even within the same family (see §3, §4). Never assume a template; copy its exact structure. - Identify the anchor ID(s) in the prompt: a
quote_id/rfq_id(quote family) oropportunity_id+customer_id(reconciliation family), plus any named event_id / voucher_code / contact. - Fetch the anchor record, then fan out to the related collections via filter (
?opportunity_id=,?quote_id=) or/api/search?q=<name>. Fetch the customer by id, the product by code, policies (all), freight-quotes by quote_id, invoices/payments/revenue-journals/events/vouchers by opportunity_id (or search). - Reconcile the narrative against the records: confirm customer, quantity, product, dates; override stale/narrative values with API values.
- Compute derived fields (pricing tiers, EXW totals, grand totals, paid/outstanding, recognition coverage) using the rules below.
- Emit ONLY valid JSON matching the template — same keys, same nesting, controlled enum values, no markdown, no prose.
2. QUOTE family — business rules
2.1 Catalog tier by quantity (THE core pricing rule)
- Product records have
price_tiers[]sorted bymin_qty(withmax_qtypossiblynull= infinity). Each tier carriesunit_price_usd,lead_time_days,lead_time_weeks, and the product carriesshelf_life_months. - Select the tier where
min_qty <= confirmed_quantity <= max_qty(treat null max as +∞). - The catalog tier OVERRIDES
prior_unit_price_usdfound in the quote line item. The prior price is a distractor; always use the active tier'sunit_price_usd. - Examples (train): WC-KIT-A qty 360 → tier [300–499] @ 118.0 (prior was 124.0 — ignore). LD-REAGENT-44 qty 1000 → tier [900–1199] @ 76.0 (prior was 79.5 — ignore).
- Module products (IEHK-*) often have a single tier (min 1, max null) — just use that price.
2.2 EXW vs freight-inclusive
exw_total_usd=unit_price_usd × confirmed_quantity. EXW excludes freight, insurance, import duty, customs clearance, last-mile (policyPOL-EXW-SCOPE/POL-INDICATIVE-EXW).grand_total_usdper freight option =exw_total_usd + freight_cost_usd(EXW plus that freight).- For EXW-only / indicative module quotes with no destination:
freight_excluded = true,quote_basis = "EXW_ONLY", no freight_options,grand_total= sum of line_totals.
2.3 Freight validity vs quote date (source-validity)
- Quote pricing is valid 30 calendar days from quote_date (
POL-QUOTE-VALIDITY). Freightvalid_untilmay expire sooner (POL-FREIGHT-RECONFIRM). - A freight quote is valid on the quote date iff
valid_until >= quote_dateANDstatus == "active". valid_until < quote_date→ expired.status == "stale"→ stale source. Either makes the option invalid/unusable for a clean recommendation.freight_reconfirmation_required = true(policyPOL-FREIGHT-RECONFIRM: rates need reconfirmation at final order). Practically always true because freightvalid_untilprecedes the 30-day quote window.all_freight_options_valid_on_quote_date = trueonly when every INCLUDED (real, active) freight option hasvalid_until >= quote_date.
2.4 Route risk enums
- Freight
route_riskvalues:low,medium,high→ emit asLOW,MEDIUM,HIGH. risk_flag/customs_border_riskis keyword-derived fromrisk_notes:- "border"/"customs" →
{LEVEL}_BORDER_RISK(e.g.MEDIUM_BORDER_RISK,HIGH_BORDER_RISK). - "shelf-life"/"shelf life" →
SHELF_LIFE_RISK. - low risk with no specific keyword →
NONE. - Read
risk_notesliterally; do not assume all medium → BORDER_RISK (a reefer sea option may be medium due to shelf-life, not border).
- "border"/"customs" →
2.5 Recommended transport mode (judgment rule)
Exclude stale/expired/distractor freight first, then:
- Cold-chain product (
product.cold_chain_required == true): recommend an active option withcold_chain_support == trueand LOW risk, preferring AIR (shortest transit protects temperature + shelf life). (train_004 LD-REAGENT-44 → AIR.) - Non-cold-chain: recommend the active LOW-risk option with the lowest grand_total (cost-effective for NGO/wholesale). If no low-risk option exists, recommend the lowest-risk available. (train_001 WC-KIT-A → SEA: both AIR & SEA are low risk, SEA grand 46360 < AIR 58680.)
- If the quote carries a
grant_delivery_need_bydate, ensurequote_date + lead_time_days + transit_days_maxfits; if not, upgrade toward AIR. Flag recommended_mode as the single mode string (e.g."AIR","SEA","ROAD"). This is the most uncertain field — weigh cold-chain, risk, transit vs deadline, and cost, in that priority.
2.6 Payment terms & customer policy
- Fetch
/api/policiesand matchpolicy_area == "payment_terms"to the customer's segment/payment_profile:- New NGO (
is_recurring == false,payment_profile == "NEW_CLIENT_REVIEW",segment == "new_ngo") → policyPOL-NEW-CLIENT-PAYMENT→PREPAY_100(prepay before production release). - Recurring NGO (
is_recurring == true,segment == "recurring_ngo") →POL-RECURRING-NGO-PAYMENT→NET_30_AFTER_PO(unless restricted grant terms say otherwise). - Commercial/government/distributor: use the customer's
payment_profile(e.g.NET_45_APPROVED,NET_30_AFTER_PO,PREPAY_50_BALANCE_BEFORE_SHIP); if no dedicated policy,customer_policy= thepayment_profilestring.
- New NGO (
payment_terms= the resolved terms_code (policyterms_codeif a policy matches, elsepayment_profile).customer_policy(001-style template) = the governing policy id (e.g.POL-RECURRING-NGO-PAYMENT) when one matches; else thepayment_profile.
2.7 Module-level granularity (IEHK / field-clinic module quotes)
- Policy
POL-MODULE-GRANULARITY: quote module RFQs at module line level only. Do NOT expandcomponents[]into component SKUs, even though the product record lists components and the RFQ may carrycomponent_composition_distractors. One line_item per requested module. - For indicative module quotes with no destination → EXW only, exclude freight (
POL-INDICATIVE-EXW). offer_validity_days = 30(POL-QUOTE-VALIDITY).- WHO documentation flag:
who_documentation_required = truefor IEHK-style / medicine-containing modules (productfamily == "emergency_health_kit"with medicine components like "essential medicines", "oral medicines", "injectables support", "malaria medicines"). These are WHO-standardized kits. False for non-medicine products. - Line item fields:
product_code,article_number(from product),quantity,unit_price(tier),lead_time_days,shelf_life_months,line_total= qty × unit_price.grand_total= Σ line_totals.
3. QUOTE family — TWO template variants (match exactly)
Variant A — "quote decision package" (train_001 style)
Top keys: quote_summary, freight_options[], policy_flags.
quote_summary: quote_id, customer_id, quote_date, product_code, confirmed_quantity, unit_price_usd, lead_time_days, shelf_life_months, quote_basis ("EXW"), exw_total_usd.freight_options[](one per active, real-destination option; EXCLUDE stale & "Distractor route"): freight_id, mode (AIR/SEA/ROAD), freight_cost_usd, transit_days (usetransit_days_texte.g."4-6 days"), valid_until, risk_level, risk_flag, grand_total_usd.policy_flags: recommended_mode, freight_reconfirmation_required, all_freight_options_valid_on_quote_date, customer_policy, payment_terms.- train_001: 3 options (AIR/SEA/ROAD all active); exclude
FR-DIS-WC-OLD-AIR(stale, Distractor) &FR-DIS-WC-HEAVY-SEA(Distractor route).
Variant B — "revised quote with source-validity warnings" (train_004 style)
Top keys: pricing, transport_decisions, client_warnings.
pricing: quote_id, customer_id, quote_date, product_code, confirmed_quantity,catalog_tier{min_quantity, max_quantity, unit_price_usd, lead_time_days, shelf_life_months}, exw_total_usd, payment_terms.transport_decisions.freight_options[]: freight_id, mode, freight_cost_usd, transit_days, valid_until, validity_status (VALIDif valid_until≥quote_date & active;EXPIREDif valid_until<quote_date), source_is_stale (bool =status=="stale"), customs_border_risk (LOW/MEDIUM/HIGHfrom route_risk), grand_total_usd. Include stale real-destination options (e.g. the stale road quote) so they can be flagged; still exclude "Distractor route" entries.transport_decisions: recommended_mode, freight_reconfirmation_required.client_warnings: road_quote_invalid_or_stale (bool true when the road option is stale/expired), freight_warning (short string describing the concern), policy_terms{quote_basis, payment_terms, freight_reconfirmation_required}.- train_004: include AIR (VALID), SEA (VALID), ROAD (EXPIRED/source_is_stale=true, customs_border_risk=HIGH); exclude
FR-DIS-LD-OLD-SEA(Distractor route).
Always read the task's own template — field names and nesting differ between variants.
4. RECONCILIATION family — business rules
4.1 Anchor & object graph
- Fetch
opportunities/<opp_id>, theninvoices?opportunity_id=,payments?opportunity_id=,revenue-journals?opportunity_id=, and the linked event+voucher (via/api/search?q=<opp or customer>or filter events/vouchers byopportunity_id). - Opportunity
stage:closed_won→WON;open→OPEN;closed_lost/lost→LOST. won_amount_usd= opportunity's won amount.phase_total_amount= Σphases[].amount_usd.opportunity_matches_milestones/opportunity_matches_phase_total= (won_amount == phase_total).total_paid_amount= Σ invoicepaid_amount_usd.outstanding_balance= Σ invoiceoutstanding_amount_usd(should equal opportunityoutstanding_amount_usd).primary_contact/contact= opportunitycontact(a name); linkcustomer_idandopportunity_id.
4.2 Milestone / invoice / payment state mapping
Each opportunity phases[] entry has a phase_id, amount_usd, completion_date, invoice_id. Join to the invoice by invoice_id (or phase_id). For each milestone:
invoice_state(enum PAID|OPEN|VOID|UNKNOWN) from invoicestatus:paid→PAID;unpaid→OPEN;overdue→OPEN;draft→OPEN (or UNKNOWN if not yet issued; lean OPEN).void→VOID. No invoice record → UNKNOWN.payment_state(PAID|PARTIAL|UNPAID|UNKNOWN):paid_amount==amount→PAID;0<paid_amount<amount→PARTIAL;paid_amount==0→UNPAID; no invoice→UNKNOWN.paid_amount= invoicepaid_amount_usd.due_date: setnullfor PAID milestones (obligation settled); report the invoicedue_dateonly for PARTIAL/UNPAID milestones. (This is a deliberate output convention — do not copy the paid invoice's due_date.)- Milestone id: some templates use
MS1|MS2|MS3(ordered ascending by phase); others use the stablephase_id/invoice_id. Use exactly what the template declares.
4.3 Revenue-recognition state mapping
Policy POL-REVREC: when a milestone is complete AND paid, recognize revenue (deferred revenue → income); unpaid future milestones remain outstanding and drive collection when due/overdue.
Per-milestone recognition_status:
- Paid milestone + a
revenue-journalsrecord exists for itsphase_id/invoice_id(statusposted) → RECOGNIZED (train_003:RECOGNIZED; train_005:RECOGNIZED). - Paid milestone + NO revenue journal → REQUIRED_MISSING (train_003 enum) / MISSING_REVENUE_JOURNAL (train_005 enum). (train_005 MS2: paid 45000 but no RJ → missing.)
- Unpaid milestone → NOT_REQUIRED_UNPAID (not yet earned).
- No invoice → UNKNOWN (train_005 only).
- Existing RJ pattern:
debit_account="Deferred Revenue",credit_account="Implementation Services Revenue".
Aggregate (train_003 revenue_recognition):
recognition_status: all paid milestones recognized →COMPLETE_FOR_PAID_MILESTONES; any paid milestone missing RJ →MISSING_FOR_PAID_MILESTONES; no paid milestones →NOT_REQUIRED.recognized_milestones[]= phase_ids with a posted RJ.missing_required_milestones[]= paid phase_ids lacking an RJ.recognized_amount= Σ RJamount_usd.- train_003 Helios: P1 recognized, P2 unpaid → COMPLETE_FOR_PAID_MILESTONES; recognized [HEL-P1]; recognized_amount 50000.
4.4 Follow-up routing (template-dependent)
train_003 style (follow_up_tasks[], task_type COLLECTION|EVENT_INVITATION, next_action COLLECT_UNPAID_MILESTONE|SEND_EVENT_INVITATION):
- One COLLECTION task per unpaid/partial milestone:
task_type=COLLECTION,next_action=COLLECT_UNPAID_MILESTONE,milestone_id=phase/invoice id,amount_due=outstanding,due_date=invoice due_date,event_id=null,voucher_code=null. - One EVENT_INVITATION task per linked event when invite not yet sent (
voucher.redemptions_used==0and event not completed/cancelled):task_type=EVENT_INVITATION,next_action=SEND_EVENT_INVITATION,event_id,voucher_code,milestone_id=null,amount_due=null,due_date=event_date. - All tasks:
linked_customer_id,linked_opportunity_id,contact_name= opportunity contact (prompt may name the contact to tie work to, e.g. "Mara Okafor"). - train_003: COLLECTION for INV-HELIOS-P2 (70000, due 2026-06-27) + EVENT_INVITATION for EVT-HELIOS-CELEBRATION (HELIOSVIP100, 2026-07-22).
train_005 style (invoice_actions + event_actions, more granular):
primary_accounting_action: a paid milestone missing RJ →RECORD_REVENUE_MS<n>; all paid milestones have RJ →VERIFY_REVENUE_ONLY; nothing paid / nothing to do →NO_ACCOUNTING_ACTION.accounting_action: action, milestone_id (MS1|MS2|MS3|NONE), amount = the missing-RJ milestone's paid amount,debit_account=DEFERRED_REVENUE,credit_account=IMPLEMENTATION_SERVICES_REVENUE,owner_queue=ACCOUNTING.collection_action/collection_task(route by due_date vs as-of date — use the prompt's as-of date, e.g. 2026-06-01; if unstated, use the env business date 2026-06-01):- Unpaid +
due_date < as_of(overdue) →SEND_COLLECTION_NOTICE,owner_queue=COLLECTIONS. - Unpaid +
due_date >= as_ofor null (not yet due) →MONITOR_UNPAID_NOT_DUE,owner_queue=ACCOUNT_MANAGEMENT. - No unpaid milestones →
NO_COLLECTION_ACTION, owner NONE. collection_task.milestone_id= the unpaid milestone,amount= its outstanding,due_date= its invoice due_date,contact_name= opportunity contact.
- Unpaid +
event_actions:event_id,event_status(map event.status: scheduled→SCHEDULED, live→ACTIVE, completed→COMPLETED, confirmed→ACTIVE, tentative→SCHEDULED, cancelled→CANCELLED, else UNKNOWN),voucher{voucher_code, voucher_status (active→ACTIVE; if valid_until<as_of treat EXPIRED; draft→DRAFT; disabled→DISABLED), discount_amount (=voucher.discount_percent as a number, e.g. 50.00), max_uses (=max_redemptions)},invite_action(redemptions_used==0 & event not completed/cancelled →SEND_BRIEFING_INVITE; redemptions_used>0 →VERIFY_INVITE_SENT; elseNO_INVITE_ACTION),invite_task{action, event_id, voucher_code, owner_queue (event.follow_up_owner=="Account Management"→ACCOUNT_MANAGEMENT else EVENTS), contact_name, customer_id}.- train_005 Meridian (as-of 2026-06-01): RECORD_REVENUE_MS2 (amount 45000, DR Deferred Revenue / CR Implementation Services Revenue, ACCOUNTING); collection MONITOR_UNPAID_NOT_DUE for MS3 (25000, due 2026-07-15 future, ACCOUNT_MANAGEMENT, contact Daniel Rees); event EVT-MERIDIAN-BRIEFING SCHEDULED, voucher MERIDIANBRIEF50 ACTIVE discount 50.00 max_uses 20, invite SEND_BRIEFING_INVITE.
4.5 Event & voucher linkage
- Linked event: filter
eventsbyopportunity_id(or the event_id named in prompt). Fields:event_date,status,primary_contact,voucher_code,follow_up_owner. - Linked voucher: code = event's
voucher_code; fieldsdiscount_percent,max_redemptions,redemptions_used,status,valid_until. - train_003
eventblock: event_id, event_date, voucher_code, voucher_discount (=discount_percent), voucher_max_uses (=max_redemptions). (Helios: EVT-HELIOS-CELEBRATION, 2026-07-22, HELIOSVIP100, 100, 4.)
5. Common pitfalls / distractors (verified in train data)
- Narrative customer-name mismatch: prompt may say "Health Horizon Aid" / "GreenHarvest Labs" but the quote's
customer_idresolves to "HealthHands Alliance" (CUST-HHA) / "Global Health Laboratories" (CUST-GHL). Always take customer_id from the quote/RFQ and fetch the canonical name from/api/customers/<id>. - prior_unit_price_usd distractor: the quote line item's prior price is NOT the price — the active catalog tier by quantity is. (train_001: 124.0→118.0; train_004: 79.5→76.0.)
- Distractor freight quotes: entries with
destination == "Distractor route", or stale/expired "old route benchmark" quotes (status stale, valid_until before quote_date) — exclude from clean option sets; in Variant B include the real-but-stale road option only to flag it (source_is_stale=true,road_quote_invalid_or_stale=true). - EXW-only excludes freight: indicative/module quotes with no destination have NO freight_options;
freight_excluded=true,quote_basis="EXW_ONLY". Don't invent freight. - Module granularity: do NOT expand
components[]/component_composition_distractorsinto component lines. One line per module. (POL-MODULE-GRANULARITY.) - New-client prepay: new NGO (
is_recurring=false,NEW_CLIENT_REVIEW) →PREPAY_100, not net terms. (POL-NEW-CLIENT-PAYMENT.) - Paid milestones → due_date null in milestone output (obligation settled); report due_date only for unpaid/partial.
- revenue-journals filter by opportunity_id, not customer_id (customer_id filter can return empty).
- Paid-without-RJ is the key reconciliation defect: a paid milestone with no matching revenue journal drives
RECORD_REVENUE_MS<n>/MISSING_FOR_PAID_MILESTONES/REQUIRED_MISSING. Always join invoices↔revenue-journals byphase_id/invoice_id. - Collection routing depends on as-of date: overdue (due_date < as_of) → SEND_COLLECTION_NOTICE/COLLECTIONS; not-yet-due → MONITOR_UNPAID_NOT_DUE/ACCOUNT_MANAGEMENT. Use the prompt's as-of date (default 2026-06-01).
- discount_amount == discount_percent: voucher records store
discount_percent; report it as the numeric discount (e.g. 50.00, 100.00) — there is no separate dollar amount. - Two quote templates & two reconciliation templates exist — always match the specific
answer_template.json. Variant A quote (risk_level/risk_flag, exclude stale) vs Variant B quote (catalog_tier/source_is_stale/validity_status/customs_border_risk, flag stale road). train_003 reconciliation (account_status/milestones/revenue_recognition/event/follow_up_tasks) vs train_005 reconciliation (engagement_reconciliation/invoice_actions/event_actions). - WHO doc flag true for IEHK/medicine modules; not applicable to non-medicine product quotes.
- Distractor RFQs/quotes: older
RFQ-DIS-*/Q-DIS-*records (draft, different dates, "Distractor" notes) — use only the ID named in the prompt.
6. Quick reference — controlled enum values
opportunity_stage/stage: WON | OPEN | LOSTinvoice_state: PAID | OPEN | VOID | UNKNOWNpayment_state(per template): PAID | PARTIAL | UNPAID | UNKNOWN (train_003 simplified: PAID | PARTIAL | UNPAID)recognition_status(train_003): RECOGNIZED | REQUIRED_MISSING | NOT_REQUIRED_UNPAIDrecognition_status(train_005): RECOGNIZED | MISSING_REVENUE_JOURNAL | NOT_REQUIRED_UNPAID | UNKNOWNrevenue_recognition.recognition_status(train_003): COMPLETE_FOR_PAID_MILESTONES | MISSING_FOR_PAID_MILESTONES | NOT_REQUIREDtask_type: COLLECTION | EVENT_INVITATIONnext_action: COLLECT_UNPAID_MILESTONE | SEND_EVENT_INVITATIONprimary_accounting_action: RECORD_REVENUE_MS2 | VERIFY_REVENUE_ONLY | NO_ACCOUNTING_ACTION (use the MSn matching the missing-RJ milestone)collection_action: MONITOR_UNPAID_NOT_DUE | SEND_COLLECTION_NOTICE | NO_COLLECTION_ACTIONaccounting_action.debit_account: DEFERRED_REVENUE | ACCOUNTS_RECEIVABLE | CASH | NONEaccounting_action.credit_account: IMPLEMENTATION_SERVICES_REVENUE | DEFERRED_REVENUE | ACCOUNTS_RECEIVABLE | NONEowner_queue(accounting/collection): ACCOUNTING | ACCOUNT_MANAGEMENT | COLLECTIONS | NONEevent_status: SCHEDULED | ACTIVE | COMPLETED | CANCELLED | UNKNOWNvoucher_status: ACTIVE | DRAFT | EXPIRED | DISABLED | UNKNOWNinvite_action: SEND_BRIEFING_INVITE | VERIFY_INVITE_SENT | NO_INVITE_ACTIONinvite_task.owner_queue: ACCOUNT_MANAGEMENT | EVENTS | NONEmode: AIR | SEA | ROADrisk_level/customs_border_risk: LOW | MEDIUM | HIGHvalidity_status: VALID | EXPIRED (STALE if template requires)quote_basis: EXW | EXW_ONLY
7. Worked train results (for self-check, NOT for copying — test tasks differ)
- train_001 Q-TR-WC-1187 / CUST-HHA: qty 360 @ 118.0, EXW 42480.00; AIR grand 58680.00 (LOW/NONE), SEA 46360.00 (LOW/NONE), ROAD 48630.00 (MEDIUM/MEDIUM_BORDER_RISK); recommended SEA; freight_reconfirm true; all_valid_on_quote_date true; customer_policy POL-RECURRING-NGO-PAYMENT; payment_terms NET_30_AFTER_PO.
- train_002 RFQ-TR-IEHK-204 / CUST-NOVAID: 5 module lines (BASIC 24200, SUPP-A 1380, SUPP-B 1525, TRAUMA 3100, MALARIA 1880), grand_total 32085.00; EXW_ONLY; freight_excluded true; payment_terms PREPAY_100; offer_validity_days 30; who_documentation_required true.
- train_003 OPP-TR-HELIOS / CUST-HELIOS: WON 120000 (matches phases 50000+70000); outstanding 70000; P1 RECOGNIZED, P2 NOT_REQUIRED_UNPAID; recognition COMPLETE_FOR_PAID_MILESTONES, recognized [HEL-P1] 50000; event EVT-HELIOS-CELEBRATION 2026-07-22 / HELIOSVIP100 / 100 / 4; follow-ups: COLLECTION (INV-HELIOS-P2, 70000, due 2026-06-27) + EVENT_INVITATION.
- train_004 Q-TR-LD-5521 / CUST-GHL: qty 1000 @ 76.0 (tier 900–1199, lead 14, shelf 18), EXW 76000.00; AIR 97400.00 (VALID, LOW), SEA 81200.00 (VALID, MEDIUM), ROAD 80800.00 (EXPIRED/source_is_stale true, HIGH); recommended AIR; road_quote_invalid_or_stale true; freight_reconfirm true; payment_terms NET_30_AFTER_PO.
- train_005 OPP-TR-MERIDIAN / CUST-MERIDIAN (as-of 2026-06-01): WON 100000 (matches 30000+45000+25000); paid 75000; outstanding 25000; MS1 RECOGNIZED, MS2 MISSING_REVENUE_JOURNAL, MS3 NOT_REQUIRED_UNPAID; RECORD_REVENUE_MS2 (45000, DR Deferred Revenue / CR Implementation Services Revenue, ACCOUNTING); collection MONITOR_UNPAID_NOT_DUE (MS3, 25000, due 2026-07-15, ACCOUNT_MANAGEMENT, Daniel Rees); event EVT-MERIDIAN-BRIEFING SCHEDULED, voucher MERIDIANBRIEF50 ACTIVE 50.00 / 20, SEND_BRIEFING_INVITE.