Add company-scoped unit list/upsert/delete and article unit patches.

Units are isolated per company; article detail exposes full unit shape, and unused units can be deleted safely via FK NO ACTION.
This commit is contained in:
seb
2026-08-01 06:12:24 +02:00
parent 54700a145e
commit 12e83a7ffb
16 changed files with 519 additions and 44 deletions

View File

@@ -93,6 +93,26 @@ const MENU = {
{ value: 'tax.ensure', label: 'ensure', hint: 'rate → id' },
],
},
unit: {
label: 'unit',
items: [
{
value: 'unit.list',
label: 'list',
hint: 'googleAuthId, companyId → rows',
},
{
value: 'unit.upsert',
label: 'upsert',
hint: 'googleAuthId, companyId, JSON unit → row',
},
{
value: 'unit.delete',
label: 'delete',
hint: 'googleAuthId, companyId, si',
},
],
},
category: {
label: 'category',
items: [
@@ -118,7 +138,7 @@ const MENU = {
{
value: 'article.updateBySku',
label: 'updateBySku',
hint: 'googleAuthId, sku, name → row',
hint: 'googleAuthId, sku, name/unitamount/unitSi → row',
},
{
value: 'article.setPricesBySku',
@@ -346,6 +366,58 @@ async function runMethod(client, action) {
if (rate == null) return;
return ['id', await client.tax.ensure(rate)];
}
case 'unit.list': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
return [
'result[]',
await client.unit.list(Number(googleAuthId), Number(companyId)),
];
}
case 'unit.upsert': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const unitJson = await askText('unit JSON', {
placeholder:
'{"si":"ml","basesi":"l","basesifactor":0.001,"symbol":"ml","name":"Milliliter"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
const parsed = JSON.parse(v);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return 'Must be a JSON object';
}
} catch {
return 'Invalid JSON';
}
},
});
if (unitJson == null) return;
return [
'result',
await client.unit.upsert(
Number(googleAuthId),
Number(companyId),
JSON.parse(unitJson),
),
];
}
case 'unit.delete': {
const googleAuthId = await askInt('google_auth id');
if (googleAuthId == null) return;
const companyId = await askInt('company id');
if (companyId == null) return;
const si = await askText('si', {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (si == null) return;
await client.unit.delete(Number(googleAuthId), Number(companyId), si);
return ['ok', true];
}
case 'lang.create': {
const iso639 = await askText('ISO 639', {
placeholder: 'de',
@@ -452,13 +524,28 @@ async function runMethod(client, action) {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (sku == null) return;
const name = await askText('name', {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
const patchJson = await askText('patch JSON', {
placeholder: '{"name":"Tea","unitamount":0.24,"unitSi":"l"}',
validate: (v) => {
if (!v?.trim()) return 'Required';
try {
const parsed = JSON.parse(v);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return 'Must be a JSON object';
}
} catch {
return 'Invalid JSON';
}
},
});
if (name == null) return;
if (patchJson == null) return;
return [
'result',
await client.article.updateBySku(Number(googleAuthId), sku, { name }),
await client.article.updateBySku(
Number(googleAuthId),
sku,
JSON.parse(patchJson),
),
];
}
case 'article.setPricesBySku': {