diff --git a/README.md b/README.md
index b8179b6..396d4bf 100644
--- a/README.md
+++ b/README.md
@@ -71,14 +71,17 @@ For `PATCH /api/company/:id`:
| Method | Signature | Returns |
| --- | --- | --- |
| `add` | `(email)` | `id` |
-| `getByEmail` | `(email)` | `{ id, email, createdAt, selectedCompanyId } \| undefined` |
-| `listAllowedEmails` | `()` | `{ id, email, createdAt, selectedCompanyId }[]` |
+| `getByEmail` | `(email)` | `{ id, email, picture, sub, createdAt, selectedCompanyId } \| undefined` |
+| `listAllowedEmails` | `()` | `{ id, email, picture, sub, createdAt, selectedCompanyId }[]` |
| `removeAllowedEmail` | `(email)` | `void` |
| `setSelectedCompany` | `(googleAuthId, companyId)` | `void` |
+| `setProfile` | `(googleAuthId, { picture?: string \| null, sub?: string \| null })` | `void` |
+
+`picture` / `sub` come from the Google ID token (`payload.picture`, `payload.sub`). `sub` is Google’s stable subject id (unique when set). `setProfile` overwrites both columns (pass `null` to clear). Errors: `User not found`.
Backend mapping:
-- Login: `getByEmail(email)`
+- Login: `getByEmail(email)` then `setProfile(id, { picture, sub })` from the verified token
- CLI user manager: `listAllowedEmails()` / `removeAllowedEmail(email)` / `add(email)`
- `PUT /api/auth/selected-company`: `setSelectedCompany(googleAuthId, companyId)`
@@ -267,7 +270,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
| `listExport` | `(googleAuthId: string, companyId: number, categoryId?: number)` | `ArticleExportEntry[]` (see below) |
| `createUnderParent` | `({ googleAuthId: string, parentId: number, name: string })` | `{ id: number, parentId: number, name: string, sku: string }` |
| `getBySku` | `(googleAuthId: string, sku: string)` | `ArticleDetail` (see below) |
-| `getHistoryBySku` | `(googleAuthId: string, sku: string)` | `{ id: number, date: Date, text: string }[]` |
+| `getHistoryBySku` | `(googleAuthId: string, sku: string)` | `{ id: number, companyuserId: number \| null, date: Date, text: string }[]` |
| `updateBySku` | `(googleAuthId: string, sku: string, { name?: string, unitamount?: number, unitSi?: string })` | `ArticleDetail` |
| `setPricesBySku` | `(googleAuthId: string, sku: string, PriceInput[])` | `ArticleDetail` |
| `setEansBySku` | `(googleAuthId: string, sku: string, string[])` | `ArticleDetail` |
@@ -381,9 +384,9 @@ type PriceEntry = {
Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Errors: `Article not found`, `Not authorized`.
-`updateBySku` patches article fields in a transaction. At least one of `name`, `unitamount`, `unitSi` required. `name` → `articletext` (slugified `slug`); empty name → `Name required`. `unitSi` → existing `unit.si` lookup (no silent ensure); unknown → `Unit not found`. `unitamount` must be finite and `> 0` → `Invalid unitamount`. When `name` and/or unit (`unitamount`/`unitSi`) actually change, inserts one `articlehistory` row in the same transaction (`text` lines joined by newline), e.g. `Name geändert von "Alt" auf "Neu"` / `Einheit geändert von "500g" auf "1kg"` (label = `unitamount` + `unittext.symbol`, fallback `unit.si`). Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `Name required`, `Unit not found`, `Invalid unitamount`.
+`updateBySku` patches article fields in a transaction. At least one of `name`, `unitamount`, `unitSi` required. `name` → `articletext` (slugified `slug`); empty name → `Name required`. `unitSi` → existing `unit.si` lookup (no silent ensure); unknown → `Unit not found`. `unitamount` must be finite and `> 0` → `Invalid unitamount`. When `name` and/or unit (`unitamount`/`unitSi`) actually change, inserts one `articlehistory` row in the same transaction with `companyuser_id` set to the auth user (`text` lines joined by newline), e.g. `Name geändert von "Alt" auf "Neu"` / `Einheit geändert von "500g" auf "1kg"` (label = `unitamount` + `unittext.symbol`, fallback `unit.si`). Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `Name required`, `Unit not found`, `Invalid unitamount`.
-`getHistoryBySku` returns `articlehistory` for the article, newest first (`date` DESC, `id` DESC). Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
+`getHistoryBySku` returns `articlehistory` for the article, newest first (`date` DESC, `id` DESC). `companyuserId` is null when the user was deleted (`ON DELETE SET NULL`). Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
`setPricesBySku` replaces all `price` rows for the article’s company `poscustomergroup_id` in one transaction. Each entry: `price` → `net`, `tax` → tax rate (ensured via `tax.ensure`), `minmultiplier` defaults to `1`. Empty array clears all POS prices. Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `POS customer group required`, `Invalid prices`.
diff --git a/schema-graph.dot b/schema-graph.dot
index e7e16a6..61ab8a2 100644
--- a/schema-graph.dot
+++ b/schema-graph.dot
@@ -11,13 +11,13 @@ digraph schema {
fontcolor="#1a365d"; margin=14;
numbers [label=<
>];
entity [label=<>];
+ companyuser [label=<>];
syncpos [label=<>];
company [label=< |
|
| default_language_id? | INT FK |
|
|
| poscustomergroup_id | INT FK |
|
|
|
|
|
>];
meta [label=<>];
customergroup [label=<>];
companysetting [label=<>];
- companyuser [label=<>];
- google_auth [label=<>];
+ google_auth [label=<>];
}
subgraph cluster_i18n {
label="Localization"; labeljust="l"; fontsize=15;
@@ -32,14 +32,14 @@ digraph schema {
label="Catalog"; labeljust="l"; fontsize=15;
style="rounded,filled"; fillcolor="#ffffff60"; color="#a0aec0";
fontcolor="#1a365d"; margin=14;
- articlehistory [label=<>];
category [label=<>];
categoryarticle [label=<>];
+ articlelistarticle [label=<>];
brand [label=<>];
articleean [label=<>];
article [label=<>];
articlelist [label=<>];
- articlelistarticle [label=<>];
+ articlehistory [label=<>];
optionslist [label=<>];
optionslistarticle [label=<>];
}
@@ -68,18 +68,18 @@ digraph schema {
unit [label=<>];
}
posterminalcategory [label=<>];
- posterminal [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:w -> company:in8:n [color="#c53030", fontcolor="#c53030", tooltip="FK_google_auth_selected_company"];
+ google_auth:selected_company:sw -> 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: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:in5:n [color="#97266d", fontcolor="#97266d", tooltip="FK_customergroup_company"];
+ customergroup:company_id:nw -> company:in6: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:in2:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_unit_company"];
@@ -88,39 +88,40 @@ digraph schema {
brand:company_id:e -> company:in1:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_brand_company"];
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:articlelist_id:se -> articlelist:in1: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: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"];
- 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: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: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: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: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"];
+ articlehistory:article_id:sw -> article:in6:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_articlehistory_article"];
+ articlehistory:companyuser_id:e -> companyuser:in0:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_articlehistory_companyuser"];
+ articleean:article_id:se -> article:in4:n [color="#38a169", fontcolor="#38a169", tooltip="FK_articleean_article"];
+ articlepic:article_id:e -> article:in1:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_articlepic_article"];
+ articlepic:pic_id:sw -> pic:in1:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_articlepic_pic"];
+ brandpic:brand_id:e -> brand:in0:n [color="#c53030", fontcolor="#c53030", tooltip="FK_brandpic_brand"];
+ brandpic:pic_id:sw -> pic:in2:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_brandpic_pic"];
+ articlelistarticle:articlelist_id:se -> articlelist:in0:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_articlelistarticle_articlelist"];
+ articlelistarticle:article_id:se -> article:in3:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_articlelistarticle_article"];
+ optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#6b46c1", fontcolor="#6b46c1", tooltip="FK_optionslistarticle_optionslist"];
+ optionslistarticle:article_id:sw -> article:in7:n [color="#c05621", fontcolor="#c05621", tooltip="FK_optionslistarticle_article"];
+ categoryarticle:category_id:sw -> category:in4:n [color="#319795", fontcolor="#319795", tooltip="FK_categoryarticle_category"];
+ categoryarticle:article_id:e -> article:in2:n [color="#97266d", fontcolor="#97266d", tooltip="FK_categoryarticle_article"];
+ categorypic:category_id:se -> category:in2:n [color="#4c51bf", fontcolor="#4c51bf", tooltip="FK_categorypic_category"];
+ categorypic:pic_id:se -> pic:in0:n [color="#276749", fontcolor="#276749", tooltip="FK_categorypic_pic"];
+ categorytext:category_id:e -> category:in0:n [color="#9c4221", fontcolor="#9c4221", tooltip="FK_categorytext_category"];
+ categorytext:language_id:sw -> language:in2:n [color="#0987a0", fontcolor="#0987a0", tooltip="FK_categorytext_language"];
+ articletext:article_id:e -> article:in0:n [color="#553c9a", fontcolor="#553c9a", tooltip="FK_articletext_article"];
+ articletext:language_id:sw -> language:in0:n [color="#742a2a", fontcolor="#742a2a", tooltip="FK_articletext_language"];
+ price:tax_id:sw -> tax:in3:n [color="#22543d", fontcolor="#22543d", tooltip="FK_price_tax"];
+ price:article_id:w -> article:in8:n [color="#1a365d", fontcolor="#1a365d", tooltip="FK_price_article"];
+ price:customergroup_id:sw -> customergroup:in2:n [color="#702459", fontcolor="#702459", tooltip="FK_price_customergroup"];
+ manifestarticle:manifest_id:sw -> manifest:in0:n [color="#744210", fontcolor="#744210", tooltip="FK_manifestarticle_manifest"];
+ manifestarticle:article_id:w -> article:in9:n [color="#285e61", fontcolor="#285e61", tooltip="FK_manifestarticle_article"];
+ manifestarticle:tax_id:sw -> tax:in4:n [color="#d53f8c", fontcolor="#d53f8c", tooltip="FK_manifestarticle_tax"];
+ numbers:company_id:se -> company:in3:n [color="#dd6b20", fontcolor="#dd6b20", tooltip="FK_numbers_company"];
+ companyuser:google_auth_id:e -> google_auth:in0:n [color="#38a169", fontcolor="#38a169", tooltip="FK_companyuser_google_auth"];
+ companyuser:company_id:se -> company:in4:n [color="#3182ce", fontcolor="#3182ce", tooltip="FK_companyuser_company"];
+ syncpos:company_id:se -> company:in5:n [color="#805ad5", fontcolor="#805ad5", tooltip="FK_syncpos_company"];
+ companysetting:company_id:sw -> company:in7:n [color="#c53030", fontcolor="#c53030", tooltip="FK_companysetting_company"];
+ posterminal:company_id:e -> company:in0:n [color="#2f855a", fontcolor="#2f855a", tooltip="FK_posterminal_company"];
+ posterminalcategory:posterminal_id:se -> posterminal:in0:n [color="#2b6cb0", fontcolor="#2b6cb0", tooltip="FK_posterminalcategory_posterminal"];
+ posterminalcategory:category_id:se -> category:in1:n [color="#b7791f", fontcolor="#b7791f", tooltip="FK_posterminalcategory_category"];
}
\ No newline at end of file
diff --git a/schema-graph.svg b/schema-graph.svg
index f661f3e..765783f 100644
--- a/schema-graph.svg
+++ b/schema-graph.svg
@@ -4,233 +4,309 @@
-