63 lines
2.7 KiB
Markdown
63 lines
2.7 KiB
Markdown
# Product Sync Protocol
|
|
|
|
## The Sync Flow
|
|
|
|
1. **The POS client polls `/api/v1/init`** with its current `lastChangedProduct` cursor (e.g. `24599`). The server counts how many products have a row version greater than that cursor and returns the count.
|
|
|
|
2. **If `product_count > 0`**, the client fetches `/api/v1/product` with the same cursor and a `limit` (e.g. 20). The server returns the changed products ordered by row version ascending.
|
|
|
|
3. **The client updates its local cursor** to the highest `lastChanged` value it received in that batch (e.g. `24609`), so the next poll only fetches newer changes.
|
|
|
|
### Example from the log
|
|
|
|
```
|
|
init with lastChangedProduct=24599 -> product_count=0 (no changes)
|
|
init with lastChangedProduct=24599 -> product_count=1 (a new product appeared)
|
|
product fetch lastChangedProduct=24599 -> returns product with lastChanged=24609
|
|
init with lastChangedProduct=24609 -> product_count=0 (caught up)
|
|
```
|
|
|
|
## What is `lastChangedProduct`?
|
|
|
|
It's the **MSSQL `bRowversion`** value -- an auto-incrementing binary counter that SQL Server bumps whenever a row is modified. The queries in `product-count.js` and `product-list.js` filter with:
|
|
|
|
```sql
|
|
WHERE CONVERT(BIGINT, a.bRowversion) > @cursor
|
|
```
|
|
|
|
The client simply persists the highest `lastChanged` value it received so it only gets new/changed rows on subsequent polls.
|
|
|
|
## How it's kept track
|
|
|
|
**Server side**: The cursor is stateless -- the client sends its last-known value every time. The server just runs the SQL query filtering rows above that value.
|
|
|
|
**Client side**: The POS client stores its `lastChangedProduct` (and `lastChangedCategory`, `lastChangedCustomerGroup`, etc.) persistently so it can resume syncing after a restart without re-downloading everything.
|
|
|
|
## Other entity types
|
|
|
|
The same pattern applies to:
|
|
|
|
- **Categories** (`lastChangedCategory` / `bRowversion` on `tKategorie`)
|
|
- **Customer groups** (`lastChangedCustomerGroup` / `bRowversion` on `tKundenGruppe`)
|
|
- **Composite products** (`lastChangedCompositeProduct` / `bRowversion` on `tArtikelBaugruppe`)
|
|
- **Deleted entities** (`lastChangedDeletedEntity` / `bLastChanged` on `Pos.vDeletedEntity`)
|
|
|
|
## Deleted entity sync
|
|
|
|
When entities are deleted in JTL-Wawi, they appear in the `Pos.vDeletedEntity` view with their entity type:
|
|
|
|
| nEntityType | Entity |
|
|
|---|---|
|
|
| 1 | Artikel (Product) |
|
|
| 2 | Kategorie (Category) |
|
|
| 7 | Stückliste (Composite Product) |
|
|
|
|
The client polls `/v1/init` with `lastChangedDeletedEntity` cursor, and if `deletedEntity_count > 0`, fetches `/v1/deletedentity` to get the list of deleted entities. The response format is:
|
|
|
|
```json
|
|
[
|
|
{ "entityId": "123", "entityType": "1", "lastChanged": "208980" }
|
|
]
|
|
```
|
|
|
|
The client then removes these entities from its local database. |