refactor: introduce database provider abstraction, isolate MSSQL layer

- Add provider contract (src/providers/provider.js): query() -> {rows,
  rowsAffected, insertId}, callback transaction(fn), exec() for stored
  procs, initialize/purge lifecycle
- Add MssqlProvider (src/providers/mssql/index.js) wrapping an mssql
  ConnectionPool with normalized results, tx commit/rollback and
  constraint-error classifiers (unique 2627/2601, FK 547)
- LibDashStore constructor now takes a provider instead of a pool
  (breaking); adds LibDashStore.create() async factory; keeps pool as
  transitional bridge until store modules are converted (see TODO.md)
- Move MSSQL-specific code into the provider: src/sql/* ->
  src/providers/mssql/queries/*, src/schema.js ->
  src/providers/mssql/schema.js, purge SQL -> purgeSql.js
- Update scripts, generate-schema-graph.js and package.json exports
  (./providers/mssql, ./providers/provider)
- Add TODO.md with the remaining provider/SQLite migration plan

No SQL or behavioral changes.
This commit is contained in:
seb
2026-09-20 13:51:01 +02:00
parent 67b3f8b6bf
commit 53a20c4353
184 changed files with 508 additions and 192 deletions

View File

@@ -0,0 +1,126 @@
/**
* MSSQL provider over an `mssql` ConnectionPool.
* Normalizes mssql results into the provider contract shape:
* { rows, rowsAffected, insertId }
*/
function normalizeRows(result) {
const recordset = result?.recordset ?? [];
const affected = Array.isArray(result?.rowsAffected)
? result.rowsAffected.reduce((a, b) => a + b, 0)
: result?.rowsAffected ?? 0;
return { rows: recordset, rowsAffected: affected, insertId: null };
}
export class MssqlExecutor {
/** @param {() => import('mssql').Request} requestFactory */
constructor(requestFactory) {
this.#requestFactory = requestFactory;
}
#requestFactory;
/** @returns {Promise<import('../provider.js').QueryResult>} */
async query(sql, params = {}) {
const req = this.#requestFactory();
for (const [key, value] of Object.entries(params)) {
req.input(key, value);
}
return normalizeRows(await req.query(sql));
}
/**
* Stored-procedure call with input/output params.
* Used by numbers.nextNumber / peekNumber
* (`getnextnumberwithprefixandpadding`).
*/
async exec(procName, { inputs = {}, outputs = [] } = {}) {
const req = this.#requestFactory();
for (const [key, value] of Object.entries(inputs)) {
req.input(key, value);
}
for (const out of outputs) {
req.output(out);
}
const result = await req.execute(procName);
return {
rows: result.recordset ?? [],
rowsAffected: Array.isArray(result.rowsAffected)
? result.rowsAffected.reduce((a, b) => a + b, 0)
: 0,
insertId: null,
outputs: result.output ?? {},
};
}
}
export class MssqlProvider {
/** @param {import('mssql').ConnectionPool} pool */
constructor(pool) {
if (!pool || typeof pool.request !== 'function') {
throw new TypeError('MssqlProvider requires an mssql ConnectionPool');
}
this.pool = pool;
}
makeExecutor(requestFactory = () => this.pool.request()) {
return new MssqlExecutor(requestFactory);
}
async query(sql, params = {}) {
return this.makeExecutor().query(sql, params);
}
async exec(procName, options) {
return this.makeExecutor().exec(procName, options);
}
/**
* Runs `fn(runner)` inside a transaction. Commit on success,
* rollback on error. `runner` has query/exec like the provider.
*
* @template T
* @param {(runner: MssqlExecutor) => Promise<T>} fn
* @returns {Promise<T>}
*/
async transaction(fn) {
const tx = this.pool.transaction();
await tx.begin();
try {
const runner = this.makeExecutor(() => tx.request());
const result = await fn(runner);
await tx.commit();
return result;
} catch (err) {
try {
await tx.rollback();
} catch {
// ignore rollback failure after original error
}
throw err;
}
}
async initialize() {
// Implemented by store/initialize.js — no-op here (keeps parity with
// other providers, which may run migrations internally instead).
}
async purge() {
// Implemented by store/purge.js — no-op here.
}
close() {
return this.pool.close();
}
}
/** mssql: 2627 = PK/unique violation, 2601 = unique index violation */
export function isUniqueConstraintError(err) {
return err?.number === 2627 || err?.number === 2601;
}
/** mssql: 547 = FK conflict */
export function isForeignKeyConstraintError(err) {
return err?.number === 547;
}

View File

@@ -0,0 +1,74 @@
/** Drop order = reverse of schema create dependency order. */
export const TABLES = [
'companyuser',
'syncpos',
'companysetting',
'posterminalcategory',
'posterminal',
'numbers',
'ledger',
'articlesupplier',
'manifestarticle',
'manifest',
'price',
'articletext',
'categorytext',
'categorypic',
'categoryarticle',
'optionslistarticle',
'articlelistarticle',
'brandpic',
'articlepic',
'articleean',
'articlehistory',
'entityhistory',
'entityaddress',
'entitycontact',
'article',
'brand',
'articlelist',
'optionslist',
'unittext',
'unit',
'entity',
'customergroup',
'google_auth',
'company',
'pic',
'category',
'tax',
'language',
'meta',
];
export const PROCEDURES = [
'getnextnumberwithprefixandpadding',
'peeknextnumberwithprefixandpadding',
];
export const VIEWS = ['v_entity_company_number'];
/** Drop before tables — breaks company ↔ customergroup cycle for purge. */
export const CONSTRAINTS = [
`IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_company_poscustomergroup')
ALTER TABLE company DROP CONSTRAINT FK_company_poscustomergroup;`,
];
export function buildPurgeSql() {
const drops = [
...PROCEDURES.map(
(name) =>
`IF OBJECT_ID(N'dbo.${name}', N'P') IS NOT NULL DROP PROCEDURE dbo.[${name}];`,
),
...VIEWS.map(
(name) =>
`IF OBJECT_ID(N'dbo.${name}', N'V') IS NOT NULL DROP VIEW dbo.[${name}];`,
),
...CONSTRAINTS,
...TABLES.map(
(name) =>
`IF OBJECT_ID(N'dbo.${name}', N'U') IS NOT NULL DROP TABLE dbo.[${name}];`,
),
];
return drops.join('\n');
}

View File

