u
This commit is contained in:
2
API.md
2
API.md
@@ -202,12 +202,14 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
|||||||
- `findByPairingpin(pairingpin: string, tx?: object): Promise<object|null>`
|
- `findByPairingpin(pairingpin: string, tx?: object): Promise<object|null>`
|
||||||
- `findByToken(token: string, tx?: object): Promise<object|null>`
|
- `findByToken(token: string, tx?: object): Promise<object|null>`
|
||||||
- `getCategoryCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
- `getCategoryCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||||
|
- `getCategoryList(input: {posterminalId: number, cursor?: number, limit?: number}, tx?: object): Promise<Array<object>>`
|
||||||
- `getCompositeProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
- `getCompositeProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||||
- `getCustomerCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
- `getCustomerCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||||
- `getCustomerGroupCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
- `getCustomerGroupCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||||
- `getDeletedEntityCount(input?: {posterminalId?: number, cursor?: number}): Promise<number>`
|
- `getDeletedEntityCount(input?: {posterminalId?: number, cursor?: number}): Promise<number>`
|
||||||
- `getMaxOrderIdCount(input: {posterminalId: number}, tx?: object): Promise<number>`
|
- `getMaxOrderIdCount(input: {posterminalId: number}, tx?: object): Promise<number>`
|
||||||
- `getProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
- `getProductCount(input: {posterminalId: number, cursor?: number}, tx?: object): Promise<number>`
|
||||||
|
- `getProductList(input: {posterminalId: number, cursor?: number, limit?: number}, tx?: object): Promise<Array<object>>`
|
||||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, token: string, pairingpin: string}>>`
|
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, name: string, token: string, pairingpin: string}>>`
|
||||||
- `pair(id: number, input: {token: string, clientserial?: string, pairedAt?: string}, tx?: object): Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>`
|
- `pair(id: number, input: {token: string, clientserial?: string, pairedAt?: string}, tx?: object): Promise<{id: number, token: string, clientserial: string|null, pairedAt: string|null}>`
|
||||||
- `setOff(googleAuthId: number, posterminalId: number, categoryId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
|
- `setOff(googleAuthId: number, posterminalId: number, categoryId: number, tx?: object): Promise<{id: number, categoryIds: Array<number>}>`
|
||||||
|
|||||||
@@ -83,6 +83,26 @@ Counts categories assigned to the POS terminal updated after the given unix time
|
|||||||
|
|
||||||
`Promise<number>` — The count of changed records.
|
`Promise<number>` — The count of changed records.
|
||||||
|
|
||||||
|
## `posterminal.getCategoryList`
|
||||||
|
|
||||||
|
*Source: `src/store/posterminal/getCategoryList.js`*
|
||||||
|
|
||||||
|
Lists categories for a POS terminal modified after the cursor.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `input` | `object` | |
|
||||||
|
| `input.posterminalId` | `number` | The POS terminal id. |
|
||||||
|
| `[input.cursor=0]` | `number` | Unix timestamp cursor. |
|
||||||
|
| `[input.limit=20]` | `number` | Max items to return. |
|
||||||
|
| `[tx]` | `object` | Optional transaction. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`Promise<Array<object>>`
|
||||||
|
|
||||||
## `posterminal.getCompositeProductCount`
|
## `posterminal.getCompositeProductCount`
|
||||||
|
|
||||||
*Source: `src/store/posterminal/getCompositeProductCount.js`*
|
*Source: `src/store/posterminal/getCompositeProductCount.js`*
|
||||||
@@ -195,6 +215,26 @@ Counts products assigned to the POS terminal updated after the given unix timest
|
|||||||
|
|
||||||
`Promise<number>` — The count of changed records.
|
`Promise<number>` — The count of changed records.
|
||||||
|
|
||||||
|
## `posterminal.getProductList`
|
||||||
|
|
||||||
|
*Source: `src/store/posterminal/getProductList.js`*
|
||||||
|
|
||||||
|
Lists products for a POS terminal modified after the cursor.
|
||||||
|
|
||||||
|
**Parameters**
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `input` | `object` | |
|
||||||
|
| `input.posterminalId` | `number` | The POS terminal id. |
|
||||||
|
| `[input.cursor=0]` | `number` | Unix timestamp cursor. |
|
||||||
|
| `[input.limit=20]` | `number` | Max items to return. |
|
||||||
|
| `[tx]` | `object` | Optional transaction. |
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
`Promise<Array<object>>`
|
||||||
|
|
||||||
## `posterminal.list`
|
## `posterminal.list`
|
||||||
|
|
||||||
*Source: `src/store/posterminal/list.js`*
|
*Source: `src/store/posterminal/list.js`*
|
||||||
|
|||||||
146
scripts/test-stueckliste-stock.js
Normal file
146
scripts/test-stueckliste-stock.js
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import LibDashStore from '../src/index.js';
|
||||||
|
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
console.log('--- Testing Stückliste Stock Reporting and Adjustment ---');
|
||||||
|
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), 'test-stueckliste-'));
|
||||||
|
const dbFile = join(tempDir, 'test.db');
|
||||||
|
const storageDir = join(tempDir, 'storage');
|
||||||
|
|
||||||
|
const provider = new BetterSqlite3Provider({
|
||||||
|
filename: dbFile,
|
||||||
|
storageDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.initialize();
|
||||||
|
const store = await new LibDashStore(provider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const googleAuthId = await store.google_auth.add('stueckliste-stock@example.com');
|
||||||
|
const company = await store.company.create({
|
||||||
|
name: 'Stückliste Stock Corp',
|
||||||
|
iso639: 'de',
|
||||||
|
taxrate: 19,
|
||||||
|
reducedtaxrate: 7,
|
||||||
|
googleAuthId,
|
||||||
|
});
|
||||||
|
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
|
||||||
|
|
||||||
|
// Create 2 warehouses
|
||||||
|
const wh1 = await store.warehouse.create(googleAuthId, { name: 'Hauptlager' });
|
||||||
|
const wh2 = await store.warehouse.create(googleAuthId, { name: 'Filiale' });
|
||||||
|
|
||||||
|
// Create 2 component articles
|
||||||
|
const comp1 = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Component Article 1',
|
||||||
|
});
|
||||||
|
const comp2 = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Component Article 2',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a bundle article
|
||||||
|
const bundle = await store.article.createUnderParent({
|
||||||
|
googleAuthId,
|
||||||
|
parentId: company.rootCategoryId,
|
||||||
|
name: 'Stückliste Bundle',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure Stückliste: 1x comp1 + 2x comp2
|
||||||
|
await store.articlelist.setItemsByArticleSku(googleAuthId, bundle.sku, [
|
||||||
|
{ sku: comp1.sku, count: 1, sort: 0 },
|
||||||
|
{ sku: comp2.sku, count: 2, sort: 1 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Initial check: all have 0 stock
|
||||||
|
const initialBundle = await store.article.getBySku(googleAuthId, bundle.sku);
|
||||||
|
assert.equal(initialBundle.article.warehouses.find((w) => w.id === wh1.id).stock, 0);
|
||||||
|
assert.equal(initialBundle.article.warehouses.find((w) => w.id === wh2.id).stock, 0);
|
||||||
|
|
||||||
|
// Book 10x comp1 and 10x comp2 into Hauptlager
|
||||||
|
await store.stock.adjust(googleAuthId, {
|
||||||
|
lines: [
|
||||||
|
{ sku: comp1.sku, warehouseId: wh1.id, amount: 10, text: 'Initial comp1' },
|
||||||
|
{ sku: comp2.sku, warehouseId: wh1.id, amount: 10, text: 'Initial comp2' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check bundle stock in Hauptlager: min(10/1, 10/2) = 5
|
||||||
|
const afterCompStock = await store.article.getBySku(googleAuthId, bundle.sku);
|
||||||
|
const wh1BundleStock = afterCompStock.article.warehouses.find((w) => w.id === wh1.id).stock;
|
||||||
|
const wh2BundleStock = afterCompStock.article.warehouses.find((w) => w.id === wh2.id).stock;
|
||||||
|
assert.equal(wh1BundleStock, 5, 'Bundle stock in Hauptlager should be 5 (min(10/1, 10/2))');
|
||||||
|
assert.equal(wh2BundleStock, 0, 'Bundle stock in Filiale should be 0');
|
||||||
|
|
||||||
|
// Book in 3x Stückliste bundle into Filiale
|
||||||
|
const adjustResult = await store.stock.adjust(googleAuthId, {
|
||||||
|
sku: bundle.sku,
|
||||||
|
warehouseId: wh2.id,
|
||||||
|
amount: 3,
|
||||||
|
text: 'Production bundle book-in',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify adjust result lines
|
||||||
|
assert.equal(adjustResult.lines.length, 2, 'Should expand into 2 component lines');
|
||||||
|
const comp1Line = adjustResult.lines.find((l) => l.sku === comp1.sku);
|
||||||
|
const comp2Line = adjustResult.lines.find((l) => l.sku === comp2.sku);
|
||||||
|
assert.equal(comp1Line.amount, 3, 'Component 1 should get 3 * 1 = 3');
|
||||||
|
assert.equal(comp2Line.amount, 6, 'Component 2 should get 3 * 2 = 6');
|
||||||
|
|
||||||
|
// Verify bundle article itself has NO entries in the ledger
|
||||||
|
const bundleLedger = await store.ledger.listBySku(googleAuthId, bundle.sku);
|
||||||
|
assert.equal(bundleLedger.length, 0, 'Bundle article must have NO ledger entries');
|
||||||
|
|
||||||
|
// Verify component articles have their ledger entries
|
||||||
|
const comp1Ledger = await store.ledger.listBySku(googleAuthId, comp1.sku, { warehouse_id: wh2.id });
|
||||||
|
const comp2Ledger = await store.ledger.listBySku(googleAuthId, comp2.sku, { warehouse_id: wh2.id });
|
||||||
|
assert.equal(comp1Ledger.length, 1);
|
||||||
|
assert.equal(comp1Ledger[0].amount, 3);
|
||||||
|
assert.equal(comp2Ledger.length, 1);
|
||||||
|
assert.equal(comp2Ledger[0].amount, 6);
|
||||||
|
|
||||||
|
// Verify bundle stock report in Filiale is now 3 (min(3/1, 6/2) = 3)
|
||||||
|
const afterBundleBookIn = await store.article.getBySku(googleAuthId, bundle.sku);
|
||||||
|
const wh2AfterBookIn = afterBundleBookIn.article.warehouses.find((w) => w.id === wh2.id).stock;
|
||||||
|
assert.equal(wh2AfterBookIn, 3, 'Bundle stock in Filiale should be 3');
|
||||||
|
|
||||||
|
// Test booking out 1x bundle from Filiale
|
||||||
|
await store.stock.adjust(googleAuthId, {
|
||||||
|
sku: bundle.sku,
|
||||||
|
warehouseId: wh2.id,
|
||||||
|
amount: -1,
|
||||||
|
text: 'Sale bundle book-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const afterRemoval = await store.article.getBySku(googleAuthId, bundle.sku);
|
||||||
|
const wh2AfterRemoval = afterRemoval.article.warehouses.find((w) => w.id === wh2.id).stock;
|
||||||
|
assert.equal(wh2AfterRemoval, 2, 'Bundle stock in Filiale should be 2 after -1 removal');
|
||||||
|
|
||||||
|
// Verify components stock after removal in Filiale: comp1 = 2, comp2 = 4
|
||||||
|
const comp1After = await store.article.getBySku(googleAuthId, comp1.sku);
|
||||||
|
const comp2After = await store.article.getBySku(googleAuthId, comp2.sku);
|
||||||
|
assert.equal(comp1After.article.warehouses.find((w) => w.id === wh2.id).stock, 2);
|
||||||
|
assert.equal(comp2After.article.warehouses.find((w) => w.id === wh2.id).stock, 4);
|
||||||
|
|
||||||
|
console.log('✓ All Stückliste stock reporting and adjustment tests passed successfully!');
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch((err) => {
|
||||||
|
console.error('Test failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -111,6 +111,8 @@ import posterminalGetCustomerGroupCount from './store/posterminal/getCustomerGro
|
|||||||
import posterminalGetCompositeProductCount from './store/posterminal/getCompositeProductCount.js';
|
import posterminalGetCompositeProductCount from './store/posterminal/getCompositeProductCount.js';
|
||||||
import posterminalGetDeletedEntityCount from './store/posterminal/getDeletedEntityCount.js';
|
import posterminalGetDeletedEntityCount from './store/posterminal/getDeletedEntityCount.js';
|
||||||
import posterminalGetMaxOrderIdCount from './store/posterminal/getMaxOrderIdCount.js';
|
import posterminalGetMaxOrderIdCount from './store/posterminal/getMaxOrderIdCount.js';
|
||||||
|
import posterminalGetCategoryList from './store/posterminal/getCategoryList.js';
|
||||||
|
import posterminalGetProductList from './store/posterminal/getProductList.js';
|
||||||
import langCreate from './store/lang/create.js';
|
import langCreate from './store/lang/create.js';
|
||||||
import langDelete from './store/lang/delete.js';
|
import langDelete from './store/lang/delete.js';
|
||||||
import langList from './store/lang/list.js';
|
import langList from './store/lang/list.js';
|
||||||
@@ -384,6 +386,8 @@ export default class LibDashStore {
|
|||||||
getCompositeProductCount: posterminalGetCompositeProductCount(provider),
|
getCompositeProductCount: posterminalGetCompositeProductCount(provider),
|
||||||
getDeletedEntityCount: posterminalGetDeletedEntityCount(provider),
|
getDeletedEntityCount: posterminalGetDeletedEntityCount(provider),
|
||||||
getMaxOrderIdCount: posterminalGetMaxOrderIdCount(provider),
|
getMaxOrderIdCount: posterminalGetMaxOrderIdCount(provider),
|
||||||
|
getCategoryList: posterminalGetCategoryList(provider),
|
||||||
|
getProductList: posterminalGetProductList(provider),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.google_auth = {
|
this.google_auth = {
|
||||||
|
|||||||
@@ -574,7 +574,33 @@ SELECT
|
|||||||
w.id,
|
w.id,
|
||||||
w.name,
|
w.name,
|
||||||
w.sort,
|
w.sort,
|
||||||
COALESCE(stock_calc.stock, 0) AS stock
|
CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN COALESCE((
|
||||||
|
SELECT CASE
|
||||||
|
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||||
|
ELSE FLOOR(MIN(COALESCE(comp_stock.stock, 0) / ala.count))
|
||||||
|
END
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
INNER JOIN article ca ON (
|
||||||
|
ma.sku = ca.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
|
)
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id = w.id
|
||||||
|
GROUP BY ca.id
|
||||||
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
), 0)
|
||||||
|
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||||
|
ELSE COALESCE(stock_calc.stock, 0)
|
||||||
|
END AS stock
|
||||||
FROM warehouse w
|
FROM warehouse w
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
||||||
|
|||||||
@@ -195,3 +195,182 @@ FROM manifest m
|
|||||||
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = m.company_id
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = m.company_id
|
||||||
WHERE m.[type] = 'order'
|
WHERE m.[type] = 'order'
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_CATEGORIES = `
|
||||||
|
SELECT TOP (@limit)
|
||||||
|
c.id,
|
||||||
|
c.parent_id AS pid,
|
||||||
|
c.sort,
|
||||||
|
COALESCE(ct.name, '') AS name,
|
||||||
|
co.root_category_id AS rootCategoryId,
|
||||||
|
(
|
||||||
|
SELECT TOP 1 p.hash
|
||||||
|
FROM categorypic cp
|
||||||
|
INNER JOIN pic p ON p.id = cp.pic_id
|
||||||
|
WHERE cp.category_id = c.id
|
||||||
|
ORDER BY cp.sort ASC, cp.id ASC
|
||||||
|
) AS imgHash,
|
||||||
|
DATEDIFF_BIG(second, '1970-01-01', c.updated_at) AS lastChanged
|
||||||
|
FROM category c
|
||||||
|
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
|
||||||
|
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
|
||||||
|
INNER JOIN company co ON co.id = pt.company_id
|
||||||
|
LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id
|
||||||
|
WHERE pc.posterminal_id = @posterminalId
|
||||||
|
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||||
|
AND DATEDIFF_BIG(second, '1970-01-01', c.updated_at) > @cursor
|
||||||
|
ORDER BY DATEDIFF_BIG(second, '1970-01-01', c.updated_at) ASC, c.id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_PRODUCTS = `
|
||||||
|
SELECT TOP (@limit)
|
||||||
|
a.id,
|
||||||
|
a.sku,
|
||||||
|
COALESCE(at.name, a.sku, '') AS name,
|
||||||
|
COALESCE(pr.net, 0) AS netPrice,
|
||||||
|
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||||
|
a.created_at AS createdAt,
|
||||||
|
DATEDIFF_BIG(second, '1970-01-01', a.updated_at) AS lastChanged,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT TOP 1 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
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SELECT TOP 1 p2.hash
|
||||||
|
FROM articlepic ap2
|
||||||
|
INNER JOIN pic p2 ON p2.id = ap2.pic_id
|
||||||
|
WHERE a.fatherarticle_id IS NOT NULL AND ap2.article_id = a.fatherarticle_id
|
||||||
|
ORDER BY ap2.sort ASC, ap2.id ASC
|
||||||
|
)
|
||||||
|
) AS imgHash,
|
||||||
|
(
|
||||||
|
SELECT TOP 1 ae.ean
|
||||||
|
FROM articleean ae
|
||||||
|
WHERE ae.article_id = a.id
|
||||||
|
ORDER BY ae.id ASC
|
||||||
|
) AS barcode,
|
||||||
|
a.is_father AS isParent,
|
||||||
|
a.fatherarticle_id AS parentArticleId,
|
||||||
|
(
|
||||||
|
SELECT TOP 1 CONCAT(fan.name, '|', av.value, '|', fan.sort, '|', a.fathersort)
|
||||||
|
FROM article fa
|
||||||
|
INNER JOIN attributename fan ON fan.id = fa.fatherattributename_id
|
||||||
|
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
||||||
|
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
|
||||||
|
WHERE a.fatherarticle_id IS NOT NULL AND fa.id = a.fatherarticle_id
|
||||||
|
) AS variantName,
|
||||||
|
CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN '1'
|
||||||
|
ELSE '0'
|
||||||
|
END AS isCompositeProduct,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM categoryarticle ca2
|
||||||
|
INNER JOIN article a2 ON a2.id = ca2.article_id
|
||||||
|
LEFT JOIN articletext at2 ON at2.article_id = a2.id AND at2.language_id = co.default_language_id
|
||||||
|
WHERE ca2.category_id = (
|
||||||
|
SELECT MIN(ca1.category_id)
|
||||||
|
FROM categoryarticle ca1
|
||||||
|
WHERE ca1.article_id = a.id
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
COALESCE(at2.name, a2.sku, '') < COALESCE(at.name, a.sku, '')
|
||||||
|
OR (COALESCE(at2.name, a2.sku, '') = COALESCE(at.name, a.sku, '') AND a2.id < a.id)
|
||||||
|
)
|
||||||
|
) AS sort,
|
||||||
|
a.track_stock AS trackStock,
|
||||||
|
CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN COALESCE((
|
||||||
|
SELECT CASE
|
||||||
|
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||||
|
ELSE FLOOR(MIN(COALESCE(comp_stock.stock, 0) / ala.count))
|
||||||
|
END
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
INNER JOIN article ca ON (
|
||||||
|
ma.sku = ca.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
|
)
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
GROUP BY ca.id
|
||||||
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
), 0)
|
||||||
|
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||||
|
ELSE COALESCE((
|
||||||
|
SELECT SUM(l.amount)
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
AND (
|
||||||
|
ma.sku = a.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||||
|
)
|
||||||
|
), 0)
|
||||||
|
END AS fBestand,
|
||||||
|
co.id AS companyId,
|
||||||
|
co.root_category_id AS rootCategoryId,
|
||||||
|
co.poscustomergroup_id AS posCustomerGroupId
|
||||||
|
FROM article a
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||||
|
INNER JOIN company co ON co.id = a.company_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id
|
||||||
|
LEFT JOIN price pr ON pr.article_id = a.id AND pr.customergroup_id = co.poscustomergroup_id AND pr.minmultiplier = 1
|
||||||
|
LEFT JOIN tax ptax ON ptax.id = pr.tax_id
|
||||||
|
LEFT JOIN tax ctax ON ctax.id = co.taxrate
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM categoryarticle ca
|
||||||
|
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||||
|
WHERE ca.article_id = a.id
|
||||||
|
AND pc.posterminal_id = @posterminalId
|
||||||
|
)
|
||||||
|
AND DATEDIFF_BIG(second, '1970-01-01', a.updated_at) > @cursor
|
||||||
|
ORDER BY DATEDIFF_BIG(second, '1970-01-01', a.updated_at) ASC, a.id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_PRODUCT_CATEGORIES = `
|
||||||
|
SELECT
|
||||||
|
ca.article_id AS articleId,
|
||||||
|
ca.category_id AS categoryId
|
||||||
|
FROM categoryarticle ca
|
||||||
|
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||||
|
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
|
||||||
|
INNER JOIN company co ON co.id = pt.company_id
|
||||||
|
WHERE pc.posterminal_id = @posterminalId
|
||||||
|
AND (co.root_category_id IS NULL OR ca.category_id <> co.root_category_id)
|
||||||
|
ORDER BY ca.article_id ASC, ca.category_id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_CUSTOMER_GROUP_IDS = `
|
||||||
|
SELECT cg.id
|
||||||
|
FROM customergroup cg
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = cg.company_id
|
||||||
|
WHERE cg.name <> 'Lieferanten'
|
||||||
|
ORDER BY cg.id ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_PRODUCT_PRICES = `
|
||||||
|
SELECT
|
||||||
|
pr.article_id AS articleId,
|
||||||
|
pr.customergroup_id AS customerGroupId,
|
||||||
|
MIN(pr.net) AS netPrice
|
||||||
|
FROM price pr
|
||||||
|
INNER JOIN article a ON a.id = pr.article_id
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||||
|
WHERE pr.minmultiplier = 1
|
||||||
|
GROUP BY pr.article_id, pr.customergroup_id
|
||||||
|
`;
|
||||||
|
|||||||
@@ -215,7 +215,34 @@ SELECT auth.*,
|
|||||||
WHERE s.article_id = a.id ORDER BY s.sort, s.id
|
WHERE s.article_id = a.id ORDER BY s.sort, s.id
|
||||||
))),
|
))),
|
||||||
'warehousesJson', json((SELECT COALESCE(json_group_array(json_object(
|
'warehousesJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||||
'id', w.id, 'name', w.name, 'sort', w.sort, 'stock', COALESCE(stock_calc.stock, 0)
|
'id', w.id, 'name', w.name, 'sort', w.sort,
|
||||||
|
'stock', CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN COALESCE((
|
||||||
|
SELECT CASE
|
||||||
|
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||||
|
ELSE CAST(MIN(COALESCE(comp_stock.stock, 0) / ala.count) AS INTEGER)
|
||||||
|
END
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
INNER JOIN article ca ON (
|
||||||
|
ma.sku = ca.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
|
)
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
AND l.warehouse_id = w.id
|
||||||
|
GROUP BY ca.id
|
||||||
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
), 0)
|
||||||
|
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||||
|
ELSE COALESCE(stock_calc.stock, 0)
|
||||||
|
END
|
||||||
)), '[]') FROM warehouse w
|
)), '[]') FROM warehouse w
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
SELECT l.warehouse_id, SUM(l.amount) AS stock
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const {
|
|||||||
FIND_BY_PAIRINGPIN, PAIR,
|
FIND_BY_PAIRINGPIN, PAIR,
|
||||||
FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS,
|
FIND_BY_TOKEN, COUNT_PRODUCTS, COUNT_CATEGORIES, COUNT_CUSTOMERS,
|
||||||
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
|
COUNT_CUSTOMER_GROUPS, COUNT_COMPOSITE_PRODUCTS, MAX_ORDER_ID,
|
||||||
|
LIST_POS_PRODUCT_CATEGORIES, LIST_POS_CUSTOMER_GROUP_IDS, LIST_POS_PRODUCT_PRICES,
|
||||||
} = queries;
|
} = queries;
|
||||||
|
|
||||||
export const SET_ON = `
|
export const SET_ON = `
|
||||||
@@ -25,4 +26,157 @@ WITH RECURSIVE cats AS (
|
|||||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
||||||
SELECT @posterminalId, id FROM cats WHERE 1
|
SELECT @posterminalId, id FROM cats WHERE 1
|
||||||
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_CATEGORIES = `
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.parent_id AS pid,
|
||||||
|
c.sort,
|
||||||
|
COALESCE(ct.name, '') AS name,
|
||||||
|
co.root_category_id AS rootCategoryId,
|
||||||
|
(
|
||||||
|
SELECT p.hash
|
||||||
|
FROM categorypic cp
|
||||||
|
INNER JOIN pic p ON p.id = cp.pic_id
|
||||||
|
WHERE cp.category_id = c.id
|
||||||
|
ORDER BY cp.sort ASC, cp.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
) AS imgHash,
|
||||||
|
CAST(strftime('%s', c.updated_at) AS INTEGER) AS lastChanged
|
||||||
|
FROM category c
|
||||||
|
INNER JOIN posterminalcategory pc ON pc.category_id = c.id
|
||||||
|
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
|
||||||
|
INNER JOIN company co ON co.id = pt.company_id
|
||||||
|
LEFT JOIN categorytext ct ON ct.category_id = c.id AND ct.language_id = co.default_language_id
|
||||||
|
WHERE pc.posterminal_id = @posterminalId
|
||||||
|
AND (co.root_category_id IS NULL OR c.id <> co.root_category_id)
|
||||||
|
AND CAST(strftime('%s', c.updated_at) AS INTEGER) > @cursor
|
||||||
|
ORDER BY lastChanged ASC, c.id ASC
|
||||||
|
LIMIT @limit
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_POS_PRODUCTS = `
|
||||||
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.sku,
|
||||||
|
COALESCE(at.name, a.sku, '') AS name,
|
||||||
|
COALESCE(pr.net, 0) AS netPrice,
|
||||||
|
COALESCE(ptax.rate, ctax.rate, 0) AS taxRate,
|
||||||
|
a.created_at AS createdAt,
|
||||||
|
CAST(strftime('%s', a.updated_at) AS INTEGER) AS lastChanged,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
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
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SELECT p2.hash
|
||||||
|
FROM articlepic ap2
|
||||||
|
INNER JOIN pic p2 ON p2.id = ap2.pic_id
|
||||||
|
WHERE a.fatherarticle_id IS NOT NULL AND ap2.article_id = a.fatherarticle_id
|
||||||
|
ORDER BY ap2.sort ASC, ap2.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
) AS imgHash,
|
||||||
|
(
|
||||||
|
SELECT ae.ean
|
||||||
|
FROM articleean ae
|
||||||
|
WHERE ae.article_id = a.id
|
||||||
|
ORDER BY ae.id ASC
|
||||||
|
LIMIT 1
|
||||||
|
) AS barcode,
|
||||||
|
a.is_father AS isParent,
|
||||||
|
a.fatherarticle_id AS parentArticleId,
|
||||||
|
(
|
||||||
|
SELECT fan.name || '|' || av.value || '|' || fan.sort || '|' || a.fathersort
|
||||||
|
FROM article fa
|
||||||
|
INNER JOIN attributename fan ON fan.id = fa.fatherattributename_id
|
||||||
|
INNER JOIN articleattributevalue aav ON aav.article_id = a.id
|
||||||
|
INNER JOIN attributevalue av ON av.id = aav.attributevalue_id AND av.attribute_id = fan.id
|
||||||
|
WHERE a.fatherarticle_id IS NOT NULL AND fa.id = a.fatherarticle_id
|
||||||
|
LIMIT 1
|
||||||
|
) AS variantName,
|
||||||
|
CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN '1'
|
||||||
|
ELSE '0'
|
||||||
|
END AS isCompositeProduct,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM categoryarticle ca2
|
||||||
|
INNER JOIN article a2 ON a2.id = ca2.article_id
|
||||||
|
LEFT JOIN articletext at2 ON at2.article_id = a2.id AND at2.language_id = co.default_language_id
|
||||||
|
WHERE ca2.category_id = (
|
||||||
|
SELECT MIN(ca1.category_id)
|
||||||
|
FROM categoryarticle ca1
|
||||||
|
WHERE ca1.article_id = a.id
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
COALESCE(at2.name, a2.sku, '') < COALESCE(at.name, a.sku, '')
|
||||||
|
OR (COALESCE(at2.name, a2.sku, '') = COALESCE(at.name, a.sku, '') AND a2.id < a.id)
|
||||||
|
)
|
||||||
|
) AS sort,
|
||||||
|
a.track_stock AS trackStock,
|
||||||
|
CASE
|
||||||
|
WHEN a.articlelist_id IS NOT NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM articlelistarticle ala WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) THEN COALESCE((
|
||||||
|
SELECT CASE
|
||||||
|
WHEN MIN(COALESCE(comp_stock.stock, 0) / ala.count) < 0 THEN 0
|
||||||
|
ELSE CAST(MIN(COALESCE(comp_stock.stock, 0) / ala.count) AS INTEGER)
|
||||||
|
END
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT ca.id AS comp_id, SUM(l.amount) AS stock
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
INNER JOIN article ca ON (
|
||||||
|
ma.sku = ca.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = ca.id)
|
||||||
|
)
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
GROUP BY ca.id
|
||||||
|
) comp_stock ON comp_stock.comp_id = ala.article_id
|
||||||
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
), 0)
|
||||||
|
WHEN a.articlelist_id IS NOT NULL THEN 0
|
||||||
|
ELSE COALESCE((
|
||||||
|
SELECT SUM(l.amount)
|
||||||
|
FROM ledger l
|
||||||
|
INNER JOIN manifestarticle ma ON ma.id = l.manifestarticle_id
|
||||||
|
INNER JOIN manifest m ON m.id = ma.manifest_id
|
||||||
|
WHERE m.company_id = a.company_id
|
||||||
|
AND (
|
||||||
|
ma.sku = a.sku
|
||||||
|
OR ma.sku IN (SELECT asup.sku FROM articlesupplier asup WHERE asup.article_id = a.id)
|
||||||
|
)
|
||||||
|
), 0)
|
||||||
|
END AS fBestand,
|
||||||
|
co.id AS companyId,
|
||||||
|
co.root_category_id AS rootCategoryId,
|
||||||
|
co.poscustomergroup_id AS posCustomerGroupId
|
||||||
|
FROM article a
|
||||||
|
INNER JOIN posterminal pt ON pt.id = @posterminalId AND pt.company_id = a.company_id
|
||||||
|
INNER JOIN company co ON co.id = a.company_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = co.default_language_id
|
||||||
|
LEFT JOIN price pr ON pr.article_id = a.id AND pr.customergroup_id = co.poscustomergroup_id AND pr.minmultiplier = 1
|
||||||
|
LEFT JOIN tax ptax ON ptax.id = pr.tax_id
|
||||||
|
LEFT JOIN tax ctax ON ctax.id = co.taxrate
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM categoryarticle ca
|
||||||
|
INNER JOIN posterminalcategory pc ON pc.category_id = ca.category_id
|
||||||
|
WHERE ca.article_id = a.id
|
||||||
|
AND pc.posterminal_id = @posterminalId
|
||||||
|
)
|
||||||
|
AND CAST(strftime('%s', a.updated_at) AS INTEGER) > @cursor
|
||||||
|
ORDER BY lastChanged ASC, a.id ASC
|
||||||
|
LIMIT @limit
|
||||||
|
`;
|
||||||
|
|||||||
43
src/store/posterminal/getCategoryList.js
Normal file
43
src/store/posterminal/getCategoryList.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { LIST_POS_CATEGORIES as MSSQL_SQL } from '../../providers/mssql/queries/posterminal.js';
|
||||||
|
import { LIST_POS_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 getCategoryList(provider) {
|
||||||
|
const sql = provider?.dialect === 'sqlite3' ? SQLITE_SQL : MSSQL_SQL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists categories for a POS terminal modified after the cursor.
|
||||||
|
*
|
||||||
|
* @param {object} input
|
||||||
|
* @param {number} input.posterminalId - The POS terminal id.
|
||||||
|
* @param {number} [input.cursor=0] - Unix timestamp cursor.
|
||||||
|
* @param {number} [input.limit=20] - Max items to return.
|
||||||
|
* @param {object} [tx] - Optional transaction.
|
||||||
|
* @returns {Promise<Array<object>>}
|
||||||
|
*/
|
||||||
|
return async ({ posterminalId, cursor = 0, limit = 20 } = {}, tx) => {
|
||||||
|
const safeLimit = Number.isInteger(limit) && limit > 0 ? limit : 20;
|
||||||
|
const result = await makeRunner(provider, tx).query(sql, {
|
||||||
|
posterminalId: Number(posterminalId),
|
||||||
|
cursor: normalizeCursor(cursor),
|
||||||
|
limit: safeLimit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.rows.map((row) => ({
|
||||||
|
_id: String(row.id),
|
||||||
|
imghash: row.imgHash ?? null,
|
||||||
|
imgsrc: row.imgHash ?? null,
|
||||||
|
name: row.name ?? '',
|
||||||
|
pid: !row.pid || row.pid === row.rootCategoryId ? '0' : String(row.pid),
|
||||||
|
discounts: [],
|
||||||
|
sort: String(row.sort ?? 0),
|
||||||
|
lastChanged: String(row.lastChanged),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
129
src/store/posterminal/getProductList.js
Normal file
129
src/store/posterminal/getProductList.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
LIST_POS_PRODUCTS as MSSQL_PRODUCTS,
|
||||||
|
LIST_POS_PRODUCT_CATEGORIES as MSSQL_CATEGORIES,
|
||||||
|
LIST_POS_CUSTOMER_GROUP_IDS as MSSQL_CG_IDS,
|
||||||
|
LIST_POS_PRODUCT_PRICES as MSSQL_PRICES,
|
||||||
|
} from '../../providers/mssql/queries/posterminal.js';
|
||||||
|
import {
|
||||||
|
LIST_POS_PRODUCTS as SQLITE_PRODUCTS,
|
||||||
|
LIST_POS_PRODUCT_CATEGORIES as SQLITE_CATEGORIES,
|
||||||
|
LIST_POS_CUSTOMER_GROUP_IDS as SQLITE_CG_IDS,
|
||||||
|
LIST_POS_PRODUCT_PRICES as SQLITE_PRICES,
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateTime(value) {
|
||||||
|
if (!value) return '0001-01-01 00:00:00';
|
||||||
|
const date = value instanceof Date ? value : new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return '0001-01-01 00:00:00';
|
||||||
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function grossPrice(netPrice, taxRate) {
|
||||||
|
return (Number(netPrice || 0) * (1 + Number(taxRate || 0) / 100)).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function getProductList(provider) {
|
||||||
|
const isSqlite = provider?.dialect === 'sqlite3';
|
||||||
|
const productsSql = isSqlite ? SQLITE_PRODUCTS : MSSQL_PRODUCTS;
|
||||||
|
const categoriesSql = isSqlite ? SQLITE_CATEGORIES : MSSQL_CATEGORIES;
|
||||||
|
const cgIdsSql = isSqlite ? SQLITE_CG_IDS : MSSQL_CG_IDS;
|
||||||
|
const pricesSql = isSqlite ? SQLITE_PRICES : MSSQL_PRICES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists products for a POS terminal modified after the cursor.
|
||||||
|
*
|
||||||
|
* @param {object} input
|
||||||
|
* @param {number} input.posterminalId - The POS terminal id.
|
||||||
|
* @param {number} [input.cursor=0] - Unix timestamp cursor.
|
||||||
|
* @param {number} [input.limit=20] - Max items to return.
|
||||||
|
* @param {object} [tx] - Optional transaction.
|
||||||
|
* @returns {Promise<Array<object>>}
|
||||||
|
*/
|
||||||
|
return async ({ posterminalId, cursor = 0, limit = 20 } = {}, tx) => {
|
||||||
|
const safeLimit = Number.isInteger(limit) && limit > 0 ? limit : 20;
|
||||||
|
const runner = makeRunner(provider, tx);
|
||||||
|
const pid = Number(posterminalId);
|
||||||
|
|
||||||
|
const [prodRes, catRes, cgRes, priceRes] = await Promise.all([
|
||||||
|
runner.query(productsSql, {
|
||||||
|
posterminalId: pid,
|
||||||
|
cursor: normalizeCursor(cursor),
|
||||||
|
limit: safeLimit,
|
||||||
|
}),
|
||||||
|
runner.query(categoriesSql, { posterminalId: pid }),
|
||||||
|
runner.query(cgIdsSql, { posterminalId: pid }),
|
||||||
|
runner.query(pricesSql, { posterminalId: pid }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const categoriesByArticle = new Map();
|
||||||
|
for (const row of catRes.rows) {
|
||||||
|
if (!categoriesByArticle.has(row.articleId)) {
|
||||||
|
categoriesByArticle.set(row.articleId, []);
|
||||||
|
}
|
||||||
|
categoriesByArticle.get(row.articleId).push(String(row.categoryId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const overridesByArticle = new Map();
|
||||||
|
for (const row of priceRes.rows) {
|
||||||
|
if (!overridesByArticle.has(row.articleId)) {
|
||||||
|
overridesByArticle.set(row.articleId, new Map());
|
||||||
|
}
|
||||||
|
overridesByArticle.get(row.articleId).set(row.customerGroupId, row.netPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerGroupIds = cgRes.rows.map((row) => row.id);
|
||||||
|
|
||||||
|
return prodRes.rows.map((product) => {
|
||||||
|
const overrides = overridesByArticle.get(product.id);
|
||||||
|
const basePrice = grossPrice(product.netPrice, product.taxRate);
|
||||||
|
|
||||||
|
const prices = customerGroupIds.map((customerGroupId) => {
|
||||||
|
const overrideNetPrice = overrides?.get(customerGroupId);
|
||||||
|
const price =
|
||||||
|
overrideNetPrice !== undefined
|
||||||
|
? grossPrice(overrideNetPrice, product.taxRate)
|
||||||
|
: basePrice;
|
||||||
|
return {
|
||||||
|
customerGroupId: String(customerGroupId),
|
||||||
|
customerId: '0',
|
||||||
|
price,
|
||||||
|
quantity: '0',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const categoryIds = categoriesByArticle.get(product.id) ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_id: String(product.id),
|
||||||
|
imghash: product.imgHash ?? null,
|
||||||
|
imgsrc: product.imgHash ?? null,
|
||||||
|
sku: product.sku ?? '',
|
||||||
|
barcode: product.barcode ?? null,
|
||||||
|
name: product.name ?? '',
|
||||||
|
tax_rate: String(Math.round(Number(product.taxRate || 0))),
|
||||||
|
price: basePrice,
|
||||||
|
created_at: formatDateTime(product.createdAt),
|
||||||
|
lastChanged: String(product.lastChanged),
|
||||||
|
sort: String(product.sort ?? 0),
|
||||||
|
categories_id: categoryIds[0] ?? '0',
|
||||||
|
categories: categoryIds.map((categoryId) => ({ categoryId })),
|
||||||
|
prices,
|
||||||
|
is_parent: product.isParent ? '1' : '0',
|
||||||
|
parent: product.parentArticleId > 0 ? String(product.parentArticleId) : '0',
|
||||||
|
variants: product.variantName ?? '',
|
||||||
|
isCompositeProduct: product.isCompositeProduct,
|
||||||
|
attributes: [],
|
||||||
|
use_stock: product.trackStock ? '1' : '0',
|
||||||
|
quantity: String(product.fBestand ?? 0),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -97,19 +97,61 @@ export default function adjust(provider) {
|
|||||||
const resolvedArticles = [];
|
const resolvedArticles = [];
|
||||||
for (const item of normalizedLines) {
|
for (const item of normalizedLines) {
|
||||||
const resolved = await resolveArticle(googleAuthId, item.sku, activeTx);
|
const resolved = await resolveArticle(googleAuthId, item.sku, activeTx);
|
||||||
const artRes = await runner.query('SELECT is_father FROM article WHERE id = @articleId', {
|
const artRes = await runner.query('SELECT is_father, articlelist_id FROM article WHERE id = @articleId', {
|
||||||
articleId: resolved.articleId,
|
articleId: resolved.articleId,
|
||||||
});
|
});
|
||||||
if (artRes.rows[0]?.is_father) {
|
if (artRes.rows[0]?.is_father) {
|
||||||
throw new Error(`A father article cannot have stock (${item.sku})`);
|
throw new Error(`A father article cannot have stock (${item.sku})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameRes = await runner.query(GET_ARTICLE_NAME_SQL, {
|
const articlelistId = artRes.rows[0]?.articlelist_id;
|
||||||
articleId: resolved.articleId,
|
if (articlelistId != null) {
|
||||||
languageId: resolved.languageId,
|
const compRes = await runner.query(`
|
||||||
});
|
SELECT
|
||||||
const articleName = nameRes.rows[0]?.name ?? item.sku;
|
ala.article_id AS articleId,
|
||||||
resolvedArticles.push({ ...item, ...resolved, articleName });
|
ala.count,
|
||||||
|
a.sku,
|
||||||
|
at.name AS articleName
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
INNER JOIN article a ON a.id = ala.article_id
|
||||||
|
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||||
|
WHERE ala.articlelist_id = @articlelistId
|
||||||
|
ORDER BY ala.sort ASC, ala.id ASC
|
||||||
|
`, {
|
||||||
|
articlelistId,
|
||||||
|
languageId: resolved.languageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compRes.rows.length === 0) {
|
||||||
|
throw new Error(`Stückliste has no items (${item.sku})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const comp of compRes.rows) {
|
||||||
|
const compCount = Number(comp.count || 1);
|
||||||
|
const compAmount = item.amount * compCount;
|
||||||
|
const compText = item.text
|
||||||
|
? `${item.text} (${item.sku})`
|
||||||
|
: (compAmount > 0
|
||||||
|
? `Manual stock addition (+${compAmount}) [${item.sku}]`
|
||||||
|
: `Manual stock removal (${compAmount}) [${item.sku}]`);
|
||||||
|
|
||||||
|
resolvedArticles.push({
|
||||||
|
sku: comp.sku,
|
||||||
|
warehouseId: item.warehouseId,
|
||||||
|
amount: compAmount,
|
||||||
|
text: compText,
|
||||||
|
articleId: comp.articleId,
|
||||||
|
articleName: comp.articleName || comp.sku,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const nameRes = await runner.query(GET_ARTICLE_NAME_SQL, {
|
||||||
|
articleId: resolved.articleId,
|
||||||
|
languageId: resolved.languageId,
|
||||||
|
});
|
||||||
|
const articleName = nameRes.rows[0]?.name ?? item.sku;
|
||||||
|
resolvedArticles.push({ ...item, ...resolved, articleName });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate unique adjustment number using the dedicated adjustment number scheme
|
// Allocate unique adjustment number using the dedicated adjustment number scheme
|
||||||
|
|||||||
Reference in New Issue
Block a user