From e491ecfa4730840e0914fda0457808dd3069ff67 Mon Sep 17 00:00:00 2001 From: seb Date: Sat, 26 Sep 2026 07:34:37 +0200 Subject: [PATCH] u --- API.md | 2 +- API.numbers.md | 2 +- generate-api-doc.js | 2 +- generate-schema-graph.js | 2 +- schema-graph.dot | 151 +- schema-graph.svg | 3253 ++++++++++---------- scripts/migrate-manifest-created-at.js | 112 + scripts/test-ledger.js | 13 +- scripts/test-manifest-two-dates.js | 147 + src/providers/mssql/queries/delivery.js | 51 +- src/providers/mssql/queries/ledger.js | 14 +- src/providers/mssql/queries/manifestDoc.js | 13 +- src/providers/mssql/schema.js | 1 + src/providers/sqlite3/queries/ledger.js | 14 +- src/providers/sqlite3/schema.js | 1 + src/store/delivery/create.js | 5 + src/store/delivery/helpers.js | 12 +- src/store/delivery/update.js | 12 +- src/store/ledger/add.js | 36 +- src/store/manifestDoc/helpers.js | 1 + src/store/order/create.js | 5 + src/store/order/update.js | 12 +- src/store/quote/create.js | 5 + src/store/quote/update.js | 12 +- 24 files changed, 2162 insertions(+), 1716 deletions(-) create mode 100644 scripts/migrate-manifest-created-at.js create mode 100644 scripts/test-manifest-two-dates.js diff --git a/API.md b/API.md index e2b754c..872ebd3 100644 --- a/API.md +++ b/API.md @@ -34,7 +34,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met - **google_auth**: `id PK, email:s(320) UQ, picture?:s(2048), picturebinary?:bin, sub?:s(255) UQ, created_at:dt, selected_company?->company` - **language**: `id PK, iso639:s(10) UQ` - **ledger**: `id PK, manifestarticle_id->manifestarticle, transaction_type:s(100), amount:f, transaction_text:txt, hash_version:i, prev_hash?:s(128) [append-only]` -- **manifest**: `id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt` +- **manifest**: `id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt` - **manifestarticle**: `id PK, manifest_id->manifest, sku?:s(100), name?:s(255), pu:f=1, amount:f, net:f, comment?:txt, rebate:f=0, taxrate:f=0` - **manifestattachment**: `id PK, manifest_id->manifest, file_id->file, sort:i=0, UQ(manifest_id, sort)` - **meta**: `key:s(255) PK, value:txt` diff --git a/API.numbers.md b/API.numbers.md index 85cdbcb..64d322f 100644 --- a/API.numbers.md +++ b/API.numbers.md @@ -6,7 +6,7 @@ This document is auto-generated from the JSDoc comments in `src/store` by runnin *Source: `src/store/numbers/list.js`* -Lists the number sequences of the user's selected company. +Lists the number sequences of the user's selected company. Ensures that default sequences ('sku', 'entity', 'delivery') exist. **Parameters** diff --git a/generate-api-doc.js b/generate-api-doc.js index b30d937..9f1c10d 100644 --- a/generate-api-doc.js +++ b/generate-api-doc.js @@ -336,7 +336,7 @@ const SCHEMA_TABLES = [ ['google_auth', 'id PK, email:s(320) UQ, picture?:s(2048), picturebinary?:bin, sub?:s(255) UQ, created_at:dt, selected_company?->company'], ['language', 'id PK, iso639:s(10) UQ'], ['ledger', 'id PK, manifestarticle_id->manifestarticle, transaction_type:s(100), amount:f, transaction_text:txt, hash_version:i, prev_hash?:s(128) [append-only]'], - ['manifest', 'id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt'], + ['manifest', 'id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt'], ['manifestarticle', 'id PK, manifest_id->manifest, sku?:s(100), name?:s(255), pu:f=1, amount:f, net:f, comment?:txt, rebate:f=0, taxrate:f=0'], ['manifestattachment', 'id PK, manifest_id->manifest, file_id->file, sort:i=0, UQ(manifest_id, sort)'], ['meta', 'key:s(255) PK, value:txt'], diff --git a/generate-schema-graph.js b/generate-schema-graph.js index c5bc26f..dadb7a6 100644 --- a/generate-schema-graph.js +++ b/generate-schema-graph.js @@ -810,7 +810,7 @@ function buildDot(tables, edges, positions = null, anchors = null) { // mclimit bumps Graphviz's own crossing-minimization iterations. ` graph [rankdir=TB, splines=spline, overlap=false, bgcolor="${COLORS.background}",`, ' nodesep=0.95, ranksep=1.0, pad=0.5, fontname="Helvetica",', - ' compound=true, newrank=true, mclimit=4.0];', + ' compound=true, mclimit=4.0];', ' node [shape=plaintext, fontname="Helvetica"];', ' edge [fontname="Helvetica", fontsize=9, penwidth=1.4, arrowsize=0.7];', '', diff --git a/schema-graph.dot b/schema-graph.dot index 3c97457..2bba96b 100644 --- a/schema-graph.dot +++ b/schema-graph.dot @@ -1,7 +1,7 @@ digraph schema { graph [rankdir=TB, splines=spline, overlap=false, bgcolor="#1e2430", nodesep=0.95, ranksep=1.0, pad=0.5, fontname="Helvetica", - compound=true, newrank=true, mclimit=4.0]; + compound=true, mclimit=4.0]; node [shape=plaintext, fontname="Helvetica"]; edge [fontname="Helvetica", fontsize=9, penwidth=1.4, arrowsize=0.7]; @@ -11,15 +11,15 @@ digraph schema { fontcolor="#e2e8f0"; margin=14; meta [label=<
meta
keyNVARCHAR(255)
valueNVARCHAR(MAX)
>]; customergroup [label=<
customergroup
idINT PK
company_id?INT FK
nameNVARCHAR(255)
sortINT
>]; - entity [label=<
entity
idINT PK
customergroup_idINT FK
first_name?NVARCHAR(255)
nameNVARCHAR(255)
number?NVARCHAR(100)
created_atDATETIME2
updated_atDATETIME2
>]; numbers [label=<
numbers
idINT PK
company_idINT FK
typeNVARCHAR(100)
prefixNVARCHAR(50)
paddingINT
nextINT
>]; entityaddress [label=<
entityaddress
idINT PK
entity_idINT FK
first_name?NVARCHAR(255)
name?NVARCHAR(255)
street?NVARCHAR(255)
streetnumber?NVARCHAR(50)
zip?NVARCHAR(50)
city?NVARCHAR(255)
county?NVARCHAR(255)
addresstypeNVARCHAR(50)
sortINT
>]; + entity [label=<
entity
idINT PK
customergroup_idINT FK
first_name?NVARCHAR(255)
nameNVARCHAR(255)
number?NVARCHAR(100)
created_atDATETIME2
updated_atDATETIME2
>]; companyuser [label=<
companyuser
idINT PK
company_idINT FK
google_auth_idINT FK
>]; entitycontact [label=<
entitycontact
idINT PK
entity_idINT FK
contacttypeNVARCHAR(50)
contactNVARCHAR(255)
sortINT
>]; - company [label=<
company
idINT PK
poscustomergroup_idINT FK
default_language_id?INT FK
root_category_id?INT FK
taxrate?INT FK
reducedtaxrate?INT FK
taxfreerate?INT FK
nameNVARCHAR(255)
>]; syncpos [label=<
syncpos
idINT PK
company_idINT FK
paircodeNVARCHAR(50)
token?NVARCHAR(255)
>]; entityhistory [label=<
entityhistory
idINT PK
entity_idINT FK
companyuser_id?INT FK
dateDATETIME2
textNVARCHAR(MAX)
>]; companysetting [label=<
companysetting
idINT PK
company_idINT FK
keyNVARCHAR(255)
valueNVARCHAR(MAX)
frontendBIT
writeonlyBIT
>]; + company [label=<
company
idINT PK
poscustomergroup_idINT FK
default_language_id?INT FK
taxrate?INT FK
reducedtaxrate?INT FK
taxfreerate?INT FK
root_category_id?INT FK
nameNVARCHAR(255)
>]; google_auth [label=<
google_auth
idINT PK
selected_company?INT FK
emailNVARCHAR(320)
picture?NVARCHAR(2048)
picturebinary?VARBINARY(MAX)
sub?NVARCHAR(255)
created_atDATETIME2
>]; } subgraph cluster_i18n { @@ -27,7 +27,7 @@ digraph schema { style="rounded,filled"; fillcolor="#202a3a"; color="#3f4c5f"; fontcolor="#e2e8f0"; margin=14; language [label=<
language
idINT PK
iso639NVARCHAR(10)
>]; - unittext [label=<
unittext
idINT PK
unit_idINT FK
language_idINT FK
symbolNVARCHAR(50)
nameNVARCHAR(255)
>]; + unittext [label=<
unittext
idINT PK
language_idINT FK
unit_idINT FK
symbolNVARCHAR(50)
nameNVARCHAR(255)
>]; categorytext [label=<
categorytext
idINT PK
language_idINT FK
category_idINT FK
nameNVARCHAR(255)
slugNVARCHAR(255)
seo?NVARCHAR(MAX)
desc?NVARCHAR(MAX)
>]; articletext [label=<
articletext
idINT PK
language_idINT FK
article_idINT FK
nameNVARCHAR(255)
slugNVARCHAR(255)
seo?NVARCHAR(MAX)
desc?NVARCHAR(MAX)
>]; } @@ -35,26 +35,26 @@ digraph schema { label="Catalog"; labeljust="l"; fontsize=15; style="rounded,filled"; fillcolor="#202a3a"; color="#3f4c5f"; fontcolor="#e2e8f0"; margin=14; - optionslist [label=<
optionslist
idINT PK
>]; - categoryarticle [label=<
categoryarticle
idINT PK
category_idINT FK
article_idINT FK
>]; - optionslistarticle [label=<
optionslistarticle
idINT PK
optionslist_idINT FK
article_idINT FK
countINT
sortINT
mandatedBIT
>]; category [label=<
category
idINT PK
parent_id?INT FK
sortINT
>]; + categoryarticle [label=<
categoryarticle
idINT PK
category_idINT FK
article_idINT FK
>]; articlehistory [label=<
articlehistory
idINT PK
companyuser_id?INT FK
article_idINT FK
dateDATETIME2
textNVARCHAR(MAX)
>]; - article [label=<
article
idINT PK
unit_idINT FK
optionslist_id?INT FK
fatherarticle_id?INT FK
articlelist_id?INT FK
brand_id?INT FK
sku?NVARCHAR(100)
unitamountFLOAT
weightFLOAT
is_fatherBIT
fathersortINT
created_atDATETIME2
updated_atDATETIME2
>]; - articlelist [label=<
articlelist
idINT PK
>]; + article [label=<
article
idINT PK
company_idINT FK
unit_idINT FK
fatherarticle_id?INT FK
articlelist_id?INT FK
optionslist_id?INT FK
brand_id?INT FK
sku?NVARCHAR(100)
unitamountFLOAT
weightFLOAT
is_fatherBIT
fathersortINT
created_atDATETIME2
updated_atDATETIME2
>]; articleean [label=<
articleean
idINT PK
article_idINT FK
eanNVARCHAR(50)
>]; + articlesupplier [label=<
articlesupplier
idINT PK
entity_idINT FK
price_id?INT FK
article_idINT FK
sku?NVARCHAR(100)
stock?FLOAT
puFLOAT
sortINT
>]; + articlelist [label=<
articlelist
idINT PK
>]; articlelistarticle [label=<
articlelistarticle
idINT PK
article_idINT FK
articlelist_idINT FK
countINT
sortINT
>]; + optionslist [label=<
optionslist
idINT PK
>]; + optionslistarticle [label=<
optionslistarticle
idINT PK
article_idINT FK
optionslist_idINT FK
countINT
sortINT
mandatedBIT
>]; brand [label=<
brand
idINT PK
company_idINT FK
nameNVARCHAR(255)
gsprpinfo?NVARCHAR(MAX)
>]; - articlesupplier [label=<
articlesupplier
idINT PK
entity_idINT FK
article_idINT FK
price_id?INT FK
sku?NVARCHAR(100)
stock?FLOAT
puFLOAT
sortINT
>]; } subgraph cluster_media { label="Media"; labeljust="l"; fontsize=15; style="rounded,filled"; fillcolor="#202a3a"; color="#3f4c5f"; fontcolor="#e2e8f0"; margin=14; - categorypic [label=<
categorypic
idINT PK
category_idINT FK
pic_idINT FK
sortINT
>]; pic [label=<
pic
idINT PK
wINT
hINT
sizeBIGINT
hashNVARCHAR(64)
thumbwINT
thumbhINT
thumbhashNVARCHAR(64)
sourceNVARCHAR(MAX)
fullbinaryVARBINARY(MAX)
thumbbinaryVARBINARY(MAX)
>]; - brandpic [label=<
brandpic
idINT PK
brand_idINT FK
pic_idINT FK
sortINT
>]; - articlepic [label=<
articlepic
idINT PK
article_idINT FK
pic_idINT FK
sortINT
>]; + categorypic [label=<
categorypic
idINT PK
pic_idINT FK
category_idINT FK
sortINT
>]; + articlepic [label=<
articlepic
idINT PK
pic_idINT FK
article_idINT FK
sortINT
>]; + brandpic [label=<
brandpic
idINT PK
pic_idINT FK
brand_idINT FK
sortINT
>]; file [label=<
file
idINT PK
hashNVARCHAR(64)
mimetypeNVARCHAR(255)
nameNVARCHAR(255)
sizeBIGINT
createdDATETIME2
modifiedDATETIME2
contentVARBINARY(MAX)
thumbavif?VARBINARY(MAX)
thumbx?INT
thumby?INT
meta?NVARCHAR(MAX)
>]; } subgraph cluster_manifests { @@ -63,7 +63,7 @@ digraph schema { fontcolor="#e2e8f0"; margin=14; manifestarticle [label=<
manifestarticle
idINT PK
manifest_idINT FK
sku?NVARCHAR(100)
name?NVARCHAR(255)
puFLOAT
amountFLOAT
netFLOAT
comment?NVARCHAR(MAX)
rebateFLOAT
taxrateFLOAT
>]; ledger [label=<
ledger
idINT PK
manifestarticle_idINT FK
transaction_typeNVARCHAR(100)
amountFLOAT
transaction_textNVARCHAR(MAX)
hash_versionINT
prev_hash?NVARCHAR(128)
created_atDATETIME2
>]; - manifest [label=<
manifest
idINT PK
entity_id?INT FK
company_idINT FK
numberNVARCHAR(100)
typeNVARCHAR(100)
external_number?NVARCHAR(100)
dateDATETIME2
>]; + manifest [label=<
manifest
idINT PK
entity_id?INT FK
company_idINT FK
numberNVARCHAR(100)
typeNVARCHAR(100)
external_number?NVARCHAR(100)
dateDATETIME2
created_atDATETIME2
>]; manifestattachment [label=<
manifestattachment
idINT PK
manifest_idINT FK
file_idINT FK
sortINT
>]; } subgraph cluster_commerce { @@ -71,7 +71,7 @@ digraph schema { style="rounded,filled"; fillcolor="#202a3a"; color="#3f4c5f"; fontcolor="#e2e8f0"; margin=14; tax [label=<
tax
idINT PK
rateDECIMAL(18,4)
>]; - price [label=<
price
idINT PK
customergroup_idINT FK
article_idINT FK
tax_idINT FK
minmultiplierDECIMAL(18,4)
netFLOAT
>]; + price [label=<
price
idINT PK
customergroup_idINT FK
tax_idINT FK
article_idINT FK
minmultiplierDECIMAL(18,4)
netFLOAT
>]; } subgraph cluster_units { label="Units"; labeljust="l"; fontsize=15; @@ -84,71 +84,72 @@ digraph schema { style="rounded,filled"; fillcolor="#202a3a"; color="#3f4c5f"; fontcolor="#e2e8f0"; margin=14; posterminal [label=<
posterminal
idINT PK
company_idINT FK
nameNVARCHAR(255)
token?NVARCHAR(255)
pairingpinNVARCHAR(50)
>]; - posterminalcategory [label=<
posterminalcategory
idINT PK
posterminal_idINT FK
category_idINT FK
>]; + posterminalcategory [label=<
posterminalcategory
idINT PK
category_idINT FK
posterminal_idINT FK
>]; } __legend [label=<
Legend
PKprimary key
FKforeign key
?nullable column
—FOREIGN KEY constraint
- -naming convention only, no constraint
>]; - google_auth:selected_company:w -> company:in7:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_google_auth_selected_company", weight=3]; + google_auth:selected_company:sw -> company:in4:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_google_auth_selected_company", weight=3]; category:parent_id:e -> category:id:e [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_category_parent", constraint=false]; - company:root_category_id:w -> category:in1:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_company_root_category", weight=3]; - company:default_language_id:se -> language:in0:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_company_default_language", constraint=false, weight=1]; - company:taxrate:e -> tax:in1:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_company_taxrate", constraint=false, weight=1]; - company:reducedtaxrate:e -> tax:in0:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_company_reducedtaxrate", constraint=false, weight=1]; + company:root_category_id:e -> category:in0:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_company_root_category", weight=3]; + company:default_language_id:ne -> language:in0:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_company_default_language", constraint=false, weight=1]; + company:taxrate:e -> tax:in3:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_company_taxrate", constraint=false, weight=1]; + company:reducedtaxrate:e -> tax:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_company_reducedtaxrate", constraint=false, weight=1]; company:taxfreerate:e -> tax:in2:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_company_taxfreerate", constraint=false, weight=1]; - customergroup:company_id:w -> company:in2:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_customergroup_company", weight=3]; - company:poscustomergroup_id:w -> customergroup:in1:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_company_poscustomergroup", constraint=false, weight=1]; - entity:customergroup_id:nw -> customergroup:in0:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_entity_customergroup", weight=3]; - entityaddress:entity_id:se -> entity:in1:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_entityaddress_entity", weight=3]; - entitycontact:entity_id:nw -> entity:in2:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_entitycontact_entity", weight=3]; - entityhistory:entity_id:w -> entity:in3:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_entityhistory_entity", weight=3]; - entityhistory:companyuser_id:sw -> companyuser:in0:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_entityhistory_companyuser", constraint=false, weight=1]; - unit:company_id:w -> company:in8:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_unit_company", weight=3]; + customergroup:company_id:w -> company:in0:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_customergroup_company", weight=3]; + company:poscustomergroup_id:w -> customergroup:in2:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_company_poscustomergroup", constraint=false, weight=1]; + entity:customergroup_id:w -> customergroup:in0:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_entity_customergroup", weight=3]; + entityaddress:entity_id:ne -> entity:in0:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_entityaddress_entity", weight=3]; + entitycontact:entity_id:sw -> entity:in1:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_entitycontact_entity", weight=3]; + entityhistory:entity_id:sw -> entity:in2:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_entityhistory_entity", weight=3]; + entityhistory:companyuser_id:se -> companyuser:in1:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_entityhistory_companyuser", constraint=false, weight=1]; + unit:company_id:e -> company:in7:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_unit_company", weight=3]; unittext:unit_id:sw -> unit:in0:n [color="#5aa7e6", fontcolor="#5aa7e6", tooltip="FK_unittext_unit", weight=3]; - unittext:language_id:nw -> language:in1:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="FK_unittext_language", constraint=false, weight=1]; - brand:company_id:e -> company:in9:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="FK_brand_company", weight=3]; - article:unit_id:w -> unit:in1:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="FK_article_unit", constraint=false, weight=1]; - article:brand_id:ne -> brand:in0:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="FK_article_brand", constraint=false, weight=1]; - article:articlelist_id:sw -> articlelist:in0:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="FK_article_articlelist", weight=3]; - article:optionslist_id:w -> optionslist:in1:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_article_optionslist", constraint=false, weight=1]; - article:fatherarticle_id:e -> article:id:e [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_article_fatherarticle", constraint=false]; - articlehistory:article_id:ne -> article:in3:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_articlehistory_article", weight=3]; - articlehistory:companyuser_id:w -> companyuser:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_articlehistory_companyuser", constraint=false, weight=1]; - articleean:article_id:sw -> article:in6:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articleean_article", weight=3]; - articlepic:article_id:e -> article:in8:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articlepic_article", weight=3]; - articlepic:pic_id:w -> pic:in2:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_articlepic_pic", constraint=false, weight=1]; - brandpic:brand_id:w -> brand:in1:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_brandpic_brand", weight=3]; - brandpic:pic_id:ne -> pic:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_brandpic_pic", constraint=false, weight=1]; - articlelistarticle:articlelist_id:sw -> articlelist:in1:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_articlelistarticle_articlelist", weight=3]; - articlelistarticle:article_id:w -> article:in9:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; - optionslistarticle:optionslist_id:sw -> optionslist:in0:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_optionslistarticle_optionslist", weight=3]; - optionslistarticle:article_id:se -> article:in2:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_optionslistarticle_article", constraint=false, weight=1]; - categoryarticle:category_id:se -> category:in3:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_categoryarticle_category", weight=3]; - categoryarticle:article_id:ne -> article:in0:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_categoryarticle_article", constraint=false, weight=1]; - categorypic:category_id:e -> category:in5:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_categorypic_category", weight=3]; - categorypic:pic_id:nw -> pic:in0:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_categorypic_pic", constraint=false, weight=1]; - categorytext:category_id:se -> category:in2:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_categorytext_category", weight=3]; - categorytext:language_id:nw -> language:in3:n [color="#5aa7e6", fontcolor="#5aa7e6", tooltip="FK_categorytext_language", constraint=false, weight=1]; - articletext:article_id:w -> article:in1:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="FK_articletext_article", weight=3]; - articletext:language_id:w -> language:in2:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="FK_articletext_language", constraint=false, weight=1]; - price:tax_id:w -> tax:in3:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="FK_price_tax", constraint=false, weight=1]; - price:article_id:e -> article:in7:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="FK_price_article", weight=3]; - price:customergroup_id:e -> customergroup:in2:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="FK_price_customergroup", constraint=false, weight=1]; - articlesupplier:article_id:sw -> article:in5:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_articlesupplier_article", weight=3]; - articlesupplier:entity_id:w -> entity:in4:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_articlesupplier_entity", constraint=false, weight=1]; - articlesupplier:price_id:e -> price:in0:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_articlesupplier_price", constraint=false, weight=1]; - manifest:company_id:e -> company:in1:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_manifest_company", weight=3]; - manifest:entity_id:e -> entity:in0:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_manifest_entity", constraint=false, weight=1]; - manifestarticle:manifest_id:se -> manifest:in0:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_manifestarticle_manifest", weight=3]; - ledger:manifestarticle_id:sw -> manifestarticle:in0:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_ledger_manifestarticle", weight=3]; - numbers:company_id:e -> company:in3:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_numbers_company", weight=3]; - companyuser:google_auth_id:e -> google_auth:in0:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_companyuser_google_auth", constraint=false, weight=1]; - companyuser:company_id:se -> company:in4:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_companyuser_company", weight=3]; - syncpos:company_id:nw -> company:in5:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_syncpos_company", weight=3]; - companysetting:company_id:sw -> company:in6:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_companysetting_company", weight=3]; - posterminal:company_id:e -> company:in0:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_posterminal_company", weight=3]; - posterminalcategory:posterminal_id:sw -> posterminal:in0:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_posterminalcategory_posterminal", weight=3]; - posterminalcategory:category_id:e -> category:in0:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_posterminalcategory_category", constraint=false, weight=1]; - manifestattachment:manifest_id:sw -> manifest:in1:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_manifestattachment_manifest", weight=3]; - manifestattachment:file_id:e -> file:in0:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_manifestattachment_file", constraint=false, weight=1]; + unittext:language_id:w -> language:in2:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="FK_unittext_language", constraint=false, weight=1]; + brand:company_id:w -> company:in9:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="FK_brand_company", weight=3]; + article:company_id:w -> company:in8:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="FK_article_company", weight=3]; + article:unit_id:w -> unit:in1:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="FK_article_unit", constraint=false, weight=1]; + article:brand_id:e -> brand:in1:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="FK_article_brand", constraint=false, weight=1]; + article:articlelist_id:e -> articlelist:in1:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_article_articlelist", constraint=false, weight=1]; + article:optionslist_id:e -> optionslist:in0:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_article_optionslist", constraint=false, weight=1]; + article:fatherarticle_id:e -> article:id:e [color="#c79bff", fontcolor="#c79bff", tooltip="FK_article_fatherarticle", constraint=false]; + articlehistory:article_id:se -> article:in4:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_articlehistory_article", weight=3]; + articlehistory:companyuser_id:w -> companyuser:in0:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_articlehistory_companyuser", constraint=false, weight=1]; + articleean:article_id:se -> article:in6:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_articleean_article", weight=3]; + articlepic:article_id:se -> article:in1:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_articlepic_article", weight=3]; + articlepic:pic_id:w -> pic:in1:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_articlepic_pic", constraint=false, weight=1]; + brandpic:brand_id:e -> brand:in0:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_brandpic_brand", weight=3]; + brandpic:pic_id:w -> pic:in2:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_brandpic_pic", constraint=false, weight=1]; + articlelistarticle:articlelist_id:sw -> articlelist:in0:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_articlelistarticle_articlelist", weight=3]; + articlelistarticle:article_id:e -> article:in8:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_articlelistarticle_article", constraint=false, weight=1]; + optionslistarticle:optionslist_id:sw -> optionslist:in1:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_optionslistarticle_optionslist", weight=3]; + optionslistarticle:article_id:e -> article:in9:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_optionslistarticle_article", constraint=false, weight=1]; + categoryarticle:category_id:se -> category:in4:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_categoryarticle_category", weight=3]; + categoryarticle:article_id:se -> article:in3:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_categoryarticle_article", constraint=false, weight=1]; + categorypic:category_id:se -> category:in2:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_categorypic_category", weight=3]; + categorypic:pic_id:e -> pic:in0:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_categorypic_pic", constraint=false, weight=1]; + categorytext:category_id:e -> category:in1:n [color="#5aa7e6", fontcolor="#5aa7e6", tooltip="FK_categorytext_category", weight=3]; + categorytext:language_id:w -> language:in1:n [color="#ff7aa8", fontcolor="#ff7aa8", tooltip="FK_categorytext_language", constraint=false, weight=1]; + articletext:article_id:e -> article:in0:n [color="#e0b25a", fontcolor="#e0b25a", tooltip="FK_articletext_article", weight=3]; + articletext:language_id:w -> language:in3:n [color="#56c8c8", fontcolor="#56c8c8", tooltip="FK_articletext_language", constraint=false, weight=1]; + price:tax_id:w -> tax:in0:n [color="#ff5f9e", fontcolor="#ff5f9e", tooltip="FK_price_tax", constraint=false, weight=1]; + price:article_id:sw -> article:in2:n [color="#ffb25a", fontcolor="#ffb25a", tooltip="FK_price_article", weight=3]; + price:customergroup_id:e -> customergroup:in1:n [color="#56d68a", fontcolor="#56d68a", tooltip="FK_price_customergroup", constraint=false, weight=1]; + articlesupplier:article_id:sw -> article:in7:n [color="#66b3ff", fontcolor="#66b3ff", tooltip="FK_articlesupplier_article", weight=3]; + articlesupplier:entity_id:w -> entity:in4:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_articlesupplier_entity", constraint=false, weight=1]; + articlesupplier:price_id:e -> price:in0:n [color="#ff6b6b", fontcolor="#ff6b6b", tooltip="FK_articlesupplier_price", constraint=false, weight=1]; + manifest:company_id:sw -> company:in6:n [color="#6bdf9a", fontcolor="#6bdf9a", tooltip="FK_manifest_company", weight=3]; + manifest:entity_id:w -> entity:in3:n [color="#5aa7ff", fontcolor="#5aa7ff", tooltip="FK_manifest_entity", constraint=false, weight=1]; + manifestarticle:manifest_id:se -> manifest:in0:n [color="#f5c15c", fontcolor="#f5c15c", tooltip="FK_manifestarticle_manifest", weight=3]; + ledger:manifestarticle_id:sw -> manifestarticle:in0:n [color="#c79bff", fontcolor="#c79bff", tooltip="FK_ledger_manifestarticle", weight=3]; + numbers:company_id:e -> company:in1:n [color="#ff9f5a", fontcolor="#ff9f5a", tooltip="FK_numbers_company", weight=3]; + companyuser:google_auth_id:e -> google_auth:in0:n [color="#5cd6d6", fontcolor="#5cd6d6", tooltip="FK_companyuser_google_auth", constraint=false, weight=1]; + companyuser:company_id:w -> company:in3:n [color="#ff7fc2", fontcolor="#ff7fc2", tooltip="FK_companyuser_company", weight=3]; + syncpos:company_id:se -> company:in2:n [color="#8f95ff", fontcolor="#8f95ff", tooltip="FK_syncpos_company", weight=3]; + companysetting:company_id:se -> company:in5:n [color="#58d18a", fontcolor="#58d18a", tooltip="FK_companysetting_company", weight=3]; + posterminal:company_id:w -> company:in10:n [color="#ff8f66", fontcolor="#ff8f66", tooltip="FK_posterminal_company", weight=3]; + posterminalcategory:posterminal_id:sw -> posterminal:in0:n [color="#4fc4e8", fontcolor="#4fc4e8", tooltip="FK_posterminalcategory_posterminal", weight=3]; + posterminalcategory:category_id:w -> category:in5:n [color="#a98cff", fontcolor="#a98cff", tooltip="FK_posterminalcategory_category", constraint=false, weight=1]; + manifestattachment:manifest_id:sw -> manifest:in1:n [color="#ff6f6f", fontcolor="#ff6f6f", tooltip="FK_manifestattachment_manifest", weight=3]; + manifestattachment:file_id:e -> file:in0:n [color="#4ecb8f", fontcolor="#4ecb8f", tooltip="FK_manifestattachment_file", constraint=false, weight=1]; } \ No newline at end of file diff --git a/schema-graph.svg b/schema-graph.svg index 2671e68..336889d 100644 --- a/schema-graph.svg +++ b/schema-graph.svg @@ -4,436 +4,562 @@ - - + + schema - + cluster_identity - -Identity & Company + +Identity & Company cluster_i18n - -Localization + +Localization cluster_catalog - -Catalog + +Catalog cluster_media - -Media + +Media cluster_manifests - -Documents + +Documents cluster_commerce - -Pricing + +Pricing cluster_units - -Units + +Units cluster_pos - -POS + +POS meta - - -meta - - -key -NVARCHAR(255) - - -value -NVARCHAR(MAX) + + +meta + + +key +NVARCHAR(255) + + +value +NVARCHAR(MAX) customergroup - - - - - -customergroup - - -id -INT -PK - - -company_id -? -INT -FK - - -name -NVARCHAR(255) - - -sort -INT + + + + + +customergroup + + +id +INT +PK + + +company_id +? +INT +FK + + +name +NVARCHAR(255) + + +sort +INT - + company - - - - - - - - - - - - -company - - -id -INT -PK - - -poscustomergroup_id -INT -FK - - -default_language_id -? -INT -FK - - -root_category_id -? -INT -FK - - -taxrate -? -INT -FK - - -reducedtaxrate -? -INT -FK - - -taxfreerate -? -INT -FK - - -name -NVARCHAR(255) + + + + + + + + + + + + + +company + + +id +INT +PK + + +poscustomergroup_id +INT +FK + + +default_language_id +? +INT +FK + + +taxrate +? +INT +FK + + +reducedtaxrate +? +INT +FK + + +taxfreerate +? +INT +FK + + +root_category_id +? +INT +FK + + +name +NVARCHAR(255) customergroup:w->company:n - - - - - - - -entity - - - - - - - -entity - - -id -INT -PK - - -customergroup_id -INT -FK - - -first_name? -NVARCHAR(255) - - -name -NVARCHAR(255) - - -number? -NVARCHAR(100) - - -created_at -DATETIME2 - - -updated_at -DATETIME2 - - - -entity:nw->customergroup:n - - - + + - + numbers - - -numbers - - -id -INT -PK - - -company_id -INT -FK - - -type -NVARCHAR(100) - - -prefix -NVARCHAR(50) - - -padding -INT - - -next -INT + + +numbers + + +id +INT +PK + + +company_id +INT +FK + + +type +NVARCHAR(100) + + +prefix +NVARCHAR(50) + + +padding +INT + + +next +INT - + numbers:e->company:n - - - + + + - + entityaddress - - -entityaddress - - -id -INT -PK - - -entity_id -INT -FK - - -first_name? -NVARCHAR(255) - - -name? -NVARCHAR(255) - - -street? -NVARCHAR(255) - - -streetnumber? -NVARCHAR(50) - - -zip? -NVARCHAR(50) - - -city? -NVARCHAR(255) - - -county? -NVARCHAR(255) - - -addresstype -NVARCHAR(50) - - -sort -INT + + +entityaddress + + +id +INT +PK + + +entity_id +INT +FK + + +first_name? +NVARCHAR(255) + + +name? +NVARCHAR(255) + + +street? +NVARCHAR(255) + + +streetnumber? +NVARCHAR(50) + + +zip? +NVARCHAR(50) + + +city? +NVARCHAR(255) + + +county? +NVARCHAR(255) + + +addresstype +NVARCHAR(50) + + +sort +INT + + + +entity + + + + + + + +entity + + +id +INT +PK + + +customergroup_id +INT +FK + + +first_name? +NVARCHAR(255) + + +name +NVARCHAR(255) + + +number? +NVARCHAR(100) + + +created_at +DATETIME2 + + +updated_at +DATETIME2 -entityaddress:se->entity:n +entityaddress:ne->entity:n - - + + + + + + + +entity:w->customergroup:n + + + companyuser - - - - -companyuser - - -id -INT -PK - - -company_id -INT -FK - - -google_auth_id -INT -FK + + + + +companyuser + + +id +INT +PK + + +company_id +INT +FK + + +google_auth_id +INT +FK - -companyuser:se->company:n - - - + +companyuser:w->company:n + + + google_auth - - - -google_auth - - -id -INT -PK - - -selected_company -? -INT -FK - - -email -NVARCHAR(320) - - -picture? -NVARCHAR(2048) - - -picturebinary? -VARBINARY(MAX) - - -sub? -NVARCHAR(255) - - -created_at -DATETIME2 + + + +google_auth + + +id +INT +PK + + +selected_company +? +INT +FK + + +email +NVARCHAR(320) + + +picture? +NVARCHAR(2048) + + +picturebinary? +VARBINARY(MAX) + + +sub? +NVARCHAR(255) + + +created_at +DATETIME2 - + companyuser:e->google_auth:n - - - + + + entitycontact - - -entitycontact - - -id -INT -PK - - -entity_id -INT -FK - - -contacttype -NVARCHAR(50) - - -contact -NVARCHAR(255) - - -sort -INT + + +entitycontact + + +id +INT +PK + + +entity_id +INT +FK + + +contacttype +NVARCHAR(50) + + +contact +NVARCHAR(255) + + +sort +INT -entitycontact:nw->entity:n +entitycontact:sw->entity:n - - + + + + + + + +syncpos + + +syncpos + + +id +INT +PK + + +company_id +INT +FK + + +paircode +NVARCHAR(50) + + +token? +NVARCHAR(255) + + + +syncpos:se->company:n + + + + + + + + +entityhistory + + +entityhistory + + +id +INT +PK + + +entity_id +INT +FK + + +companyuser_id +? +INT +FK + + +date +DATETIME2 + + +text +NVARCHAR(MAX) + + + +entityhistory:sw->entity:n + + + + + + + + +entityhistory:se->companyuser:n + + + + + + + + +companysetting + + +companysetting + + +id +INT +PK + + +company_id +INT +FK + + +key +NVARCHAR(255) + + +value +NVARCHAR(MAX) + + +frontend +BIT + + +writeonly +BIT + + + +companysetting:se->company:n + + + @@ -441,103 +567,103 @@ company:w->customergroup:n - - + + language - - - - - - -language - - -id -INT -PK - - -iso639 -NVARCHAR(10) + + + + + + +language + + +id +INT +PK + + +iso639 +NVARCHAR(10) -company:se->language:n +company:ne->language:n - - + + - + category - - - - - - - - -category - - -id -INT -PK - - -parent_id -? -INT -FK - - -sort -INT + + + + + + + + +category + + +id +INT +PK + + +parent_id +? +INT +FK + + +sort +INT -company:w->category:n +company:e->category:n - - + + tax - - - - - - -tax - - -id -INT -PK - - -rate -DECIMAL(18,4) + + + + + + +tax + + +id +INT +PK + + +rate +DECIMAL(18,4) company:e->tax:n - - + + @@ -545,8 +671,8 @@ company:e->tax:n - - + + @@ -554,518 +680,292 @@ company:e->tax:n - - - - - - - -syncpos - - -syncpos - - -id -INT -PK - - -company_id -INT -FK - - -paircode -NVARCHAR(50) - - -token? -NVARCHAR(255) - - - -syncpos:nw->company:n - - - - - - - - -entityhistory - - -entityhistory - - -id -INT -PK - - -entity_id -INT -FK - - -companyuser_id -? -INT -FK - - -date -DATETIME2 - - -text -NVARCHAR(MAX) - - - -entityhistory:w->entity:n - - - - - - - - -entityhistory:sw->companyuser:n - - - - - - - - -companysetting - - -companysetting - - -id -INT -PK - - -company_id -INT -FK - - -key -NVARCHAR(255) - - -value -NVARCHAR(MAX) - - -frontend -BIT - - -writeonly -BIT - - - -companysetting:sw->company:n - - - + + -google_auth:w->company:n +google_auth:sw->company:n - - + + unittext - - -unittext - - -id -INT -PK - - -unit_id -INT -FK - - -language_id -INT -FK - - -symbol -NVARCHAR(50) - - -name -NVARCHAR(255) + + +unittext + + +id +INT +PK + + +language_id +INT +FK + + +unit_id +INT +FK + + +symbol +NVARCHAR(50) + + +name +NVARCHAR(255) -unittext:nw->language:n +unittext:w->language:n - - + + unit - - - - -unit - - -id -INT -PK - - -company_id -INT -FK - - -si -NVARCHAR(50) - - -basesi -NVARCHAR(50) - - -basesifactor -FLOAT + + + + +unit + + +id +INT +PK + + +company_id +INT +FK + + +si +NVARCHAR(50) + + +basesi +NVARCHAR(50) + + +basesifactor +FLOAT unittext:sw->unit:n - - + + categorytext - - -categorytext - - -id -INT -PK - - -language_id -INT -FK - - -category_id -INT -FK - - -name -NVARCHAR(255) - - -slug -NVARCHAR(255) - - -seo? -NVARCHAR(MAX) - - -desc? -NVARCHAR(MAX) + + +categorytext + + +id +INT +PK + + +language_id +INT +FK + + +category_id +INT +FK + + +name +NVARCHAR(255) + + +slug +NVARCHAR(255) + + +seo? +NVARCHAR(MAX) + + +desc? +NVARCHAR(MAX) - -categorytext:nw->language:n - - - + +categorytext:w->language:n + + + - -categorytext:se->category:n - - - + +categorytext:e->category:n + + + articletext - - -articletext - - -id -INT -PK - - -language_id -INT -FK - - -article_id -INT -FK - - -name -NVARCHAR(255) - - -slug -NVARCHAR(255) - - -seo? -NVARCHAR(MAX) - - -desc? -NVARCHAR(MAX) + + +articletext + + +id +INT +PK + + +language_id +INT +FK + + +article_id +INT +FK + + +name +NVARCHAR(255) + + +slug +NVARCHAR(255) + + +seo? +NVARCHAR(MAX) + + +desc? +NVARCHAR(MAX) - + articletext:w->language:n - - - + + + - + article - - - - - - - - - - - - -article - - -id -INT -PK - - -unit_id -INT -FK - - -optionslist_id -? -INT -FK - - -fatherarticle_id -? -INT -FK - - -articlelist_id -? -INT -FK - - -brand_id -? -INT -FK - - -sku? -NVARCHAR(100) - - -unitamount -FLOAT - - -weight -FLOAT - - -is_father -BIT - - -fathersort -INT - - -created_at -DATETIME2 - - -updated_at -DATETIME2 + + + + + + + + + + + + +article + + +id +INT +PK + + +company_id +INT +FK + + +unit_id +INT +FK + + +fatherarticle_id +? +INT +FK + + +articlelist_id +? +INT +FK + + +optionslist_id +? +INT +FK + + +brand_id +? +INT +FK + + +sku? +NVARCHAR(100) + + +unitamount +FLOAT + + +weight +FLOAT + + +is_father +BIT + + +fathersort +INT + + +created_at +DATETIME2 + + +updated_at +DATETIME2 - -articletext:w->article:n - - - - - - - - -optionslist - - - - -optionslist - - -id -INT -PK - - - -categoryarticle - - -categoryarticle - - -id -INT -PK - - -category_id -INT -FK - - -article_id -INT -FK - - - -categoryarticle:se->category:n - - - - - - - - -categoryarticle:ne->article:n - - - - - - - - -optionslistarticle - - -optionslistarticle - - -id -INT -PK - - -optionslist_id -INT -FK - - -article_id -INT -FK - - -count -INT - - -sort -INT - - -mandated -BIT - - - -optionslistarticle:sw->optionslist:n - - - - - - - - -optionslistarticle:se->article:n - - - + +articletext:e->article:n + + + @@ -1073,938 +973,1057 @@ category:e->category:e - - + + + + + + + +categoryarticle + + +categoryarticle + + +id +INT +PK + + +category_id +INT +FK + + +article_id +INT +FK + + + +categoryarticle:se->category:n + + + + + + + + +categoryarticle:se->article:n + + + - + articlehistory - - -articlehistory - - -id -INT -PK - - -companyuser_id -? -INT -FK - - -article_id -INT -FK - - -date -DATETIME2 - - -text -NVARCHAR(MAX) + + +articlehistory + + +id +INT +PK + + +companyuser_id +? +INT +FK + + +article_id +INT +FK + + +date +DATETIME2 + + +text +NVARCHAR(MAX) - + articlehistory:w->companyuser:n - - - + + + - -articlehistory:ne->article:n - - - + +articlehistory:se->article:n + + + - - -article:w->optionslist:n - - - + + +article:w->company:n + + + - + article:e->article:e - - - + + + articlelist - - - - -articlelist - - -id -INT -PK + + + + +articlelist + + +id +INT +PK - -article:sw->articlelist:n - - - + +article:e->articlelist:n + + + + + + + + +optionslist + + + + +optionslist + + +id +INT +PK + + + +article:e->optionslist:n + + + - + brand - - - - -brand - - -id -INT -PK - - -company_id -INT -FK - - -name -NVARCHAR(255) - - -gsprpinfo? -NVARCHAR(MAX) + + + + +brand + + +id +INT +PK + + +company_id +INT +FK + + +name +NVARCHAR(255) + + +gsprpinfo? +NVARCHAR(MAX) - -article:ne->brand:n - - - + +article:e->brand:n + + + - + article:w->unit:n - - - + + + - + articleean - - -articleean - - -id -INT -PK - - -article_id -INT -FK - - -ean -NVARCHAR(50) + + +articleean + + +id +INT +PK + + +article_id +INT +FK + + +ean +NVARCHAR(50) - -articleean:sw->article:n - - - - - - - - -articlelistarticle - - -articlelistarticle - - -id -INT -PK - - -article_id -INT -FK - - -articlelist_id -INT -FK - - -count -INT - - -sort -INT - - - -articlelistarticle:w->article:n - - - - - - - - -articlelistarticle:sw->articlelist:n - - - - - - - - -brand:e->company:n - - - + +articleean:se->article:n + + + - + articlesupplier - - -articlesupplier - - -id -INT -PK - - -entity_id -INT -FK - - -article_id -INT -FK - - -price_id -? -INT -FK - - -sku? -NVARCHAR(100) - - -stock? -FLOAT - - -pu -FLOAT - - -sort -INT + + +articlesupplier + + +id +INT +PK + + +entity_id +INT +FK + + +price_id +? +INT +FK + + +article_id +INT +FK + + +sku? +NVARCHAR(100) + + +stock? +FLOAT + + +pu +FLOAT + + +sort +INT - + articlesupplier:w->entity:n - - - + + + - + articlesupplier:sw->article:n - - - + + + price - - - -price - - -id -INT -PK - - -customergroup_id -INT -FK - - -article_id -INT -FK - - -tax_id -INT -FK - - -minmultiplier -DECIMAL(18,4) - - -net -FLOAT + + + +price + + +id +INT +PK + + +customergroup_id +INT +FK + + +tax_id +INT +FK + + +article_id +INT +FK + + +minmultiplier +DECIMAL(18,4) + + +net +FLOAT - + articlesupplier:e->price:n - - - + + + - - -categorypic - - -categorypic - - -id -INT -PK - - -category_id -INT -FK - - -pic_id -INT -FK - - -sort -INT + + +articlelistarticle + + +articlelistarticle + + +id +INT +PK + + +article_id +INT +FK + + +articlelist_id +INT +FK + + +count +INT + + +sort +INT - - -categorypic:e->category:n - - - + + +articlelistarticle:e->article:n + + + + + + + + +articlelistarticle:sw->articlelist:n + + + + + + + + +optionslistarticle + + +optionslistarticle + + +id +INT +PK + + +article_id +INT +FK + + +optionslist_id +INT +FK + + +count +INT + + +sort +INT + + +mandated +BIT + + + +optionslistarticle:e->article:n + + + + + + + + +optionslistarticle:sw->optionslist:n + + + + + + + + +brand:w->company:n + + + - + pic - - - - - -pic - - -id -INT -PK - - -w -INT - - -h -INT - - -size -BIGINT - - -hash -NVARCHAR(64) - - -thumbw -INT - - -thumbh -INT - - -thumbhash -NVARCHAR(64) - - -source -NVARCHAR(MAX) - - -fullbinary -VARBINARY(MAX) - - -thumbbinary -VARBINARY(MAX) + + + + + +pic + + +id +INT +PK + + +w +INT + + +h +INT + + +size +BIGINT + + +hash +NVARCHAR(64) + + +thumbw +INT + + +thumbh +INT + + +thumbhash +NVARCHAR(64) + + +source +NVARCHAR(MAX) + + +fullbinary +VARBINARY(MAX) + + +thumbbinary +VARBINARY(MAX) + + + +categorypic + + +categorypic + + +id +INT +PK + + +pic_id +INT +FK + + +category_id +INT +FK + + +sort +INT + + + +categorypic:se->category:n + + + + + - -categorypic:nw->pic:n - - - - - - - - -brandpic - - -brandpic - - -id -INT -PK - - -brand_id -INT -FK - - -pic_id -INT -FK - - -sort -INT - - - -brandpic:w->brand:n - - - - - - - - -brandpic:ne->pic:n - - - + +categorypic:e->pic:n + + + - + articlepic - - -articlepic - - -id -INT -PK - - -article_id -INT -FK - - -pic_id -INT -FK - - -sort -INT + + +articlepic + + +id +INT +PK + + +pic_id +INT +FK + + +article_id +INT +FK + + +sort +INT - -articlepic:e->article:n - - - + +articlepic:se->article:n + + + - + articlepic:w->pic:n - - - + + + + + + + + +brandpic + + +brandpic + + +id +INT +PK + + +pic_id +INT +FK + + +brand_id +INT +FK + + +sort +INT + + + +brandpic:e->brand:n + + + + + + + + +brandpic:w->pic:n + + + file - - - -file - - -id -INT -PK - - -hash -NVARCHAR(64) - - -mimetype -NVARCHAR(255) - - -name -NVARCHAR(255) - - -size -BIGINT - - -created -DATETIME2 - - -modified -DATETIME2 - - -content -VARBINARY(MAX) - - -thumbavif? -VARBINARY(MAX) - - -thumbx? -INT - - -thumby? -INT - - -meta? -NVARCHAR(MAX) + + + +file + + +id +INT +PK + + +hash +NVARCHAR(64) + + +mimetype +NVARCHAR(255) + + +name +NVARCHAR(255) + + +size +BIGINT + + +created +DATETIME2 + + +modified +DATETIME2 + + +content +VARBINARY(MAX) + + +thumbavif? +VARBINARY(MAX) + + +thumbx? +INT + + +thumby? +INT + + +meta? +NVARCHAR(MAX) manifestarticle - - - -manifestarticle - - -id -INT -PK - - -manifest_id -INT -FK - - -sku? -NVARCHAR(100) - - -name? -NVARCHAR(255) - - -pu -FLOAT - - -amount -FLOAT - - -net -FLOAT - - -comment? -NVARCHAR(MAX) - - -rebate -FLOAT - - -taxrate -FLOAT + + + +manifestarticle + + +id +INT +PK + + +manifest_id +INT +FK + + +sku? +NVARCHAR(100) + + +name? +NVARCHAR(255) + + +pu +FLOAT + + +amount +FLOAT + + +net +FLOAT + + +comment? +NVARCHAR(MAX) + + +rebate +FLOAT + + +taxrate +FLOAT manifest - - - - -manifest - - -id -INT -PK - - -entity_id -? -INT -FK - - -company_id -INT -FK - - -number -NVARCHAR(100) - - -type -NVARCHAR(100) - - -external_number? -NVARCHAR(100) - - -date -DATETIME2 + + + + +manifest + + +id +INT +PK + + +entity_id +? +INT +FK + + +company_id +INT +FK + + +number +NVARCHAR(100) + + +type +NVARCHAR(100) + + +external_number? +NVARCHAR(100) + + +date +DATETIME2 + + +created_at +DATETIME2 - + manifestarticle:se->manifest:n - - - + + + ledger - - -ledger - - -id -INT -PK - - -manifestarticle_id -INT -FK - - -transaction_type -NVARCHAR(100) - - -amount -FLOAT - - -transaction_text -NVARCHAR(MAX) - - -hash_version -INT - - -prev_hash? -NVARCHAR(128) - - -created_at -DATETIME2 + + +ledger + + +id +INT +PK + + +manifestarticle_id +INT +FK + + +transaction_type +NVARCHAR(100) + + +amount +FLOAT + + +transaction_text +NVARCHAR(MAX) + + +hash_version +INT + + +prev_hash? +NVARCHAR(128) + + +created_at +DATETIME2 - + ledger:sw->manifestarticle:n - - - + + + - -manifest:e->entity:n - - - + +manifest:w->entity:n + + + - -manifest:e->company:n - - - + +manifest:sw->company:n + + + manifestattachment - - -manifestattachment - - -id -INT -PK - - -manifest_id -INT -FK - - -file_id -INT -FK - - -sort -INT + + +manifestattachment + + +id +INT +PK + + +manifest_id +INT +FK + + +file_id +INT +FK + + +sort +INT - + manifestattachment:e->file:n - - - + + + - + manifestattachment:sw->manifest:n - - - + + + - + price:e->customergroup:n - - - + + + - -price:e->article:n - - - + +price:sw->article:n + + + - + price:w->tax:n - - - + + + -unit:w->company:n +unit:e->company:n - - + + posterminal - - - -posterminal - - -id -INT -PK - - -company_id -INT -FK - - -name -NVARCHAR(255) - - -token? -NVARCHAR(255) - - -pairingpin -NVARCHAR(50) + + + +posterminal + + +id +INT +PK + + +company_id +INT +FK + + +name +NVARCHAR(255) + + +token? +NVARCHAR(255) + + +pairingpin +NVARCHAR(50) - -posterminal:e->company:n - - - + +posterminal:w->company:n + + + posterminalcategory - - -posterminalcategory - - -id -INT -PK - - -posterminal_id -INT -FK - - -category_id -INT -FK + + +posterminalcategory + + +id +INT +PK + + +category_id +INT +FK + + +posterminal_id +INT +FK - -posterminalcategory:e->category:n - - - + +posterminalcategory:w->category:n + + + - + posterminalcategory:sw->posterminal:n - - - + + + __legend - - - -Legend - -PK - -primary key - -FK - -foreign key - -? - -nullable column - -— - -FOREIGN KEY constraint - -- - - -naming convention only, no constraint + + + +Legend + +PK + +primary key + +FK + +foreign key + +? + +nullable column + +— + +FOREIGN KEY constraint + +- - + +naming convention only, no constraint diff --git a/scripts/migrate-manifest-created-at.js b/scripts/migrate-manifest-created-at.js new file mode 100644 index 0000000..71b4af4 --- /dev/null +++ b/scripts/migrate-manifest-created-at.js @@ -0,0 +1,112 @@ +import 'dotenv/config'; +import Database from 'better-sqlite3'; +import { MssqlProvider } from '../src/providers/mssql/index.js'; + +async function migrateSqlite(sqlitePath) { + console.log(`Migrating SQLite database at: ${sqlitePath}`); + const db = new Database(sqlitePath); + + const columns = db.pragma('table_info("manifest")'); + const hasCreatedAt = columns.some((c) => c.name === 'created_at'); + if (hasCreatedAt) { + console.log('SQLite manifest already has created_at column.'); + db.close(); + return; + } + + db.pragma('foreign_keys = OFF'); + db.exec(` + BEGIN TRANSACTION; + CREATE TABLE manifest_new ( + id INTEGER PRIMARY KEY, + company_id INTEGER NOT NULL, + "number" TEXT NOT NULL, + "type" TEXT NOT NULL, + entity_id INTEGER, + external_number TEXT, + "date" TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), + FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION, + FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL + ); + INSERT INTO manifest_new (id, company_id, "number", "type", entity_id, external_number, "date", created_at) + SELECT id, company_id, "number", "type", entity_id, external_number, "date", "date" + FROM manifest; + DROP TABLE manifest; + ALTER TABLE manifest_new RENAME TO manifest; + COMMIT; + `); + db.pragma('foreign_keys = ON'); + + const fkErrors = db.pragma('foreign_key_check'); + if (fkErrors.length > 0) { + console.error('Foreign key check failed after SQLite migration:', fkErrors); + } else { + console.log('SQLite migration completed successfully.'); + } + + console.log('Updated SQLite manifest columns:', db.pragma('table_info("manifest")')); + console.log('Updated SQLite manifest sample row:', db.prepare('SELECT * FROM manifest LIMIT 1').get()); + db.close(); +} + +async function migrateMssql() { + console.log('Migrating MSSQL database...'); + const config = { + server: process.env.MSSQL_SERVER, + database: process.env.MSSQL_DATABASE, + user: process.env.MSSQL_USER, + password: process.env.MSSQL_PASSWORD, + options: { + encrypt: false, + trustServerCertificate: true, + }, + }; + + const provider = new MssqlProvider(config); + try { + const pool = await provider.ensurePool(); + const checkRes = await pool.request().query(` + SELECT * FROM sys.columns + WHERE object_id = OBJECT_ID(N'manifest') AND name = 'created_at' + `); + + if (checkRes.recordset.length > 0) { + console.log('MSSQL manifest already has created_at column.'); + } else { + await pool.request().query(` + ALTER TABLE manifest ADD created_at DATETIME2 NOT NULL CONSTRAINT DF_manifest_created_at DEFAULT SYSUTCDATETIME(); + `); + await pool.request().query(` + UPDATE manifest SET created_at = [date]; + `); + console.log('MSSQL migration completed successfully.'); + } + + const colsRes = await pool.request().query(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'manifest' + `); + console.log('Updated MSSQL manifest columns:', colsRes.recordset); + const sampleRes = await pool.request().query(` + SELECT TOP 1 * FROM manifest + `); + console.log('Updated MSSQL manifest sample row:', sampleRes.recordset[0]); + await provider.close(); + } catch (err) { + console.error('MSSQL migration failed:', err); + await provider.close(); + throw err; + } +} + +async function run() { + await migrateSqlite('/home/seb/src/dashback/data/dash.db'); + await migrateMssql(); +} + +run().catch((err) => { + console.error('Migration failed:', err); + process.exit(1); +}); diff --git a/scripts/test-ledger.js b/scripts/test-ledger.js index 681e6e5..1ed1f6f 100644 --- a/scripts/test-ledger.js +++ b/scripts/test-ledger.js @@ -55,6 +55,7 @@ async function run() { // Create a delivery manifest with lines const delivery = await store.delivery.create(googleAuthId, { + supplierId: supplier.id, externalNumber: 'DEL-1', date: new Date().toISOString(), lines: [ @@ -217,9 +218,15 @@ async function run() { const history = await store.article.getHistoryBySku(googleAuthId, article.sku); const historyTexts = history.map((h) => h.text); console.log('Article history texts:', historyTexts); - assert(historyTexts.includes('Initial delivery movement'), 'History should include initial delivery movement'); - assert(historyTexts.includes('Damaged item written off'), 'History should include damaged item written off'); - assert(historyTexts.includes('Second line goods receipt'), 'History should include second line goods receipt'); + assert( + historyTexts.some((t) => t.includes('Initial delivery movement') && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 11.90') && t.includes('Menge: 5')), + 'History should include initial delivery movement with supplier, EK brutto, and amount', + ); + assert(historyTexts.includes('Damaged item written off'), 'History should include undamaged adjustment memo as is'); + assert( + historyTexts.some((t) => t.includes('Second line goods receipt') && t.includes('Lieferant: "Supplier X"') && t.includes('EK brutto: 23.80') && t.includes('Menge: 10')), + 'History should include second line goods receipt with details', + ); console.log('✓ All ledger tests passed successfully!'); } finally { diff --git a/scripts/test-manifest-two-dates.js b/scripts/test-manifest-two-dates.js new file mode 100644 index 0000000..0c14d5c --- /dev/null +++ b/scripts/test-manifest-two-dates.js @@ -0,0 +1,147 @@ +import assert from 'node:assert/strict'; +import 'dotenv/config'; +import LibDashStore from '../src/index.js'; +import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; +import { MssqlProvider } from '../src/providers/mssql/index.js'; + +async function testProvider(name, provider) { + console.log(`\n=== Testing ${name} ===`); + await provider.initialize(); + const store = await new LibDashStore(provider); + + // 1. Setup company and user + const email = `test-manifest-${Date.now()}@example.com`; + const googleAuthId = await store.google_auth.add(email); + const company = await store.company.create({ + name: `Test Manifest Corp ${Date.now()}`, + iso639: 'de', + taxrate: 19, + reducedtaxrate: 7, + googleAuthId, + }); + await store.google_auth.setSelectedCompany(googleAuthId, company.id); + + // 2. Test delivery with both dates + console.log('Testing delivery creation with date and createdAt...'); + const deliveryDate = '2026-10-15T12:00:00.000Z'; + const customCreatedAt = '2026-09-01T08:30:00.000Z'; + + const createdDelivery = await store.delivery.create(googleAuthId, { + externalNumber: 'DEL-2DATES-1', + date: deliveryDate, + createdAt: customCreatedAt, + lines: [ + { name: 'Article 1', amount: 2, net: 50, taxrate: 19 }, + ], + }); + + assert(createdDelivery.id, 'Delivery should have an id'); + assert(createdDelivery.date, 'Delivery should have a delivery date'); + assert(createdDelivery.createdAt, 'Delivery should have createdAt'); + console.log('Delivery created:', { + id: createdDelivery.id, + date: createdDelivery.date, + createdAt: createdDelivery.createdAt, + }); + + // Verify delivery getById + const fetchedDelivery = await store.delivery.getById(googleAuthId, createdDelivery.id); + assert.equal(new Date(fetchedDelivery.date).toISOString().slice(0, 10), '2026-10-15'); + assert.equal(new Date(fetchedDelivery.createdAt).toISOString().slice(0, 10), '2026-09-01'); + + // Verify delivery update + const updatedDeliveryDate = '2026-11-20T12:00:00.000Z'; + const updatedDelivery = await store.delivery.update(googleAuthId, createdDelivery.id, { + date: updatedDeliveryDate, + }); + assert.equal(new Date(updatedDelivery.date).toISOString().slice(0, 10), '2026-11-20'); + assert.equal(new Date(updatedDelivery.createdAt).toISOString().slice(0, 10), '2026-09-01'); + + // Verify delivery.list ledger info before booking + let listBefore = await store.delivery.list(googleAuthId); + const foundBefore = listBefore.find((d) => d.id === createdDelivery.id); + assert(foundBefore, 'Delivery should be in delivery.list'); + assert.equal(foundBefore.ledger.totalPositions, 1, 'Delivery ledger should have 1 totalPosition'); + assert.equal(foundBefore.ledger.completelyOnLedgerPositions, 0, 'Delivery ledger should have 0 completelyOnLedgerPositions initially'); + assert.equal(foundBefore.totalPositions, undefined, 'totalPositions should not be at top level'); + assert.equal(foundBefore.completelyOnLedgerPositions, undefined, 'completelyOnLedgerPositions should not be at top level'); + + // Book line to ledger + await store.ledger.add(googleAuthId, { + manifestarticle_id: createdDelivery.lines[0].id, + transaction_type: 'DELIVERY', + amount: 2, + transaction_text: `Lieferung ${createdDelivery.id}`, + }); + + // Verify delivery.list ledger info after booking + let listAfter = await store.delivery.list(googleAuthId); + const foundAfter = listAfter.find((d) => d.id === createdDelivery.id); + assert(foundAfter, 'Delivery should be in delivery.list after booking'); + assert.equal(foundAfter.ledger.totalPositions, 1, 'Delivery ledger should have 1 totalPosition'); + assert.equal(foundAfter.ledger.completelyOnLedgerPositions, 1, 'Delivery ledger should have 1 completelyOnLedgerPosition after booking'); + console.log('✓ Verified delivery.list ledger info (unbooked -> booked):', foundAfter.ledger); + + // 3. Test quote with both dates + console.log('Testing quote creation...'); + const quote = await store.quote.create(googleAuthId, { + number: `Q-${Date.now()}`, + date: '2026-12-01T00:00:00.000Z', + createdAt: '2026-09-10T00:00:00.000Z', + lines: [{ name: 'Quote item', amount: 1, net: 100, taxrate: 19 }], + }); + assert(quote.createdAt, 'Quote should have createdAt'); + assert(quote.date, 'Quote should have date'); + + // 4. Test order with default createdAt + console.log('Testing order creation with default createdAt...'); + const order = await store.order.create(googleAuthId, { + number: `ORD-${Date.now()}`, + date: '2026-12-15T00:00:00.000Z', + lines: [{ name: 'Order item', amount: 3, net: 25, taxrate: 19 }], + }); + assert(order.createdAt, 'Order should have default createdAt populated'); + assert(order.date, 'Order should have date'); + console.log('Order created with auto createdAt:', { + id: order.id, + date: order.date, + createdAt: order.createdAt, + }); + + console.log(`✓ All tests passed for ${name}!`); +} + +async function main() { + // Test SQLite provider with existing dashback database + const sqliteProvider = new BetterSqlite3Provider({ + filename: '/home/seb/src/dashback/data/dash.db', + }); + try { + await testProvider('SQLite (dash.db)', sqliteProvider); + } finally { + sqliteProvider.close(); + } + + // Test MSSQL provider + const mssqlConfig = { + server: process.env.MSSQL_SERVER, + database: process.env.MSSQL_DATABASE, + user: process.env.MSSQL_USER, + password: process.env.MSSQL_PASSWORD, + options: { + encrypt: false, + trustServerCertificate: true, + }, + }; + const mssqlProvider = new MssqlProvider(mssqlConfig); + try { + await testProvider('MSSQL (dashback)', mssqlProvider); + } finally { + await mssqlProvider.close(); + } +} + +main().catch((err) => { + console.error('Test failed:', err); + process.exit(1); +}); diff --git a/src/providers/mssql/queries/delivery.js b/src/providers/mssql/queries/delivery.js index 31f67df..105fd72 100644 --- a/src/providers/mssql/queries/delivery.js +++ b/src/providers/mssql/queries/delivery.js @@ -7,10 +7,30 @@ SELECT m.[type], m.entity_id AS supplierId, m.external_number AS externalNumber, - m.[date] + m.[date], + m.created_at AS createdAt, + COUNT(ma.id) AS totalPositions, + COUNT(CASE WHEN ma.id IS NOT NULL AND ( + (ma.amount > 0 AND COALESCE(ma_ledger.booked_amount, 0) >= ma.amount) OR + (ma.amount <= 0 AND COALESCE(ma_ledger.booked_amount, 0) > 0) + ) THEN 1 END) AS completelyOnLedgerPositions FROM manifest m +LEFT JOIN manifestarticle ma ON ma.manifest_id = m.id +LEFT JOIN ( + SELECT manifestarticle_id, SUM(amount) AS booked_amount + FROM ledger + GROUP BY manifestarticle_id +) ma_ledger ON ma_ledger.manifestarticle_id = ma.id WHERE m.company_id = @companyId AND m.[type] = @type +GROUP BY + m.id, + m.[number], + m.[type], + m.entity_id, + m.external_number, + m.[date], + m.created_at ORDER BY m.[date] DESC, m.id DESC `; @@ -21,11 +41,31 @@ SELECT m.[type], m.entity_id AS supplierId, m.external_number AS externalNumber, - m.[date] + m.[date], + m.created_at AS createdAt, + COUNT(ma.id) AS totalPositions, + COUNT(CASE WHEN ma.id IS NOT NULL AND ( + (ma.amount > 0 AND COALESCE(ma_ledger.booked_amount, 0) >= ma.amount) OR + (ma.amount <= 0 AND COALESCE(ma_ledger.booked_amount, 0) > 0) + ) THEN 1 END) AS completelyOnLedgerPositions FROM manifest m +LEFT JOIN manifestarticle ma ON ma.manifest_id = m.id +LEFT JOIN ( + SELECT manifestarticle_id, SUM(amount) AS booked_amount + FROM ledger + GROUP BY manifestarticle_id +) ma_ledger ON ma_ledger.manifestarticle_id = ma.id WHERE m.id = @id AND m.company_id = @companyId AND m.[type] = @type +GROUP BY + m.id, + m.[number], + m.[type], + m.entity_id, + m.external_number, + m.[date], + m.created_at `; export const GET_DELIVERY_LINES = ` @@ -45,9 +85,9 @@ ORDER BY ma.id `; export const CREATE_MANIFEST = ` -INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date]) +INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date], created_at) OUTPUT INSERTED.id -VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date) +VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date, COALESCE(@createdAt, SYSUTCDATETIME())) `; export const UPDATE_MANIFEST = ` @@ -56,7 +96,8 @@ SET [number] = @number, entity_id = @entityId, external_number = @externalNumber, - [date] = @date + [date] = @date, + created_at = COALESCE(@createdAt, created_at) WHERE id = @id AND company_id = @companyId AND [type] = @type diff --git a/src/providers/mssql/queries/ledger.js b/src/providers/mssql/queries/ledger.js index f23b687..0ac018a 100644 --- a/src/providers/mssql/queries/ledger.js +++ b/src/providers/mssql/queries/ledger.js @@ -1,7 +1,19 @@ export const CHECK_MANIFESTARTICLE_ACCESS = ` -SELECT ma.id, ma.sku +SELECT + ma.id, + ma.sku, + ma.amount AS lineAmount, + ma.net, + ma.rebate, + ma.taxrate, + m.id AS manifestId, + m.[number] AS manifestNumber, + m.[type] AS manifestType, + m.external_number AS externalNumber, + e.name AS supplierName FROM manifestarticle ma INNER JOIN manifest m ON ma.manifest_id = m.id +LEFT JOIN entity e ON e.id = m.entity_id WHERE ma.id = @manifestarticleId AND m.company_id = @companyId; `; diff --git a/src/providers/mssql/queries/manifestDoc.js b/src/providers/mssql/queries/manifestDoc.js index 0956f5f..ae41c03 100644 --- a/src/providers/mssql/queries/manifestDoc.js +++ b/src/providers/mssql/queries/manifestDoc.js @@ -13,7 +13,8 @@ SELECT m.[type], m.entity_id AS entityId, m.external_number AS externalNumber, - m.[date] + m.[date], + m.created_at AS createdAt FROM manifest m WHERE m.company_id = @companyId AND m.[type] = @type @@ -27,7 +28,8 @@ SELECT m.[type], m.entity_id AS entityId, m.external_number AS externalNumber, - m.[date] + m.[date], + m.created_at AS createdAt FROM manifest m WHERE m.id = @id AND m.company_id = @companyId @@ -51,9 +53,9 @@ ORDER BY ma.id `; export const CREATE_MANIFEST = ` -INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date]) +INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date], created_at) OUTPUT INSERTED.id -VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date) +VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date, COALESCE(@createdAt, SYSUTCDATETIME())) `; export const UPDATE_MANIFEST = ` @@ -62,7 +64,8 @@ SET [number] = @number, entity_id = @entityId, external_number = @externalNumber, - [date] = @date + [date] = @date, + created_at = COALESCE(@createdAt, created_at) WHERE id = @id AND company_id = @companyId AND [type] = @type diff --git a/src/providers/mssql/schema.js b/src/providers/mssql/schema.js index b53c334..f47264a 100644 --- a/src/providers/mssql/schema.js +++ b/src/providers/mssql/schema.js @@ -565,6 +565,7 @@ BEGIN entity_id INT NULL, external_number NVARCHAR(100) NULL, [date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date DEFAULT SYSUTCDATETIME(), + created_at DATETIME2 NOT NULL CONSTRAINT DF_manifest_created_at DEFAULT SYSUTCDATETIME(), CONSTRAINT FK_manifest_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION, CONSTRAINT FK_manifest_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL ); diff --git a/src/providers/sqlite3/queries/ledger.js b/src/providers/sqlite3/queries/ledger.js index d2d5af5..08bd456 100644 --- a/src/providers/sqlite3/queries/ledger.js +++ b/src/providers/sqlite3/queries/ledger.js @@ -1,7 +1,19 @@ export const CHECK_MANIFESTARTICLE_ACCESS = ` -SELECT ma.id, ma.sku +SELECT + ma.id, + ma.sku, + ma.amount AS lineAmount, + ma.net, + ma.rebate, + ma.taxrate, + m.id AS manifestId, + m.number AS manifestNumber, + m.type AS manifestType, + m.external_number AS externalNumber, + e.name AS supplierName FROM manifestarticle ma INNER JOIN manifest m ON ma.manifest_id = m.id +LEFT JOIN entity e ON e.id = m.entity_id WHERE ma.id = @manifestarticleId AND m.company_id = @companyId; `; diff --git a/src/providers/sqlite3/schema.js b/src/providers/sqlite3/schema.js index 9301dd5..b699f03 100644 --- a/src/providers/sqlite3/schema.js +++ b/src/providers/sqlite3/schema.js @@ -404,6 +404,7 @@ CREATE TABLE IF NOT EXISTS manifest ( entity_id INTEGER, external_number TEXT, "date" TEXT NOT NULL DEFAULT (${UTC_NOW}), + created_at TEXT NOT NULL DEFAULT (${UTC_NOW}), FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION, FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL ); diff --git a/src/store/delivery/create.js b/src/store/delivery/create.js index a71e5c7..8ae42d5 100644 --- a/src/store/delivery/create.js +++ b/src/store/delivery/create.js @@ -21,6 +21,10 @@ export default function create(provider) { if (!date) { throw new Error('Invalid delivery'); } + const createdAt = body.createdAt ? parseDate(body.createdAt) : new Date(); + if (body.createdAt && !createdAt) { + throw new Error('Invalid delivery'); + } const externalNumber = body.externalNumber?.trim?.() ? body.externalNumber.trim() : null; @@ -46,6 +50,7 @@ export default function create(provider) { entityId: supplierId, externalNumber, date, + createdAt, }); const manifestId = created.rows[0]?.id ?? created.insertId; diff --git a/src/store/delivery/helpers.js b/src/store/delivery/helpers.js index 50bd861..e86dd76 100644 --- a/src/store/delivery/helpers.js +++ b/src/store/delivery/helpers.js @@ -7,14 +7,24 @@ import { loadAttachments } from './attachments.js'; export const DELIVERY_TYPE = mssqlQueries.DELIVERY_TYPE; export function shapeSummary(row) { - return { + const summary = { id: row.id, number: row.number, type: DELIVERY_TYPE, supplierId: row.supplierId ?? null, externalNumber: row.externalNumber ?? null, date: row.date, + createdAt: row.createdAt ?? row.created_at ?? null, }; + + if (row.totalPositions !== undefined || row.completelyOnLedgerPositions !== undefined) { + summary.ledger = { + totalPositions: Number(row.totalPositions ?? 0), + completelyOnLedgerPositions: Number(row.completelyOnLedgerPositions ?? 0), + }; + } + + return summary; } export async function loadDelivery(provider, tx, companyId, id) { diff --git a/src/store/delivery/update.js b/src/store/delivery/update.js index 06382c0..9d10bd1 100644 --- a/src/store/delivery/update.js +++ b/src/store/delivery/update.js @@ -21,6 +21,7 @@ export default function update(provider) { const hasNumber = Object.prototype.hasOwnProperty.call(patch, 'number'); const hasDate = Object.prototype.hasOwnProperty.call(patch, 'date'); + const hasCreatedAt = Object.prototype.hasOwnProperty.call(patch, 'createdAt'); const hasSupplierId = Object.prototype.hasOwnProperty.call( patch, 'supplierId', @@ -29,7 +30,7 @@ export default function update(provider) { const hasLines = Object.prototype.hasOwnProperty.call(patch, 'lines'); const hasFiles = Object.prototype.hasOwnProperty.call(patch, 'files'); - if (!hasNumber && !hasDate && !hasSupplierId && !hasExternalNumber && !hasLines && !hasFiles) { + if (!hasNumber && !hasDate && !hasCreatedAt && !hasSupplierId && !hasExternalNumber && !hasLines && !hasFiles) { throw new Error('Invalid delivery'); } @@ -70,6 +71,14 @@ export default function update(provider) { } } + let createdAt = summary.createdAt ? parseDate(summary.createdAt) : null; + if (hasCreatedAt) { + createdAt = parseDate(patch.createdAt); + if (!createdAt) { + throw new Error('Invalid delivery'); + } + } + let supplierId = summary.supplierId; if (hasSupplierId) { supplierId = await assertSupplier( @@ -95,6 +104,7 @@ export default function update(provider) { entityId: supplierId, externalNumber, date, + createdAt, }); if (hasLines) { diff --git a/src/store/ledger/add.js b/src/store/ledger/add.js index d21318f..2cb95eb 100644 --- a/src/store/ledger/add.js +++ b/src/store/ledger/add.js @@ -142,11 +142,45 @@ export default function add(provider) { companyId, }); + const upperType = String(transactionType).toUpperCase(); + const isDelivery = + upperType === 'DELIVERY' || + upperType === 'GOODS_RECEIPT' || + (manifestArticle.manifestType === 'delivery' && + !upperType.includes('ADJUST') && + !upperType.includes('LOSS') && + !upperType.includes('WRITE_OFF')); + + let historyText = transactionText; + if (isDelivery) { + const details = []; + if (manifestArticle.supplierName) { + details.push(`Lieferant: "${manifestArticle.supplierName}"`); + } + if (manifestArticle.net != null && Number.isFinite(Number(manifestArticle.net))) { + const net = Number(manifestArticle.net); + const rebate = Number(manifestArticle.rebate ?? 0); + const taxrate = Number(manifestArticle.taxrate ?? 0); + const netAfterRebate = net * (1 - rebate / 100); + const brutto = netAfterRebate * (1 + taxrate / 100); + const ekBrutto = (Math.round(brutto * 100) / 100).toFixed(2); + details.push(`EK brutto: ${ekBrutto}`); + } + if (amount != null && Number.isFinite(Number(amount))) { + details.push(`Menge: ${amount}`); + } + const suffix = details.length ? ` (${details.join(', ')})` : ''; + const base = + transactionText?.trim() || + (manifestArticle.manifestNumber ? `Lieferung ${manifestArticle.manifestNumber}` : 'Lieferung'); + historyText = `${base}${suffix}`; + } + for (const match of articleMatches.rows) { await runner.query(queries.INSERT_ARTICLE_HISTORY, { articleId: match.articleId, companyuserId, - text: transactionText, + text: historyText, }); } } diff --git a/src/store/manifestDoc/helpers.js b/src/store/manifestDoc/helpers.js index b1464ce..a547705 100644 --- a/src/store/manifestDoc/helpers.js +++ b/src/store/manifestDoc/helpers.js @@ -27,6 +27,7 @@ export function shapeSummary(type, row) { entityId: row.entityId ?? null, externalNumber: row.externalNumber ?? null, date: row.date, + createdAt: row.createdAt ?? row.created_at ?? null, }; } diff --git a/src/store/order/create.js b/src/store/order/create.js index deafcf2..63a4347 100644 --- a/src/store/order/create.js +++ b/src/store/order/create.js @@ -44,6 +44,10 @@ export default function create(provider) { if (!number || !date) { throw new Error('Invalid order'); } + const createdAt = body.createdAt ? parseDate(body.createdAt) : new Date(); + if (body.createdAt && !createdAt) { + throw new Error('Invalid order'); + } const externalNumber = body.externalNumber?.trim?.() ? body.externalNumber.trim() : null; @@ -66,6 +70,7 @@ export default function create(provider) { entityId, externalNumber, date, + createdAt, }); const manifestId = created.rows[0]?.id ?? created.insertId; diff --git a/src/store/order/update.js b/src/store/order/update.js index 7471a81..08c8b19 100644 --- a/src/store/order/update.js +++ b/src/store/order/update.js @@ -52,11 +52,12 @@ export default function update(provider) { const hasNumber = Object.prototype.hasOwnProperty.call(patch, 'number'); const hasDate = Object.prototype.hasOwnProperty.call(patch, 'date'); + const hasCreatedAt = Object.prototype.hasOwnProperty.call(patch, 'createdAt'); const hasEntityId = Object.prototype.hasOwnProperty.call(patch, 'entityId'); const hasExternalNumber = Object.prototype.hasOwnProperty.call(patch, 'externalNumber'); const hasLines = Object.prototype.hasOwnProperty.call(patch, 'lines'); - if (!hasNumber && !hasDate && !hasEntityId && !hasExternalNumber && !hasLines) { + if (!hasNumber && !hasDate && !hasCreatedAt && !hasEntityId && !hasExternalNumber && !hasLines) { throw new Error('Invalid order'); } @@ -97,6 +98,14 @@ export default function update(provider) { } } + let createdAt = summary.createdAt ? parseDate(summary.createdAt) : null; + if (hasCreatedAt) { + createdAt = parseDate(patch.createdAt); + if (!createdAt) { + throw new Error('Invalid order'); + } + } + let entityId = summary.entityId; if (hasEntityId) { entityId = await assertCustomerEntity( @@ -121,6 +130,7 @@ export default function update(provider) { entityId, externalNumber, date, + createdAt, }); if (hasLines) { diff --git a/src/store/quote/create.js b/src/store/quote/create.js index 11b7e00..578d4f7 100644 --- a/src/store/quote/create.js +++ b/src/store/quote/create.js @@ -44,6 +44,10 @@ export default function create(provider) { if (!number || !date) { throw new Error('Invalid quote'); } + const createdAt = body.createdAt ? parseDate(body.createdAt) : new Date(); + if (body.createdAt && !createdAt) { + throw new Error('Invalid quote'); + } const externalNumber = body.externalNumber?.trim?.() ? body.externalNumber.trim() : null; @@ -66,6 +70,7 @@ export default function create(provider) { entityId, externalNumber, date, + createdAt, }); const manifestId = created.rows[0]?.id ?? created.insertId; diff --git a/src/store/quote/update.js b/src/store/quote/update.js index 46c3dc0..e54738e 100644 --- a/src/store/quote/update.js +++ b/src/store/quote/update.js @@ -52,11 +52,12 @@ export default function update(provider) { const hasNumber = Object.prototype.hasOwnProperty.call(patch, 'number'); const hasDate = Object.prototype.hasOwnProperty.call(patch, 'date'); + const hasCreatedAt = Object.prototype.hasOwnProperty.call(patch, 'createdAt'); const hasEntityId = Object.prototype.hasOwnProperty.call(patch, 'entityId'); const hasExternalNumber = Object.prototype.hasOwnProperty.call(patch, 'externalNumber'); const hasLines = Object.prototype.hasOwnProperty.call(patch, 'lines'); - if (!hasNumber && !hasDate && !hasEntityId && !hasExternalNumber && !hasLines) { + if (!hasNumber && !hasDate && !hasCreatedAt && !hasEntityId && !hasExternalNumber && !hasLines) { throw new Error('Invalid quote'); } @@ -97,6 +98,14 @@ export default function update(provider) { } } + let createdAt = summary.createdAt ? parseDate(summary.createdAt) : null; + if (hasCreatedAt) { + createdAt = parseDate(patch.createdAt); + if (!createdAt) { + throw new Error('Invalid quote'); + } + } + let entityId = summary.entityId; if (hasEntityId) { entityId = await assertCustomerEntity( @@ -121,6 +130,7 @@ export default function update(provider) { entityId, externalNumber, date, + createdAt, }); if (hasLines) {