Add articlehistory on updateBySku and getHistoryBySku
This commit is contained in:
@@ -267,6 +267,7 @@ Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
|||||||
| `listExport` | `(googleAuthId: string, companyId: number, categoryId?: number)` | `ArticleExportEntry[]` (see below) |
|
| `listExport` | `(googleAuthId: string, companyId: number, categoryId?: number)` | `ArticleExportEntry[]` (see below) |
|
||||||
| `createUnderParent` | `({ googleAuthId: string, parentId: number, name: string })` | `{ id: number, parentId: number, name: string, sku: string }` |
|
| `createUnderParent` | `({ googleAuthId: string, parentId: number, name: string })` | `{ id: number, parentId: number, name: string, sku: string }` |
|
||||||
| `getBySku` | `(googleAuthId: string, sku: string)` | `ArticleDetail` (see below) |
|
| `getBySku` | `(googleAuthId: string, sku: string)` | `ArticleDetail` (see below) |
|
||||||
|
| `getHistoryBySku` | `(googleAuthId: string, sku: string)` | `{ id: number, date: Date, text: string }[]` |
|
||||||
| `updateBySku` | `(googleAuthId: string, sku: string, { name?: string, unitamount?: number, unitSi?: string })` | `ArticleDetail` |
|
| `updateBySku` | `(googleAuthId: string, sku: string, { name?: string, unitamount?: number, unitSi?: string })` | `ArticleDetail` |
|
||||||
| `setPricesBySku` | `(googleAuthId: string, sku: string, PriceInput[])` | `ArticleDetail` |
|
| `setPricesBySku` | `(googleAuthId: string, sku: string, PriceInput[])` | `ArticleDetail` |
|
||||||
| `setEansBySku` | `(googleAuthId: string, sku: string, string[])` | `ArticleDetail` |
|
| `setEansBySku` | `(googleAuthId: string, sku: string, string[])` | `ArticleDetail` |
|
||||||
@@ -380,7 +381,9 @@ type PriceEntry = {
|
|||||||
|
|
||||||
Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Errors: `Article not found`, `Not authorized`.
|
Auth via the article's `categoryarticle` category + `resolveCategoryAccess`. Errors: `Article not found`, `Not authorized`.
|
||||||
|
|
||||||
`updateBySku` patches article fields in a transaction. At least one of `name`, `unitamount`, `unitSi` required. `name` → `articletext` (slugified `slug`); empty name → `Name required`. `unitSi` → existing `unit.si` lookup (no silent ensure); unknown → `Unit not found`. `unitamount` must be finite and `> 0` → `Invalid unitamount`. Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `Name required`, `Unit not found`, `Invalid unitamount`.
|
`updateBySku` patches article fields in a transaction. At least one of `name`, `unitamount`, `unitSi` required. `name` → `articletext` (slugified `slug`); empty name → `Name required`. `unitSi` → existing `unit.si` lookup (no silent ensure); unknown → `Unit not found`. `unitamount` must be finite and `> 0` → `Invalid unitamount`. When `name` and/or unit (`unitamount`/`unitSi`) actually change, inserts one `articlehistory` row in the same transaction (`text` lines joined by newline), e.g. `Name geändert von "Alt" auf "Neu"` / `Einheit geändert von "500g" auf "1kg"` (label = `unitamount` + `unittext.symbol`, fallback `unit.si`). Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `Name required`, `Unit not found`, `Invalid unitamount`.
|
||||||
|
|
||||||
|
`getHistoryBySku` returns `articlehistory` for the article, newest first (`date` DESC, `id` DESC). Auth like `getBySku`. Errors: `Article not found`, `Not authorized`.
|
||||||
|
|
||||||
`setPricesBySku` replaces all `price` rows for the article’s company `poscustomergroup_id` in one transaction. Each entry: `price` → `net`, `tax` → tax rate (ensured via `tax.ensure`), `minmultiplier` defaults to `1`. Empty array clears all POS prices. Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `POS customer group required`, `Invalid prices`.
|
`setPricesBySku` replaces all `price` rows for the article’s company `poscustomergroup_id` in one transaction. Each entry: `price` → `net`, `tax` → tax rate (ensured via `tax.ensure`), `minmultiplier` defaults to `1`. Empty array clears all POS prices. Returns `ArticleDetail`. Errors: `Article not found`, `Not authorized`, `POS customer group required`, `Invalid prices`.
|
||||||
|
|
||||||
@@ -461,6 +464,7 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
|||||||
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
||||||
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||||
|
| `GET /api/article/:sku/history` | `article.getHistoryBySku(googleAuthId, sku)` |
|
||||||
| `GET /api/articleIndex` | `article.listSearchIndex(googleAuthId, companyId)` |
|
| `GET /api/articleIndex` | `article.listSearchIndex(googleAuthId, companyId)` |
|
||||||
| `GET /api/article/export.zip` | `article.listExport(googleAuthId, companyId, categoryId?)` |
|
| `GET /api/article/export.zip` | `article.listExport(googleAuthId, companyId, categoryId?)` |
|
||||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ const MENU = {
|
|||||||
label: 'getBySku',
|
label: 'getBySku',
|
||||||
hint: 'googleAuthId, sku → row',
|
hint: 'googleAuthId, sku → row',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'article.getHistoryBySku',
|
||||||
|
label: 'getHistoryBySku',
|
||||||
|
hint: 'googleAuthId, sku → history[]',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'article.updateBySku',
|
value: 'article.updateBySku',
|
||||||
label: 'updateBySku',
|
label: 'updateBySku',
|
||||||
@@ -779,6 +784,18 @@ async function runMethod(client, action) {
|
|||||||
await client.article.getBySku(Number(googleAuthId), sku),
|
await client.article.getBySku(Number(googleAuthId), sku),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
case 'article.getHistoryBySku': {
|
||||||
|
const googleAuthId = await askInt('google_auth id');
|
||||||
|
if (googleAuthId == null) return;
|
||||||
|
const sku = await askText('sku', {
|
||||||
|
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||||
|
});
|
||||||
|
if (sku == null) return;
|
||||||
|
return [
|
||||||
|
'result',
|
||||||
|
await client.article.getHistoryBySku(Number(googleAuthId), sku),
|
||||||
|
];
|
||||||
|
}
|
||||||
case 'article.updateBySku': {
|
case 'article.updateBySku': {
|
||||||
const googleAuthId = await askInt('google_auth id');
|
const googleAuthId = await askInt('google_auth id');
|
||||||
if (googleAuthId == null) return;
|
if (googleAuthId == null) return;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import articleOrderImages from './store/article/orderImages.js';
|
|||||||
import articleAddImage from './store/article/addImage.js';
|
import articleAddImage from './store/article/addImage.js';
|
||||||
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
import articleCreateUnderParent from './store/article/createUnderParent.js';
|
||||||
import articleGetBySku from './store/article/getBySku.js';
|
import articleGetBySku from './store/article/getBySku.js';
|
||||||
|
import articleGetHistoryBySku from './store/article/getHistoryBySku.js';
|
||||||
import articleListSearchIndex from './store/article/listSearchIndex.js';
|
import articleListSearchIndex from './store/article/listSearchIndex.js';
|
||||||
import articleListExport from './store/article/listExport.js';
|
import articleListExport from './store/article/listExport.js';
|
||||||
import articleUpdateBySku from './store/article/updateBySku.js';
|
import articleUpdateBySku from './store/article/updateBySku.js';
|
||||||
@@ -97,6 +98,7 @@ export default class LibDashStore {
|
|||||||
this.article = {
|
this.article = {
|
||||||
createUnderParent: articleCreateUnderParent(pool),
|
createUnderParent: articleCreateUnderParent(pool),
|
||||||
getBySku: articleGetBySku(pool),
|
getBySku: articleGetBySku(pool),
|
||||||
|
getHistoryBySku: articleGetHistoryBySku(pool),
|
||||||
listSearchIndex: articleListSearchIndex(pool),
|
listSearchIndex: articleListSearchIndex(pool),
|
||||||
listExport: articleListExport(pool),
|
listExport: articleListExport(pool),
|
||||||
updateBySku: articleUpdateBySku(pool),
|
updateBySku: articleUpdateBySku(pool),
|
||||||
|
|||||||
11
src/sql/articlehistory.js
Normal file
11
src/sql/articlehistory.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export const INSERT = `
|
||||||
|
INSERT INTO articlehistory (article_id, [text])
|
||||||
|
VALUES (@articleId, @text)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LIST_BY_ARTICLE_ID = `
|
||||||
|
SELECT id, [date], [text]
|
||||||
|
FROM articlehistory
|
||||||
|
WHERE article_id = @articleId
|
||||||
|
ORDER BY [date] DESC, id DESC
|
||||||
|
`;
|
||||||
21
src/store/article/getHistoryBySku.js
Normal file
21
src/store/article/getHistoryBySku.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { LIST_BY_ARTICLE_ID } from '../../sql/articlehistory.js';
|
||||||
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
|
|
||||||
|
export default function getHistoryBySku(pool) {
|
||||||
|
const resolve = resolveArticleBySku(pool);
|
||||||
|
|
||||||
|
return async (googleAuthId, sku) => {
|
||||||
|
const { articleId } = await resolve(googleAuthId, sku);
|
||||||
|
|
||||||
|
const result = await pool
|
||||||
|
.request()
|
||||||
|
.input('articleId', articleId)
|
||||||
|
.query(LIST_BY_ARTICLE_ID);
|
||||||
|
|
||||||
|
return result.recordset.map((row) => ({
|
||||||
|
id: row.id,
|
||||||
|
date: row.date,
|
||||||
|
text: row.text,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,13 +3,23 @@ import {
|
|||||||
UPDATE_UNITAMOUNT,
|
UPDATE_UNITAMOUNT,
|
||||||
UPDATE_UNIT_AND_AMOUNT,
|
UPDATE_UNIT_AND_AMOUNT,
|
||||||
} from '../../sql/article.js';
|
} from '../../sql/article.js';
|
||||||
|
import { INSERT as INSERT_HISTORY } from '../../sql/articlehistory.js';
|
||||||
import { UPDATE_BY_ARTICLE_AND_LANGUAGE } from '../../sql/articletext.js';
|
import { UPDATE_BY_ARTICLE_AND_LANGUAGE } from '../../sql/articletext.js';
|
||||||
import { GET_BY_SI } from '../../sql/unit.js';
|
import { GET_DETAIL_BY_ARTICLE_ID } from '../../sql/articleDetail.js';
|
||||||
|
import { GET_FULL_BY_SI } from '../../sql/unit.js';
|
||||||
import request from '../request.js';
|
import request from '../request.js';
|
||||||
import queryBySku from './queryBySku.js';
|
import queryBySku from './queryBySku.js';
|
||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import resolveArticleBySku from './resolveArticleBySku.js';
|
||||||
import slugify from '../slugify.js';
|
import slugify from '../slugify.js';
|
||||||
|
|
||||||
|
function formatUnitLabel(amount, symbol) {
|
||||||
|
const sym = symbol ?? '';
|
||||||
|
if (amount == null || !Number.isFinite(Number(amount))) {
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
return `${Number(amount)}${sym}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function updateBySku(pool) {
|
export default function updateBySku(pool) {
|
||||||
const resolve = resolveArticleBySku(pool);
|
const resolve = resolveArticleBySku(pool);
|
||||||
const load = queryBySku(pool);
|
const load = queryBySku(pool);
|
||||||
@@ -33,7 +43,18 @@ export default function updateBySku(pool) {
|
|||||||
|
|
||||||
const { articleId, languageId, companyId } = await resolve(googleAuthId, sku);
|
const { articleId, languageId, companyId } = await resolve(googleAuthId, sku);
|
||||||
|
|
||||||
|
const currentResult = await pool
|
||||||
|
.request()
|
||||||
|
.input('articleId', articleId)
|
||||||
|
.input('languageId', languageId)
|
||||||
|
.query(GET_DETAIL_BY_ARTICLE_ID);
|
||||||
|
const current = currentResult.recordset[0];
|
||||||
|
if (!current) {
|
||||||
|
throw new Error('Article not found');
|
||||||
|
}
|
||||||
|
|
||||||
let unitId;
|
let unitId;
|
||||||
|
let newUnitSymbol = current.unitSymbol ?? current.unitSi ?? '';
|
||||||
if (hasUnitSi) {
|
if (hasUnitSi) {
|
||||||
if (!patch.unitSi?.trim()) {
|
if (!patch.unitSi?.trim()) {
|
||||||
throw new Error('Unit not found');
|
throw new Error('Unit not found');
|
||||||
@@ -42,11 +63,36 @@ export default function updateBySku(pool) {
|
|||||||
.request()
|
.request()
|
||||||
.input('companyId', companyId)
|
.input('companyId', companyId)
|
||||||
.input('si', patch.unitSi.trim())
|
.input('si', patch.unitSi.trim())
|
||||||
.query(GET_BY_SI);
|
.input('languageId', languageId)
|
||||||
unitId = unit.recordset[0]?.id;
|
.query(GET_FULL_BY_SI);
|
||||||
|
const unitRow = unit.recordset[0];
|
||||||
|
unitId = unitRow?.id;
|
||||||
if (!unitId) {
|
if (!unitId) {
|
||||||
throw new Error('Unit not found');
|
throw new Error('Unit not found');
|
||||||
}
|
}
|
||||||
|
newUnitSymbol = unitRow.symbol ?? unitRow.si ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const historyLines = [];
|
||||||
|
if (hasName) {
|
||||||
|
const oldName = current.name ?? '';
|
||||||
|
const newName = patch.name.trim();
|
||||||
|
if (oldName !== newName) {
|
||||||
|
historyLines.push(`Name geändert von "${oldName}" auf "${newName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasUnitSi || hasUnitamount) {
|
||||||
|
const oldLabel = formatUnitLabel(
|
||||||
|
current.unitamount,
|
||||||
|
current.unitSymbol ?? current.unitSi ?? '',
|
||||||
|
);
|
||||||
|
const newLabel = formatUnitLabel(
|
||||||
|
hasUnitamount ? patch.unitamount : current.unitamount,
|
||||||
|
newUnitSymbol,
|
||||||
|
);
|
||||||
|
if (oldLabel !== newLabel) {
|
||||||
|
historyLines.push(`Einheit geändert von "${oldLabel}" auf "${newLabel}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx = pool.transaction();
|
const tx = pool.transaction();
|
||||||
@@ -85,6 +131,13 @@ export default function updateBySku(pool) {
|
|||||||
.query(UPDATE_UNITAMOUNT);
|
.query(UPDATE_UNITAMOUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (historyLines.length) {
|
||||||
|
await request(pool, tx)
|
||||||
|
.input('articleId', articleId)
|
||||||
|
.input('text', historyLines.join('\n'))
|
||||||
|
.query(INSERT_HISTORY);
|
||||||
|
}
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user