search update
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||||
|
|
||||||
Fuzzy article search against JTL-Wawi (MSSQL). Use this to build a search box / result list.
|
Fuzzy article search. Use this to build a search box / result list with category facets.
|
||||||
|
|
||||||
## Query parameters
|
## Query parameters
|
||||||
|
|
||||||
@@ -10,14 +10,17 @@ Fuzzy article search against JTL-Wawi (MSSQL). Use this to build a search box /
|
|||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| `target` | yes | Search string. Split on spaces; each word must match (AND). Each word is matched with `LIKE %word%` against EAN, supplier article number, name, article number, or barcode. |
|
| `target` | yes | Search string. Split on spaces; each word must match (AND). Each word is matched with `LIKE %word%` against EAN, supplier article number, name, article number, or barcode. |
|
||||||
| `filter` | no | Comma-separated category IDs. Omit or leave empty to search all categories. See [Category filter](#category-filter). |
|
| `filter` | no | Comma-separated category IDs. Omit or leave empty to search all categories. See [Category filter](#category-filter). |
|
||||||
|
| `offset` | no | Pagination offset (default `0`). |
|
||||||
|
| `maxCount` | no | Page size (default `10`). |
|
||||||
|
|
||||||
## Search behaviour
|
## Search behaviour
|
||||||
|
|
||||||
- Multiple spaces are collapsed to one.
|
- Multiple spaces are collapsed to one.
|
||||||
- Example: `target=bizz allmix` → both `bizz` **and** `allmix` must match somewhere on the article.
|
- Example: `target=bizz allmix` → both `bizz` **and** `allmix` must match somewhere on the article.
|
||||||
- By default only **active** articles (`cAktiv = 'Y'`) are returned.
|
- By default only **active** articles (`cAktiv = 'Y'`) are returned.
|
||||||
- Without a category filter, at most **2000** rows are returned.
|
- Without a category filter, at most **2000** rows are returned (before pagination).
|
||||||
- Results are ordered by product name ascending.
|
- Results are ordered by product name ascending.
|
||||||
|
- `length` is the total match count (before `offset` / `maxCount`); `recordset` is the current page.
|
||||||
|
|
||||||
## Category filter
|
## Category filter
|
||||||
|
|
||||||
@@ -29,57 +32,74 @@ Fuzzy article search against JTL-Wawi (MSSQL). Use this to build a search box /
|
|||||||
|
|
||||||
Pass category IDs as a comma-separated list: `filter=16,39`.
|
Pass category IDs as a comma-separated list: `filter=16,39`.
|
||||||
|
|
||||||
|
Whenever `filter` is present, **`categories` always includes every id from `filter`**, even when `length` is `0` or a filtered id has no hits. Those entries use master-data `name` / `parent` and may have `count: 0`. Other facet categories with hits are still returned as usual.
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"katagg": {
|
"length": 42,
|
||||||
"16": 12,
|
|
||||||
"39": 8
|
|
||||||
},
|
|
||||||
"recordset": [
|
"recordset": [
|
||||||
{
|
{
|
||||||
"katList": "16,39",
|
"categories": "16,39",
|
||||||
"fVerfuegbar": 0,
|
"available": 0,
|
||||||
"kArtikel": 49,
|
"id": 49,
|
||||||
"fBestand": null,
|
"stock": null,
|
||||||
"cArtNr": "42000044",
|
"sku": "42000044",
|
||||||
"cName": "BioBizz AllMix Peat Free 20L",
|
"name": "BioBizz AllMix Peat Free 20L",
|
||||||
"fVKNEtto": 11.3445378151261,
|
"price": 13.5,
|
||||||
"kSteuerklasse": 1
|
"tax": 19
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"categories": {
|
||||||
|
"16": { "name": "Dünger", "count": 42, "parent": 0 },
|
||||||
|
"39": { "name": "Erde", "count": 18, "parent": 16 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Empty result with an active filter still returns the applied category:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"length": 0,
|
||||||
|
"recordset": [],
|
||||||
|
"categories": {
|
||||||
|
"16": { "name": "Dünger", "count": 0, "parent": 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Top-level fields
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|-------|------|-------|
|
||||||
|
| `length` | number | Total matching articles (unpaginated). |
|
||||||
|
| `recordset` | array | Current page of shaped article rows. |
|
||||||
|
| `categories` | object | Map keyed by category id (string). Facet counts for the current search (+ filters). |
|
||||||
|
|
||||||
### `recordset[]` fields
|
### `recordset[]` fields
|
||||||
|
|
||||||
| Field | Type | Notes |
|
| Field | Type | Notes |
|
||||||
|-------|------|-------|
|
|-------|------|-------|
|
||||||
| `kArtikel` | number | Internal article ID |
|
| `id` | number | Internal article ID (`kArtikel`) |
|
||||||
| `cArtNr` | string | Article number — pass to [`GET /article/get`](get.md) |
|
| `sku` | string | Article number — pass to [`GET /article/get`](get.md) |
|
||||||
| `cName` | string | Display name |
|
| `name` | string | Display name |
|
||||||
| `fBestand` | number \| null | Stock in warehouse 1 |
|
| `stock` | number \| null | Stock in warehouse 1 |
|
||||||
| `fVerfuegbar` | number \| null | Available stock |
|
| `available` | number \| null | Available stock |
|
||||||
| `fVKNEtto` | number | Net sales price |
|
| `price` | number \| null | Brutto VK, rounded to 1 decimal |
|
||||||
| `kSteuerklasse` | number | Tax class: `1` = 19%, `2` = 7%, `3` = 0% |
|
| `tax` | number \| null | Tax rate percent for the article’s Steuerklasse |
|
||||||
| `katList` | string \| null | Comma-separated category IDs on the article |
|
| `categories` | string \| null | Comma-separated category IDs on the article |
|
||||||
|
|
||||||
### `katagg`
|
### `categories` map entries
|
||||||
|
|
||||||
Map of category ID → count of matching articles in that category (including parent categories in the tree). Use for category sidebar / facet counts.
|
| Field | Type | Notes |
|
||||||
|
|-------|------|-------|
|
||||||
|
| `name` | string \| null | Display label from category master data |
|
||||||
|
| `count` | number | Hit count under the current search (+ other filters); may be `0` |
|
||||||
|
| `parent` | number \| null | Parent category id, or `0` if root |
|
||||||
|
|
||||||
### Display price (frontend)
|
Counts include parent categories rolled up from each article’s category path. Use for category sidebar / facet UI. Resolve labels via `categories[id].name` (skip ids without a name).
|
||||||
|
|
||||||
Brutto VK is not returned; compute client-side using [`GET /misc/getSteuer`](../misc/getSteuer.md):
|
|
||||||
|
|
||||||
```js
|
|
||||||
function getBrutto(netto, kSteuerklasse, steuerklassen) {
|
|
||||||
const row = steuerklassen.find((s) => s.kSteuerklasse === kSteuerklasse);
|
|
||||||
if (!row) return null;
|
|
||||||
return netto * (1 + row.fSteuersatz / 100);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Errors
|
## Errors
|
||||||
|
|
||||||
@@ -93,8 +113,8 @@ function getBrutto(netto, kSteuerklasse, steuerklassen) {
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
'http://localhost:4208/api/article/search?target=bizz%20allmix',
|
'http://localhost:4208/api/article/search?target=bizz%20allmix&filter=16&maxCount=10',
|
||||||
{ credentials: 'include' }
|
{ credentials: 'include' }
|
||||||
);
|
);
|
||||||
const { katagg, recordset } = await res.json();
|
const { categories, length, recordset } = await res.json();
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -77,6 +77,22 @@ function shapeCategories(counts, byId) {
|
|||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ensure every applied filter id appears in categories (count 0 if no hits). */
|
||||||
|
function ensureFilterCategories(categories, filter, byId) {
|
||||||
|
for (const id of filter) {
|
||||||
|
if (id === '' || id === null || categories[id]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const meta = byId[id] ?? {};
|
||||||
|
categories[id] = {
|
||||||
|
count: 0,
|
||||||
|
name: meta.name ?? null,
|
||||||
|
parent: meta.parent ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
function shapeArticle(row, steuersaetze) {
|
function shapeArticle(row, steuersaetze) {
|
||||||
const taxRow = steuersaetze.find((item) => item.kSteuerklasse === row.kSteuerklasse);
|
const taxRow = steuersaetze.find((item) => item.kSteuerklasse === row.kSteuerklasse);
|
||||||
const brutto = bruttoFromNetto(row.fVKNEtto, row.kSteuerklasse, steuersaetze);
|
const brutto = bruttoFromNetto(row.fVKNEtto, row.kSteuerklasse, steuersaetze);
|
||||||
@@ -184,8 +200,12 @@ export async function searchArticles(target, filter = [''], offset = 0, maxCount
|
|||||||
loadCategoryCache(pool),
|
loadCategoryCache(pool),
|
||||||
loadSteuersatz(),
|
loadSteuersatz(),
|
||||||
]);
|
]);
|
||||||
const categories = shapeCategories(
|
const categories = ensureFilterCategories(
|
||||||
buildKatAgg(result.recordset, categoryData.parentMap),
|
shapeCategories(
|
||||||
|
buildKatAgg(result.recordset, categoryData.parentMap),
|
||||||
|
categoryData.byId
|
||||||
|
),
|
||||||
|
filter,
|
||||||
categoryData.byId
|
categoryData.byId
|
||||||
);
|
);
|
||||||
const length = result.recordset.length;
|
const length = result.recordset.length;
|
||||||
|
|||||||
Reference in New Issue
Block a user