diff --git a/README.md b/README.md
index 652c842..3dfcde1 100644
--- a/README.md
+++ b/README.md
@@ -182,6 +182,37 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
---
+## `posterminal`
+
+Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `list` | `(googleAuthId, companyId)` | `{ id, token, pairingpin }[]` |
+| `upsert` | `(googleAuthId, companyId, { id?, token?, pairingpin })` | `{ id, token, pairingpin }` |
+| `delete` | `(googleAuthId, companyId, id)` | `void` |
+| `setOn` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
+| `setOff` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
+| `setOnAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
+| `setOffAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
+| `getCategoryPosterminalIds` | `(companyId)` | `{ [categoryId]: posterminalId[] }` (internal, used by category tree) |
+
+`posterminal.company_id` **NOT NULL**. `token` is nullable and unique (pairing code assigned during setup). `pairingpin` is required.
+
+`upsert`: if `id` is provided, updates the existing posterminal (must belong to company); otherwise creates a new one. Errors: `Company not found`, `Not authorized`, `Invalid posterminal`, `Posterminal not found`.
+
+`setOn` / `setOff`: toggle a single category's visibility on a single terminal. `setOn` inserts the row (idempotent — no error if already present); `setOff` deletes it (idempotent). Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal or category`.
+
+`setOnAll` / `setOffAll`: toggle **all** company categories on a single terminal. `setOnAll` walks the company's category tree and inserts any missing links; `setOffAll` removes all category links for the terminal. Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal`.
+
+`delete` cascades to `posterminalcategory`. Errors: `Company not found`, `Not authorized`, `Posterminal not found`.
+
+### Category tree integration
+
+`getTreeForCompany` returns a `posterminalIds` array on each tree node — the list of posterminal ids (of the current company) for which that category is visible. A category with an empty `posterminalIds` array is visible on all terminals (or has no terminal-specific filtering).
+
+---
+
## `category`
| Method | Signature | Returns |
@@ -335,6 +366,13 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
| `GET /api/brand?companyId=` | `brand.list(googleAuthId, companyId)` |
| `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` |
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` |
+| `GET /api/posterminal?companyId=` | `posterminal.list(googleAuthId, companyId)` |
+| `PUT /api/posterminal` | `posterminal.upsert(googleAuthId, companyId, { id?, token?, pairingpin })` |
+| `DELETE /api/posterminal/:id` | `posterminal.delete(googleAuthId, companyId, id)` |
+| `PUT /api/posterminal/:id/category/:categoryId` | `posterminal.setOn(googleAuthId, companyId, id, categoryId)` |
+| `DELETE /api/posterminal/:id/category/:categoryId` | `posterminal.setOff(googleAuthId, companyId, id, categoryId)` |
+| `PUT /api/posterminal/:id/categories` | `posterminal.setOnAll(googleAuthId, companyId, id)` |
+| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
diff --git a/schema-graph.dot b/schema-graph.dot
index f341dee..e7e16a6 100644
--- a/schema-graph.dot
+++ b/schema-graph.dot
@@ -12,7 +12,7 @@ digraph schema {
numbers [label=<
>];
entity [label=<>];
syncpos [label=<>];
- company [label=< |
|
| default_language_id? | INT FK |
|
|
| poscustomergroup_id | INT FK |
|
|
|
|
|
>];
+ company [label=< |
|
| default_language_id? | INT FK |
|
|
| poscustomergroup_id | INT FK |
|
|
|
|
|
>];
meta [label=<>];
customergroup [label=<>];
companysetting [label=<>];
@@ -33,11 +33,11 @@ digraph schema {
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14;
articlehistory [label=<>];
- category [label=<>];
- articleean [label=<>];
- article [label=<>];
- brand [label=<>];
+ category [label=<>];
categoryarticle [label=<>];
+ brand [label=<>];
+ articleean [label=<>];
+ article [label=<>];
articlelist [label=<>];
articlelistarticle [label=<>];
optionslist [label=<>];
@@ -67,30 +67,32 @@ digraph schema {
fontcolor="#1a365d"; margin=14;
unit [label=<>];
}
+ posterminalcategory [label=<>];
+ posterminal [label=<>];
__legend [label=<| Legend |
| PK | primary key |
| FK | foreign key |
| ? | nullable column |
| — | FOREIGN KEY constraint |
| - - | naming convention only, no constraint |
>];
- google_auth:selected_company:sw -> company:in7:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"];
+ google_auth:selected_company:w -> company:in8:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"];
category:parent_id:e -> category:id:e [color="#2f855a", fontcolor="#2f855a", tooltip="FK_category_parent"];
- company:root_category_id:w -> category:in4:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_company_root_category"];
+ company:root_category_id:w -> category:in5:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_company_root_category"];
company:default_language_id:w -> language:in3:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_company_default_language"];
company:taxrate:e -> tax:in2:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_company_taxrate"];
company:reducedtaxrate:e -> tax:in0:n [color="#c05621", fontcolor="#c05621", tooltip="FK_company_reducedtaxrate"];
company:taxfreerate:e -> tax:in1:n [color="#319795", fontcolor="#319795", tooltip="FK_company_taxfreerate"];
- customergroup:company_id:nw -> company:in4:n [color="#97266d", fontcolor="#97266d", tooltip="FK_customergroup_company"];
+ customergroup:company_id:nw -> company:in5:n [color="#97266d", fontcolor="#97266d", tooltip="FK_customergroup_company"];
company:poscustomergroup_id:se -> customergroup:in1:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_company_poscustomergroup"];
entity:customergroup_id:e -> customergroup:in0:n [color="#276749", fontcolor="#276749", tooltip="FK_entity_customergroup"];
- unit:company_id:e -> company:in0:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_unit_company"];
+ unit:company_id:e -> company:in2:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_unit_company"];
unittext:unit_id:e -> unit:in0:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_unittext_unit"];
unittext:language_id:sw -> language:in1:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_unittext_language"];
brand:company_id:e -> company:in1:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_brand_company"];
- article:unit_id:w -> unit:in1:n [color="#22543d", fontcolor="#22543d", tooltip="FK_article_unit"];
- article:brand_id:se -> brand:in1:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_article_brand"];
+ article:unit_id:e -> unit:in1:n [color="#22543d", fontcolor="#22543d", tooltip="FK_article_unit"];
+ article:brand_id:sw -> brand:in1:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_article_brand"];
article:articlelist_id:se -> articlelist:in0:n [color="#702459", fontcolor="#702459", tooltip="FK_article_articlelist"];
article:optionslist_id:se -> optionslist:in0:n [color="#744210", fontcolor="#744210", tooltip="FK_article_optionslist"];
article:fatherarticle_id:e -> article:id:e [color="#285e61", fontcolor="#285e61", tooltip="FK_article_fatherarticle"];
- articlehistory:article_id:se -> article:in2:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_articlehistory_article"];
- articleean:article_id:se -> article:in3:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_articleean_article"];
+ articlehistory:article_id:e -> article:in2:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_articlehistory_article"];
+ articleean:article_id:se -> article:in4:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_articleean_article"];
articlepic:article_id:e -> article:in1:n [color="#38a169", fontcolor="#38a169", tooltip="FK_articlepic_article"];
articlepic:pic_id:sw -> pic:in1:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_articlepic_pic"];
brandpic:brand_id:e -> brand:in0:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_brandpic_brand"];
@@ -98,24 +100,27 @@ digraph schema {
articlelistarticle:articlelist_id:sw -> articlelist:in1:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_articlelistarticle_articlelist"];
articlelistarticle:article_id:sw -> article:in6:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_articlelistarticle_article"];
optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_optionslistarticle_optionslist"];
- optionslistarticle:article_id:w -> article:in7:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_optionslistarticle_article"];
- categoryarticle:category_id:sw -> category:in3:n [color="#c05621", fontcolor="#c05621", tooltip="FK_categoryarticle_category"];
- categoryarticle:article_id:sw -> article:in5:n [color="#319795", fontcolor="#319795", tooltip="FK_categoryarticle_article"];
- categorypic:category_id:se -> category:in1:n [color="#97266d", fontcolor="#97266d", tooltip="FK_categorypic_category"];
+ optionslistarticle:article_id:sw -> article:in7:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_optionslistarticle_article"];
+ categoryarticle:category_id:sw -> category:in4:n [color="#c05621", fontcolor="#c05621", tooltip="FK_categoryarticle_category"];
+ categoryarticle:article_id:se -> article:in3:n [color="#319795", fontcolor="#319795", tooltip="FK_categoryarticle_article"];
+ categorypic:category_id:se -> category:in2:n [color="#97266d", fontcolor="#97266d", tooltip="FK_categorypic_category"];
categorypic:pic_id:se -> pic:in0:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_categorypic_pic"];
categorytext:category_id:e -> category:in0:n [color="#276749", fontcolor="#276749", tooltip="FK_categorytext_category"];
categorytext:language_id:sw -> language:in2:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_categorytext_language"];
articletext:article_id:e -> article:in0:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_articletext_article"];
- articletext:language_id:se -> language:in0:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_articletext_language"];
+ articletext:language_id:sw -> language:in0:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_articletext_language"];
price:tax_id:sw -> tax:in3:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_price_tax"];
price:article_id:w -> article:in8:n [color="#22543d", fontcolor="#22543d", tooltip="FK_price_article"];
price:customergroup_id:sw -> customergroup:in2:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_price_customergroup"];
manifestarticle:manifest_id:sw -> manifest:in0:n [color="#702459", fontcolor="#702459", tooltip="FK_manifestarticle_manifest"];
manifestarticle:article_id:w -> article:in9:n [color="#744210", fontcolor="#744210", tooltip="FK_manifestarticle_article"];
manifestarticle:tax_id:sw -> tax:in4:n [color="#285e61", fontcolor="#285e61", tooltip="FK_manifestarticle_tax"];
- numbers:company_id:se -> company:in2:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_numbers_company"];
+ numbers:company_id:se -> company:in3:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_numbers_company"];
companyuser:google_auth_id:se -> google_auth:in0:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_companyuser_google_auth"];
- companyuser:company_id:sw -> company:in6:n [color="#38a169", fontcolor="#38a169", tooltip="FK_companyuser_company"];
- syncpos:company_id:se -> company:in3:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_syncpos_company"];
- companysetting:company_id:sw -> company:in5:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_companysetting_company"];
+ companyuser:company_id:sw -> company:in7:n [color="#38a169", fontcolor="#38a169", tooltip="FK_companyuser_company"];
+ syncpos:company_id:se -> company:in4:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_syncpos_company"];
+ companysetting:company_id:sw -> company:in6:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_companysetting_company"];
+ posterminal:company_id:e -> company:in0:n [color="#c53030", fontcolor="#c53030", tooltip="FK_posterminal_company"];
+ posterminalcategory:posterminal_id:se -> posterminal:in0:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_posterminalcategory_posterminal"];
+ posterminalcategory:category_id:se -> category:in1:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_posterminalcategory_category"];
}
\ No newline at end of file
diff --git a/schema-graph.svg b/schema-graph.svg
index d7e6295..f661f3e 100644
--- a/schema-graph.svg
+++ b/schema-graph.svg
@@ -4,232 +4,233 @@
-