@@ -0,0 +1,506 @@
export const ARTICLE_LIST_JOINS = `
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = @languageId
LEFT JOIN price pr
ON pr.article_id = a.id
AND pr.customergroup_id = @posCustomerGroupId
AND pr.minmultiplier = 1
LEFT JOIN tax t ON t.id = pr.tax_id
OUTER APPLY (
SELECT TOP 1 ap.pic_id
FROM articlepic ap
WHERE ap.article_id = a.id
ORDER BY ap.sort ASC, ap.id ASC
) first_pic
LEFT JOIN pic ON pic.id = first_pic.pic_id
LEFT JOIN article father ON father.id = a.fatherarticle_id
`;
export const ARTICLE_LIST_COLUMNS = `
a.id,
a.sku,
at.name,
at.slug,
pr.net,
t.rate AS taxRate,
CASE
WHEN a.optionslist_id IS NOT NULL THEN (
SELECT COUNT(*)
FROM optionslistarticle ola
WHERE ola.optionslist_id = a.optionslist_id
)
ELSE 0
END AS optionsCount,
CASE
WHEN a.articlelist_id IS NOT NULL THEN (
SELECT COUNT(*)
FROM articlelistarticle ala
WHERE ala.articlelist_id = a.articlelist_id
)
ELSE 0
END AS articleListCount,
(
SELECT COUNT(*)
FROM article child
WHERE child.fatherarticle_id = a.id
) AS childCount,
a.fatherarticle_id AS fatherArticleId,
father.sku AS fatherSku,
pic.hash AS fullHash,
pic.thumbw AS thumbW,
pic.thumbh AS thumbH,
pic.thumbhash AS thumbHash
`;
export const GET_CATEGORY_ID_BY_SKU = `
SELECT TOP 1 ca.category_id AS categoryId, a.id AS articleId
FROM article a
INNER JOIN categoryarticle ca ON ca.article_id = a.id
WHERE a.sku = @sku
`;
/** Resolve an article id by sku (used to wire father/child compound links). */
export const GET_ID_BY_SKU = `
SELECT TOP 1 id AS articleId
FROM article
WHERE sku = @sku
`;
/** True if an EAN is already assigned to any article (duplicate guard). */
export const EAN_EXISTS = `
SELECT TOP 1 1 AS used
FROM articleean
WHERE ean = @ean
`;
/** Link an article to its father article and mark the father. */
export const UPDATE_FATHER_BY_SKU = `
UPDATE article
SET fatherarticle_id = @fatherArticleId
WHERE sku = @sku
`;
export const SET_IS_FATHER = `
UPDATE article
SET is_father = 1
WHERE id = @articleId
`;
export const SET_FATHER_SORT = `
UPDATE article
SET fathersort = @fathersort
WHERE id = @articleId AND fatherarticle_id = @fatherArticleId
`;
/** SKU → article/category + company auth in one round-trip. */
export const RESOLVE_ARTICLE_ACCESS_BY_SKU = `
WITH article_cat AS (
SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId
FROM article a
INNER JOIN categoryarticle ca ON ca.article_id = a.id
WHERE a.sku = @sku
),
ancestors AS (
SELECT c.id, c.parent_id
FROM category c
INNER JOIN article_cat ac ON c.id = ac.categoryId
UNION ALL
SELECT c.id, c.parent_id
FROM category c
INNER JOIN ancestors a ON c.id = a.parent_id
),
root AS (
SELECT TOP 1 id AS rootCategoryId
FROM ancestors
WHERE parent_id IS NULL
)
SELECT
ac.articleId,
ac.categoryId,
r.rootCategoryId,
co.id AS companyId,
co.default_language_id AS languageId,
co.poscustomergroup_id AS posCustomerGroupId,
cu.id AS companyuserId
FROM article_cat ac
LEFT JOIN root r ON 1 = 1
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
LEFT JOIN companyuser cu
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
`;
export const UPDATE_WEIGHT = `
UPDATE article
SET weight = @weight
WHERE id = @articleId
`;
export const UPDATE_BRAND = `
UPDATE article
SET brand_id = @brandId
WHERE id = @articleId
`;
export const UPDATE_UNIT = `
UPDATE article
SET unit_id = @unitId
WHERE id = @articleId
`;
export const UPDATE_UNITAMOUNT = `
UPDATE article
SET unitamount = @unitamount
WHERE id = @articleId
`;
export const UPDATE_UNIT_AND_AMOUNT = `
UPDATE article
SET unit_id = @unitId, unitamount = @unitamount
WHERE id = @articleId
`;
export const CREATE = `
INSERT INTO article (sku, unit_id, unitamount, weight)
OUTPUT INSERTED.id
VALUES (@sku, @unitId, @unitamount, @weight)
`;
export const DELETE_EANS_BY_ARTICLE = `
DELETE FROM articleean
WHERE article_id = @articleId
`;
export const ADD_EAN = `
INSERT INTO articleean (article_id, ean)
VALUES (@articleId, @ean)
`;
export const DELETE_CATEGORIES_BY_ARTICLE = `
DELETE FROM categoryarticle
WHERE article_id = @articleId
`;
export const ADD_CATEGORY = `
INSERT INTO categoryarticle (category_id, article_id)
VALUES (@categoryId, @articleId)
`;
export const GET_BRAND_BY_COMPANY_AND_ID = `
SELECT id
FROM brand
WHERE id = @brandId AND company_id = @companyId
`;
/**
* Company-wide article search index.
* Returns one row per article with id, sku, name, and all eans as a JSON array.
* Used by the client for local search / autocomplete.
*/
export const SEARCH_ARTICLES = `
;WITH category_tree AS (
SELECT id
FROM category
WHERE id = @rootCategoryId
UNION ALL
SELECT c.id
FROM category c
INNER JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT
a.id,
a.sku,
at.name,
(
SELECT ae.ean
FROM articleean ae
WHERE ae.article_id = a.id
ORDER BY ae.ean
FOR JSON PATH
) AS eansJson,
(
SELECT ca2.category_id
FROM categoryarticle ca2
WHERE ca2.article_id = a.id
ORDER BY ca2.category_id
FOR JSON PATH
) AS categoryIdsJson
FROM article a
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = @languageId
WHERE EXISTS (
SELECT 1
FROM categoryarticle ca
INNER JOIN category_tree ct ON ct.id = ca.category_id
WHERE ca.article_id = a.id
)
ORDER BY a.id
`;
/**
* Flat category tree (id, parentId, name) for a root category, used to resolve
* breadcrumbs for the article export.
*/
export const GET_CATEGORY_TREE_FLAT = `
;WITH tree AS (
SELECT id, parent_id, sort
FROM category
WHERE id = @rootId
UNION ALL
SELECT c.id, c.parent_id, c.sort
FROM category c
INNER JOIN tree t ON c.parent_id = t.id
)
SELECT
t.id,
t.parent_id AS parentId,
ct.name
FROM tree t
LEFT JOIN categorytext ct
ON ct.category_id = t.id AND ct.language_id = @languageId
`;
/**
* Export rows for every article in a category subtree (root = @categoryId).
* One row per article with the columns used by the export CSV, including the
* POS price (minmultiplier = 1 for the company poscustomergroup), brand, unit,
* eans, categoryIds and all photo hashes as JSON arrays.
*/
export const GET_ARTICLE_EXPORT = `
;WITH category_tree AS (
SELECT id
FROM category
WHERE id = @categoryId
UNION ALL
SELECT c.id
FROM category c
INNER JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT
a.id,
a.sku,
at.name,
at.slug,
u.si AS unitSi,
ut.symbol AS unitSymbol,
a.unitamount,
b.name AS brandName,
b.name AS brandName,
pr.net AS net,
t.rate AS taxRate,
(
SELECT ae.ean
FROM articleean ae
WHERE ae.article_id = a.id
ORDER BY ae.ean
FOR JSON PATH
) AS eansJson,
(
SELECT ca2.category_id
FROM categoryarticle ca2
WHERE ca2.article_id = a.id
ORDER BY ca2.category_id
FOR JSON PATH
) AS categoryIdsJson,
(
SELECT p.hash
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = a.id
ORDER BY ap.sort ASC, ap.id ASC
FOR JSON PATH
) AS fullHashesJson
FROM article a
INNER JOIN categoryarticle ca ON ca.article_id = a.id
INNER JOIN category_tree ct ON ct.id = ca.category_id
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = @languageId
LEFT JOIN unit u ON u.id = a.unit_id
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = @languageId
LEFT JOIN brand b ON b.id = a.brand_id
LEFT JOIN price pr
ON pr.article_id = a.id
AND pr.customergroup_id = @posCustomerGroupId
AND pr.minmultiplier = 1
LEFT JOIN tax t ON t.id = pr.tax_id
ORDER BY a.id
`;
/**
* Single-query replacement for resolveArticleBySku + queryBySku.
* Resolves SKU → article + company + auth, then loads full article detail
* including categories, photos, eans, and prices via FOR JSON sub-queries.
* Returns one row with articleJson, or empty if not found/unauthorized.
*/
export const GET_ARTICLE_BY_SKU_FULL = `
;WITH article_cat AS (
SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId
FROM article a
INNER JOIN categoryarticle ca ON ca.article_id = a.id
WHERE a.sku = @sku
),
ancestors AS (
SELECT c.id, c.parent_id
FROM category c
INNER JOIN article_cat ac ON c.id = ac.categoryId
UNION ALL
SELECT c.id, c.parent_id
FROM category c
INNER JOIN ancestors a ON c.id = a.parent_id
),
root AS (
SELECT TOP 1 id AS rootCategoryId
FROM ancestors
WHERE parent_id IS NULL
),
auth AS (
SELECT
ac.articleId,
ac.categoryId,
r.rootCategoryId,
co.id AS companyId,
co.default_language_id AS languageId,
co.poscustomergroup_id AS posCustomerGroupId,
cu.id AS companyuserId
FROM article_cat ac
LEFT JOIN root r ON 1 = 1
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
LEFT JOIN companyuser cu
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
)
SELECT
auth.articleId,
auth.categoryId,
auth.rootCategoryId,
auth.companyId,
auth.languageId,
auth.posCustomerGroupId,
auth.companyuserId,
(
SELECT
a.id,
a.sku,
at.name,
at.slug,
a.unitamount,
a.weight,
u.si AS unitSi,
u.basesi AS unitBasesi,
u.basesifactor AS unitBasesifactor,
ut.symbol AS unitSymbol,
ut.name AS unitName,
b.name AS brandName,
bp.thumbw AS brandThumbW,
bp.thumbh AS brandThumbH,
bp.thumbhash AS brandThumbHash,
(
SELECT COUNT(*)
FROM optionslistarticle ola
WHERE ola.optionslist_id = a.optionslist_id
) AS optionsCount,
(
SELECT COUNT(*)
FROM articlelistarticle ala
WHERE ala.articlelist_id = a.articlelist_id
) AS articleListCount,
(
SELECT COUNT(*)
FROM article child
WHERE child.fatherarticle_id = a.id
) AS childCount,
a.fatherarticle_id AS fatherArticleId,
fa.sku AS fatherSku,
(
SELECT ca2.category_id
FROM categoryarticle ca2
WHERE ca2.article_id = a.id
ORDER BY ca2.category_id
FOR JSON PATH
) AS categoryIdsJson,
(
SELECT
ap.sort,
p.hash AS fullHash,
p.w AS fullW,
p.h AS fullH,
p.size AS fullSize,
p.thumbw AS thumbW,
p.thumbh AS thumbH,
p.thumbhash AS thumbHash
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = a.id
ORDER BY ap.sort ASC, ap.id ASC
FOR JSON PATH
) AS photosJson,
(
SELECT ae.ean
FROM articleean ae
WHERE ae.article_id = a.id
ORDER BY ae.ean
FOR JSON PATH
) AS eansJson,
(
SELECT
pr.minmultiplier,
pr.net,
t.rate AS taxRate,
pr.customergroup_id AS customergroupId
FROM price pr
INNER JOIN tax t ON t.id = pr.tax_id
WHERE pr.article_id = a.id
ORDER BY pr.customergroup_id, pr.minmultiplier
FOR JSON PATH
) AS pricesJson,
(
SELECT
asup.id,
asup.article_id AS articleId,
asup.entity_id AS entityId,
e.name AS supplierName,
asup.sku,
asup.pu,
asup.stock,
asup.sort,
asup.price_id AS priceId,
p.net AS [price.net],
p.tax_id AS [price.taxId],
t.rate AS [price.taxRate]
FROM articlesupplier asup
INNER JOIN entity e ON e.id = asup.entity_id
LEFT JOIN price p ON p.id = asup.price_id
LEFT JOIN tax t ON t.id = p.tax_id
WHERE asup.article_id = a.id
ORDER BY asup.sort ASC, asup.id ASC
FOR JSON PATH
) AS suppliersJson
FROM article a
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = auth.languageId
LEFT JOIN unit u ON u.id = a.unit_id
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = auth.languageId
LEFT JOIN brand b ON b.id = a.brand_id
OUTER APPLY (
SELECT TOP 1 bp2.pic_id
FROM brandpic bp2
WHERE bp2.brand_id = b.id
ORDER BY bp2.sort ASC, bp2.id ASC
) first_brand_pic
LEFT JOIN pic bp ON bp.id = first_brand_pic.pic_id
LEFT JOIN article fa ON fa.id = a.fatherarticle_id
WHERE a.id = auth.articleId
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) AS articleJson
FROM auth
`;

View File

@@ -0,0 +1,34 @@
export const GET_CHILD_COUNT = `
SELECT COUNT(*) AS count
FROM article
WHERE fatherarticle_id = @articleId
`;
export const GET_LIST_IDS = `
SELECT articlelist_id AS articlelistId, optionslist_id AS optionslistId
FROM article
WHERE id = @articleId
`;
export const DELETE_PICS_BY_ARTICLE = `
DELETE p
FROM pic p
INNER JOIN articlepic ap ON ap.pic_id = p.id
WHERE ap.article_id = @articleId
`;
export const DELETE_BY_ID = `
DELETE FROM article
WHERE id = @articleId
`;
export const DELETE_ARTICLELIST = `
DELETE FROM articlelist
WHERE id = @id
`;
export const DELETE_OPTIONSLIST = `
DELETE FROM optionslist
WHERE id = @id
`;

View File

@@ -0,0 +1,124 @@
export const ARTICLE_DETAIL_COLUMNS = `
a.id,
a.sku,
at.name,
at.slug,
a.unitamount,
a.weight,
u.si AS unitSi,
u.basesi AS unitBasesi,
u.basesifactor AS unitBasesifactor,
ut.symbol AS unitSymbol,
ut.name AS unitName,
b.name AS brandName,
brand_pic.thumbw AS brandThumbW,
brand_pic.thumbh AS brandThumbH,
brand_pic.thumbhash AS brandThumbHash,
CASE
WHEN a.optionslist_id IS NOT NULL THEN (
SELECT COUNT(*)
FROM optionslistarticle ola
WHERE ola.optionslist_id = a.optionslist_id
)
ELSE 0
END AS optionsCount,
CASE
WHEN a.articlelist_id IS NOT NULL THEN (
SELECT COUNT(*)
FROM articlelistarticle ala
WHERE ala.articlelist_id = a.articlelist_id
)
ELSE 0
END AS articleListCount,
(
SELECT COUNT(*)
FROM article child
WHERE child.fatherarticle_id = a.id
) AS childCount,
a.fatherarticle_id AS fatherArticleId,
father.sku AS fatherSku
`;
export const GET_DETAIL_BY_SKU = `
SELECT
${ARTICLE_DETAIL_COLUMNS}
FROM article a
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = @languageId
LEFT JOIN unit u ON u.id = a.unit_id
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = @languageId
LEFT JOIN brand b ON b.id = a.brand_id
OUTER APPLY (
SELECT TOP 1 bp.pic_id
FROM brandpic bp
WHERE bp.brand_id = b.id
ORDER BY bp.sort ASC, bp.id ASC
) first_brand_pic
LEFT JOIN pic brand_pic ON brand_pic.id = first_brand_pic.pic_id
LEFT JOIN article father ON father.id = a.fatherarticle_id
WHERE a.sku = @sku
`;
export const GET_DETAIL_BY_ARTICLE_ID = `
SELECT
${ARTICLE_DETAIL_COLUMNS}
FROM article a
LEFT JOIN articletext at
ON at.article_id = a.id AND at.language_id = @languageId
LEFT JOIN unit u ON u.id = a.unit_id
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = @languageId
LEFT JOIN brand b ON b.id = a.brand_id
OUTER APPLY (
SELECT TOP 1 bp.pic_id
FROM brandpic bp
WHERE bp.brand_id = b.id
ORDER BY bp.sort ASC, bp.id ASC
) first_brand_pic
LEFT JOIN pic brand_pic ON brand_pic.id = first_brand_pic.pic_id
LEFT JOIN article father ON father.id = a.fatherarticle_id
WHERE a.id = @articleId
`;
export const GET_CATEGORY_IDS_BY_ARTICLE_ID = `
SELECT ca.category_id AS categoryId
FROM categoryarticle ca
WHERE ca.article_id = @articleId
ORDER BY ca.category_id
`;
export const GET_PHOTOS_BY_ARTICLE_ID = `
SELECT
ap.sort,
p.hash AS fullHash,
p.w AS fullW,
p.h AS fullH,
p.size AS fullSize,
p.thumbw AS thumbW,
p.thumbh AS thumbH,
p.thumbhash AS thumbHash
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = @articleId
ORDER BY ap.sort ASC, ap.id ASC
`;
export const GET_EANS_BY_ARTICLE_ID = `
SELECT ae.ean
FROM articleean ae
WHERE ae.article_id = @articleId
ORDER BY ae.ean
`;
export const GET_PRICES_BY_ARTICLE_ID = `
SELECT
pr.minmultiplier AS minmultiplier,
pr.net,
t.rate AS taxRate,
pr.customergroup_id AS customergroupId
FROM price pr
INNER JOIN tax t ON t.id = pr.tax_id
WHERE pr.article_id = @articleId
ORDER BY pr.customergroup_id, pr.minmultiplier
`;

View File

