CRM / NetSuite Replatform · Partner Compensation
Master Partner Parameters
Design & Implementation Guide
Analysis of the Treadstone Commercial Compensation Model against the Hyperion OMNI Partner Agreement (v1 Mar 2024 and v6 Jun 2025) — with a complete field specification for the NetSuite Master Partner Parameters tab and technical steps for PostgreSQL synchronization.
Scope of documents reviewed
Treadstone_Commercial_Compensation_Model-v2.docx · Hyperion OMNI-Partner Agreement HYP-OMNI-03212024 (v1) · Hyperion OMNI-Partner Agreement v6-06022025 (v6). The model targets Carrier Activation (Upfront), Residual, and SPIFF categories, which maps to Exhibit A + B of the Partner Agreement. Exhibits C.1, D, D.1, E, and F capture additional revenue streams the model references less specifically.
Section 1
Gap Analysis
The Treadstone model is architecturally sound — its 14 normalized parameters provide a valid framework. However, several contractual parameters in the Partner Agreement either fall between the model's parameters or require more granular sub-fields to be calculable in NetSuite and PostgreSQL.
Core Parameters — Coverage Confirmed
| Treadstone Parameter | Agreement Source | Status |
|---|---|---|
| Partner | Main Agreement — Partner Information Table | ✓ Covered |
| Agreement Type | Main Agreement — Relationship definition | ✓ Covered |
| Agreement (effective dates, version, status) | Signature block; Exhibit G change requests | ✓ Covered |
| Commercial Program / Category | Exhibit A (carrier activation), B (rev share), C.1, D, D.1, E, F | ✓ Covered |
| Eligibility | Exhibit A §2 — deal registration approval; Exhibit B — ESCR eligibility rules | ✓ Covered |
| Master Agent / Direction | Exhibit A §1 — Hyperion as default Master Agent; bidirectional optional | ✓ Covered |
| Revenue Source | Exhibit B — carrier; C.1 — customer (HYP paper); D/D.1 — VAST proceeds; E/F — gross revenue | ✓ Covered |
| Compensation Type | One-time (activation, Ex B/C.1/D.1/E/F one-time), recurring (residual, Ex D), milestone (Ex D.1) | ✓ Covered |
| Calculation Basis | ESCR (Ex B); Net Proceeds (Ex C.1/D); Gross Revenue (Ex E/F); flat $ per unit (Ex D.1) | ✓ Covered |
| Compensation % / Amount | 50%/60%/70%/80% (Ex B tiered) or flat 50% (v6); 10% (Ex C.1, D); flat $ (Ex D.1); configurable % (Ex E/F) | ✓ Covered |
| Payment Trigger | Carrier payment to Hyperion (Ex A §3); customer payment to Hyperion (Ex C.1/D); monthly recognition (Ex D residual) | ✓ Covered |
| Recovery / Chargeback Rule | Exhibit A §7 — withholding and chargeback; 210-day final hold; offset/invoice mechanisms | ✓ Covered |
| Exception / Override | Exhibit A §4.5 — Hyperion sole discretion modifications; Exhibit C.1 §15 — custom pricing comp | ✓ Covered |
| Source of Authority | Agreement + active Exhibit(s) + Exhibit G change requests | ✓ Covered |
Gaps — Parameters Missing or Under-Specified
These 10 items exist in the agreements but have no dedicated field in the current Treadstone model
Each gap is described below with its contractual source and recommended NetSuite field action.
| # | Gap | Agreement Clause | Impact | Action |
|---|---|---|---|---|
| G1 | Active Exhibits (per-partner YES/NO) Each of Exhibits A–H may be included or excluded per partner. The model treats all exhibits as always applicable. | Main Agreement — Recitals §D exhibit table; deployment instructions §18 | High | Add a boolean checkbox per exhibit on the partner record |
| G2 | Commission Structure Type — Tiered vs Flat v1 uses a 6MRA tier system (SELECT/PREMIER/ELITE/TITAN at 50/60/70/80%). v6 primary language uses a flat 50%; v6 also includes the tiered structure as "Alternate Language." Different partners may be on different language versions. | Exhibit B §1 (v1 tiered) vs Exhibit B §1 (v6 flat 50% primary + ALTERNATE LANGUAGE tiered) | High | Add Commission Structure Type field (Tiered | Flat), plus tier threshold and rate fields |
| G3 | SPIFF Exclusion & Separate SPIFF Rate Exhibit B explicitly excludes SPIFFs from the pass-through calculation ("exclusive of spiffs, MDF, co-op"). The model lists SPIFF as a compensation type but doesn't flag that it is NOT passed through via the standard % — it must be tracked and paid separately if at all. | Exhibit B §1 — "exclusive of spiffs, MDF, co-op, and other bonuses not directly attributable to Partner" | High | Add SPIFF Included in Pass-Through boolean (default: No) and separate SPIFF Rate % field |
| G4 | 6MRA Tier Tracking Parameters When on tiered Exhibit B, the tier is determined by a 6-month rolling average of ESCR. Partners are eligible beginning the 7th month. Need: tier start date, current 6MRA value, current tier, and tier assignment date. | Exhibit B §1.1 (v1) and ALTERNATE LANGUAGE in v6 | Medium | Add Tier Eligibility Start Date, Current Tier, Current 6MRA ($), Tier Last Updated |
| G5 | Withholding Trigger (300% Rule) Exhibit A §7.3: if a single commission period exceeds 300% of the monthly commission run rate, Hyperion may withhold up to 20% until after the chargeback period expires. This is a specific risk-control rule not captured by the generic "Recovery/Chargeback Rule" parameter. | Exhibit A §7.3 (both v1 and v6) | Medium | Add Withholding Threshold Multiplier (default: 300%), Withholding Hold % (default: 20%) |
| G6 | Final Payment Hold Period (210 days) After termination or expiration, Hyperion holds all compensation for 210 days pending final true-up. This is a concrete operational parameter needed for payment scheduling. | Exhibit A §8.2; Exhibit C.1 §3; Exhibit D §6 (all agreements) | Medium | Add Post-Termination Hold Days (default: 210) |
| G7 | VAST Track — Exhibit D.1 (new in v6) Exhibit D.1 is entirely new in v6. It covers VAST Track products with a per-unit flat dollar commission (not a percentage). The amount is a blank placeholder negotiated per partner. The Treadstone model has no reference to VAST Track. | Exhibit D.1 §3.2.1 — "flat dollar amount per device/service sold" (v6 only) | High | Add VAST Track section: VAST Track Active, VAST Track Commission Amount ($), VAST Track Registration Method |
| G8 | Post-Termination Residual Continuation Flag Exhibit A §6.2: if Hyperion terminates for convenience (§4.2), residual commissions continue flowing to the partner until the Supplier's residual period expires. For all other termination types, compensation stops immediately. This distinction affects payment scheduling after termination. | Exhibit A §6.2 vs §6.1; Exhibit D §5 post-termination language | Medium | Add Post-Termination Residual Continues boolean (set by system based on termination type) |
| G9 | Exhibit E/F Eligible & Ineligible Product Scope Exhibits E and F contain blank fields for eligible products, ineligible products, and applicable commission rates. These are negotiated and left intentionally open in the template. Without capturing these per-partner, the commission calculation engine cannot determine which referrals earn a fee. | Exhibit E §2–4; Exhibit F §2–4 (both versions) | High | Add Exhibit E and F sub-sections with Eligible Products, Ineligible Products, one-time %, recurring % for each direction |
| G10 | Agreement Version / Language Variant Tracking A partner may be on v1 (tiered Exhibit B), v6 flat-rate, or v6 with alternate tiered language. The governing version determines the entire calculation path. The model captures "Agreement" but not the version's impact on which calculation logic applies. | HYP-OMNI-01302024 (v1) vs HYP-OMNI-v5-01262025 (v6); Exhibit B alternate language annotation | Medium | Add Agreement Template Version and Exhibit B Language Variant (Standard Tiered | Flat Rate | Custom) |
Section 2
NetSuite — Master Partner Parameters Tab
Complete field specification for the Master Partner Parameters tab on the Partner (Vendor/Customer) form. All fields are implemented as custom fields on either the Partner record or a child custom record linked one-to-one per active agreement.
Implementation approach
Create a Custom Record type called Partner Agreement Parameters (one record per partner agreement) linked via a custom field to the Partner record. This allows version history and amendment tracking. Surface this record in the Master Partner Parameters subtab on the Partner form using a Custom Sublist. The NetSuite internal ID convention used below follows custrecord_hyp_<field>.
Agreement Identification
All Partners| Field Name | NS Internal ID | Type | Options / Default | Notes |
|---|---|---|---|---|
| Agreement Type | custrecord_hyp_agmt_type | Strategic Partner | Partner Referral | Determines which exhibits and calc rules apply | |
| Agreement Template Version | custrecord_hyp_agmt_tmpl_ver | HYP-OMNI-03212024 | HYP-OMNI-v5-01262025 | Custom | Tracks which template version was used — affects Exhibit B logic (Gap G10) | |
| Agreement Reference / ID | custrecord_hyp_agmt_ref | Text | e.g. HYP-OMNI-ACME-04012024 | |
| Effective Date | custrecord_hyp_agmt_eff_date | Date | ||
| Expiration Date | custrecord_hyp_agmt_exp_date | Date | ||
| Agreement Status | custrecord_hyp_agmt_status | Active | Expired | Terminated | Pending | Suspended | ||
| Termination Date | custrecord_hyp_agmt_term_date | Date | Set when agreement terminates | |
| Termination Type | custrecord_hyp_agmt_term_type | Convenience (§4.2) | For Cause (§4.4) | Immediate (§4.5) | Non-Renewal | Drives post-term residual continuation (Gap G8) | |
| Post-Term Residual Continues | custrecord_hyp_postterm_residual | Boolean | Default: No | Auto-set by SuiteScript: Yes only when Termination Type = Convenience (§4.2) |
| Post-Termination Hold Days | custrecord_hyp_final_hold_days | Integer | Default: 210 | 210-day final payment hold (Gap G6) |
| Bidirectional Enabled | custrecord_hyp_bidirectional | Boolean | Default: No | Enables Exhibit B/E Partner-as-Master-Agent direction |
Active Exhibits (Gap G1)
Gap Addressed| Field Name | NS Internal ID | Type | Notes |
|---|---|---|---|
| Exhibit A Active — Supplier referral terms | custrecord_hyp_exh_a | Boolean | Mandatory per agreement; default Yes |
| Exhibit B Active — Revenue share | custrecord_hyp_exh_b | Boolean | Mandatory; default Yes |
| Exhibit C.1 Active — HYP-billed products | custrecord_hyp_exh_c1 | Boolean | Optional |
| Exhibit D Active — VAST Smart SIM | custrecord_hyp_exh_d | Boolean | Optional |
| Exhibit D.1 Active — VAST Track New v6 | custrecord_hyp_exh_d1 | Boolean | Optional; v6 only — hide on v1 template forms |
| Exhibit E Active — HYP-generated lead | custrecord_hyp_exh_e | Boolean | Optional |
| Exhibit F Active — Partner-generated lead | custrecord_hyp_exh_f | Boolean | Optional |
| Exhibit H Active — Master Services Agreement | custrecord_hyp_exh_h | Boolean | Optional |
Exhibit B — Carrier Revenue Share (Upfront / Residual)
Gaps G2, G3, G4, G5| Field Name | NS Internal ID | Type | Options / Default | Notes |
|---|---|---|---|---|
| Commission Structure Type | custrecord_hyp_b_struct | Tiered (6MRA) | Flat Rate | Custom | Gap G2 — drives which calc path is used | |
| Exhibit B Language Variant | custrecord_hyp_b_lang | Standard Tiered (v1) | Flat Rate Primary (v6) | Alt Tiered (v6) | Gap G10 | |
| Flat Rate Pass-Through % | custrecord_hyp_b_flat_pct | Percent | Default: 50 | Active when Structure = Flat Rate; v6 default |
| SELECT Tier Rate % | custrecord_hyp_b_tier_select | Percent | Default: 50 | Active when Structure = Tiered; 6MRA < $7,500 |
| PREMIER Tier Rate % | custrecord_hyp_b_tier_premier | Percent | Default: 60 | 6MRA $7,500–$37,499 |
| ELITE Tier Rate % | custrecord_hyp_b_tier_elite | Percent | Default: 70 | 6MRA $37,500–$149,999 |
| TITAN Tier Rate % | custrecord_hyp_b_tier_titan | Percent | Default: 80 | 6MRA ≥ $150,000 |
| Current Tier | custrecord_hyp_b_cur_tier | SELECT | PREMIER | ELITE | TITAN | Gap G4 — updated monthly by scheduled script | |
| Tier Eligibility Start Date | custrecord_hyp_b_tier_start | Date | 7th month after agreement execution; Gap G4 | |
| Current 6MRA ($) | custrecord_hyp_b_6mra | Currency | Calculated field updated by scheduled script; Gap G4 | |
| Tier Last Evaluated | custrecord_hyp_b_tier_eval_dt | Date | Date of last tier assignment; Gap G4 | |
| SPIFF Included in Pass-Through | custrecord_hyp_b_spiff_passthru | Boolean | Default: No | Gap G3 — Exhibit B explicitly excludes SPIFFs |
| SPIFF Rate % (if separate) | custrecord_hyp_b_spiff_rate | Percent | Gap G3 — applicable only when SPIFFs paid separately | |
| Withholding Threshold Multiplier | custrecord_hyp_b_withhold_thresh | Percent | Default: 300 | Gap G5 — Exhibit A §7.3: if period ESCR ≥ X% of run rate, withhold applies |
| Withholding Hold % | custrecord_hyp_b_withhold_pct | Percent | Default: 20 | Gap G5 — % of commission withheld pending chargeback period |
| Minimum Commission Period ($) | custrecord_hyp_b_min_pmt | Currency | Exhibit A §3.8 — no payment obligation if amount below threshold (check agreement) |
Exhibit C.1 — HYP-Billed Products / Services Referral
Optional Exhibit| Field Name | NS Internal ID | Type | Default | Notes |
|---|---|---|---|---|
| C.1 Referral Fee % | custrecord_hyp_c1_fee_pct | Percent | 10 | 10% of Net Sales Proceeds |
| C.1 Calculation Basis | custrecord_hyp_c1_basis | Net Proceeds | Gross minus taxes, shipping, fees, processing | |
| C.1 Payment Terms (days) | custrecord_hyp_c1_pay_terms | Integer | 30 | Net-30 from HYP acceptance of partner invoice |
| C.1 Custom Comp Note | custrecord_hyp_c1_custom_note | Text Area | Exhibits §15 — custom % for discounted SRP transactions |
Exhibit D / D.1 — VAST (Smart SIM + Track)
Exhibit D.1 is Gap G7| Field Name | NS Internal ID | Type | Default | Notes |
|---|---|---|---|---|
| VAST SIM Commission % | custrecord_hyp_d_sim_pct | Percent | 10 | Exhibit D §3.3.1 — 10% of VAST Proceeds (recurring) |
| VAST SIM Discounted Rate | custrecord_hyp_d_sim_disc_pct | Percent | Custom rate for discounted SIM; left blank until agreed | |
| VAST Track Active v6 | custrecord_hyp_d1_active | Boolean | No | Gap G7 |
| VAST Track Commission Amount ($) | custrecord_hyp_d1_flat_amt | Currency | Gap G7 — per-unit flat dollar; negotiated blank in template | |
| VAST Track Registration Method | custrecord_hyp_d1_reg_method | Discount Code | Discount Code | Registered Lead (Exhibit D.1 §1.1) |
Exhibit E & F — Bidirectional Referral Commissions
Gap G9 Addressed| Field Name | NS Internal ID | Type | Notes |
|---|---|---|---|
| Exhibit E — One-Time Commission % (to HYP) | custrecord_hyp_e_onetime_pct | Percent | % of Gross Revenue; blank in template — fill at signing |
| Exhibit E — Recurring Commission % (to HYP) | custrecord_hyp_e_recur_pct | Percent | % of Gross Revenue for MRC transactions |
| Exhibit E — Eligible Products | custrecord_hyp_e_elig_prod | Text Area | Partner products/services in scope; blank in template |
| Exhibit E — Ineligible Products | custrecord_hyp_e_inelig_prod | Text Area | Exclusions |
| Exhibit F — One-Time Commission % (to Partner) | custrecord_hyp_f_onetime_pct | Percent | % of Gross Revenue; blank in template — fill at signing |
| Exhibit F — Recurring Commission % (to Partner) | custrecord_hyp_f_recur_pct | Percent | % of Gross Revenue for MRC transactions |
| Exhibit F — Eligible Products | custrecord_hyp_f_elig_prod | Text Area | HYP products/services in scope |
| Exhibit F — Ineligible Products | custrecord_hyp_f_inelig_prod | Text Area | Exclusions |
Exception / Override Parameters
All Partners| Field Name | NS Internal ID | Type | Notes |
|---|---|---|---|
| Exception Applies | custrecord_hyp_exc_active | Boolean | Triggers display of exception sub-fields |
| Exception Type | custrecord_hyp_exc_type | Partner-Level | Deal/Opportunity | Inside Seller | |
| Exception Authorized By | custrecord_hyp_exc_auth | Text | Name/role of approver |
| Exception Effective Date | custrecord_hyp_exc_eff_date | Date | |
| Exception Expiry Date | custrecord_hyp_exc_exp_date | Date | |
| Exception Notes | custrecord_hyp_exc_notes | Text Area | Describe deviation from standard terms |
Section 3
PostgreSQL Schema
The PostgreSQL database stores the authoritative compensation parameters (synced from NetSuite) and performs all compensation calculations. NetSuite is the system of record for parameters; PostgreSQL is the calculation and payment engine.
-- ============================================================ -- HYPERION PARTNER COMPENSATION — PostgreSQL Schema -- ============================================================ -- Partners (master record, synced from NetSuite Partner/Vendor) CREATE TABLE partners ( partner_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), ns_internal_id INTEGER UNIQUE NOT NULL, -- NetSuite internalid entity_name TEXT NOT NULL, legal_address TEXT, status TEXT NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() ); -- Partner Agreement Parameters (one row per active agreement) CREATE TABLE partner_agreement_params ( param_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), partner_id UUID REFERENCES partners(partner_id) ON DELETE CASCADE, ns_record_id INTEGER UNIQUE, -- NS custom record internalid -- Agreement identification agreement_type TEXT NOT NULL, -- 'strategic_partner' | 'partner_referral' agreement_template_ver TEXT, agreement_reference TEXT, effective_date DATE, expiration_date DATE, agreement_status TEXT DEFAULT 'active', termination_date DATE, termination_type TEXT, postterm_residual_continues BOOLEAN DEFAULT false, postterm_hold_days INTEGER DEFAULT 210, bidirectional_enabled BOOLEAN DEFAULT false, -- Active exhibits exhibit_a_active BOOLEAN DEFAULT true, exhibit_b_active BOOLEAN DEFAULT true, exhibit_c1_active BOOLEAN DEFAULT false, exhibit_d_active BOOLEAN DEFAULT false, exhibit_d1_active BOOLEAN DEFAULT false, -- v6 only exhibit_e_active BOOLEAN DEFAULT false, exhibit_f_active BOOLEAN DEFAULT false, exhibit_h_active BOOLEAN DEFAULT false, -- Exhibit B — carrier revenue share b_commission_struct TEXT, -- 'tiered' | 'flat' | 'custom' b_lang_variant TEXT, b_flat_rate_pct NUMERIC(5,2), -- e.g. 50.00 b_tier_select_pct NUMERIC(5,2) DEFAULT 50, b_tier_premier_pct NUMERIC(5,2) DEFAULT 60, b_tier_elite_pct NUMERIC(5,2) DEFAULT 70, b_tier_titan_pct NUMERIC(5,2) DEFAULT 80, b_current_tier TEXT, -- 'SELECT' | 'PREMIER' | 'ELITE' | 'TITAN' b_tier_elig_start_date DATE, b_current_6mra NUMERIC(12,2), b_tier_last_evaluated DATE, b_spiff_in_passthru BOOLEAN DEFAULT false, b_spiff_rate_pct NUMERIC(5,2), b_withhold_thresh_mult NUMERIC(5,2) DEFAULT 300, -- 300% trigger b_withhold_hold_pct NUMERIC(5,2) DEFAULT 20, b_min_payment_amt NUMERIC(12,2), -- Exhibit C.1 — HYP-billed products c1_fee_pct NUMERIC(5,2) DEFAULT 10, c1_basis TEXT DEFAULT 'net', c1_payment_terms_days INTEGER DEFAULT 30, c1_custom_note TEXT, -- Exhibit D — VAST Smart SIM d_sim_pct NUMERIC(5,2) DEFAULT 10, d_sim_discounted_pct NUMERIC(5,2), -- Exhibit D.1 — VAST Track (v6) d1_flat_commission_amt NUMERIC(12,2), d1_reg_method TEXT, -- 'discount_code' | 'registered_lead' -- Exhibit E — HYP-generated lead (commission to HYP) e_onetime_pct NUMERIC(5,2), e_recurring_pct NUMERIC(5,2), e_eligible_products TEXT, e_ineligible_products TEXT, -- Exhibit F — Partner-generated lead (commission to Partner) f_onetime_pct NUMERIC(5,2), f_recurring_pct NUMERIC(5,2), f_eligible_products TEXT, f_ineligible_products TEXT, -- Exception / override exception_active BOOLEAN DEFAULT false, exception_type TEXT, exception_authorized_by TEXT, exception_eff_date DATE, exception_exp_date DATE, exception_notes TEXT, -- Sync bookkeeping ns_last_modified TIMESTAMPTZ, pg_synced_at TIMESTAMPTZ DEFAULT now(), sync_version INTEGER DEFAULT 1 ); -- Audit trail for all parameter changes CREATE TABLE parameter_audit_log ( log_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), param_id UUID REFERENCES partner_agreement_params(param_id), changed_at TIMESTAMPTZ DEFAULT now(), changed_by TEXT, -- 'netsuite_sync' | 'manual' | 'scheduled_script' field_name TEXT NOT NULL, old_value TEXT, new_value TEXT, source_ref TEXT -- NS record ID or change order ref ); -- Compensation calculations (results, not parameters) CREATE TABLE compensation_calculations ( calc_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), partner_id UUID REFERENCES partners(partner_id), param_id UUID REFERENCES partner_agreement_params(param_id), commission_period DATE NOT NULL, -- first day of month exhibit TEXT NOT NULL, -- 'B' | 'C1' | 'D' | 'D1' | 'E' | 'F' carrier TEXT, gross_revenue NUMERIC(14,2), net_revenue NUMERIC(14,2), escr NUMERIC(14,2), applied_rate_pct NUMERIC(5,2), applied_tier TEXT, comp_type TEXT, -- 'upfront' | 'residual' | 'spiff' | 'vast' | 'referral' gross_comp NUMERIC(14,2), withholding_amt NUMERIC(14,2) DEFAULT 0, chargeback_offset NUMERIC(14,2) DEFAULT 0, net_payable NUMERIC(14,2), payment_status TEXT DEFAULT 'pending', calculated_at TIMESTAMPTZ DEFAULT now() ); -- Index for common queries CREATE INDEX idx_calc_partner_period ON compensation_calculations(partner_id, commission_period); CREATE INDEX idx_params_ns_id ON partner_agreement_params(ns_record_id);
Section 4
Sync Architecture — NetSuite ↔ PostgreSQL
Parameters are authored and governed in NetSuite (source of record). PostgreSQL receives them via a push-on-save mechanism and a nightly reconciliation batch. Calculations flow in the opposite direction for visibility in NetSuite.
Partner Record
Custom Record
for Parameters
AfterSubmit
(SuiteScript 2.x)
Node.js / Python
REST Microservice
+ Validation
with HMAC
auth
partner_agreement
_params
+ History
Option A — Push on Save (Recommended for real-time)
A SuiteScript 2.x UserEvent Script fires after any save of the Partner Agreement Parameters custom record. It calls a RESTlet (deployed on NetSuite) or an external HTTPS endpoint that writes to PostgreSQL.
// SuiteScript 2.x — UserEvent AfterSubmit // File: hyp_ue_partner_params_sync.js define(['N/https', 'N/record', 'N/log'], function(https, record, log) { function afterSubmit(context) { if (context.type === context.UserEventType.DELETE) return; var rec = context.newRecord; var payload = { ns_record_id: rec.id, partner_ns_id: rec.getValue('custrecord_hyp_partner'), agreement_type: rec.getValue('custrecord_hyp_agmt_type'), agreement_template_ver: rec.getValue('custrecord_hyp_agmt_tmpl_ver'), agreement_reference: rec.getValue('custrecord_hyp_agmt_ref'), effective_date: rec.getValue('custrecord_hyp_agmt_eff_date'), exhibit_b_active: rec.getValue('custrecord_hyp_exh_b'), b_commission_struct: rec.getValue('custrecord_hyp_b_struct'), b_flat_rate_pct: rec.getValue('custrecord_hyp_b_flat_pct'), b_current_tier: rec.getValue('custrecord_hyp_b_cur_tier'), b_spiff_in_passthru: rec.getValue('custrecord_hyp_b_spiff_passthru'), b_withhold_thresh_mult: rec.getValue('custrecord_hyp_b_withhold_thresh'), b_withhold_hold_pct: rec.getValue('custrecord_hyp_b_withhold_pct'), exhibit_d1_active: rec.getValue('custrecord_hyp_exh_d1'), d1_flat_commission_amt: rec.getValue('custrecord_hyp_d1_flat_amt'), // ... all other fields ... ns_last_modified: new Date().toISOString() }; var response = https.post({ url: 'https://your-sync-api.internal/api/partner-params/upsert', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + runtime.getCurrentScript() .getParameter('custscript_hyp_sync_token') } }); log.audit('ParamSync', 'Status ' + response.code + ' for record ' + rec.id); if (response.code !== 200) { log.error('SyncFailed', response.body); // Queue for retry via custom record } } return { afterSubmit: afterSubmit }; });
Sync API — Upsert Endpoint (Node.js/Express example)
// POST /api/partner-params/upsert // Receives NS payload, transforms, upserts into PostgreSQL app.post('/api/partner-params/upsert', verifyHmac, async (req, res) => { const p = req.body; const sql = ` INSERT INTO partner_agreement_params ( ns_record_id, partner_id, agreement_type, agreement_template_ver, effective_date, exhibit_b_active, b_commission_struct, b_flat_rate_pct, b_current_tier, b_spiff_in_passthru, b_withhold_thresh_mult, b_withhold_hold_pct, exhibit_d1_active, d1_flat_commission_amt, ns_last_modified, pg_synced_at ) VALUES ($1, (SELECT partner_id FROM partners WHERE ns_internal_id=$2), $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, now()) ON CONFLICT (ns_record_id) DO UPDATE SET agreement_type = EXCLUDED.agreement_type, b_flat_rate_pct = EXCLUDED.b_flat_rate_pct, b_current_tier = EXCLUDED.b_current_tier, b_withhold_thresh_mult = EXCLUDED.b_withhold_thresh_mult, exhibit_d1_active = EXCLUDED.exhibit_d1_active, d1_flat_commission_amt = EXCLUDED.d1_flat_commission_amt, ns_last_modified = EXCLUDED.ns_last_modified, pg_synced_at = now(), sync_version = partner_agreement_params.sync_version + 1 RETURNING param_id, sync_version`; await db.query(sql, [p.ns_record_id, p.partner_ns_id, p.agreement_type, ...]); await logAuditDiff(p); // write field-level diffs to parameter_audit_log res.json({ ok: true }); });
Option B — Nightly Reconciliation Batch (Safety net)
A NetSuite Scheduled Script runs nightly and exports all custrecord_hyp_partner_params records modified since last sync. This catches any saves that missed the UserEvent push (e.g., CSV imports, bulk updates).
// SuiteScript 2.x Scheduled Script — nightly delta export // Runs at 2 AM; queries NS saved search, pushes deltas to PG define(['N/search', 'N/https'], function(search, https) { function execute(context) { var s = search.create({ type: 'customrecord_hyp_partner_params', filters: [['lastmodified', 'onorafter', 'yesterday']], // or use a stored last-run timestamp columns: [/* all custrecord_ fields */] }); var batch = []; s.run().each(function(result) { batch.push(mapResultToPayload(result)); if (batch.length >= 50) { flushBatch(batch); batch = []; } return true; }); if (batch.length) flushBatch(batch); } return { execute: execute }; });
Option C — PostgreSQL → NetSuite (write-back)
For calculated fields (Current 6MRA, Current Tier, Last Tier Evaluated), PostgreSQL pushes results back to NetSuite after each monthly calculation run, using the NetSuite REST API:
# Python — write 6MRA + tier back to NS custom record after monthly calc import requests, json def write_tier_to_netsuite(ns_record_id: int, tier: str, sixmra: float, eval_date: str): url = (f"https://<account>.suitetalk.api.netsuite.com" f"/services/rest/record/v1" f"/customrecord_hyp_partner_params/{ns_record_id}") payload = { "custrecord_hyp_b_cur_tier": tier, "custrecord_hyp_b_6mra": sixmra, "custrecord_hyp_b_tier_last_eval": eval_date } resp = requests.patch(url, json=payload, auth=OAuth1(consumer_key, consumer_secret, token, token_secret), headers={"Content-Type": "application/json"}) resp.raise_for_status()
Section 5
Implementation Plan
Recommended sequence across four phases. Phases 1–2 can be run in parallel after alignment; Phase 3 requires Phase 1 custom records to exist in NetSuite; Phase 4 requires Phase 3 endpoints to be live.
Phase 1 — NetSuite Configuration
-
Create Custom Record Type:
Partner Agreement ParametersIn NetSuite: Setup → Customization → Record Types → New. Name: "Partner Agreement Parameters"; Internal ID:customrecord_hyp_partner_params. Add a parent relationship field (custrecord_hyp_partner) linking to the Partner/Vendor record. Set "Show in List" and "Allow Quick Add" as appropriate. Enable versioning via async_versioninteger field. -
Add all custom fields per the specification in Section 2Create each field listed in the Master Partner Parameters spec above. Group them by exhibit section using field groups. Use List/Record type for all enumerated fields to enforce valid values. For Exhibit D.1 fields, set a display condition: show only when Agreement Template Version = v6 or Exhibit D.1 Active = true.
-
Build the "Master Partner Parameters" subtab on the Partner formNavigate to Customization → Forms → Partner Form → Subtabs. Add a new subtab labeled "Master Partner Parameters." Use a Custom Sublist to display all
customrecord_hyp_partner_paramsrecords linked to the current partner. This allows viewing all agreement versions in one place. -
Create validation SuiteScript (UserEvent BeforeSubmit)Write a BeforeSubmit script to: (a) enforce that Exhibit A is always active if Exhibit B is active; (b) require VAST Track Commission Amount when Exhibit D.1 is activated; (c) auto-set Post-Term Residual Continues based on Termination Type; (d) calculate Tier Eligibility Start Date as Effective Date + 6 months. Deploy to the custom record type.
-
Deploy UserEvent AfterSubmit sync scriptDeploy the SuiteScript push-on-save shown in Section 4. Store the sync API token in a Script Parameter (Setup → Script → Deployment → Parameters). Set governance usage to ≤100 units. Test with a sandbox partner record first.
-
Create Saved Search for nightly delta exportCreate a saved search on the custom record type filtering
Last Modified on or after {yesterday}with all compensation fields as columns. This saved search is used by both the nightly Scheduled Script and as an ODBC data source for reporting.
Phase 2 — PostgreSQL Setup
-
Deploy the schema from Section 3Run the DDL script in your PostgreSQL instance (14+ recommended for
gen_random_uuid()default). Enablepg_auditextension for row-level auditing, or use theparameter_audit_logtable with triggers. Create a dedicated database userhyp_comp_apiwith INSERT/UPDATE/SELECT on the three tables and no DDL access. -
Build the Sync API microserviceDeploy a lightweight Node.js (Express) or Python (FastAPI) service with two endpoints:
POST /api/partner-params/upsert(single record) andPOST /api/partner-params/batch(up to 50 records). Implement HMAC-SHA256 request verification using a shared secret stored in NetSuite Script Parameters and in the microservice's environment variables. -
Implement audit diff loggingIn the upsert endpoint, before executing the INSERT … ON CONFLICT, SELECT the current row and compute field-level diffs. Insert one row per changed field into
parameter_audit_login the same transaction. This provides a complete amendment history aligned with Exhibit G (Change Requests). -
Write the 6MRA calculation functionCreate a PostgreSQL function
calculate_6mra(partner_id UUID, as_of_date DATE)that queries the last 6 months of ESCR fromcompensation_calculations, computes the average, determines the applicable tier, and updatesb_current_tier,b_current_6mra, andb_tier_last_evaluatedinpartner_agreement_params. Schedule this as a monthlypg_cronjob or call it from the nightly batch.
Phase 3 — Initial Data Load
-
Map existing partner agreements to the new field structureFor each active partner, review the executed agreement to identify: which template version was used (v1 or v6), which exhibits are active, and the agreed rates. Particularly check Exhibit B for tiered vs flat structure and Exhibit E/F for any filled-in eligible product lists. Document any partners with custom rates.
-
Bulk-load existing partners via NetSuite CSV ImportUse NetSuite's CSV Import → Custom Records to load all partners' agreement parameters. The import maps spreadsheet columns to
custrecord_hyp_field IDs. Validate that each record triggers the AfterSubmit sync script (check Sync API logs) or run the nightly batch manually after import. -
Backfill historical ESCR data into
compensation_calculationsFor tiered partners, backfill historical monthly ESCR from your existing system into PostgreSQL. This allows the 6MRA function to calculate accurate current tiers at go-live rather than resetting all partners to SELECT tier.
Phase 4 — Ongoing Operations
-
Run monthly compensation calculationCall
calculate_6mra()for all tiered partners at the start of each commission period. Then run the compensation calculation job, which readspartner_agreement_params, applies the applicable rate (tier or flat), applies the withholding check (300% rule), and writes results tocompensation_calculations. -
Push tier updates back to NetSuiteAfter each monthly 6MRA calculation, use the NetSuite REST API PATCH call (Python example in Section 4) to update
custrecord_hyp_b_cur_tier,custrecord_hyp_b_6mra, andcustrecord_hyp_b_tier_eval_dton the partner's NS record. This ensures the NS form always shows current tier for the Channel team's reference. -
Monitor sync healthBuild a reconciliation query: compare
COUNT(*)of NS custom records (via ODBC or Scheduled Script export) against PostgreSQL rows byns_record_id. Alert if any NS record hasns_last_modified > pg_synced_at + 5 minutes— that record missed the real-time push and will need the nightly batch to catch it. Log to async_healthtable and expose a dashboard. -
Process Exhibit G change requestsWhen a partner signs a Change Order (Exhibit G), update the relevant fields on the NS custom record. The AfterSubmit script auto-pushes the delta to PostgreSQL. The audit log captures the old and new values with a reference to the Change Order number (store in
exception_notesor a dedicatedchange_order_reffield). Future amendments automatically version viasync_versionincrement.
Key risk: Exhibit E/F blanks
Many existing agreements have blank eligible/ineligible product fields in Exhibits E and F. Before enabling compensation calculations for those exhibits, ensure the Channel team has completed these fields for all active partners. Consider a NetSuite validation rule that prevents Exhibit E/F from being marked Active unless the eligible products field is populated.
VAST Track (Exhibit D.1) migration note
Partners currently on v1 agreements do not have Exhibit D.1. When re-papering or amending to v6, Exhibit D.1 will need to be evaluated. The VAST Track Commission Amount ($) is a negotiated blank in the template — ensure this is captured before setting Exhibit D.1 Active = true, or the calculation engine will produce zero compensation for VAST Track transactions.