This commit is contained in:
seb
2026-09-30 09:48:01 +02:00
parent 9885c489f0
commit 280a45a11f
28 changed files with 539 additions and 16 deletions

View File

@@ -82,6 +82,11 @@ export default function setPricesBySku(provider) {
throw new Error('POS customer group required');
}
const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId });
if (artCheck.rows[0]?.is_father) {
throw new Error('A father article cannot have prices');
}
const taxIds = new Map();
for (const entry of normalized) {
if (!taxIds.has(entry.tax)) {

View File

@@ -53,6 +53,26 @@ export default function toggleFather(provider) {
throw new Error('An article with options cannot become a father');
}
const stockRes = await runner.query(
'SELECT COALESCE(SUM(l.amount), 0) AS totalStock FROM ledger l WHERE l.article_id = @articleId',
{ articleId },
);
const totalStock = Number(stockRes.rows[0]?.totalStock ?? 0);
if (totalStock !== 0) {
throw new Error('An article with existing stock cannot become a father article');
}
const supRes = await runner.query(
'SELECT COUNT(*) AS count FROM articlesupplier WHERE article_id = @articleId',
{ articleId },
);
if ((supRes.rows[0]?.count ?? 0) > 0) {
throw new Error('An article with assigned suppliers cannot become a father article');
}
await runner.query('DELETE FROM price WHERE article_id = @articleId', { articleId });
await runner.query('UPDATE article SET track_stock = 0 WHERE id = @articleId', { articleId });
// If article is a child, verify attribute is not used by father or any father up the chain
let currentFatherId = currentArt?.fatherarticle_id;
const visited = new Set();

View File

@@ -39,9 +39,6 @@ export default function setItemsByArticleSku(provider) {
if (article.is_father) {
throw new Error('A father article cannot be a Stückliste');
}
if (article.optionslist_id != null) {
throw new Error('An article with options cannot be a Stückliste');
}
// If items is empty: clear Stückliste
if (items.length === 0) {

View File

@@ -91,6 +91,11 @@ export default function create(provider) {
runner,
);
const artCheck = await runner.query('SELECT is_father FROM article WHERE id = @articleId', { articleId });
if (artCheck.rows[0]?.is_father) {
throw new Error('A father article cannot have suppliers');
}
await assertSupplier(runner, companyId, entityId);
const existingCheck = await runner.query(queries.CHECK_EXISTS, {

View File

@@ -39,9 +39,6 @@ export default function setOptionsByArticleSku(provider) {
if (article.is_father) {
throw new Error('A father article cannot have options');
}
if (article.articlelist_id != null) {
throw new Error('A Stückliste article cannot have options');
}
// If options is empty: clear optionslist
if (options.length === 0) {

View File

@@ -0,0 +1,23 @@
import { FIND_BY_TOKEN as MSSQL_FIND } from '../../providers/mssql/queries/posterminal.js';
import { FIND_BY_TOKEN as SQLITE_FIND } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
export default function findByToken(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_FIND : MSSQL_FIND;
/**
* Finds a posterminal by its auth token.
*
* @param {string} token - The auth token to search for.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<object|null>} The posterminal row, or null if not found.
*/
return async (token, tx) => {
const trimmed = String(token ?? '').trim();
if (!trimmed) return null;
const result = await makeRunner(provider, tx).query(sql, {
token: trimmed,
});
return result.rows[0] || null;
};
}

View File

@@ -0,0 +1,30 @@
import { COUNT_CATEGORIES as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { COUNT_CATEGORIES as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
function normalizeCursor(cursor) {
const n = Number(cursor) || 0;
if (n <= 0) return 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
}
export default function getCategoryCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Counts categories assigned to the POS terminal updated after the given unix timestamp cursor.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records.
*/
return async ({ posterminalId, cursor = 0 } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
cursor: normalizeCursor(cursor),
});
return Number(result.rows[0]?.count ?? 0);
};
}

View File

@@ -0,0 +1,30 @@
import { COUNT_COMPOSITE_PRODUCTS as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { COUNT_COMPOSITE_PRODUCTS as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
function normalizeCursor(cursor) {
const n = Number(cursor) || 0;
if (n <= 0) return 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
}
export default function getCompositeProductCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Counts composite products (Stücklisten) assigned to the POS terminal updated after the given unix timestamp cursor.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records.
*/
return async ({ posterminalId, cursor = 0 } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
cursor: normalizeCursor(cursor),
});
return Number(result.rows[0]?.count ?? 0);
};
}

View File

@@ -0,0 +1,30 @@
import { COUNT_CUSTOMERS as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { COUNT_CUSTOMERS as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
function normalizeCursor(cursor) {
const n = Number(cursor) || 0;
if (n <= 0) return 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
}
export default function getCustomerCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Counts customers of the POS terminal company updated after the given unix timestamp cursor.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records.
*/
return async ({ posterminalId, cursor = 0 } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
cursor: normalizeCursor(cursor),
});
return Number(result.rows[0]?.count ?? 0);
};
}

View File

@@ -0,0 +1,30 @@
import { COUNT_CUSTOMER_GROUPS as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { COUNT_CUSTOMER_GROUPS as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
function normalizeCursor(cursor) {
const n = Number(cursor) || 0;
if (n <= 0) return 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
}
export default function getCustomerGroupCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Counts customer groups of the POS terminal company updated after the given unix timestamp cursor.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records.
*/
return async ({ posterminalId, cursor = 0 } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
cursor: normalizeCursor(cursor),
});
return Number(result.rows[0]?.count ?? 0);
};
}

View File

@@ -0,0 +1,11 @@
export default function getDeletedEntityCount() {
/**
* Counts deleted entities after the given cursor (currently always 0).
*
* @param {object} [input]
* @param {number} [input.posterminalId] - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor.
* @returns {Promise<number>} Always 0.
*/
return async () => 0;
}

View File

@@ -0,0 +1,22 @@
import { MAX_ORDER_ID as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { MAX_ORDER_ID as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
export default function getMaxOrderIdCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Returns the maximum order manifest id for the POS terminal company.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} Maximum order id or 0.
*/
return async ({ posterminalId } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
});
return Number(result.rows[0]?.maxOrderId ?? 0);
};
}

View File

@@ -0,0 +1,30 @@
import { COUNT_PRODUCTS as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
import { COUNT_PRODUCTS as SQLITE_SQL } from '../../providers/sqlite3/queries/posterminal.js';
import makeRunner from '../runner.js';
function normalizeCursor(cursor) {
const n = Number(cursor) || 0;
if (n <= 0) return 0;
return n >= 1e11 ? Math.floor(n / 1000) : Math.floor(n);
}
export default function getProductCount(provider) {
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
/**
* Counts products assigned to the POS terminal updated after the given unix timestamp cursor.
*
* @param {object} input
* @param {number} input.posterminalId - The POS terminal id.
* @param {number} [input.cursor=0] - Unix timestamp cursor of last update.
* @param {object} [tx] - Optional transaction to run within.
* @returns {Promise<number>} The count of changed records.
*/
return async ({ posterminalId, cursor = 0 } = {}, tx) => {
const result = await makeRunner(provider, tx).query(sql, {
posterminalId: Number(posterminalId),
cursor: normalizeCursor(cursor),
});
return Number(result.rows[0]?.count ?? 0);
};
}

View File

@@ -97,6 +97,13 @@ export default function adjust(provider) {
const resolvedArticles = [];
for (const item of normalizedLines) {
const resolved = await resolveArticle(googleAuthId, item.sku, activeTx);
const artRes = await runner.query('SELECT is_father FROM article WHERE id = @articleId', {
articleId: resolved.articleId,
});
if (artRes.rows[0]?.is_father) {
throw new Error(`A father article cannot have stock (${item.sku})`);
}
const nameRes = await runner.query(GET_ARTICLE_NAME_SQL, {
articleId: resolved.articleId,
languageId: resolved.languageId,