@@ -0,0 +1,11 @@
export const INSERT = `
INSERT INTO articlehistory (article_id, companyuser_id, [text])
VALUES (@articleId, @companyuserId, @text)
`;
export const LIST_BY_ARTICLE_ID = `
SELECT id, companyuser_id AS companyuserId, [date], [text]
FROM articlehistory
WHERE article_id = @articleId
ORDER BY [date] DESC, id DESC
`;

View File

@@ -0,0 +1,32 @@
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM articlepic
WHERE article_id = @articleId
`;
export const ADD = `
INSERT INTO articlepic (article_id, pic_id, sort)
VALUES (@articleId, @picId, @sort)
`;
export const LIST_HASHES_BY_ARTICLE = `
SELECT p.hash AS fullHash
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = @articleId
ORDER BY ap.sort ASC, ap.id ASC
`;
export const SET_TEMP_SORTS = `
UPDATE articlepic
SET sort = -id
WHERE article_id = @articleId
`;
export const UPDATE_SORT_BY_HASH = `
UPDATE ap
SET sort = @sort
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = @articleId AND p.hash = @fullHash
`;

View File

@@ -0,0 +1,174 @@
export const LIST_BY_ARTICLE_ID = `
SELECT
asup.id,
asup.article_id AS articleId,
asup.entity_id AS entityId,
e.name AS supplierName,
asup.sku,
asup.pu,
asup.stock,
asup.sort,
asup.price_id AS priceId,
p.net AS priceNet,
p.tax_id AS priceTaxId,
t.rate AS priceTaxRate
FROM articlesupplier asup
INNER JOIN entity e ON e.id = asup.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
LEFT JOIN price p ON p.id = asup.price_id
LEFT JOIN tax t ON t.id = p.tax_id
WHERE asup.article_id = @articleId
AND cg.company_id = @companyId
ORDER BY asup.sort ASC, asup.id ASC;
`;
export const GET_BY_ID_AND_ARTICLE_ID = `
SELECT
asup.id,
asup.article_id AS articleId,
asup.entity_id AS entityId,
e.name AS supplierName,
asup.sku,
asup.pu,
asup.stock,
asup.sort,
asup.price_id AS priceId,
p.net AS priceNet,
p.tax_id AS priceTaxId,
t.rate AS priceTaxRate
FROM articlesupplier asup
INNER JOIN entity e ON e.id = asup.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
LEFT JOIN price p ON p.id = asup.price_id
LEFT JOIN tax t ON t.id = p.tax_id
WHERE asup.id = @id
AND asup.article_id = @articleId
AND cg.company_id = @companyId;
`;
export const CHECK_EXISTS = `
SELECT TOP 1 id
FROM articlesupplier
WHERE article_id = @articleId AND entity_id = @entityId;
`;
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM articlesupplier
WHERE article_id = @articleId;
`;
export const CREATE = `
INSERT INTO articlesupplier (article_id, entity_id, price_id, sku, stock, pu, sort)
OUTPUT INSERTED.id
VALUES (@articleId, @entityId, @priceId, @sku, @stock, @pu, @sort);
`;
export const UPDATE = `
UPDATE articlesupplier
SET
sku = @sku,
stock = @stock,
pu = @pu,
price_id = @priceId,
sort = @sort
WHERE id = @id AND article_id = @articleId;
`;
export const DELETE_BY_ID_AND_ARTICLE_ID = `
DELETE FROM articlesupplier
WHERE id = @id AND article_id = @articleId;
`;
export const LIST_IDS_BY_ARTICLE = `
SELECT id
FROM articlesupplier
WHERE article_id = @articleId
ORDER BY sort ASC, id ASC;
`;
export const SET_TEMP_SORTS = `
UPDATE articlesupplier
SET sort = -id
WHERE article_id = @articleId;
`;
export const UPDATE_SORT = `
UPDATE articlesupplier
SET sort = @sort
WHERE id = @id AND article_id = @articleId;
`;
export const UPDATE_PRICE = `
UPDATE price
SET
net = @net,
tax_id = @taxId
WHERE id = @id AND article_id = @articleId;
`;
export const DELETE_PRICE = `
DELETE FROM price
WHERE id = @id AND article_id = @articleId;
`;
export const GET_COMPANY_DEFAULT_TAX = `
SELECT taxrate
FROM company
WHERE id = @companyId;
`;
export const LIST_BY_ENTITY_AND_SKUS = `
SELECT
asup.id,
asup.article_id AS articleId,
asup.entity_id AS entityId,
e.name AS supplierName,
asup.sku,
asup.pu,
asup.stock,
asup.sort,
asup.price_id AS priceId,
p.net AS priceNet,
p.tax_id AS priceTaxId,
t.rate AS priceTaxRate,
a.id AS a_id,
a.sku AS a_sku,
at.name AS a_name,
at.slug AS a_slug,
(
SELECT
ap.sort,
p.hash,
p.thumbhash,
p.thumbw,
p.thumbh
FROM articlepic ap
INNER JOIN pic p ON p.id = ap.pic_id
WHERE ap.article_id = a.id
ORDER BY ap.sort ASC, ap.id ASC
FOR JSON PATH
) AS photosJson,
(
SELECT ae.ean
FROM articleean ae
WHERE ae.article_id = a.id
ORDER BY ae.ean
FOR JSON PATH
) AS eansJson
FROM articlesupplier asup
INNER JOIN entity e ON e.id = asup.entity_id
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
INNER JOIN article a ON a.id = asup.article_id
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
LEFT JOIN price p ON p.id = asup.price_id
LEFT JOIN tax t ON t.id = p.tax_id
WHERE asup.entity_id = @entityId
AND cg.company_id = @companyId
AND (
@skusJson IS NULL
OR a.sku IN (SELECT value FROM OPENJSON(@skusJson))
OR asup.sku IN (SELECT value FROM OPENJSON(@skusJson))
)
ORDER BY asup.sort ASC, asup.id ASC;
`;

View File

@@ -0,0 +1,10 @@
export const CREATE = `
INSERT INTO articletext (article_id, language_id, name, slug)
VALUES (@articleId, @languageId, @name, @slug)
`;
export const UPDATE_BY_ARTICLE_AND_LANGUAGE = `
UPDATE articletext
SET name = @name, slug = @slug
WHERE article_id = @articleId AND language_id = @languageId
`;

View File

@@ -0,0 +1,36 @@
export const LIST_BY_COMPANY = `
SELECT
id,
name,
gsprpinfo
FROM brand
WHERE company_id = @companyId
ORDER BY name
`;
export const GET_BY_COMPANY_AND_NAME = `
SELECT
id,
name,
gsprpinfo
FROM brand
WHERE company_id = @companyId
AND name = @name
`;
export const CREATE = `
INSERT INTO brand (company_id, name, gsprpinfo)
OUTPUT INSERTED.id
VALUES (@companyId, @name, @gsprpinfo)
`;
export const UPDATE_BY_ID = `
UPDATE brand
SET name = @name, gsprpinfo = @gsprpinfo
WHERE id = @id AND company_id = @companyId
`;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE FROM brand
WHERE id = @id AND company_id = @companyId
`;

View File

@@ -0,0 +1,144 @@
export const GET_TREE_FOR_COMPANY = `
WITH tree AS (
SELECT id, parent_id, sort
FROM category
WHERE id = @rootId
UNION ALL
SELECT c.id, c.parent_id, c.sort
FROM category c
INNER JOIN tree t ON c.parent_id = t.id
)
SELECT
t.id,
t.parent_id AS parentId,
t.sort,
ct.name,
ct.slug,
p.hash AS fullHash,
p.thumbw AS thumbW,
p.thumbh AS thumbH,
p.thumbhash AS thumbHash
FROM tree t
LEFT JOIN categorytext ct
ON ct.category_id = t.id AND ct.language_id = @languageId
OUTER APPLY (
SELECT TOP 1 cp.pic_id
FROM categorypic cp
WHERE cp.category_id = t.id
ORDER BY cp.sort ASC, cp.id ASC
) first_pic
LEFT JOIN pic p ON p.id = first_pic.pic_id
ORDER BY t.sort, t.id
`;
import {
ARTICLE_LIST_COLUMNS,
ARTICLE_LIST_JOINS,
} from './article.js';
export const GET_CATEGORY_ARTICLES = `
SELECT
${ARTICLE_LIST_COLUMNS}
FROM categoryarticle ca
INNER JOIN article a ON a.id = ca.article_id
${ARTICLE_LIST_JOINS}
WHERE ca.category_id = @categoryId
ORDER BY a.id
`;
export const CREATE = `
INSERT INTO category (parent_id, sort)
OUTPUT INSERTED.id, INSERTED.parent_id AS parentId, INSERTED.sort
VALUES (@parentId, @sort)
`;
export const RESOLVE_CATEGORY_ACCESS = `
WITH ancestors AS (
SELECT id, parent_id
FROM category
WHERE id = @categoryId
UNION ALL
SELECT c.id, c.parent_id
FROM category c
INNER JOIN ancestors a ON c.id = a.parent_id
),
root AS (
SELECT TOP 1 id AS rootCategoryId
FROM ancestors
WHERE parent_id IS NULL
)
SELECT
p.id AS categoryId,
r.rootCategoryId,
co.id AS companyId,
co.default_language_id AS languageId,
co.poscustomergroup_id AS posCustomerGroupId,
cu.id AS companyuserId
FROM category p
LEFT JOIN root r ON 1 = 1
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
LEFT JOIN companyuser cu
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
WHERE p.id = @categoryId
`;
export const GET_NEXT_SIBLING_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM category
WHERE parent_id = @parentId
`;
/** Resolve a child category by its parent id and name (language-sensitive). */
export const GET_BY_PARENT_AND_NAME = `
SELECT c.id
FROM category c
INNER JOIN categorytext ct
ON ct.category_id = c.id AND ct.language_id = @languageId
WHERE c.parent_id = @parentId
AND ct.name = @name
`;
export const LIST_CHILD_IDS = `
SELECT id
FROM category
WHERE parent_id = @parentId
`;
export const UPDATE_SORT = `
UPDATE category
SET sort = @sort
WHERE id = @id AND parent_id = @parentId
`;
/** True if @id is an ancestor of @parentId (would create a cycle when moving). */
export const IS_ANCESTOR = `
WITH ancestors AS (
SELECT id, parent_id
FROM category
WHERE id = @parentId
UNION ALL
SELECT c.id, c.parent_id
FROM category c
INNER JOIN ancestors a ON c.id = a.parent_id
)
SELECT id
FROM ancestors
WHERE id = @id
`;
export const MOVE = `
UPDATE category
SET parent_id = @parentId, sort = @sort
WHERE id = @id
`;
export const DELETE = `
DELETE FROM category
WHERE id = @id
`;

View File

@@ -0,0 +1,4 @@
export const ADD = `
INSERT INTO categoryarticle (category_id, article_id)
VALUES (@categoryId, @articleId)
`;

View File

