feat(article): add article import with V/K compound linking

Add article.importRows to create articles by SKU from CSV-style rows,
running in two passes so fatherSku links to rows later in the file still
resolve. Existing SKUs are skipped, duplicate EANs are dropped, prices and
taxes are ensured, brands are upserted by name, and parent categories are
resolved/created via breadcrumb path.

Also add:
- article.setFatherBySku to link/unlink a child to its father article
- category.ensureByPath to resolve or create categories by path (tx-aware)
- supporting SQL constants (GET_ID_BY_SKU, EAN_EXISTS, father-link helpers,
  GET_BY_PARENT_AND_NAME)
- regenerated API docs
This commit is contained in:
seb
2026-08-11 05:36:07 +02:00
parent fb88cb92a5
commit af416246b4
8 changed files with 655 additions and 0 deletions

View File

@@ -160,6 +160,48 @@ const history = await store.article.getHistoryBySku(42, 'SKU-1');
// => [{ id, companyuserId, date, text }]
```
## `article.importRows`
*Source: `src/store/article/importRows.js`*
Imports article rows, creating articles by their SKU and returning per-row import, skip and error outcomes. The import runs in **two passes** so a `fatherSku` that references a row appearing later in the file still resolves. A row is **skipped** when its SKU already exists in the company (upsert mode is not applied), and an EAN is skipped when already used by another row.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | The import input. |
| `input.googleAuthId` | `number` | The id of the Google auth user. |
| `input.rows` | `Array<object>` | The article rows to import. |
| `input.rows[].sku` | `string` | The article SKU. |
| `input.rows[].name` | `string` | The article name. |
| `[input.rows[].vk]` | `string` | `'V'` (father/compound) or `'K'` (variant) marker. |
| `[input.rows[].parentId]` | `number|string` | The id of the parent category. |
| `[input.rows[].parentCategory]` | `string` | The category breadcrumb path, e.g. `'Flowers | CBN'`. |
| `[input.rows[].brand]` | `string` | The brand name to upsert. |
| `[input.rows[].net]` | `number|string` | The POS net price (accepts German decimals). |
| `[input.rows[].tax]` | `number|string` | The tax rate in percent (e.g. `19`). |
| `[input.rows[].eans]` | `Array<string>|string` | The EANs to set. |
| `[input.rows[].fatherSku]` | `string` | The father article SKU for a V/K compound link. |
| `[input.rows[].deposit]` | `number|string` | The deposit (Pfand) value; currently ignored. |
**Returns**
`Promise<{imported: Array<object>, skipped: Array<object>, errors: Array<object>}>` — The import outcome.
**Example**
```js
const result = await store.article.importRows({
googleAuthId: 42,
rows: [
{ sku: '42001190-1', name: '420Strainz 1g', vk: 'K', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '11,00', tax: 19, eans: ['4262557690455'], fatherSku: '42001190' },
{ sku: '42001190', name: '420Strainz', vk: 'V', parentCategory: 'Flowers | CBN', brand: 'Seedstockers', net: '0,00', tax: 19 },
],
});
// => { imported: [...], skipped: [...], errors: [...] }
```
## `article.listExport`
*Source: `src/store/article/listExport.js`*
@@ -328,6 +370,35 @@ const article = await store.article.setEansBySku(42, 'SKU-1', ['4001234567890'])
// => { id, sku, name, ... }
```
## `article.setFatherBySku`
*Source: `src/store/article/setFatherBySku.js`*
Links an article to a father article (V/K compound) by their SKUs.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `googleAuthId` | `number` | The id of the Google auth user. |
| `sku` | `string` | The child article SKU. |
| `fatherSku` | `string|null` | The father article SKU, or `null` to unlink. |
**Returns**
`Promise<object>` — The updated child article.
**Throws**
`Error` — If the article or father is not found, or the link is invalid.
**Example**
```js
const article = await store.article.setFatherBySku(42, '42001190-1', '42001190');
// => { id, sku, name, fatherSku: '42001190', ... }
```
## `article.setPricesBySku`
*Source: `src/store/article/setPricesBySku.js`*