POS & ERP Sync Patterns
In vendor rebate and trade promotion reconciliation, financial accuracy depends on the deterministic alignment between two record systems that were never designed to agree. The point-of-sale estate reports sell-through — what physically scanned at the register, store by store, day by day — while the ERP reports sell-in: the purchase orders, vendor invoices, and ledger postings that move money. POS and ERP sync patterns are the sub-problem of keeping those two planes coherent in time so that an accrual computed against sell-through can be defended against the sell-in the ledger actually booked. When that synchronization degrades, the failures are predictable and expensive: duplicate accruals on re-sent files, promotional volume matched against sales dated outside the deal window, and vendor disputes that surface weeks after month-end with no record of which extract produced the disputed number.
This page is one of the implementation areas under data ingestion normalization pipelines, the upstream layer that turns heterogeneous feeds into typed, reconciliation-ready records. It specifies the sync envelope schema that anchors every extract, the watermark and change-data-capture logic that bridges the temporal mismatch between POS and ERP cadences, the decimal-safe settlement handoff, the idempotent ETL patterns that make a re-run a no-op, and the drift-detection, dispute-routing, and access controls that let trade finance defend every figure under audit.
Positioning within the ingestion pipeline
Sync sits at the seam between extraction and reconciliation. The raw payloads themselves are parsed and typed elsewhere — file-based feeds flow through CSV & EDI Parsing Workflows, and the projection of those typed records onto a canonical reconciliation model is governed by Field Mapping Strategies. What this layer owns is temporal coherence: ensuring that when the reconciliation engine joins a POS sell-through line to an ERP sell-in posting, both sides represent the same business period, the same canonical product, and the same version of the truth.
That responsibility runs strictly before any downstream financial logic. Clean, time-aligned records are what the core architecture promotion mapping layer maps onto agreements, and what the claim validation rule engine later adjudicates a vendor claim against. If sync is loose — if an intraday extract overwrites a more complete nightly one, or a late void never propagates to the ledger view — the error is baked in before the first rule fires. POS systems aggregate at store or regional levels on their own cadence; ERPs post on fixed ledger cycles. A decoupled staging layer that isolates extraction from reconciliation is what absorbs that cadence mismatch without blocking operational throughput, and it is where high-volume nightly or intraday dumps are processed without backpressure during peak retail windows. The asynchronous execution underneath this staging layer is detailed in Async Batch Processing.
Entity topology and schema specification
Every sync run is itself a first-class, versioned entity — not an anonymous job. Two record families anchor the layer: the sync envelope, which records what was pulled and how far, and the reconciliation fact, the time-aligned row the join operates on. Modeling the envelope explicitly is what makes a re-pull safe: the high-water mark, the source cursor, and the content hash together let the pipeline prove a batch was already consumed rather than re-applying it blindly. The table below specifies the minimum contract the sync layer emits.
| Field | Type | Entity | Constraint |
|---|---|---|---|
sync_id |
str (ULID) |
SyncEnvelope | immutable, primary key |
source_system |
enum |
SyncEnvelope | pos | erp, required |
extract_kind |
enum |
SyncEnvelope | bulk | delta, required |
watermark_from / watermark_to |
datetime (UTC) |
SyncEnvelope | half-open [from, to) cursor interval |
source_cursor |
str |
SyncEnvelope | CDC log sequence number or extract offset |
payload_hash |
str (sha256) |
SyncEnvelope | over the raw batch, dedupe anchor |
parser_version |
str (semver) |
SyncEnvelope | pins the schema that read the batch |
canonical_sku |
str |
ReconFact | resolved key, never the raw POS/ERP code |
period_key |
str |
ReconFact | fiscal period the row anchors to |
qty_sellthrough |
Decimal |
ReconFact | >= 0, scale 4, POS plane |
amount_sellin |
Decimal |
ReconFact | scale 2, ERP plane, settlement currency |
currency |
str (ISO 4217) |
ReconFact | required |
recon_key |
str (sha256) |
ReconFact | over (canonical_sku, period_key, partner, source_doc) |
as_of |
datetime (UTC) |
ReconFact | version timestamp for late-arrival resolution |
Two design choices carry the schema. First, the watermark interval is half-open ([watermark_from, watermark_to)): adjacent bulk extracts abut without overlap, so no transaction is double-counted at a period boundary and none is skipped. Second, every reconciliation fact is versioned by as_of rather than mutated in place. POS and ERP routinely disagree about when a transaction happened — a sale scans on the 31st but posts to the ledger on the 1st — so a row is never overwritten; a newer as_of supersedes an older one, and the prior version is retained for replay and audit. The parser_version and source_cursor together let the pipeline reconstruct exactly which extract produced any given fact, which is the lineage auditors ask for first.
Conditional logic and rule integration
The defining problem of POS-ERP sync is reconciling two cadences. POS feeds arrive as high-volume nightly or intraday bulk dumps; ERPs post on fixed ledger cycles; and a steady trickle of mid-cycle corrections — voids, price overrides, retroactive promotional applications — arrives out of band. A robust pattern runs two planes rather than one. The bulk plane advances a high-water mark and pulls everything in the new [from, to) window. The delta plane captures point corrections via change-data-capture log sequence numbers or webhook payloads, applied as superseding versions rather than as fresh inserts.
Resolution is keyed on the canonical product and the fiscal period, not on raw codes or wall-clock timestamps. A POS line is only joined to an ERP posting when both resolve to the same canonical_sku and the same period_key, and temporal alignment is what decides the period: a transaction’s POS scan date is mapped to the ERP posting period through a calendar-aware rule so accruals fall in the correct fiscal quarter. Promotional scope layers on top — the discount codes, BOGO indicators, and markdown flags a POS line carries are matched to promotion identifiers defined upstream, and a row whose period falls outside the deal’s active window is short-circuited before it reaches settlement, exactly as the eligibility rule framework gates qualifying volume. This ordering lets the engine treat the recon_key as a cache key: an out-of-period or out-of-scope row never reaches accrual arithmetic.
Financial settlement layer
The settlement responsibility of this layer is narrow but absolute: hand the reconciliation engine a correct, time-aligned pairing of sell-through quantity and sell-in amount, in the agreement’s settlement currency, so that downstream tier math operates on real figures. Sync errors translate directly into money. An intraday extract applied over a more complete nightly one understates sell-through and underpays the rebate; a late void that never propagates leaves phantom volume that overpays it.
All quantity-to-currency arithmetic in the handoff uses decimal.Decimal. Float accumulation across hundreds of thousands of POS lines drifts by cents and fails the variance reconciliation that finance runs at close.
from decimal import Decimal, ROUND_HALF_UP
def accrual_variance(sellthrough_qty: Decimal, per_unit_rate: Decimal,
booked_sellin: Decimal) -> Decimal:
"""Return the signed cent-level gap between POS-derived accrual and the
ERP-booked sell-in amount. Both operands are Decimal so the variance is
exact and survives cent-level audit reconciliation.
"""
derived = (sellthrough_qty * per_unit_rate).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP
)
return derived - booked_sellin
Currency is normalized to the settlement currency upstream so no sync rule performs conversion inline. Tier boundary arithmetic itself is not this layer’s job — it belongs to payout structure modeling — but the integrity of the inputs that math consumes is. A correctly aligned sell-through/sell-in pair is the difference between an accrual that clears straight through and one that becomes a deduction dispute.
ETL implementation patterns
Resilient sync ETL rests on three properties: idempotency under replay, chunked processing that survives volume spikes, and explicit state in the sync envelope rather than in operator memory. The envelope is validated on the way in with Pydantic v2, and the reconciliation fact carries a computed recon_key that becomes the upsert target.
from datetime import datetime
from decimal import Decimal
from enum import Enum
from hashlib import sha256
from pydantic import BaseModel, Field, computed_field, model_validator
class SourceSystem(str, Enum):
pos = "pos"
erp = "erp"
class ReconFact(BaseModel):
canonical_sku: str = Field(min_length=1)
period_key: str
partner_id: str
source_doc: str
qty_sellthrough: Decimal = Field(ge=0)
amount_sellin: Decimal
currency: str = Field(pattern=r"^[A-Z]{3}$")
as_of: datetime
@model_validator(mode="after")
def _resolved(self) -> "ReconFact":
if not self.canonical_sku:
raise ValueError("canonical_sku must be resolved before sync")
return self
@computed_field
@property
def recon_key(self) -> str:
payload = "|".join((
self.canonical_sku, self.period_key,
self.partner_id, self.source_doc,
))
return sha256(payload.encode()).hexdigest()
The upsert key is the recon_key. Because the hash folds in the canonical SKU, the fiscal period, the trading partner, and the source document, a re-ingested batch maps to the same target rows and the write becomes a no-op — the pipeline is idempotent under replay. Late-arriving corrections are resolved by as_of: the warehouse MERGE (or UPSERT) only overwrites a target row when the incoming as_of is newer, so an out-of-order delta never regresses the fact to a stale version. In-memory transformation uses pandas or polars, but batch sizes are capped so a holiday-volume extract cannot exhaust memory; the high-water mark makes resumable chunking safe because each chunk advances the cursor only after a successful commit. Schema evolution follows the same discipline as agreement schema design: new optional fields default safely, and any field feeding the recon_key is versioned rather than mutated. The store-level extraction that feeds this loop is implemented in Automating POS data extraction for CPG.
Drift detection and validation
A correct sync still degrades when an upstream cadence shifts. Drift detection monitors sync lag (the gap between a POS business date and the time its fact lands), the rate of late-arriving deltas, and the accrual variance between the POS-derived figure and the ERP sell-in. Each is compared against a rolling baseline, and a breach routes records to quarantine with an explicit code rather than failing the whole run, so clean records still settle on schedule.
Sync failures are inevitable; unmanaged failures are catastrophic. A multi-tier error taxonomy separates recoverable transport faults from structural data defects so each class is routed to the right owner.
| Error Tier | Classification | Resolution Path |
|---|---|---|
| Tier 1 | Transient network timeout, SFTP auth retry | Automated exponential backoff with circuit breaker |
| Tier 2 | Schema drift, missing mandatory fields | Quarantine to staging table, alert data steward |
| Tier 3 | Invalid GTIN, mismatched currency, negative quantities | Route to dead-letter queue, flag for vendor ops review |
| Tier 4 | Ledger mismatch, duplicate accrual trigger | Manual finance override, immutable audit entry |
Tier 1 failures self-heal. Tier 2 and 3 records feed vendor management dashboards so issues are raised before month-end close rather than after. A common Tier 4 signal is accrual drift beyond a configured threshold — for example a sustained variance above ±2% between POS sell-through and ERP sell-in — which alerts finance without halting the pipeline. Every drift decision writes a hash-based record of the input and output state so the resolution is reproducible.
Fallback and dispute routing
Not every batch reconciles programmatically. When a POS period has no corresponding ERP posting, or a delta arrives for a period that has already been frozen at close, the pipeline triggers fallback routing rather than accruing optimistically. The default policy is conservative: an unmatched or conflicting line is held at zero accrual in a quarantine table, never settled on a guess. Fallback chains prioritize the agreement’s documented terms, vendor-approved reconciliation exhibits, and historical precedent, and they inherit the same escalation contract as the platform-wide fallback routing logic.
Unresolved records route to a structured exception queue where vendor managers and trade finance analysts adjudicate, triaged by dollar impact rather than record count so the largest leakage risks clear first. Every override — an approved cross-period match, a manual variance acceptance, a corrected currency — is written to an immutable audit log with operator identity, the prior and new state hash, and a reason code. Once a correction is approved it is backfilled and the reconciliation job re-executes via delta processing, recomputing only the affected period partition rather than the full table. Decoupling hard transport failures from soft reconciliation exceptions keeps the accrual cycle moving while preserving a defensible trail.
Security and access boundaries
Sync data is financial control data and is protected accordingly. Sync envelopes, watermarks, and reconciliation facts are tagged for role-based access: trade finance analysts and vendor managers may inspect variances and propose corrections, but only a controller role may approve a manual variance acceptance or release a frozen period, enforced as a four-eyes check in the configuration store. Sync schedules, SLA windows, and threshold definitions flow through a configuration-as-code path — versioned in Git with pull-request review — so reconciliation parameters are never altered by direct database mutation.
Field-level controls protect the most sensitive attributes. Vendor-specific pricing and contractual terms carried on a sell-in posting are encrypted at rest, transport credentials for POS SFTP and ERP API connectors are held in a secrets store and rotated on a fixed schedule, and every read and write of a reconciliation fact is logged with actor, timestamp, and state hash. Raw, staged, and reconciled snapshots are retained for the full audit horizon — commonly seven years — so an auditor can trace any posted accrual back to the exact, signed extract that produced it.
Frequently asked questions
How does the pipeline stay idempotent when the same POS file is re-sent?
Every batch carries a payload_hash on its sync envelope and every fact a recon_key hashed over the canonical SKU, fiscal period, partner, and source document. The upsert target is keyed on that hash, so a re-sent file maps to the same target rows and the write is a no-op. Re-running the job over identical inputs produces identical output.
What happens to a transaction that scans in one period but posts to the ledger in the next?
The fact is anchored to a period_key derived from a calendar-aware rule, not from the raw timestamp, so it lands in the fiscal period the agreement settles against. Because facts are versioned by as_of, a late ledger posting arrives as a superseding version rather than a duplicate, and the prior version is retained for replay and audit.
How are mid-cycle corrections like voids and price overrides synced without a full re-pull?
They flow through the delta plane as change-data-capture events or webhook payloads, applied as superseding versions keyed on recon_key and resolved by as_of. Only the affected period partition is recomputed via delta processing; the bulk plane and its high-water mark are untouched.
When does an accrual variance stop the pipeline?
It does not stop the pipeline. A variance beyond the configured threshold — for example ±2% between POS sell-through and ERP sell-in — routes the affected records to quarantine with a Tier 4 code and raises a finance alert. Clean records continue to settle on schedule while the flagged variance is adjudicated.
Related
- Data Ingestion & Normalization Pipelines — the parent layer this sync pattern sits inside.
- CSV & EDI Parsing Workflows — parses the raw POS and EDI payloads before sync aligns them.
- Field Mapping Strategies — projects typed records onto the canonical model sync joins on.
- Async Batch Processing — the asynchronous execution layer beneath bulk sync.
- Automating POS data extraction for CPG — the store-level extraction step that feeds this loop.
Up one level: Data Ingestion & Normalization Pipelines.