@@ -0,0 +1,23 @@
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM categorypic
WHERE category_id = @categoryId
`;
export const ADD = `
INSERT INTO categorypic (category_id, pic_id, sort)
VALUES (@categoryId, @picId, @sort)
`;
export const DELETE_BY_CATEGORY = `
DELETE FROM categorypic
WHERE category_id = @categoryId
`;
export const GET_FIRST_PIC_BY_CATEGORY = `
SELECT TOP 1 p.id AS picId, p.thumbw AS thumbW, p.thumbh AS thumbH, p.thumbhash AS thumbHash
FROM categorypic cp
INNER JOIN pic p ON p.id = cp.pic_id
WHERE cp.category_id = @categoryId
ORDER BY cp.sort ASC, cp.id ASC
`;

View File

@@ -0,0 +1,10 @@
export const CREATE = `
INSERT INTO categorytext (category_id, language_id, name, slug)
VALUES (@categoryId, @languageId, @name, @slug)
`;
export const UPDATE_BY_CATEGORY_AND_LANGUAGE = `
UPDATE categorytext
SET name = @name, slug = @slug
WHERE category_id = @categoryId AND language_id = @languageId
`;

View File

@@ -0,0 +1,69 @@
export const GET_BY_NAME = `
SELECT id, name, root_category_id, default_language_id, taxrate, reducedtaxrate, taxfreerate
FROM company
WHERE name = @name
`;
export const GET_BY_ID = `
SELECT id, name, root_category_id, default_language_id, taxrate, reducedtaxrate, taxfreerate
FROM company
WHERE id = @id
`;
export const GET_COMPANY_ROOT_CATEGORY = `
SELECT root_category_id AS rootCategoryId FROM company WHERE id = @companyId
`;
export const CREATE = `
INSERT INTO company (
name,
root_category_id,
default_language_id,
taxrate,
reducedtaxrate,
taxfreerate,
poscustomergroup_id
)
OUTPUT INSERTED.id
VALUES (
@name,
@rootCategoryId,
@languageId,
@taxrate,
@reducedtaxrate,
@taxfreerate,
@posCustomerGroupId
)
`;
export const UPDATE = `
UPDATE company
SET
name = @name,
default_language_id = @languageId,
taxrate = @taxrate,
reducedtaxrate = @reducedtaxrate
WHERE id = @id
`;
export const LIST = `
SELECT
c.id,
c.name,
c.root_category_id AS rootCategoryId,
c.default_language_id AS languageId,
tr.rate AS taxrate,
rtr.rate AS reducedtaxrate,
tfr.rate AS taxfreerate
FROM company c
INNER JOIN companyuser cu ON cu.company_id = c.id
LEFT JOIN tax tr ON tr.id = c.taxrate
LEFT JOIN tax rtr ON rtr.id = c.reducedtaxrate
LEFT JOIN tax tfr ON tfr.id = c.taxfreerate
WHERE cu.google_auth_id = @googleAuthId
`;
export const DELETE = `
DELETE FROM company
WHERE id = @companyId
`;

View File

@@ -0,0 +1,44 @@
export const LIST_BY_COMPANY = `
SELECT
[key],
value,
CAST(frontend AS INT) AS frontend,
CAST(writeonly AS INT) AS writeonly
FROM companysetting
WHERE company_id = @companyId
ORDER BY [key]
`;
export const LIST_BY_COMPANY_FRONTEND = `
SELECT
[key],
value,
CAST(frontend AS INT) AS frontend,
CAST(writeonly AS INT) AS writeonly
FROM companysetting
WHERE company_id = @companyId
AND frontend = 1
ORDER BY [key]
`;
export const GET_BY_COMPANY_AND_KEY = `
SELECT
[key],
value,
CAST(frontend AS INT) AS frontend,
CAST(writeonly AS INT) AS writeonly
FROM companysetting
WHERE company_id = @companyId
AND [key] = @key
`;
export const UPSERT = `
MERGE companysetting AS target
USING (SELECT @companyId AS company_id, @key AS [key]) AS src
ON target.company_id = src.company_id AND target.[key] = src.[key]
WHEN MATCHED THEN
UPDATE SET value = @value, frontend = @frontend, writeonly = @writeonly
WHEN NOT MATCHED THEN
INSERT (company_id, [key], value, frontend, writeonly)
VALUES (@companyId, @key, @value, @frontend, @writeonly);
`;

View File

@@ -0,0 +1,29 @@
export const GET_BY_EMAIL = `
SELECT cu.id
FROM companyuser cu
INNER JOIN google_auth ga ON ga.id = cu.google_auth_id
WHERE ga.email = @email
`;
export const GET_COMPANY_ACCESS = `
SELECT
c.root_category_id AS rootCategoryId,
c.default_language_id AS languageId,
c.poscustomergroup_id AS posCustomerGroupId,
cu.id AS companyuserId
FROM company c
LEFT JOIN companyuser cu
ON cu.company_id = c.id AND cu.google_auth_id = @googleAuthId
WHERE c.id = @companyId
`;
export const ADD = `
INSERT INTO companyuser (google_auth_id, company_id)
OUTPUT INSERTED.id
VALUES (@googleAuthId, @companyId)
`;
export const DELETE = `
DELETE FROM companyuser
WHERE id = @id
`;

View File

@@ -0,0 +1,66 @@
export const CREATE = `
INSERT INTO customergroup (name, company_id, sort)
OUTPUT INSERTED.id, INSERTED.name, INSERTED.sort
VALUES (@name, @companyId, @sort)
`;
export const CREATE_WITHOUT_COMPANY = `
INSERT INTO customergroup (name, sort)
OUTPUT INSERTED.id, INSERTED.name, INSERTED.sort
VALUES (@name, @sort)
`;
export const SET_COMPANY = `
UPDATE customergroup
SET company_id = @companyId
WHERE id = @id
`;
export const GET_BY_COMPANY_AND_NAME = `
SELECT id, name, sort
FROM customergroup
WHERE company_id = @companyId AND name = @name
`;
export const LIST_BY_COMPANY = `
SELECT id, name, sort
FROM customergroup
WHERE company_id = @companyId
ORDER BY sort ASC, name ASC, id ASC
`;
export const GET_BY_COMPANY_AND_ID = `
SELECT id, name, sort
FROM customergroup
WHERE id = @id AND company_id = @companyId
`;
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM customergroup
WHERE company_id = @companyId
`;
export const LIST_IDS_BY_COMPANY = `
SELECT id
FROM customergroup
WHERE company_id = @companyId
`;
export const UPDATE = `
UPDATE customergroup
SET name = @name
OUTPUT INSERTED.id, INSERTED.name, INSERTED.sort
WHERE id = @id AND company_id = @companyId
`;
export const UPDATE_SORT = `
UPDATE customergroup
SET sort = @sort
WHERE id = @id AND company_id = @companyId
`;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE FROM customergroup
WHERE id = @id AND company_id = @companyId
`;

View File

@@ -0,0 +1,117 @@
export const DELIVERY_TYPE = 'delivery';
export const LIST_DELIVERIES = `
SELECT
m.id,
m.[number],
m.[type],
m.entity_id AS supplierId,
m.external_number AS externalNumber,
m.[date]
FROM manifest m
WHERE m.company_id = @companyId
AND m.[type] = @type
ORDER BY m.[date] DESC, m.id DESC
`;
export const GET_DELIVERY_HEADER = `
SELECT
m.id,
m.[number],
m.[type],
m.entity_id AS supplierId,
m.external_number AS externalNumber,
m.[date]
FROM manifest m
WHERE m.id = @id
AND m.company_id = @companyId
AND m.[type] = @type
`;
export const GET_DELIVERY_LINES = `
SELECT
ma.id,
ma.sku,
ma.name,
ma.pu,
ma.amount,
ma.net,
ma.rebate,
ma.taxrate,
ma.comment
FROM manifestarticle ma
WHERE ma.manifest_id = @manifestId
ORDER BY ma.id
`;
export const CREATE_MANIFEST = `
INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date])
OUTPUT INSERTED.id
VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date)
`;
export const UPDATE_MANIFEST = `
UPDATE manifest
SET
[number] = @number,
entity_id = @entityId,
external_number = @externalNumber,
[date] = @date
WHERE id = @id
AND company_id = @companyId
AND [type] = @type
`;
export const DELETE_MANIFEST = `
DELETE FROM manifest
WHERE id = @id
AND company_id = @companyId
AND [type] = @type
`;
export const DELETE_LINES_BY_MANIFEST = `
DELETE FROM manifestarticle
WHERE manifest_id = @manifestId
`;
export const GET_NEXT_ATTACHMENT_SORT = `
SELECT ISNULL(MAX(ma.sort), -1) + 1 AS sort
FROM manifestattachment ma
WHERE ma.manifest_id = @manifestId
`;
export const CREATE_FILE = `
INSERT INTO [file] (hash, mimetype, name, size, created, modified, content, thumbavif, thumbx, thumby, meta)
OUTPUT INSERTED.id
VALUES (@hash, @mimetype, @name, @size, @created, @modified, @content, CONVERT(VARBINARY(MAX), @thumbavif), @thumbx, @thumby, @meta)
`;
export const CREATE_ATTACHMENT = `
INSERT INTO manifestattachment (manifest_id, file_id, sort)
OUTPUT INSERTED.id
VALUES (@manifestId, @fileId, @sort)
`;
export const LIST_DELIVERY_FILES = `
SELECT
ma.id,
ma.manifest_id AS manifestId,
ma.file_id AS fileId,
ma.sort,
f.hash,
f.mimetype,
f.name,
f.size,
f.created,
f.modified,
f.thumbavif,
f.thumbx,
f.thumby,
f.meta
FROM manifestattachment ma
INNER JOIN [file] f ON f.id = ma.file_id
INNER JOIN manifest m ON m.id = ma.manifest_id
WHERE m.company_id = @companyId
AND m.[type] = @type
ORDER BY ma.manifest_id, ma.sort ASC, ma.id ASC
`;

View File

@@ -0,0 +1,163 @@
export const SUPPLIER_GROUP_NAME = 'Lieferanten';
export const POS_GROUP_NAME = 'POS';
export const ENTITY_COLUMNS = `
e.id,
e.customergroup_id AS customergroupId,
e.first_name AS firstName,
e.name,
e.[number] AS number,
e.created_at AS createdAt,
e.updated_at AS updatedAt
`;
export const LIST_BY_COMPANY = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
ORDER BY e.name, e.id
`;
export const LIST_BY_COMPANY_AND_GROUP = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND e.customergroup_id = @customergroupId
ORDER BY e.name, e.id
`;
/** Entities whose group is not Lieferanten and not POS (customer picker). */
export const LIST_CUSTOMERS_BY_COMPANY = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND cg.name NOT IN (@supplierGroupName, @posGroupName)
ORDER BY e.name, e.id
`;
export const GET_BY_COMPANY_AND_ID = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
`;
/** Occupied when another entity in the company already has this number. */
export const NUMBER_TAKEN_IN_COMPANY = `
SELECT TOP 1 e.id
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND e.[number] = @number
AND (@excludeEntityId IS NULL OR e.id <> @excludeEntityId)
`;
/** Customer entity: company-scoped and not Lieferanten/POS. */
export const GET_CUSTOMER_BY_ID = `
SELECT
${ENTITY_COLUMNS}
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
AND cg.name NOT IN (@supplierGroupName, @posGroupName)
`;
export const LIST_SUPPLIERS_BY_COMPANY = `
SELECT
e.id,
e.name,
e.created_at AS createdAt,
e.updated_at AS updatedAt
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE cg.company_id = @companyId
AND cg.name = @groupName
ORDER BY e.name, e.id
`;
export const GET_SUPPLIER_BY_ID = `
SELECT
e.id,
e.name,
e.created_at AS createdAt,
e.updated_at AS updatedAt
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
AND cg.name = @groupName
`;
export const CREATE = `
INSERT INTO entity (customergroup_id, first_name, name, [number])
OUTPUT
INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.[number] AS number,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
VALUES (@customergroupId, @firstName, @name, @number)
`;
export const UPDATE = `
UPDATE entity
SET
first_name = @firstName,
name = @name,
[number] = @number,
updated_at = SYSUTCDATETIME()
OUTPUT
INSERTED.id,
INSERTED.customergroup_id AS customergroupId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.[number] AS number,
INSERTED.created_at AS createdAt,
INSERTED.updated_at AS updatedAt
WHERE id = @id
`;
export const UPDATE_NAME = `
UPDATE entity
SET name = @name, updated_at = SYSUTCDATETIME()
OUTPUT INSERTED.id, INSERTED.name, INSERTED.created_at AS createdAt, INSERTED.updated_at AS updatedAt
WHERE id = @id
AND customergroup_id = @customergroupId
`;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE e
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
`;
export const DELETE_BY_ID = `
DELETE e
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
AND cg.name = @groupName
`;
export const GET_GROUP_ID_FOR_SUPPLIER = `
SELECT e.customergroup_id AS customergroupId, e.name
FROM entity e
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
WHERE e.id = @id
AND cg.company_id = @companyId
AND cg.name = @groupName
`;

View File

