Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
479be36
docs(sync): umbrella architecture spec for branch↔central sync
engahmed1190 Apr 5, 2026
e9411f9
docs(sync): add Plan 1 (Foundation) for branch↔central sync
engahmed1190 Apr 5, 2026
2ff6973
feat(sync): add Sync DocType Rule child doctype
engahmed1190 Apr 5, 2026
1473420
feat(sync): add Sync Sibling Branch child doctype
engahmed1190 Apr 5, 2026
165fd92
feat(sync): add Sync Site Config doctype with cardinality validation
engahmed1190 Apr 5, 2026
1ff83c5
refactor(sync): explicit cardinality filter + specific exception in test
engahmed1190 Apr 5, 2026
7ad8ce7
feat(sync): add POS_NEXT_SYNC_ALLOW_HTTP dev escape hatch
engahmed1190 Apr 5, 2026
81f8e55
feat(sync): add two-bench dev environment setup helpers
engahmed1190 Apr 6, 2026
97081a3
docs(sync): add dev environment topology and version-agnostic protoco…
engahmed1190 Apr 6, 2026
520deb6
feat(sync): add Sync Outbox with compaction on pending updates
engahmed1190 Apr 6, 2026
eab4a1d
feat(sync): add Sync Watermark and Sync Tombstone doctypes
engahmed1190 Apr 6, 2026
999abe5
feat(sync): add tracking doctypes (record state, field timestamp, con…
engahmed1190 Apr 6, 2026
0b76a0b
feat(sync): add defaults, exceptions, and payload helpers
engahmed1190 Apr 6, 2026
6ecc0ea
feat(sync): add BaseSyncAdapter and adapter registry
engahmed1190 Apr 6, 2026
2866fcd
feat(sync): add conflict resolution engine with 5 strategies
engahmed1190 Apr 6, 2026
779ea4b
feat(sync): add SyncSession auth + transport factory
engahmed1190 Apr 6, 2026
a0f1e42
feat(sync): add Test Sync Connection button on Sync Site Config form
engahmed1190 Apr 6, 2026
55471c7
feat(sync): install sync_uuid, origin_branch, synced_from_failover cu…
engahmed1190 Apr 6, 2026
b9552a1
feat(sync): backfill sync_uuid on existing transaction rows
engahmed1190 Apr 6, 2026
f796625
feat(sync): create POS Next Sync Agent role
engahmed1190 Apr 6, 2026
74c4b92
feat(sync): add default Sync DocType Rule seeds
engahmed1190 Apr 6, 2026
bab2753
feat(sync): auto-apply default rules when creating Sync Site Config
engahmed1190 Apr 6, 2026
e79b317
feat(sync): add sync custom fields and role to fixtures
engahmed1190 Apr 6, 2026
7fde7a6
feat(sync): auto-fill sync_uuid + origin_branch on before_insert
engahmed1190 Apr 6, 2026
807b445
test(sync): add full Plan 1 test-suite runner
engahmed1190 Apr 6, 2026
ff35efb
fix(sync): restore roles and permissions wiped by export-fixtures
engahmed1190 Apr 6, 2026
27ff317
test(sync): add cross-bench connectivity test
engahmed1190 Apr 6, 2026
c33dedc
docs(sync): add Masters Pull sub-spec (Plan 2)
engahmed1190 Apr 6, 2026
4b830cb
docs(sync): add Masters Pull implementation plan (Plan 2)
engahmed1190 Apr 6, 2026
b08922b
feat(sync): add changes_since API endpoint for masters pull
engahmed1190 Apr 6, 2026
dc15ad1
feat(sync): add health API endpoint
engahmed1190 Apr 6, 2026
da10065
feat(sync): add GenericMasterAdapter for simple masters
engahmed1190 Apr 6, 2026
7dcb2af
feat(sync): add ItemAdapter with child table and variant handling
engahmed1190 Apr 6, 2026
4072b4a
feat(sync): add ItemPriceAdapter with composite conflict key
engahmed1190 Apr 6, 2026
e4240f8
feat(sync): add CustomerAdapter with mobile_no dedup
engahmed1190 Apr 6, 2026
a63ecfc
feat(sync): add MastersPuller engine for branch-side masters pull
engahmed1190 Apr 6, 2026
5b62891
feat(sync): add tombstone on_trash hooks + masters pull scheduler
engahmed1190 Apr 6, 2026
219710c
test(sync): add Plan 2 test runner
engahmed1190 Apr 6, 2026
ced3952
test(sync): add end-to-end masters pull integration test
engahmed1190 Apr 6, 2026
768a4e4
test(sync): add full pull cycle e2e test with priority ordering
engahmed1190 Apr 6, 2026
9c0a707
fix(sync): bypass link/validate/mandatory checks on synced data
engahmed1190 Apr 6, 2026
e8b29b5
fix(sync): add ignore_conflict flag to bypass modified timestamp check
engahmed1190 Apr 6, 2026
beeb2fb
fix(sync): skip modified/owner fields in upsert to prevent conflict d…
engahmed1190 Apr 6, 2026
42490b5
chore(sync): add debug and watermark cleanup helpers
engahmed1190 Apr 6, 2026
2b66e7a
fix(sync): use db_update for sync upserts to bypass all hooks
engahmed1190 Apr 6, 2026
80dc72d
feat(sync): add Sync Status dashboard on Sync Site Config form
engahmed1190 Apr 6, 2026
ea2c904
chore: add sync log check helper
engahmed1190 Apr 6, 2026
1a7c0f3
chore: add scheduler debug helper
engahmed1190 Apr 6, 2026
d263c14
fix: simplify scheduler check for v16 compat
engahmed1190 Apr 6, 2026
17f2f74
fix(sync): auto-import adapters in pull_if_due for worker context
engahmed1190 Apr 6, 2026
5dc84ab
chore: add cron job check helper
engahmed1190 Apr 6, 2026
8bbb3ff
chore(sync): clean up temporary debug helpers
engahmed1190 Apr 6, 2026
991f80b
refactor(sync): simplify adapters and fix efficiency issues
engahmed1190 Apr 6, 2026
ee18263
docs(sync): add Transactions Push sub-spec (Plan 3)
engahmed1190 Apr 6, 2026
386be2e
docs(sync): add Transactions Push implementation plan (Plan 3)
engahmed1190 Apr 6, 2026
cca89ae
feat(sync): add SubmittableAdapter base for docstatus-aware sync
engahmed1190 Apr 6, 2026
3e0a819
feat(sync): add transaction adapters (Sales Invoice, Payment Entry, P…
engahmed1190 Apr 6, 2026
9bce4f6
feat(sync): add outbox hooks for transaction event capture
engahmed1190 Apr 6, 2026
9097cdd
feat(sync): add central ingest API for transaction push
engahmed1190 Apr 6, 2026
1182d49
feat(sync): add OutboxDrainer with backoff and dead letter handling
engahmed1190 Apr 6, 2026
e322618
feat(sync): wire outbox hooks + push scheduler into hooks.py
engahmed1190 Apr 6, 2026
b01d4d7
test(sync): add Plan 3 test runner
engahmed1190 Apr 6, 2026
e49d30e
test(sync): add e2e push integration test
engahmed1190 Apr 6, 2026
1f59e68
docs(sync): add comprehensive user and developer documentation
engahmed1190 Apr 6, 2026
4281dee
Replace Custom Field with Export Costomizations
MostafaKadry Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,154 changes: 4,154 additions & 0 deletions docs/superpowers/plans/2026-04-05-sync-foundation-plan-1.md

Large diffs are not rendered by default.

1,701 changes: 1,701 additions & 0 deletions docs/superpowers/plans/2026-04-06-masters-pull-plan-2.md

Large diffs are not rendered by default.

1,363 changes: 1,363 additions & 0 deletions docs/superpowers/plans/2026-04-06-transactions-push-plan-3.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

248 changes: 248 additions & 0 deletions docs/superpowers/specs/2026-04-06-masters-pull-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# Masters Pull — Sub-Spec (Plan 2)

**Status:** Approved
**Date:** 2026-04-06
**Parent Spec:** `docs/superpowers/specs/2026-04-05-branch-central-architecture-design.md`
**Scope:** Central-side API endpoints, branch-side masters puller, first adapters, tombstone hooks, scheduler integration. First real data flow — branch pulls master data from central.

---

## 1. Purpose

After Plan 1 laid the foundation (DocTypes, module skeleton, custom fields, registry), Plan 2 delivers the first real sync flow: branch pulls master data from central. After Plan 2, changing an Item on central will automatically appear on the branch within 5 minutes.

---

## 2. Components

| Component | Location | Purpose |
|-----------|----------|---------|
| `changes_since` API | `pos_next/sync/api/changes.py` | Central endpoint: returns upserts + tombstones since watermark |
| `health` API | `pos_next/sync/api/health.py` | Central endpoint: server time, version info |
| `MastersPuller` | `pos_next/sync/masters_puller.py` | Branch job: iterates registry, calls changes_since, applies via adapter |
| Tombstone hooks | `pos_next/sync/hooks.py` | Central: on_trash writes tombstones for synced masters |
| Item adapter | `pos_next/sync/adapters/item.py` | Serialize/apply Item with children (barcodes, etc.) |
| Item Price adapter | `pos_next/sync/adapters/item_price.py` | Composite conflict key |
| Customer adapter | `pos_next/sync/adapters/customer.py` | Bidirectional, mobile-no dedup |
| Generic master adapter | `pos_next/sync/adapters/generic_master.py` | Default upsert for ~20 simple masters |
| Scheduler | `pos_next/hooks.py` | `pull_if_due` cron every minute |

**Not in scope:** Push transactions (Plan 3), failover (Plan 3), POS client changes, Sync Status dashboard.

---

## 3. Central-side API

### 3.1 `changes_since` endpoint

**Endpoint:** `GET /api/method/pos_next.sync.api.changes.changes_since`

**Parameters:**
- `doctype` — e.g. "Item"
- `since` — ISO datetime (the branch's watermark)
- `limit` — batch size (default 100)

**Response:**
```json
{
"upserts": [
{"name": "ITEM-001", "item_name": "Apple", "modified": "2026-04-06 10:00:00", "...": "..."},
],
"tombstones": [
{"reference_name": "ITEM-OLD", "deleted_at": "2026-04-06 09:00:00"}
],
"next_since": "2026-04-06 10:00:00",
"has_more": true
}
```

**Logic:**
1. Query `tab{doctype}` where `modified > since` ordered by `modified ASC`, limit + 1 (to detect `has_more`).
2. Query `Sync Tombstone` where `reference_doctype = doctype` and `deleted_at > since`.
3. `next_since` = max `modified` from returned upserts (branch advances its watermark to this).
4. Serialize each record via the adapter's `serialize()` method if an adapter is registered, otherwise `doc.as_dict()`.

**Security:** Whitelisted endpoint. Requires authentication — only accessible to users with `POS Next Sync Agent` role or System Manager. No branch-specific filtering needed for masters (all branches get the same masters).

### 3.2 `health` endpoint

**Endpoint:** `GET /api/method/pos_next.sync.api.health.health`

**Response:**
```json
{
"server_time": "2026-04-06 10:00:00",
"frappe_version": "15.97.0",
"pos_next_version": "1.16.0",
"site_role": "Central"
}
```

Used by branch to check connectivity and clock reference. No auth required (public).

---

## 4. Branch-side MastersPuller

**Class:** `MastersPuller` in `pos_next/sync/masters_puller.py`

### 4.1 Entry point

`pull_if_due()` — called every minute by the scheduler. Checks:
1. Is this site a Branch? (read Sync Site Config). If not, return.
2. Is `now() - last_pull_masters_at >= pull_masters_interval_seconds`? If not, return.
3. Is sync enabled? If not, return.
4. Run the pull cycle.

### 4.2 Pull cycle

1. Build a `SyncSession` via `transport.build_session_from_config()`.
2. Read Sync DocType Rules where `direction` includes `Central→Branch` and `enabled=1`, sorted by `priority ASC`.
3. For each rule:
- Get adapter from registry (or use `BaseSyncAdapter` default).
- Read `Sync Watermark` for this DocType (or `"2000-01-01 00:00:00"` if first pull).
- Loop:
- Call `changes_since(doctype, since=watermark, limit=batch_size)` via `SyncSession.get()`.
- Apply upserts via adapter.
- Delete tombstoned records.
- Advance watermark to `next_since`.
- Break when `has_more=false`.
4. Update `last_pull_masters_at` on the Sync Site Config.
5. Log result to `Sync Log`.

### 4.3 Applying upserts

For each record in the upserts list:
1. Call `adapter.validate_incoming(payload)` — skip if raises, log warning.
2. Compute hash via `payload.compute_hash(payload_dict)`.
3. Check `Sync Record State` — if hash matches `last_synced_hash`, skip (no change since last sync).
4. Call `adapter.apply_incoming(payload, "update")` — creates or updates locally.
5. Update `Sync Record State` with new hash and source="central".

### 4.4 Applying tombstones

For each tombstone:
- If the local record exists, delete it via `frappe.delete_doc(doctype, name, ignore_permissions=True, force=True)`.
- Tombstones don't go through the adapter (delete is universal).
- Remove the corresponding `Sync Record State` row if it exists.

### 4.5 Error handling

- **Single record fails to apply:** Log to `Sync Log` with error details, skip it, continue with the rest of the batch. Don't advance watermark past the failed record's `modified` — it will be retried next cycle.
- **HTTP call to central fails:** Log error, set `last_sync_error` on Sync Site Config, stop the pull cycle. Retry next tick.
- **Network errors don't advance the watermark** — so no records are missed.

---

## 5. Adapters

### 5.1 ItemAdapter

**File:** `pos_next/sync/adapters/item.py`

- Serializes Item with child tables: Item Barcode, Item Default.
- On apply: standard upsert by name. Handles `has_variants` flag — doesn't delete template items that have local variants referencing them.
- Conflict key: `("name",)` (default).

### 5.2 ItemPriceAdapter

**File:** `pos_next/sync/adapters/item_price.py`

- Standard upsert.
- Conflict key: `("item_code", "price_list", "uom")` — Item Price names are auto-generated and may differ between sites, so identity is by the composite key.
- On apply: look up existing by composite key first. If found, update. If not, insert.

### 5.3 CustomerAdapter

**File:** `pos_next/sync/adapters/customer.py`

- Bidirectional (but in Plan 2 we only implement the pull direction — central→branch).
- Conflict key: `("mobile_no",)`.
- On apply: if a customer with the same `mobile_no` exists under a different name, return existing name (dedup — don't create duplicate). Otherwise standard upsert.

### 5.4 GenericMasterAdapter

**File:** `pos_next/sync/adapters/generic_master.py`

Covers all remaining Central→Branch masters with default `BaseSyncAdapter` behavior (upsert by name, no special logic):

POS Profile, Warehouse, Mode of Payment, Item Group, UOM, Price List, Company, Currency, Branch, Customer Group, Sales Person, Employee, User, Role Profile, Sales Taxes and Charges Template, Item Tax Template, POS Settings, POS Offer, POS Coupon, Loyalty Program, Item Barcode.

One class, registered for all these DocTypes at import time. If any later needs custom logic, extract into its own adapter file.

---

## 6. Tombstone Hooks

**File:** `pos_next/sync/hooks.py`

Register `on_trash` for every Central→Branch synced DocType:

```python
def write_tombstone_on_trash(doc, method=None):
"""on_trash hook: record deletion for branch replication."""
from pos_next.sync.registry import get_adapter
if not get_adapter(doc.doctype):
return # not a synced DocType
SyncTombstone.record(doc.doctype, doc.name)
```

Registered via `doc_events` in `pos_next/hooks.py`. Only fires on sites where the DocType's adapter is registered (both central and branch — tombstones are useful on both sides for different flows).

---

## 7. Scheduler

Add to `pos_next/hooks.py` `scheduler_events`:

```python
scheduler_events = {
"cron": {
"* * * * *": [
"pos_next.sync.masters_puller.pull_if_due",
]
}
}
```

`pull_if_due` is self-throttled: compares `now() - last_pull_masters_at` against `pull_masters_interval_seconds` from Sync Site Config. On Central sites, it's a no-op (no Branch config exists).

---

## 8. Testing Strategy

### 8.1 Unit tests

- `test_changes_api.py` — mock Frappe ORM, verify `changes_since` returns correct upserts/tombstones/pagination.
- `test_masters_puller.py` — mock SyncSession HTTP responses, verify watermark advancement, error handling, skip-on-hash-match.
- `test_item_adapter.py` — verify serialize includes children, apply creates/updates correctly.
- `test_item_price_adapter.py` — verify composite conflict key lookup.
- `test_customer_adapter.py` — verify mobile_no dedup.
- `test_generic_adapter.py` — verify registration covers all expected DocTypes.

### 8.2 Integration tests (two-bench)

Using the dev environment (frappe-bench port 8000 as central, frappe-bench-16 port 8001 as branch):

1. **Happy path:** Create an Item on central → trigger pull on branch → verify Item exists on branch with correct data.
2. **Update propagation:** Update Item name/price on central → pull → verify updated on branch.
3. **Tombstone:** Delete Item on central → pull → verify deleted on branch.
4. **Pagination:** Create 150 Items on central → pull with batch_size=100 → verify all 150 arrive (two pages).
5. **Idempotency:** Pull twice → verify no duplicate records, hash-match skip works.
6. **Customer dedup:** Create Customer with same mobile_no on both sites → pull → verify single record (not duplicated).

### 8.3 Test runner

Add a `pos_next/sync/tests/run_plan2_tests.py` that runs all Plan 2 test modules.

---

## 9. End Result

After Plan 2 is implemented and deployed:

- Create/edit/delete an Item on central → within `pull_masters_interval_seconds` (default 5 min) → appears/updates/disappears on branch.
- Same for all 23+ master DocTypes in the Synced DocTypes Registry.
- Pull is paginated, idempotent, and resilient to transient network errors.
- Every pull cycle is logged to Sync Log.
- Watermarks track progress per DocType — if the branch goes offline for a day, it catches up on reconnect without missing records.
Loading
Loading