diff --git a/README.md b/README.md
index 0a44535..9f64d07 100644
--- a/README.md
+++ b/README.md
@@ -149,6 +149,39 @@ Deletes the unit for `(companyId, si)`. Blocked by FK if any article still refer
---
+## `companysetting`
+
+Company-scoped key/value settings. Auth via `resolveCompanyAccess`.
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `list` | `(googleAuthId, companyId, { frontendOnly? })` | `{ key, value, frontend }[]` |
+| `get` | `(googleAuthId, companyId, key, { frontendOnly? })` | `{ key, value, frontend } \| undefined` |
+| `set` | `(googleAuthId, companyId, { key, value, frontend? })` | `{ key, value, frontend }` |
+| `delete` | `(googleAuthId, companyId, key)` | `void` |
+
+- `value` is always a string (`NVARCHAR(MAX)`).
+- `frontend: true` → readable by frontend routes (`frontendOnly: true`). Default on `set` is `false` (backend-only).
+- With `frontendOnly: true`, backend-only keys are omitted / `get` returns `undefined` (no leak via error type beyond missing).
+
+Errors: `Company not found`, `Not authorized`, `Invalid setting`, `Setting not found`.
+
+---
+
+## `brand`
+
+Company-scoped brands. Auth via `resolveCompanyAccess`.
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `list` | `(googleAuthId, companyId)` | `{ id, name, gsprpinfo }[]` |
+| `upsert` | `(googleAuthId, companyId, { name, gsprpinfo? })` | `{ id, name, gsprpinfo }` |
+| `delete` | `(googleAuthId, companyId, id)` | `void` |
+
+`brand.company_id` **NOT NULL**, unique `(company_id, name)`. `upsert` matches by `(companyId, name)`. `delete` blocked by FK if any article references it (`Brand in use`). Errors: `Company not found`, `Not authorized`, `Invalid brand`, `Brand not found`, `Brand in use`.
+
+---
+
## `category`
| Method | Signature | Returns |
@@ -272,6 +305,12 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
| `GET /api/unit?companyId=` | `unit.list(googleAuthId, companyId)` |
| `PUT /api/unit` | `unit.upsert(googleAuthId, companyId, { si, basesi, basesifactor, symbol, name })` |
| `DELETE /api/unit` | `unit.delete(googleAuthId, companyId, si)` |
+| `GET /api/company/:id/settings` | `companysetting.list(googleAuthId, companyId, { frontendOnly: true })` |
+| `PUT /api/company/:id/settings` | `companysetting.set(googleAuthId, companyId, { key, value, frontend? })` |
+| `DELETE /api/company/:id/settings/:key` | `companysetting.delete(googleAuthId, companyId, key)` |
+| `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)` |
| `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/generate-schema-graph.js b/generate-schema-graph.js
index 7cd76be..d9edbbc 100644
--- a/generate-schema-graph.js
+++ b/generate-schema-graph.js
@@ -271,6 +271,7 @@ const CLUSTERS = [
'companyuser',
'syncpos',
'company',
+ 'companysetting',
'customergroup',
'entity',
'numbers',
diff --git a/schema-graph.dot b/schema-graph.dot
index 28f3a7b..f341dee 100644
--- a/schema-graph.dot
+++ b/schema-graph.dot
@@ -12,44 +12,45 @@ digraph schema {
numbers [label=<
>];
entity [label=<>];
syncpos [label=<>];
+ company [label=< |
|
| default_language_id? | INT FK |
|
|
| poscustomergroup_id | INT FK |
|
|
|
|
|
>];
meta [label=<>];
- company [label=< |
|
| default_language_id? | INT FK |
|
|
| poscustomergroup_id | INT FK |
|
|
|
|
|
>];
- companyuser [label=<>];
customergroup [label=<>];
+ companysetting [label=<>];
+ companyuser [label=<>];
google_auth [label=<>];
}
subgraph cluster_i18n {
label="Localization"; labeljust="l"; fontsize=15;
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14;
- categorytext [label=<>];
+ language [label=<>];
articletext [label=<>];
unittext [label=<>];
- language [label=<>];
+ categorytext [label=<>];
}
subgraph cluster_catalog {
label="Catalog"; labeljust="l"; fontsize=15;
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14;
articlehistory [label=<>];
- articleean [label=<>];
- categoryarticle [label=<>];
- articlelist [label=<>];
- article [label=<>];
category [label=<>];
- articlelistarticle [label=<>];
+ articleean [label=<>];
+ article [label=<>];
+ brand [label=<>];
+ categoryarticle [label=<>];
+ articlelist [label=<>];
+ articlelistarticle [label=<>];
optionslist [label=<>];
- brand [label=<>];
optionslistarticle [label=<>];
}
subgraph cluster_media {
label="Media"; labeljust="l"; fontsize=15;
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14;
- brandpic [label=<>];
- articlepic [label=<>];
+ categorypic [label=<>];
pic [label=< |
|
|
|
|
|
|
|
|
|
|
| thumbbinary | VARBINARY(MAX) |
|
>];
- categorypic [label=<>];
+ articlepic [label=<>];
+ brandpic [label=<>];
}
subgraph cluster_commerce {
label="Pricing & Documents"; labeljust="l"; fontsize=15;
@@ -69,7 +70,7 @@ digraph schema {
__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:in5:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"];
+ google_auth:selected_company:sw -> company:in7: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:default_language_id:w -> language:in3:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_company_default_language"];
@@ -80,39 +81,41 @@ digraph schema {
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"];
- unittext:unit_id:se -> unit:in0:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_unittext_unit"];
- unittext:language_id:se -> language:in2:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_unittext_language"];
- article:unit_id:w -> unit:in1:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_article_unit"];
- article:brand_id:se -> brand:in0:n [color="#22543d", fontcolor="#22543d", tooltip="FK_article_brand"];
- article:articlelist_id:sw -> articlelist:in0:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_article_articlelist"];
- article:optionslist_id:se -> optionslist:in0:n [color="#702459", fontcolor="#702459", tooltip="FK_article_optionslist"];
- article:fatherarticle_id:e -> article:id:e [color="#744210", fontcolor="#744210", tooltip="FK_article_fatherarticle"];
- articlehistory:article_id:e -> article:in1:n [color="#285e61", fontcolor="#285e61", tooltip="FK_articlehistory_article"];
- articleean:article_id:se -> article:in2:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_articleean_article"];
- articlepic:article_id:w -> article:in7:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_articlepic_article"];
- articlepic:pic_id:se -> pic:in1:n [color="#38a169", fontcolor="#38a169", tooltip="FK_articlepic_pic"];
- brandpic:brand_id:sw -> brand:in1:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_brandpic_brand"];
- brandpic:pic_id:se -> pic:in0:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_brandpic_pic"];
- articlelistarticle:articlelist_id:sw -> articlelist:in1:n [color="#c53030", fontcolor="#c53030", tooltip="FK_articlelistarticle_articlelist"];
- articlelistarticle:article_id:sw -> article:in5:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_articlelistarticle_article"];
- optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_optionslistarticle_optionslist"];
- optionslistarticle:article_id:sw -> article:in6:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_optionslistarticle_article"];
- categoryarticle:category_id:se -> category:in1:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_categoryarticle_category"];
- categoryarticle:article_id:se -> article:in3:n [color="#c05621", fontcolor="#c05621", tooltip="FK_categoryarticle_article"];
- categorypic:category_id:sw -> category:in3:n [color="#319795", fontcolor="#319795", tooltip="FK_categorypic_category"];
- categorypic:pic_id:sw -> pic:in2:n [color="#97266d", fontcolor="#97266d", tooltip="FK_categorypic_pic"];
- categorytext:category_id:e -> category:in0:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_categorytext_category"];
- categorytext:language_id:se -> language:in0:n [color="#276749", fontcolor="#276749", tooltip="FK_categorytext_language"];
- articletext:article_id:e -> article:in0:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_articletext_article"];
- articletext:language_id:se -> language:in1:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_articletext_language"];
- price:tax_id:sw -> tax:in3:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_price_tax"];
- price:article_id:w -> article:in8:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_price_article"];
- price:customergroup_id:sw -> customergroup:in2:n [color="#22543d", fontcolor="#22543d", tooltip="FK_price_customergroup"];
- manifestarticle:manifest_id:sw -> manifest:in0:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_manifestarticle_manifest"];
- manifestarticle:article_id:w -> article:in9:n [color="#702459", fontcolor="#702459", tooltip="FK_manifestarticle_article"];
- manifestarticle:tax_id:sw -> tax:in4:n [color="#744210", fontcolor="#744210", tooltip="FK_manifestarticle_tax"];
- numbers:company_id:se -> company:in1:n [color="#285e61", fontcolor="#285e61", tooltip="FK_numbers_company"];
- companyuser:google_auth_id:se -> google_auth:in0:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_companyuser_google_auth"];
- companyuser:company_id:sw -> company:in3:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_companyuser_company"];
- syncpos:company_id:se -> company:in2:n [color="#38a169", fontcolor="#38a169", tooltip="FK_syncpos_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: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"];
+ 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"];
+ brandpic:pic_id:sw -> pic:in2:n [color="#c53030", fontcolor="#c53030", tooltip="FK_brandpic_pic"];
+ 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"];
+ 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"];
+ 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"];
+ 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"];
}
\ No newline at end of file
diff --git a/schema-graph.svg b/schema-graph.svg
index 42e7d88..d7e6295 100644
--- a/schema-graph.svg
+++ b/schema-graph.svg
@@ -4,349 +4,336 @@
-