@@ -0,0 +1,105 @@
export const ADDRESS_COLUMNS = `
id,
entity_id AS entityId,
first_name AS firstName,
name,
street,
streetnumber AS streetNumber,
zip,
city,
county,
addresstype AS addressType,
sort
`;
export const LIST_BY_ENTITY = `
SELECT
${ADDRESS_COLUMNS}
FROM entityaddress
WHERE entity_id = @entityId
ORDER BY sort ASC, id ASC
`;
export const GET_BY_ENTITY_AND_ID = `
SELECT
${ADDRESS_COLUMNS}
FROM entityaddress
WHERE id = @id AND entity_id = @entityId
`;
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM entityaddress
WHERE entity_id = @entityId
`;
export const CREATE = `
INSERT INTO entityaddress (
entity_id,
first_name,
name,
street,
streetnumber,
zip,
city,
county,
addresstype,
sort
)
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.street,
INSERTED.streetnumber AS streetNumber,
INSERTED.zip,
INSERTED.city,
INSERTED.county,
INSERTED.addresstype AS addressType,
INSERTED.sort
VALUES (
@entityId,
@firstName,
@name,
@street,
@streetNumber,
@zip,
@city,
@county,
@addressType,
@sort
)
`;
export const UPDATE = `
UPDATE entityaddress
SET
first_name = @firstName,
name = @name,
street = @street,
streetnumber = @streetNumber,
zip = @zip,
city = @city,
county = @county,
addresstype = @addressType,
sort = @sort
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.first_name AS firstName,
INSERTED.name,
INSERTED.street,
INSERTED.streetnumber AS streetNumber,
INSERTED.zip,
INSERTED.city,
INSERTED.county,
INSERTED.addresstype AS addressType,
INSERTED.sort
WHERE id = @id AND entity_id = @entityId
`;
export const DELETE_BY_ENTITY_AND_ID = `
DELETE FROM entityaddress
WHERE id = @id AND entity_id = @entityId
`;

View File

@@ -0,0 +1,56 @@
export const CONTACT_COLUMNS = `
id,
entity_id AS entityId,
contacttype AS contactType,
contact,
sort
`;
export const LIST_BY_ENTITY = `
SELECT
${CONTACT_COLUMNS}
FROM entitycontact
WHERE entity_id = @entityId
ORDER BY sort ASC, id ASC
`;
export const GET_BY_ENTITY_AND_ID = `
SELECT
${CONTACT_COLUMNS}
FROM entitycontact
WHERE id = @id AND entity_id = @entityId
`;
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM entitycontact
WHERE entity_id = @entityId
`;
export const CREATE = `
INSERT INTO entitycontact (entity_id, contacttype, contact, sort)
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.contacttype AS contactType,
INSERTED.contact,
INSERTED.sort
VALUES (@entityId, @contactType, @contact, @sort)
`;
export const UPDATE = `
UPDATE entitycontact
SET contacttype = @contactType, contact = @contact, sort = @sort
OUTPUT
INSERTED.id,
INSERTED.entity_id AS entityId,
INSERTED.contacttype AS contactType,
INSERTED.contact,
INSERTED.sort
WHERE id = @id AND entity_id = @entityId
`;
export const DELETE_BY_ENTITY_AND_ID = `
DELETE FROM entitycontact
WHERE id = @id AND entity_id = @entityId
`;

View File

@@ -0,0 +1,11 @@
export const INSERT = `
INSERT INTO entityhistory (entity_id, companyuser_id, [text])
VALUES (@entityId, @companyuserId, @text)
`;
export const LIST_BY_ENTITY_ID = `
SELECT id, companyuser_id AS companyuserId, [date], [text]
FROM entityhistory
WHERE entity_id = @entityId
ORDER BY [date] DESC, id DESC
`;

View File

@@ -0,0 +1,58 @@
export const ADD = `
INSERT INTO google_auth (email)
OUTPUT INSERTED.id
VALUES (@email)
`;
export const GOOGLE_AUTH_COLUMNS = `
id,
email,
picture,
picturebinary AS pictureBinary,
sub,
created_at AS createdAt,
selected_company AS selectedCompanyId
`;
export const GET_BY_EMAIL = `
SELECT
${GOOGLE_AUTH_COLUMNS}
FROM google_auth
WHERE email = @email
`;
export const GET_BY_ID = `
SELECT
${GOOGLE_AUTH_COLUMNS}
FROM google_auth
WHERE id = @googleAuthId
`;
export const LIST_ALLOWED_EMAILS = `
SELECT
id,
email,
picture,
sub,
created_at AS createdAt,
selected_company AS selectedCompanyId
FROM google_auth
ORDER BY email
`;
export const REMOVE_ALLOWED_EMAIL = `
DELETE FROM google_auth
WHERE email = @email
`;
export const SET_SELECTED_COMPANY = `
UPDATE google_auth
SET selected_company = @companyId
WHERE id = @googleAuthId
`;
export const SET_PROFILE = `
UPDATE google_auth
SET picture = @picture, picturebinary = @pictureBinary, sub = @sub
WHERE id = @googleAuthId
`;

View File

@@ -0,0 +1,21 @@
export const CREATE = `
INSERT INTO language (iso639)
OUTPUT INSERTED.id
VALUES (@iso639)
`;
export const GET_BY_ISO639 = `
SELECT id, iso639
FROM language
WHERE iso639 = @iso639
`;
export const LIST = `
SELECT id, iso639
FROM language
`;
export const DELETE = `
DELETE FROM language
WHERE id = @id
`;

View File

@@ -0,0 +1,107 @@
/**
* Shared SQL for typed manifest documents (quote / order).
* Delivery keeps its own SQL with supplierId naming.
*/
export const QUOTE_TYPE = 'quote';
export const ORDER_TYPE = 'order';
export const LIST_MANIFESTS = `
SELECT
m.id,
m.[number],
m.[type],
m.entity_id AS entityId,
m.external_number AS externalNumber,
m.[date]
FROM manifest m
WHERE m.company_id = @companyId
AND m.[type] = @type
ORDER BY m.[date] DESC, m.id DESC
`;
export const GET_MANIFEST_HEADER = `
SELECT
m.id,
m.[number],
m.[type],
m.entity_id AS entityId,
m.external_number AS externalNumber,
m.[date]
FROM manifest m
WHERE m.id = @id
AND m.company_id = @companyId
AND m.[type] = @type
`;
export const GET_MANIFEST_LINES = `
SELECT
ma.id,
ma.sku,
ma.name,
ma.pu,
ma.amount,
ma.net,
ma.rebate,
ma.taxrate,
ma.comment
FROM manifestarticle ma
WHERE ma.manifest_id = @manifestId
ORDER BY ma.id
`;
export const CREATE_MANIFEST = `
INSERT INTO manifest (company_id, [number], [type], entity_id, external_number, [date])
OUTPUT INSERTED.id
VALUES (@companyId, @number, @type, @entityId, @externalNumber, @date)
`;
export const UPDATE_MANIFEST = `
UPDATE manifest
SET
[number] = @number,
entity_id = @entityId,
external_number = @externalNumber,
[date] = @date
WHERE id = @id
AND company_id = @companyId
AND [type] = @type
`;
export const DELETE_MANIFEST = `
DELETE FROM manifest
WHERE id = @id
AND company_id = @companyId
AND [type] = @type
`;
export const DELETE_LINES_BY_MANIFEST = `
DELETE FROM manifestarticle
WHERE manifest_id = @manifestId
`;
export const CREATE_LINE = `
INSERT INTO manifestarticle (
manifest_id,
sku,
name,
pu,
amount,
net,
comment,
rebate,
taxrate
)
OUTPUT INSERTED.id
VALUES (
@manifestId,
@sku,
@name,
@pu,
@amount,
@net,
@comment,
@rebate,
@taxrate
)
`;

View File

@@ -0,0 +1,104 @@
export const CHECK_MANIFEST_ACCESS = `
SELECT id, company_id AS companyId
FROM manifest
WHERE id = @manifestId AND company_id = @companyId
`;
export const CREATE_FILE = `
INSERT INTO [file] (hash, mimetype, name, size, created, modified, content, thumbavif, thumbx, thumby, meta)
OUTPUT INSERTED.id
VALUES (@hash, @mimetype, @name, @size, @created, @modified, @content, CONVERT(VARBINARY(MAX), @thumbavif), @thumbx, @thumby, @meta)
`;
export const GET_NEXT_SORT = `
SELECT ISNULL(MAX(sort), -1) + 1 AS sort
FROM manifestattachment
WHERE manifest_id = @manifestId;
`;
export const CREATE_ATTACHMENT = `
INSERT INTO manifestattachment (manifest_id, file_id, sort)
OUTPUT INSERTED.id
VALUES (@manifestId, @fileId, @sort)
`;
export const LIST_BY_MANIFEST = `
SELECT
ma.id,
ma.manifest_id AS manifestId,
ma.file_id AS fileId,
ma.sort,
f.hash,
f.mimetype,
f.name,
f.size,
f.created,
f.modified,
f.thumbavif,
f.thumbx,
f.thumby,
f.meta
FROM manifestattachment ma
INNER JOIN [file] f ON f.id = ma.file_id
WHERE ma.manifest_id = @manifestId
ORDER BY ma.sort ASC, ma.id ASC
`;
export const GET_BY_ID = `
SELECT
ma.id,
ma.manifest_id AS manifestId,
ma.file_id AS fileId,
ma.sort,
f.hash,
f.mimetype,
f.name,
f.size,
f.created,
f.modified,
f.content,
f.thumbavif,
f.thumbx,
f.thumby,
f.meta
FROM manifestattachment ma
INNER JOIN [file] f ON f.id = ma.file_id
WHERE ma.manifest_id = @manifestId
AND (ma.id = @id OR ma.file_id = @id)
`;
export const FIND_ATTACHMENT = `
SELECT ma.id, ma.file_id AS fileId
FROM manifestattachment ma
WHERE ma.manifest_id = @manifestId
AND (ma.id = @id OR ma.file_id = @id)
`;
export const DELETE_ATTACHMENT = `
DELETE FROM manifestattachment
WHERE id = @id
`;
export const DELETE_FILE = `
DELETE FROM [file]
WHERE id = @fileId
`;
export const LIST_IDS_BY_MANIFEST = `
SELECT id
FROM manifestattachment
WHERE manifest_id = @manifestId
ORDER BY sort ASC, id ASC;
`;
export const SET_TEMP_SORTS = `
UPDATE manifestattachment
SET sort = -id
WHERE manifest_id = @manifestId;
`;
export const UPDATE_SORT = `
UPDATE manifestattachment
SET sort = @sort
WHERE id = @id AND manifest_id = @manifestId;
`;

View File

@@ -0,0 +1,46 @@
export const ENSURE_TYPE = `
IF NOT EXISTS (
SELECT 1
FROM numbers
WHERE company_id = @companyId AND [type] = @type
)
BEGIN
INSERT INTO numbers (company_id, [type])
VALUES (@companyId, @type);
END
`;
export const LIST_BY_COMPANY = `
SELECT
id,
[type],
prefix,
padding,
[next] AS [next]
FROM numbers
WHERE company_id = @companyId
ORDER BY [type]
`;
export const GET_BY_COMPANY_AND_ID = `
SELECT
id,
[type],
prefix,
padding,
[next] AS [next]
FROM numbers
WHERE id = @id
AND company_id = @companyId
`;
export const UPDATE_BY_COMPANY_AND_ID = `
UPDATE numbers
SET
prefix = @prefix,
padding = @padding,
[next] = @next
OUTPUT INSERTED.id, INSERTED.[type], INSERTED.prefix, INSERTED.padding, INSERTED.[next]
WHERE id = @id
AND company_id = @companyId
`;

View File

@@ -0,0 +1,46 @@
export const CREATE = `
INSERT INTO pic (
w, h, size, hash, thumbw, thumbh, thumbhash, source, fullbinary, thumbbinary
)
OUTPUT INSERTED.id
VALUES (
@w, @h, @size, @hash, @thumbw, @thumbh, @thumbhash, @source, @fullbinary, @thumbbinary
)
`;
export const GET_CATEGORY_ID_BY_HASH = `
SELECT TOP 1 categoryId
FROM (
SELECT ca.category_id AS categoryId
FROM pic p
INNER JOIN articlepic ap ON ap.pic_id = p.id
INNER JOIN categoryarticle ca ON ca.article_id = ap.article_id
WHERE p.hash = @hash
UNION
SELECT cp.category_id AS categoryId
FROM pic p
INNER JOIN categorypic cp ON cp.pic_id = p.id
WHERE p.hash = @hash
) AS combined
`;
export const GET_FULL_BY_HASH = `
SELECT w, h, size, fullbinary AS buffer
FROM pic
WHERE hash = @hash
`;
export const GET_THUMB_BY_HASH = `
SELECT thumbw AS w, thumbh AS h, thumbbinary AS buffer
FROM pic
WHERE hash = @hash
`;
export const DELETE_BY_ARTICLE_AND_HASH = `
DELETE p
FROM pic p
INNER JOIN articlepic ap ON ap.pic_id = p.id AND ap.article_id = @articleId
WHERE p.hash = @fullHash
`;

View File

@@ -0,0 +1,91 @@
export const LIST_BY_COMPANY = `
SELECT
id,
name,
token,
pairingpin
FROM posterminal
WHERE company_id = @companyId
ORDER BY id
`;
export const GET_BY_ID = `
SELECT
id,
company_id AS companyId,
name,
token,
pairingpin
FROM posterminal
WHERE id = @id
`;
export const CREATE = `
INSERT INTO posterminal (company_id, name, token, pairingpin)
OUTPUT INSERTED.id, INSERTED.name, INSERTED.token, INSERTED.pairingpin
VALUES (@companyId, @name, @token, @pairingpin)
`;
export const UPDATE = `
UPDATE posterminal
SET name = @name, token = @token, pairingpin = @pairingpin
WHERE id = @id AND company_id = @companyId
`;
export const DELETE_BY_COMPANY_AND_ID = `
DELETE FROM posterminal
WHERE id = @id AND company_id = @companyId
`;
export const LIST_CATEGORY_IDS = `
SELECT category_id AS categoryId
FROM posterminalcategory
WHERE posterminal_id = @posterminalId
`;
/** setOn: insert if not exists (single terminal + single category). */
export const SET_ON = `
IF NOT EXISTS (
SELECT 1 FROM posterminalcategory
WHERE posterminal_id = @posterminalId AND category_id = @categoryId
)
INSERT INTO posterminalcategory (posterminal_id, category_id)
VALUES (@posterminalId, @categoryId)
`;
/** setOff: remove a single category from a single terminal. */
export const SET_OFF = `
DELETE FROM posterminalcategory
WHERE posterminal_id = @posterminalId AND category_id = @categoryId
`;
/** setOnAll: enable ALL company categories on one terminal. */
export const SET_ON_ALL = `
;WITH cats AS (
SELECT id FROM category WHERE id = @rootCategoryId
UNION ALL
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
)
INSERT INTO posterminalcategory (posterminal_id, category_id)
SELECT @posterminalId, cats.id
FROM cats
WHERE NOT EXISTS (
SELECT 1 FROM posterminalcategory pc
WHERE pc.posterminal_id = @posterminalId AND pc.category_id = cats.id
)
`;
/** setOffAll: remove ALL category links from one terminal. */
export const SET_OFF_ALL = `
DELETE FROM posterminalcategory WHERE posterminal_id = @posterminalId
`;
/** For category tree: map category -> posterminal ids (company-scoped). */
export const POSTERMINAL_IDS_BY_COMPANY = `
SELECT
pc.category_id AS categoryId,
pc.posterminal_id AS posterminalId
FROM posterminalcategory pc
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
WHERE pt.company_id = @companyId
`;

View File

@@ -0,0 +1,11 @@
export const DELETE_BY_ARTICLE_AND_CUSTOMERGROUP = `
DELETE FROM price
WHERE article_id = @articleId
AND customergroup_id = @customerGroupId
`;
export const CREATE = `
INSERT INTO price (tax_id, article_id, minmultiplier, customergroup_id, net)
OUTPUT INSERTED.id
VALUES (@taxId, @articleId, @minmultiplier, @customerGroupId, @net)
`;

View File

@@ -0,0 +1,16 @@
export const GET_BY_RATE = `
SELECT id, rate
FROM tax
WHERE rate = @rate
`;
export const CREATE = `
INSERT INTO tax (rate)
OUTPUT INSERTED.id
VALUES (@rate)
`;
export const LIST = `
SELECT id, rate
FROM tax
`;

View File

@@ -0,0 +1,50 @@
export const UNIT_COLUMNS = `
u.si,
u.basesi,
u.basesifactor,
ut.symbol,
ut.name
`;
export const GET_BY_SI = `
SELECT id
FROM unit
WHERE company_id = @companyId AND si = @si
`;
export const GET_FULL_BY_SI = `
SELECT
u.id,
${UNIT_COLUMNS}
FROM unit u
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = @languageId
WHERE u.company_id = @companyId AND u.si = @si
`;
export const LIST_BY_COMPANY = `
SELECT
${UNIT_COLUMNS}
FROM unit u
LEFT JOIN unittext ut
ON ut.unit_id = u.id AND ut.language_id = @languageId
WHERE u.company_id = @companyId
ORDER BY ut.symbol, ut.name, u.si
`;
export const CREATE = `
INSERT INTO unit (company_id, si, basesi, basesifactor)
OUTPUT INSERTED.id
VALUES (@companyId, @si, @basesi, @basesifactor)
`;
export const UPDATE_BY_SI = `
UPDATE unit
SET basesi = @basesi, basesifactor = @basesifactor
WHERE company_id = @companyId AND si = @si
`;
export const DELETE_BY_COMPANY_AND_SI = `
DELETE FROM unit
WHERE company_id = @companyId AND si = @si
`;

View File

@@ -0,0 +1,16 @@
export const CREATE = `
INSERT INTO unittext (unit_id, language_id, symbol, name)
VALUES (@unitId, @languageId, @symbol, @name)
`;
export const UPDATE_BY_UNIT_AND_LANGUAGE = `
UPDATE unittext
SET symbol = @symbol, name = @name
WHERE unit_id = @unitId AND language_id = @languageId
`;
export const GET_BY_UNIT_AND_LANGUAGE = `
SELECT id
FROM unittext
WHERE unit_id = @unitId AND language_id = @languageId
`;

View File

@@ -0,0 +1,876 @@
export const INIT_META = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'meta')
BEGIN
CREATE TABLE meta (
[key] NVARCHAR(255) NOT NULL,
value NVARCHAR(MAX) NOT NULL,
CONSTRAINT PK_meta PRIMARY KEY ([key])
);
END
IF NOT EXISTS (SELECT 1 FROM meta WHERE [key] = N'version')
BEGIN
INSERT INTO meta ([key], value)
VALUES (N'version', N'1');
END
`;
export const INIT_GOOGLE_AUTH = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'google_auth')
BEGIN
CREATE TABLE google_auth (
id INT IDENTITY(1,1) PRIMARY KEY,
email NVARCHAR(320) NOT NULL,
picture NVARCHAR(2048) NULL,
picturebinary VARBINARY(MAX) NULL,
sub NVARCHAR(255) NULL,
created_at DATETIME2 NOT NULL CONSTRAINT DF_google_auth_created_at DEFAULT SYSUTCDATETIME(),
selected_company INT NULL,
CONSTRAINT UQ_google_auth_email UNIQUE (email),
CONSTRAINT FK_google_auth_selected_company FOREIGN KEY (selected_company) REFERENCES company(id) ON DELETE SET NULL
);
CREATE UNIQUE INDEX UQ_google_auth_sub ON google_auth(sub) WHERE sub IS NOT NULL;
END
`;
export const INIT_LANGUAGE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'language')
BEGIN
CREATE TABLE language (
id INT IDENTITY(1,1) PRIMARY KEY,
iso639 NVARCHAR(10) NOT NULL,
CONSTRAINT UQ_language_iso639 UNIQUE (iso639)
);
END
`;
export const INIT_TAX = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'tax')
BEGIN
CREATE TABLE tax (
id INT IDENTITY(1,1) PRIMARY KEY,
rate DECIMAL(18,4) NOT NULL,
CONSTRAINT UQ_tax_rate UNIQUE (rate)
);
END
IF NOT EXISTS (SELECT 1 FROM tax WHERE rate = 0)
BEGIN
INSERT INTO tax (rate) VALUES (0);
END
IF NOT EXISTS (SELECT 1 FROM tax WHERE rate = 7)
BEGIN
INSERT INTO tax (rate) VALUES (7);
END
IF NOT EXISTS (SELECT 1 FROM tax WHERE rate = 19)
BEGIN
INSERT INTO tax (rate) VALUES (19);
END
`;
export const INIT_CATEGORY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'category')
BEGIN
CREATE TABLE category (
id INT IDENTITY(1,1) PRIMARY KEY,
parent_id INT NULL,
sort INT NOT NULL CONSTRAINT DF_category_sort DEFAULT 0,
CONSTRAINT FK_category_parent FOREIGN KEY (parent_id) REFERENCES category(id) ON DELETE NO ACTION
);
END
`;
export const INIT_PIC = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'pic')
BEGIN
CREATE TABLE pic (
id INT IDENTITY(1,1) PRIMARY KEY,
w INT NOT NULL,
h INT NOT NULL,
size BIGINT NOT NULL,
hash NVARCHAR(64) NOT NULL,
thumbw INT NOT NULL,
thumbh INT NOT NULL,
thumbhash NVARCHAR(64) NOT NULL,
source NVARCHAR(MAX) NOT NULL,
fullbinary VARBINARY(MAX) NOT NULL,
thumbbinary VARBINARY(MAX) NOT NULL,
CONSTRAINT UQ_pic_hash UNIQUE (hash)
);
END
`;
export const INIT_COMPANY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'company')
BEGIN
CREATE TABLE company (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(255) NOT NULL,
root_category_id INT NULL,
default_language_id INT NULL,
taxrate INT NULL,
reducedtaxrate INT NULL,
taxfreerate INT NULL,
poscustomergroup_id INT NOT NULL,
CONSTRAINT UQ_company_name UNIQUE (name),
CONSTRAINT FK_company_root_category FOREIGN KEY (root_category_id) REFERENCES category(id) ON DELETE SET NULL,
CONSTRAINT FK_company_default_language FOREIGN KEY (default_language_id) REFERENCES language(id) ON DELETE SET NULL,
CONSTRAINT FK_company_taxrate FOREIGN KEY (taxrate) REFERENCES tax(id) ON DELETE NO ACTION,
CONSTRAINT FK_company_reducedtaxrate FOREIGN KEY (reducedtaxrate) REFERENCES tax(id) ON DELETE NO ACTION,
CONSTRAINT FK_company_taxfreerate FOREIGN KEY (taxfreerate) REFERENCES tax(id) ON DELETE NO ACTION
);
END
`;
export const INIT_CUSTOMERGROUP = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'customergroup')
BEGIN
CREATE TABLE customergroup (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(255) NOT NULL,
sort INT NOT NULL CONSTRAINT DF_customergroup_sort DEFAULT 0,
-- Nullable only for company-create chicken-egg (POS group first, then company, then SET).
-- Always set before commit; never left null for live rows.
company_id INT NULL,
CONSTRAINT FK_customergroup_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
);
END
`;
export const INIT_COMPANY_POS_CUSTOMERGROUP_FK = `
IF NOT EXISTS (
SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_company_poscustomergroup'
)
BEGIN
ALTER TABLE company
ADD CONSTRAINT FK_company_poscustomergroup
FOREIGN KEY (poscustomergroup_id) REFERENCES customergroup(id) ON DELETE NO ACTION;
END
`;
export const INIT_ENTITY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entity')
BEGIN
CREATE TABLE entity (
id INT IDENTITY(1,1) PRIMARY KEY,
customergroup_id INT NOT NULL,
first_name NVARCHAR(255) NULL,
name NVARCHAR(255) NOT NULL,
[number] NVARCHAR(100) NULL,
created_at DATETIME2 NOT NULL CONSTRAINT DF_entity_created_at DEFAULT SYSUTCDATETIME(),
updated_at DATETIME2 NOT NULL CONSTRAINT DF_entity_updated_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_entity_customergroup FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE
);
END
`;
/** Unique (company_id, number) for non-null entity numbers via customergroup. */
export const INIT_ENTITY_COMPANY_NUMBER_UQ = `
IF OBJECT_ID(N'dbo.v_entity_company_number', N'V') IS NULL
BEGIN
EXEC('
CREATE VIEW dbo.v_entity_company_number
WITH SCHEMABINDING
AS
SELECT
e.id AS entity_id,
cg.company_id,
e.[number]
FROM dbo.entity AS e
INNER JOIN dbo.customergroup AS cg ON cg.id = e.customergroup_id
WHERE e.[number] IS NOT NULL
');
END
IF NOT EXISTS (
SELECT 1 FROM sys.indexes
WHERE name = N'UQ_entity_company_number'
AND object_id = OBJECT_ID(N'dbo.v_entity_company_number')
)
BEGIN
CREATE UNIQUE CLUSTERED INDEX UQ_entity_company_number
ON dbo.v_entity_company_number (company_id, [number]);
END
`;
export const INIT_ENTITYADDRESS = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityaddress')
BEGIN
CREATE TABLE entityaddress (
id INT IDENTITY(1,1) PRIMARY KEY,
entity_id INT NOT NULL,
first_name NVARCHAR(255) NULL,
name NVARCHAR(255) NULL,
street NVARCHAR(255) NULL,
streetnumber NVARCHAR(50) NULL,
zip NVARCHAR(50) NULL,
city NVARCHAR(255) NULL,
county NVARCHAR(255) NULL,
addresstype NVARCHAR(50) NOT NULL,
sort INT NOT NULL CONSTRAINT DF_entityaddress_sort DEFAULT 0,
CONSTRAINT FK_entityaddress_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
);
END
`;
export const INIT_ENTITYCONTACT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entitycontact')
BEGIN
CREATE TABLE entitycontact (
id INT IDENTITY(1,1) PRIMARY KEY,
entity_id INT NOT NULL,
contacttype NVARCHAR(50) NOT NULL,
contact NVARCHAR(255) NOT NULL,
sort INT NOT NULL CONSTRAINT DF_entitycontact_sort DEFAULT 0,
CONSTRAINT FK_entitycontact_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
);
END
`;
export const INIT_ENTITYHISTORY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityhistory')
BEGIN
CREATE TABLE entityhistory (
id INT IDENTITY(1,1) PRIMARY KEY,
entity_id INT NOT NULL,
companyuser_id INT NULL,
[date] DATETIME2 NOT NULL CONSTRAINT DF_entityhistory_date DEFAULT SYSUTCDATETIME(),
[text] NVARCHAR(MAX) NOT NULL,
CONSTRAINT FK_entityhistory_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
CONSTRAINT FK_entityhistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE NO ACTION
);
END
`;
export const INIT_UNIT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'unit')
BEGIN
CREATE TABLE unit (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
si NVARCHAR(50) NOT NULL,
basesi NVARCHAR(50) NOT NULL,
basesifactor FLOAT NOT NULL,
CONSTRAINT UQ_unit_company_si UNIQUE (company_id, si),
CONSTRAINT FK_unit_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION
);
END
`;
export const INIT_UNITTEXT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'unittext')
BEGIN
CREATE TABLE unittext (
id INT IDENTITY(1,1) PRIMARY KEY,
unit_id INT NOT NULL,
language_id INT NOT NULL,
symbol NVARCHAR(50) NOT NULL,
name NVARCHAR(255) NOT NULL,
CONSTRAINT FK_unittext_unit FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE CASCADE,
CONSTRAINT FK_unittext_language FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE,
CONSTRAINT UQ_unittext_unit_language UNIQUE (unit_id, language_id)
);
END
`;
export const INIT_OPTIONSLIST = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'optionslist')
BEGIN
CREATE TABLE optionslist (
id INT IDENTITY(1,1) PRIMARY KEY
);
END
`;
export const INIT_ARTICLELIST = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlelist')
BEGIN
CREATE TABLE articlelist (
id INT IDENTITY(1,1) PRIMARY KEY
);
END
`;
export const INIT_BRAND = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'brand')
BEGIN
CREATE TABLE brand (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
name NVARCHAR(255) NOT NULL,
gsprpinfo NVARCHAR(MAX) NULL,
CONSTRAINT UQ_brand_company_name UNIQUE (company_id, name),
CONSTRAINT FK_brand_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION
);
END
`;
export const INIT_ARTICLE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'article')
BEGIN
CREATE TABLE article (
id INT IDENTITY(1,1) PRIMARY KEY,
sku NVARCHAR(100) NULL,
unit_id INT NOT NULL,
unitamount FLOAT NOT NULL,
weight FLOAT NOT NULL,
brand_id INT NULL,
articlelist_id INT NULL,
optionslist_id INT NULL,
is_father BIT NOT NULL CONSTRAINT DF_article_is_father DEFAULT 0,
fatherarticle_id INT NULL,
fathersort INT NOT NULL CONSTRAINT DF_article_fathersort DEFAULT 0,
created_at DATETIME2 NOT NULL CONSTRAINT DF_article_created_at DEFAULT SYSUTCDATETIME(),
updated_at DATETIME2 NOT NULL CONSTRAINT DF_article_updated_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_article_unit FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE NO ACTION,
CONSTRAINT FK_article_brand FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL,
CONSTRAINT FK_article_articlelist FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL,
CONSTRAINT FK_article_optionslist FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE SET NULL,
CONSTRAINT FK_article_fatherarticle FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION,
CONSTRAINT UQ_article_sku UNIQUE (sku)
);
-- Only enforce sibling sort uniqueness among children of the same father.
-- (SQL Server UNIQUE treats NULLs as equal, which would block multiple root articles.)
CREATE UNIQUE INDEX UQ_article_fatherarticle_fathersort
ON article (fatherarticle_id, fathersort)
WHERE fatherarticle_id IS NOT NULL;
END
`;
export const INIT_ARTICLEHISTORY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlehistory')
BEGIN
CREATE TABLE articlehistory (
id INT IDENTITY(1,1) PRIMARY KEY,
article_id INT NOT NULL,
companyuser_id INT NULL,
[date] DATETIME2 NOT NULL CONSTRAINT DF_articlehistory_date DEFAULT SYSUTCDATETIME(),
[text] NVARCHAR(MAX) NOT NULL,
CONSTRAINT FK_articlehistory_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT FK_articlehistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE SET NULL
);
END
`;
export const INIT_ARTICLEEAN = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articleean')
BEGIN
CREATE TABLE articleean (
id INT IDENTITY(1,1) PRIMARY KEY,
article_id INT NOT NULL,
ean NVARCHAR(50) NOT NULL,
CONSTRAINT FK_articleean_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT UQ_articleean_ean UNIQUE (ean)
);
-- Hot path: getBySku EANs by article (UQ is ean-leading only)
CREATE INDEX IX_articleean_article_ean
ON articleean (article_id, ean);
END
`;
export const INIT_ARTICLEPIC = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlepic')
BEGIN
CREATE TABLE articlepic (
id INT IDENTITY(1,1) PRIMARY KEY,
article_id INT NOT NULL,
pic_id INT NOT NULL,
sort INT NOT NULL CONSTRAINT DF_articlepic_sort DEFAULT 0,
CONSTRAINT FK_articlepic_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT FK_articlepic_pic FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE,
CONSTRAINT UQ_articlepic_article_pic UNIQUE (article_id, pic_id),
CONSTRAINT UQ_articlepic_article_sort UNIQUE (article_id, sort)
);
END
`;
export const INIT_BRANDPIC = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'brandpic')
BEGIN
CREATE TABLE brandpic (
id INT IDENTITY(1,1) PRIMARY KEY,
brand_id INT NOT NULL,
pic_id INT NOT NULL,
sort INT NOT NULL CONSTRAINT DF_brandpic_sort DEFAULT 0,
CONSTRAINT FK_brandpic_brand FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE CASCADE,
CONSTRAINT FK_brandpic_pic FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE,
CONSTRAINT UQ_brandpic_brand_pic UNIQUE (brand_id, pic_id),
CONSTRAINT UQ_brandpic_brand_sort UNIQUE (brand_id, sort)
);
END
`;
export const INIT_ARTICLELISTARTICLE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlelistarticle')
BEGIN
CREATE TABLE articlelistarticle (
id INT IDENTITY(1,1) PRIMARY KEY,
articlelist_id INT NOT NULL,
article_id INT NOT NULL,
count INT NOT NULL CONSTRAINT DF_articlelistarticle_count DEFAULT 1,
sort INT NOT NULL CONSTRAINT DF_articlelistarticle_sort DEFAULT 0,
CONSTRAINT FK_articlelistarticle_articlelist FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE CASCADE,
CONSTRAINT FK_articlelistarticle_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT UQ_articlelistarticle_articlelist_article UNIQUE (articlelist_id, article_id),
CONSTRAINT UQ_articlelistarticle_articlelist_sort UNIQUE (articlelist_id, sort)
);
END
`;
export const INIT_OPTIONSLISTARTICLE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'optionslistarticle')
BEGIN
CREATE TABLE optionslistarticle (
id INT IDENTITY(1,1) PRIMARY KEY,
optionslist_id INT NOT NULL,
article_id INT NOT NULL,
count INT NOT NULL CONSTRAINT DF_optionslistarticle_count DEFAULT 1,
sort INT NOT NULL CONSTRAINT DF_optionslistarticle_sort DEFAULT 0,
mandated BIT NOT NULL CONSTRAINT DF_optionslistarticle_mandated DEFAULT 0,
CONSTRAINT FK_optionslistarticle_optionslist FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE CASCADE,
CONSTRAINT FK_optionslistarticle_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT UQ_optionslistarticle_optionslist_article UNIQUE (optionslist_id, article_id),
CONSTRAINT UQ_optionslistarticle_optionslist_sort UNIQUE (optionslist_id, sort)
);
END
`;
export const INIT_CATEGORYARTICLE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'categoryarticle')
BEGIN
CREATE TABLE categoryarticle (
id INT IDENTITY(1,1) PRIMARY KEY,
category_id INT NOT NULL,
article_id INT NOT NULL,
CONSTRAINT FK_categoryarticle_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE NO ACTION,
CONSTRAINT FK_categoryarticle_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT UQ_categoryarticle_category_article UNIQUE (category_id, article_id)
);
-- Hot path: getBySku / resolve by article (UQ is category-leading)
CREATE INDEX IX_categoryarticle_article_category
ON categoryarticle (article_id, category_id);
END
`;
export const INIT_CATEGORYPIC = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'categorypic')
BEGIN
CREATE TABLE categorypic (
id INT IDENTITY(1,1) PRIMARY KEY,
category_id INT NOT NULL,
pic_id INT NOT NULL,
sort INT NOT NULL CONSTRAINT DF_categorypic_sort DEFAULT 0,
CONSTRAINT FK_categorypic_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE,
CONSTRAINT FK_categorypic_pic FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE,
CONSTRAINT UQ_categorypic_category_pic UNIQUE (category_id, pic_id),
CONSTRAINT UQ_categorypic_category_sort UNIQUE (category_id, sort)
);
END
`;
export const INIT_CATEGORYTEXT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'categorytext')
BEGIN
CREATE TABLE categorytext (
id INT IDENTITY(1,1) PRIMARY KEY,
category_id INT NOT NULL,
language_id INT NOT NULL,
name NVARCHAR(255) NOT NULL,
slug NVARCHAR(255) NOT NULL,
seo NVARCHAR(MAX) NULL,
[desc] NVARCHAR(MAX) NULL,
CONSTRAINT FK_categorytext_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE,
CONSTRAINT FK_categorytext_language FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE,
CONSTRAINT UQ_categorytext_category_language UNIQUE (category_id, language_id)
);
END
`;
export const INIT_ARTICLENAME = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articletext')
BEGIN
CREATE TABLE articletext (
id INT IDENTITY(1,1) PRIMARY KEY,
article_id INT NOT NULL,
language_id INT NOT NULL,
name NVARCHAR(255) NOT NULL,
slug NVARCHAR(255) NOT NULL,
seo NVARCHAR(MAX) NULL,
[desc] NVARCHAR(MAX) NULL,
CONSTRAINT FK_articletext_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT FK_articletext_language FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE,
CONSTRAINT UQ_articletext_article_language UNIQUE (article_id, language_id)
);
END
`;
export const INIT_PRICE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'price')
BEGIN
CREATE TABLE price (
id INT IDENTITY(1,1) PRIMARY KEY,
tax_id INT NOT NULL,
article_id INT NOT NULL,
minmultiplier DECIMAL(18,4) NOT NULL CONSTRAINT DF_price_minmultiplier DEFAULT 1,
customergroup_id INT NOT NULL,
net FLOAT NOT NULL,
CONSTRAINT FK_price_tax FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE CASCADE,
CONSTRAINT FK_price_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT FK_price_customergroup FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE
);
-- Hot path: getBySku prices by article (covers ORDER BY + SELECT cols)
CREATE INDEX IX_price_article_cg_mult
ON price (article_id, customergroup_id, minmultiplier)
INCLUDE (net, tax_id);
END
`;
export const INIT_ARTICLE_SUPPLIER = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'articlesupplier')
BEGIN
CREATE TABLE articlesupplier (
id INT IDENTITY(1,1) PRIMARY KEY,
article_id INT NOT NULL,
entity_id INT NOT NULL,
price_id INT NULL,
sku NVARCHAR(100) NULL,
stock FLOAT NULL,
pu FLOAT NOT NULL CONSTRAINT DF_articlesupplier_pu DEFAULT 1,
sort INT NOT NULL CONSTRAINT DF_articlesupplier_sort DEFAULT 0,
CONSTRAINT FK_articlesupplier_article FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
CONSTRAINT FK_articlesupplier_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
CONSTRAINT FK_articlesupplier_price FOREIGN KEY (price_id) REFERENCES price(id) ON DELETE NO ACTION,
CONSTRAINT UQ_articlesupplier_article_entity UNIQUE (article_id, entity_id),
CONSTRAINT UQ_articlesupplier_article_sort UNIQUE (article_id, sort)
);
CREATE INDEX IX_articlesupplier_article_entity
ON articlesupplier (article_id, entity_id);
END
`;
export const INIT_MANIFEST = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'manifest')
BEGIN
CREATE TABLE manifest (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
[number] NVARCHAR(100) NOT NULL,
[type] NVARCHAR(100) NOT NULL,
entity_id INT NULL,
external_number NVARCHAR(100) NULL,
[date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date 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
);
END
`;
export const INIT_MANIFESTARTICLE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'manifestarticle')
BEGIN
CREATE TABLE manifestarticle (
id INT IDENTITY(1,1) PRIMARY KEY,
manifest_id INT NOT NULL,
sku NVARCHAR(100) NULL,
name NVARCHAR(255) NULL,
pu FLOAT NOT NULL CONSTRAINT DF_manifestarticle_pu DEFAULT 1,
amount FLOAT NOT NULL,
net FLOAT NOT NULL,
comment NVARCHAR(MAX) NULL,
rebate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_rebate DEFAULT 0,
taxrate FLOAT NOT NULL CONSTRAINT DF_manifestarticle_taxrate DEFAULT 0,
CONSTRAINT FK_manifestarticle_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
);
END
`;
export const INIT_LEDGER = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'ledger')
BEGIN
CREATE TABLE ledger (
id INT IDENTITY(1,1) PRIMARY KEY,
manifestarticle_id INT NOT NULL,
transaction_type NVARCHAR(100) NOT NULL,
amount FLOAT NOT NULL,
transaction_text NVARCHAR(MAX) NOT NULL,
hash_version INT NOT NULL,
prev_hash NVARCHAR(128) NULL,
CONSTRAINT FK_ledger_manifestarticle FOREIGN KEY (manifestarticle_id) REFERENCES manifestarticle(id) ON DELETE NO ACTION
) WITH (LEDGER = ON (APPEND_ONLY = ON));
END
`;
export const INIT_NUMBERS = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'numbers')
BEGIN
CREATE TABLE numbers (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
[type] NVARCHAR(100) NOT NULL,
prefix NVARCHAR(50) NOT NULL CONSTRAINT DF_numbers_prefix DEFAULT '',
padding INT NOT NULL CONSTRAINT DF_numbers_padding DEFAULT 0,
[next] INT NOT NULL CONSTRAINT DF_numbers_next DEFAULT 1,
CONSTRAINT FK_numbers_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
CONSTRAINT UQ_numbers_company_type UNIQUE (company_id, [type])
);
END
`;
// EXEC dbo.getnextnumberwithprefixandpadding
// @company_id = 1, @type = N'articlesku', @value = @sku OUTPUT;
export const INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING = `
IF OBJECT_ID(N'dbo.getnextnumberwithprefixandpadding', N'P') IS NOT NULL
DROP PROCEDURE dbo.getnextnumberwithprefixandpadding;
EXEC('
CREATE PROCEDURE dbo.getnextnumberwithprefixandpadding
@company_id INT,
@type NVARCHAR(100),
@value NVARCHAR(100) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @seq TABLE (prefix NVARCHAR(50), padding INT, num INT);
UPDATE n
SET [next] = [next] + 1
OUTPUT INSERTED.prefix, INSERTED.padding, DELETED.[next]
INTO @seq(prefix, padding, num)
FROM numbers n WITH (UPDLOCK, ROWLOCK)
WHERE n.company_id = @company_id AND n.[type] = @type;
IF @@ROWCOUNT = 0
THROW 50000, ''Number type not found for company'', 1;
SELECT @value = prefix + CASE
WHEN padding > 0 THEN RIGHT(REPLICATE(''0'', padding) + CAST(num AS NVARCHAR(20)), padding)
ELSE CAST(num AS NVARCHAR(20))
END
FROM @seq;
END
');
`;
// EXEC dbo.peeknextnumberwithprefixandpadding
// @company_id = 1, @type = N'entity', @value = @n OUTPUT;
export const INIT_PEEK_NEXT_NUMBER_WITH_PREFIX_AND_PADDING = `
IF OBJECT_ID(N'dbo.peeknextnumberwithprefixandpadding', N'P') IS NOT NULL
DROP PROCEDURE dbo.peeknextnumberwithprefixandpadding;
EXEC('
CREATE PROCEDURE dbo.peeknextnumberwithprefixandpadding
@company_id INT,
@type NVARCHAR(100),
@value NVARCHAR(100) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @prefix NVARCHAR(50);
DECLARE @padding INT;
DECLARE @num INT;
SELECT
@prefix = n.prefix,
@padding = n.padding,
@num = n.[next]
FROM numbers n WITH (UPDLOCK, ROWLOCK)
WHERE n.company_id = @company_id AND n.[type] = @type;
IF @@ROWCOUNT = 0
THROW 50000, ''Number type not found for company'', 1;
SET @value = @prefix + CASE
WHEN @padding > 0 THEN RIGHT(REPLICATE(''0'', @padding) + CAST(@num AS NVARCHAR(20)), @padding)
ELSE CAST(@num AS NVARCHAR(20))
END;
END
');
`;
export const INIT_COMPANYUSER = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'companyuser')
BEGIN
CREATE TABLE companyuser (
id INT IDENTITY(1,1) PRIMARY KEY,
google_auth_id INT NOT NULL,
company_id INT NOT NULL,
CONSTRAINT UQ_companyuser_google_auth_company UNIQUE (google_auth_id, company_id),
CONSTRAINT FK_companyuser_google_auth FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE,
CONSTRAINT FK_companyuser_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
);
END
`;
export const INIT_SYNCPOS = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'syncpos')
BEGIN
CREATE TABLE syncpos (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
paircode NVARCHAR(50) NOT NULL,
token NVARCHAR(255) NULL,
CONSTRAINT FK_syncpos_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
CONSTRAINT UQ_syncpos_paircode UNIQUE (paircode),
CONSTRAINT UQ_syncpos_token UNIQUE (token)
);
END
`;
export const INIT_COMPANYSETTING = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'companysetting')
BEGIN
CREATE TABLE companysetting (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
[key] NVARCHAR(255) NOT NULL,
value NVARCHAR(MAX) NOT NULL,
frontend BIT NOT NULL CONSTRAINT DF_companysetting_frontend DEFAULT 0,
writeonly BIT NOT NULL CONSTRAINT DF_companysetting_writeonly DEFAULT 0,
CONSTRAINT UQ_companysetting_company_key UNIQUE (company_id, [key]),
CONSTRAINT FK_companysetting_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
);
END
`;
export const INIT_POSTERMINAL = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'posterminal')
BEGIN
CREATE TABLE posterminal (
id INT IDENTITY(1,1) PRIMARY KEY,
company_id INT NOT NULL,
name NVARCHAR(255) NOT NULL,
token NVARCHAR(255) NULL,
pairingpin NVARCHAR(50) NOT NULL,
CONSTRAINT FK_posterminal_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX UQ_posterminal_token ON posterminal(token) WHERE token IS NOT NULL;
END
`;
export const INIT_POSTERMINALCATEGORY = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'posterminalcategory')
BEGIN
CREATE TABLE posterminalcategory (
id INT IDENTITY(1,1) PRIMARY KEY,
posterminal_id INT NOT NULL,
category_id INT NOT NULL,
CONSTRAINT FK_posterminalcategory_posterminal FOREIGN KEY (posterminal_id) REFERENCES posterminal(id) ON DELETE CASCADE,
CONSTRAINT FK_posterminalcategory_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE NO ACTION,
CONSTRAINT UQ_posterminalcategory_posterminal_category UNIQUE (posterminal_id, category_id)
);
END
`;
export const INIT_FILE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'file')
BEGIN
CREATE TABLE [file] (
id INT IDENTITY(1,1) PRIMARY KEY,
hash NVARCHAR(64) NOT NULL,
mimetype NVARCHAR(255) NOT NULL,
name NVARCHAR(255) NOT NULL,
size BIGINT NOT NULL,
created DATETIME2 NOT NULL CONSTRAINT DF_file_created DEFAULT SYSUTCDATETIME(),
modified DATETIME2 NOT NULL CONSTRAINT DF_file_modified DEFAULT SYSUTCDATETIME(),
content VARBINARY(MAX) NOT NULL,
thumbavif VARBINARY(MAX) NULL,
thumbx INT NULL,
thumby INT NULL,
meta NVARCHAR(MAX) NULL
);
END
`;
export const INIT_MANIFESTATTACHMENT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'manifestattachment')
BEGIN
CREATE TABLE manifestattachment (
id INT IDENTITY(1,1) PRIMARY KEY,
manifest_id INT NOT NULL,
file_id INT NOT NULL,
sort INT NOT NULL CONSTRAINT DF_manifestattachment_sort DEFAULT 0,
CONSTRAINT FK_manifestattachment_manifest FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
CONSTRAINT FK_manifestattachment_file FOREIGN KEY (file_id) REFERENCES [file](id) ON DELETE CASCADE,
CONSTRAINT UQ_manifestattachment_manifest_sort UNIQUE (manifest_id, sort)
);
END
`;
export const INIT_MANIFESTATTACHMENT_TRIGGER = `
IF OBJECT_ID(N'dbo.TR_manifestattachment_delete_file', N'TR') IS NOT NULL
DROP TRIGGER dbo.TR_manifestattachment_delete_file;
EXEC('
CREATE TRIGGER dbo.TR_manifestattachment_delete_file
ON manifestattachment
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DELETE f
FROM [file] f
INNER JOIN deleted d ON f.id = d.file_id
WHERE NOT EXISTS (
SELECT 1 FROM manifestattachment ma WHERE ma.file_id = f.id
);
END
');
`;
/** DDL scripts in dependency order for schema init. */
export const SCHEMA_SCRIPTS = [
INIT_META,
INIT_LANGUAGE,
INIT_TAX,
INIT_CATEGORY,
INIT_PIC,
INIT_COMPANY,
INIT_GOOGLE_AUTH,
INIT_CUSTOMERGROUP,
INIT_COMPANY_POS_CUSTOMERGROUP_FK,
INIT_ENTITY,
INIT_ENTITY_COMPANY_NUMBER_UQ,
INIT_ENTITYADDRESS,
INIT_ENTITYCONTACT,
INIT_UNIT,
INIT_UNITTEXT,
INIT_OPTIONSLIST,
INIT_ARTICLELIST,
INIT_BRAND,
INIT_ARTICLE,
INIT_ARTICLEEAN,
INIT_ARTICLEPIC,
INIT_BRANDPIC,
INIT_ARTICLELISTARTICLE,
INIT_OPTIONSLISTARTICLE,
INIT_CATEGORYARTICLE,
INIT_CATEGORYPIC,
INIT_CATEGORYTEXT,
INIT_ARTICLENAME,
INIT_PRICE,
INIT_ARTICLE_SUPPLIER,
INIT_MANIFEST,
INIT_MANIFESTARTICLE,
INIT_LEDGER,
INIT_NUMBERS,
INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
INIT_PEEK_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
INIT_COMPANYUSER,
INIT_ARTICLEHISTORY,
INIT_ENTITYHISTORY,
INIT_SYNCPOS,
INIT_COMPANYSETTING,
INIT_POSTERMINAL,
INIT_POSTERMINALCATEGORY,
INIT_FILE,
INIT_MANIFESTATTACHMENT,
INIT_MANIFESTATTACHMENT_TRIGGER,
];