u
This commit is contained in:
@@ -58,7 +58,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
#### Create article in category — `POST /article`
|
#### Create article in category — `POST /article`
|
||||||
- Params: `body: { categoryId: int, name: str, track_stock?: bool }`
|
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
|
||||||
- Returns: `201 T31`
|
- Returns: `201 T31`
|
||||||
#### Export articles as ZIP — `GET /article/export.zip`
|
#### Export articles as ZIP — `GET /article/export.zip`
|
||||||
- Params: `query: categoryId?: int`
|
- Params: `query: categoryId?: int`
|
||||||
@@ -70,7 +70,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
- Returns: `200 T1`
|
- Returns: `200 T1`
|
||||||
#### Update article by SKU — `PATCH /article/{sku}`
|
#### Update article by SKU — `PATCH /article/{sku}`
|
||||||
- Params: `sku: str, body: { name?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
- Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
||||||
- Returns: `200 T1`
|
- Returns: `200 T1`
|
||||||
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
|
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
|
||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
#### Create article in category — `POST /article`
|
#### Create article in category — `POST /article`
|
||||||
- Params: `body: { categoryId: int, name: str, track_stock?: bool }`
|
- Params: `body: { categoryId: int, name: str, desc?: str, seo?: str, track_stock?: bool }`
|
||||||
- Returns: `201 T29`
|
- Returns: `201 T29`
|
||||||
#### Delete article by SKU — `DELETE /article/{sku}`
|
#### Delete article by SKU — `DELETE /article/{sku}`
|
||||||
- Params: `sku: str, query: cascade?: bool`
|
- Params: `sku: str, query: cascade?: bool`
|
||||||
@@ -62,7 +62,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
|||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
- Returns: `200 T1`
|
- Returns: `200 T1`
|
||||||
#### Update article by SKU — `PATCH /article/{sku}`
|
#### Update article by SKU — `PATCH /article/{sku}`
|
||||||
- Params: `sku: str, body: { name?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
- Params: `sku: str, body: { name?: str, desc?: str, seo?: str, unitamount?: num, unitSi?: str, track_stock?: bool }`
|
||||||
- Returns: `200 T1`
|
- Returns: `200 T1`
|
||||||
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
|
#### Get Stückliste (bundle items) for an article — `GET /article/{sku}/articlelist`
|
||||||
- Params: `sku: str`
|
- Params: `sku: str`
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ Creates an article under an authorized category (`parentId`).
|
|||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `categoryId` | yes | integer | Category ID under which to create the article |
|
| `categoryId` | yes | integer | Category ID under which to create the article |
|
||||||
| `name` | yes | string | |
|
| `name` | yes | string | |
|
||||||
|
| `desc` | no | string | Article description text/HTML |
|
||||||
|
| `seo` | no | string | SEO text |
|
||||||
| `track_stock` | no | boolean | Whether to track inventory stock (defaults to true) |
|
| `track_stock` | no | boolean | Whether to track inventory stock (defaults to true) |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ Patches article fields. At least one of `name`, `unitamount`, `unitSi` required.
|
|||||||
| Field | Required | Type | Description |
|
| Field | Required | Type | Description |
|
||||||
|-------|----------|------|-------------|
|
|-------|----------|------|-------------|
|
||||||
| `name` | no | string | |
|
| `name` | no | string | |
|
||||||
|
| `desc` | no | string | Article description text/HTML |
|
||||||
|
| `seo` | no | string | SEO text |
|
||||||
| `unitamount` | no | number | Must be finite and `> 0` |
|
| `unitamount` | no | number | Must be finite and `> 0` |
|
||||||
| `unitSi` | no | string | Existing company unit `si` |
|
| `unitSi` | no | string | Existing company unit `si` |
|
||||||
| `track_stock` | no | boolean | Whether to track inventory stock |
|
| `track_stock` | no | boolean | Whether to track inventory stock |
|
||||||
|
|||||||
@@ -167,6 +167,12 @@ export function register(app, basePath = '') {
|
|||||||
* description: Category ID under which to create the article
|
* description: Category ID under which to create the article
|
||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
|
* desc:
|
||||||
|
* type: string
|
||||||
|
* description: Article description text/HTML
|
||||||
|
* seo:
|
||||||
|
* type: string
|
||||||
|
* description: SEO text
|
||||||
* track_stock:
|
* track_stock:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* description: Whether to track inventory stock (defaults to true)
|
* description: Whether to track inventory stock (defaults to true)
|
||||||
@@ -215,7 +221,7 @@ export function register(app, basePath = '') {
|
|||||||
* $ref: '#/components/schemas/Error'
|
* $ref: '#/components/schemas/Error'
|
||||||
*/
|
*/
|
||||||
app.post(`${basePath}/article`, async (req, res) => {
|
app.post(`${basePath}/article`, async (req, res) => {
|
||||||
const rawParent = req.body?.categoryId ?? req.body?.parentId;
|
const rawParent = req.body?.categoryId;
|
||||||
if (rawParent === undefined || rawParent === null) {
|
if (rawParent === undefined || rawParent === null) {
|
||||||
return res.status(400).json({ error: 'categoryId is required' });
|
return res.status(400).json({ error: 'categoryId is required' });
|
||||||
}
|
}
|
||||||
@@ -229,7 +235,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: 'name is required' });
|
return res.status(400).json({ error: 'name is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawTrack = req.body?.track_stock ?? req.body?.trackStock;
|
const rawTrack = req.body?.track_stock;
|
||||||
if (rawTrack !== undefined && typeof rawTrack !== 'boolean' && rawTrack !== 0 && rawTrack !== 1) {
|
if (rawTrack !== undefined && typeof rawTrack !== 'boolean' && rawTrack !== 0 && rawTrack !== 1) {
|
||||||
return res.status(400).json({ error: 'Invalid track_stock' });
|
return res.status(400).json({ error: 'Invalid track_stock' });
|
||||||
}
|
}
|
||||||
@@ -237,12 +243,15 @@ export function register(app, basePath = '') {
|
|||||||
const customSku = typeof req.body?.sku === 'string' && req.body.sku.trim() ? req.body.sku.trim() : undefined;
|
const customSku = typeof req.body?.sku === 'string' && req.body.sku.trim() ? req.body.sku.trim() : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const desc = req.body?.desc;
|
||||||
const created = await db.createUnderParent({
|
const created = await db.createUnderParent({
|
||||||
googleAuthId: req.user.id,
|
googleAuthId: req.user.id,
|
||||||
parentId,
|
parentId,
|
||||||
name,
|
name,
|
||||||
track_stock,
|
track_stock,
|
||||||
sku: customSku,
|
sku: customSku,
|
||||||
|
desc: desc !== undefined ? desc : undefined,
|
||||||
|
seo: req.body?.seo !== undefined ? req.body.seo : undefined,
|
||||||
});
|
});
|
||||||
emitArticlesSignal(req);
|
emitArticlesSignal(req);
|
||||||
const article = {
|
const article = {
|
||||||
@@ -438,6 +447,12 @@ export function register(app, basePath = '') {
|
|||||||
* properties:
|
* properties:
|
||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
|
* desc:
|
||||||
|
* type: string
|
||||||
|
* description: Article description text/HTML
|
||||||
|
* seo:
|
||||||
|
* type: string
|
||||||
|
* description: SEO text
|
||||||
* unitamount:
|
* unitamount:
|
||||||
* type: number
|
* type: number
|
||||||
* description: Must be finite and `> 0`
|
* description: Must be finite and `> 0`
|
||||||
@@ -672,12 +687,23 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(body, 'track_stock')) {
|
if (Object.prototype.hasOwnProperty.call(body, 'track_stock')) {
|
||||||
patch.track_stock = body.track_stock;
|
patch.track_stock = body.track_stock;
|
||||||
} else if (Object.prototype.hasOwnProperty.call(body, 'trackStock')) {
|
}
|
||||||
patch.track_stock = body.trackStock;
|
if (Object.prototype.hasOwnProperty.call(body, 'desc')) {
|
||||||
|
patch.desc = body.desc;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(body, 'seo')) {
|
||||||
|
patch.seo = body.seo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!('name' in patch) && !('unitamount' in patch) && !('unitSi' in patch) && !('track_stock' in patch)) {
|
if (
|
||||||
return res.status(400).json({ error: 'name, unitamount, unitSi, or track_stock is required' });
|
!('name' in patch) &&
|
||||||
|
!('unitamount' in patch) &&
|
||||||
|
!('unitSi' in patch) &&
|
||||||
|
!('track_stock' in patch) &&
|
||||||
|
!('desc' in patch) &&
|
||||||
|
!('seo' in patch)
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ error: 'name, unitamount, unitSi, track_stock, desc, or seo is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1443,7 +1469,7 @@ export function register(app, basePath = '') {
|
|||||||
return res.status(400).json({ error: skuParsed.error });
|
return res.status(400).json({ error: skuParsed.error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = req.body?.track_stock ?? req.body?.trackStock;
|
const raw = req.body?.track_stock;
|
||||||
if (raw === undefined || (typeof raw !== 'boolean' && raw !== 0 && raw !== 1)) {
|
if (raw === undefined || (typeof raw !== 'boolean' && raw !== 0 && raw !== 1)) {
|
||||||
return res.status(400).json({ error: 'Invalid track_stock' });
|
return res.status(400).json({ error: 'Invalid track_stock' });
|
||||||
}
|
}
|
||||||
@@ -1783,7 +1809,7 @@ export function register(app, basePath = '') {
|
|||||||
if (typeof isParent !== 'boolean') {
|
if (typeof isParent !== 'boolean') {
|
||||||
return res.status(400).json({ error: 'isParent boolean is required' });
|
return res.status(400).json({ error: 'isParent boolean is required' });
|
||||||
}
|
}
|
||||||
const attributeNameId = req.body?.attributeNameId ?? req.body?.parentAttributeNameId;
|
const attributeNameId = req.body?.attributeNameId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await db.toggleParent(req.user.id, parsed.sku, {
|
const result = await db.toggleParent(req.user.id, parsed.sku, {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getStore } from './store.js';
|
import { getStore } from './store.js';
|
||||||
|
|
||||||
export async function createUnderParent({ googleAuthId, parentId, name, track_stock, sku }) {
|
export async function createUnderParent(args) {
|
||||||
const store = await getStore();
|
const store = await getStore();
|
||||||
return store.article.createUnderParent({ googleAuthId, parentId, name, track_stock, sku });
|
return store.article.createUnderParent(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBySku(googleAuthId, sku) {
|
export async function getBySku(googleAuthId, sku) {
|
||||||
|
|||||||
@@ -70,10 +70,17 @@ function wrapStoreWithTiming(rawStore) {
|
|||||||
storeLog.debug(`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)}`);
|
storeLog.debug(`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)}`);
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
storeLog.error(
|
const isNotFound = /not found/i.test(err?.message);
|
||||||
`${namespace}.${method} ${chalk.red('FAILED')} ` +
|
if (isNotFound) {
|
||||||
`${chalk.gray(`(${Date.now() - start}ms)`)} ${chalk.red(err.message)}`
|
storeLog.debug(
|
||||||
);
|
`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)} ${chalk.gray(err.message)}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
storeLog.error(
|
||||||
|
`${namespace}.${method} ${chalk.red('FAILED')} ` +
|
||||||
|
`${chalk.gray(`(${Date.now() - start}ms)`)} ${chalk.red(err.message)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -343,9 +343,17 @@ export async function executeCompanyImport({
|
|||||||
jtlArtIdToSku.set(jtlArtId, sku);
|
jtlArtIdToSku.set(jtlArtId, sku);
|
||||||
|
|
||||||
let name = '';
|
let name = '';
|
||||||
|
let desc = '';
|
||||||
|
let seo = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const desc = JSON.parse(a.descriptions || '[]');
|
const descriptions = JSON.parse(a.descriptions || '[]');
|
||||||
name = desc[0]?.cName || '';
|
const descItem = Array.isArray(descriptions) ? descriptions[0] : descriptions;
|
||||||
|
if (descItem) {
|
||||||
|
name = String(descItem.cName || '').trim();
|
||||||
|
desc = String(descItem.cBeschreibung || '').trim();
|
||||||
|
seo = String(descItem.cKurzBeschreibung || '').trim();
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
if (!name) name = `Artikel ${sku}`;
|
if (!name) name = `Artikel ${sku}`;
|
||||||
|
|
||||||
@@ -388,6 +396,8 @@ export async function executeCompanyImport({
|
|||||||
await store.article.updateBySku(googleAuthId, sku, {
|
await store.article.updateBySku(googleAuthId, sku, {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
track_stock: trackStock,
|
track_stock: trackStock,
|
||||||
|
desc: desc || undefined,
|
||||||
|
seo: seo || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update prices
|
// Update prices
|
||||||
@@ -432,6 +442,8 @@ export async function executeCompanyImport({
|
|||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
sku,
|
sku,
|
||||||
track_stock: trackStock,
|
track_stock: trackStock,
|
||||||
|
desc: desc || undefined,
|
||||||
|
seo: seo || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set price
|
// Set price
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { createPdfReadTool } from '../tools/pdf-read.js';
|
|||||||
import { createSearchArticlesTool } from '../tools/search-articles.js';
|
import { createSearchArticlesTool } from '../tools/search-articles.js';
|
||||||
import { createEanChecksumTool } from '../tools/ean-checksum.js';
|
import { createEanChecksumTool } from '../tools/ean-checksum.js';
|
||||||
import { createNetGrossCalcTool } from '../tools/net-gross-calc.js';
|
import { createNetGrossCalcTool } from '../tools/net-gross-calc.js';
|
||||||
|
import { createSetUserRouteTool } from '../tools/set-user-route.js';
|
||||||
import { resolveOpenRouterKey, resolveDefaultModel, resolveModelReasoning, RETIRED_MODELS, hasConfiguredOpenRouterKey } from '../tools/llm-key.js';
|
import { resolveOpenRouterKey, resolveDefaultModel, resolveModelReasoning, RETIRED_MODELS, hasConfiguredOpenRouterKey } from '../tools/llm-key.js';
|
||||||
|
|
||||||
const KEEP_ALIVE_MS = 20_000;
|
const KEEP_ALIVE_MS = 20_000;
|
||||||
@@ -132,6 +133,7 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
|
|||||||
'16. edit_image: Generate or edit images using the configured company image model (modelslugimage). Supports attachments (low/high quality), size tiers (1K/2K/4K), and aspect ratios (1:1/4:3/3:4). Writes output to VFS (always AVIF format).',
|
'16. edit_image: Generate or edit images using the configured company image model (modelslugimage). Supports attachments (low/high quality), size tiers (1K/2K/4K), and aspect ratios (1:1/4:3/3:4). Writes output to VFS (always AVIF format).',
|
||||||
'17. ean_checksum: Validate EAN/GTIN/UPC barcode check digits or calculate missing check digit for barcodes (EAN-13, EAN-8, UPC-A, GTIN-14).',
|
'17. ean_checksum: Validate EAN/GTIN/UPC barcode check digits or calculate missing check digit for barcodes (EAN-13, EAN-8, UPC-A, GTIN-14).',
|
||||||
'18. net_gross_calc: Convert between net and gross prices with tax/VAT rate (default 19%). Provides 2-decimal display values and high-precision net prices for API article creation.',
|
'18. net_gross_calc: Convert between net and gross prices with tax/VAT rate (default 19%). Provides 2-decimal display values and high-precision net prices for API article creation.',
|
||||||
|
'19. set-user-route: Navigate the user to a frontend route (e.g. when the user says "show me the article", "open quote 1001", or asks to view a page/entity).',
|
||||||
'',
|
'',
|
||||||
'VIRTUAL FILESYSTEM:',
|
'VIRTUAL FILESYSTEM:',
|
||||||
'- Persistent per-company VFS. Paths are root-relative forward-slash (e.g. "labels/sheet1.png").',
|
'- Persistent per-company VFS. Paths are root-relative forward-slash (e.g. "labels/sheet1.png").',
|
||||||
@@ -157,7 +159,37 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) {
|
|||||||
'- GROSS vs NET PRICES: User prices are almost always GROSS (incl. VAT); API requires NET. Use net_gross_calc to compute high-precision (4–6 decimals) NET prices for API requests: `net = gross / (1 + taxRate / 100)`.',
|
'- GROSS vs NET PRICES: User prices are almost always GROSS (incl. VAT); API requires NET. Use net_gross_calc to compute high-precision (4–6 decimals) NET prices for API requests: `net = gross / (1 + taxRate / 100)`.',
|
||||||
'- DISPLAYING GENERATED IMAGES: Inline markdown images supported. Embed written VFS image paths: ``. Other files (PDF, JSON, CSV) do not render inline; provide download links.',
|
'- DISPLAYING GENERATED IMAGES: Inline markdown images supported. Embed written VFS image paths: ``. Other files (PDF, JSON, CSV) do not render inline; provide download links.',
|
||||||
'- LABEL TEMPLATES (/labeltemplate): Must use Dashback JSON schema `{ "objects": [ ... ] }`. Do NOT use ZPL.',
|
'- LABEL TEMPLATES (/labeltemplate): Must use Dashback JSON schema `{ "objects": [ ... ] }`. Do NOT use ZPL.',
|
||||||
|
'- USER NAVIGATION: When the user says "show me the article", "open this order", or asks to navigate to a screen/entity, call set-user-route with the appropriate path from the Route Construction Matrix.',
|
||||||
'- Always Format output with markdown and emoji.',
|
'- Always Format output with markdown and emoji.',
|
||||||
|
'',
|
||||||
|
'USER PERSPECTIVE ROUTE CONSTRUCTION MATRIX:',
|
||||||
|
'| Destination | Path Pattern | Parameters / Construction | Example |',
|
||||||
|
'|:---|:---|:---|:---|',
|
||||||
|
'| **Article Search / Index** | `/artikel` | Query params: `?target={query}&filter={filter}&offset={n}` | `/artikel?target=kabel` |',
|
||||||
|
'| **Article Detail** | `/artikel/:sku` | Encode SKU: `/artikel/${encodeURIComponent(sku)}` | `/artikel/ART-10042` |',
|
||||||
|
'| **Warehouse / Stock** | `/artikel/lager` | Static | `/artikel/lager` |',
|
||||||
|
'| **Brands** | `/artikel/marken` | Static | `/artikel/marken` |',
|
||||||
|
'| **Label Templates** | `/artikel/vorlagen` | Static | `/artikel/vorlagen` |',
|
||||||
|
'| **Attributes & Features** | `/artikel/merkmale` | Static | `/artikel/merkmale` |',
|
||||||
|
'| **Category Tree Root** | `/kategorien` | Static | `/kategorien` |',
|
||||||
|
'| **Category Subtree** | `/kategorien/*` | Join encoded slugs: `/kategorien/${slug1}/${slug2}` | `/kategorien/elektronik/kabel` |',
|
||||||
|
'| **Delivery List** | `/lieferung` | Static | `/lieferung` |',
|
||||||
|
'| **New Delivery Draft** | `/lieferung/neu` | Static (receipt & draft line editor) | `/lieferung/neu` |',
|
||||||
|
'| **Delivery Detail** | `/lieferung/:deliveryId` | Delivery document ID: `/lieferung/${deliveryId}` | `/lieferung/42` |',
|
||||||
|
'| **Suppliers Directory** | `/lieferung/lieferanten` | Static | `/lieferung/lieferanten` |',
|
||||||
|
'| **Quote List (Angebote)** | `/auftrag` | Static | `/auftrag` |',
|
||||||
|
'| **New Quote** | `/auftrag/neu` | Static draft form | `/auftrag/neu` |',
|
||||||
|
'| **Quote Detail** | `/auftrag/:quoteId` | Quote document ID: `/auftrag/${quoteId}` | `/auftrag/1001` |',
|
||||||
|
'| **Order List (Bestellungen)**| `/auftrag/bestellung` | Static | `/auftrag/bestellung` |',
|
||||||
|
'| **New Order** | `/auftrag/bestellung/neu` | Static draft form | `/auftrag/bestellung/neu` |',
|
||||||
|
'| **Order Detail** | `/auftrag/bestellung/:orderId` | Order document ID: `/auftrag/bestellung/${orderId}` | `/auftrag/bestellung/2005` |',
|
||||||
|
'| **Customer List** | `/kunde` | Static | `/kunde` |',
|
||||||
|
'| **New Customer** | `/kunde/neu` | Static form | `/kunde/neu` |',
|
||||||
|
'| **Customer Detail** | `/kunde/:entityId` | Customer entity ID: `/kunde/${entityId}` | `/kunde/312` |',
|
||||||
|
'| **Customer Groups** | `/kunde/gruppen` | Static (pricing & customer tiers) | `/kunde/gruppen` |',
|
||||||
|
'| **Article History** | `/infos` or `/infos/articlehistory` | Static | `/infos/articlehistory` |',
|
||||||
|
'| **Global History** | `/infos/globalhistory` | Static audit log | `/infos/globalhistory` |',
|
||||||
|
'| **Statistics** | `/infos/statistics` | Static KPIs & charts | `/infos/statistics` |',
|
||||||
...(routesDoc ? [
|
...(routesDoc ? [
|
||||||
'',
|
'',
|
||||||
'DASHBACK API ROUTES:',
|
'DASHBACK API ROUTES:',
|
||||||
@@ -874,6 +906,11 @@ export class ConversationManager {
|
|||||||
createSearchArticlesTool({ googleAuthId, companyId: this.companyId }),
|
createSearchArticlesTool({ googleAuthId, companyId: this.companyId }),
|
||||||
createEanChecksumTool(),
|
createEanChecksumTool(),
|
||||||
createNetGrossCalcTool(),
|
createNetGrossCalcTool(),
|
||||||
|
createSetUserRouteTool({
|
||||||
|
onRoute: (route) => {
|
||||||
|
this.broadcast('route', { route });
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
46
src/tools/set-user-route.js
Normal file
46
src/tools/set-user-route.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { defineTool } from 'libllmorbiter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the set-user-route tool.
|
||||||
|
* Sends the user to a specific frontend route (e.g. when the user says "show me the article",
|
||||||
|
* "open quote 1001", "go to orders", etc.).
|
||||||
|
*
|
||||||
|
* @param {{ onRoute?: (route: string) => void }} [options]
|
||||||
|
*/
|
||||||
|
export function createSetUserRouteTool({ onRoute } = {}) {
|
||||||
|
return defineTool({
|
||||||
|
name: 'set-user-route',
|
||||||
|
description:
|
||||||
|
'Navigate the user to a frontend route. Use this when the user says "show me the article" or asks to see, open, or view a specific page or entity (e.g. an article detail page, orders, quotes, deliveries, or customer). Refer to the User Perspective Route Construction Matrix for valid route patterns.',
|
||||||
|
strict: false,
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['route'],
|
||||||
|
properties: {
|
||||||
|
route: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target frontend route path according to the Route Construction Matrix (e.g. "/artikel/ART-10042", "/kategorien/elektronik/kabel", "/auftrag/1001").',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invoke: async ({ route }) => {
|
||||||
|
const trimmedRoute = typeof route === 'string' ? route.trim() : '';
|
||||||
|
if (!trimmedRoute) {
|
||||||
|
throw new Error('route is required');
|
||||||
|
}
|
||||||
|
if (typeof onRoute === 'function') {
|
||||||
|
try {
|
||||||
|
onRoute(trimmedRoute);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('set-user-route onRoute callback error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
route: trimmedRoute,
|
||||||
|
message: `Navigated user to ${trimmedRoute}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
19
todo.md
19
todo.md
@@ -10,3 +10,22 @@ Mein Aufräumskript löste eine Fehlermeldung aus GET /articleIndex( windowSize
|
|||||||
~25 Artikel mit den Bezeichnungen ZZ-*(ART-000478 … ART-000503)
|
~25 Artikel mit den Bezeichnungen ZZ-*(ART-000478 … ART-000503)
|
||||||
Attributname ZZ-StressAttr(ID 14) + seine Werte, Eigenschaftsname ZZ-StressPropund zwei Kategorien mit den Namen ZZ-STRESS(IDs 76, 77)
|
Attributname ZZ-StressAttr(ID 14) + seine Werte, Eigenschaftsname ZZ-StressPropund zwei Kategorien mit den Namen ZZ-STRESS(IDs 76, 77)
|
||||||
attributename/orderDer Aufruf von Note war [1, 14]erfolgreich, daher wurde auch die Sortierposition von Farbe geändert – möglicherweise ist eine manuelle Wiederherstellung erforderlich. Geben Sie mir Bescheid, und ich führe den Teardown erneut auf 1000 Seiten durch.
|
attributename/orderDer Aufruf von Note war [1, 14]erfolgreich, daher wurde auch die Sortierposition von Farbe geändert – möglicherweise ist eine manuelle Wiederherstellung erforderlich. Geben Sie mir Bescheid, und ich führe den Teardown erneut auf 1000 Seiten durch.
|
||||||
|
|
||||||
|
|
||||||
|
# ERP Ledger Architecture: TSE Genesis Seed
|
||||||
|
|
||||||
|
## Core Idea
|
||||||
|
We use a KassenSichV **`SonstigerVorgang`** transaction as the absolute cryptographic "Genesis Block" for our ERP ledger. Instead of using artificial software zero-hashes, the ledger's initial trust state is anchored in a legally binding, physical hardware/cloud TSE signature.
|
||||||
|
|
||||||
|
## Mapping Overview
|
||||||
|
|
||||||
|
| ERP Ledger Element | TSE Implementation | Purpose |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Genesis Entry** | `processType: "SonstigerVorgang"` | Legally logs system initialization for tax auditors. |
|
||||||
|
| **Seed Metadata** | `processData: "System-Event: Ledger-Init..."` | Explains the entry purpose transparently. |
|
||||||
|
| **`prev_hash` Anchor** | Returned TSE `signature` / `tx_id` | Becomes the cryptographic baseline for Transaction #2. |
|
||||||
|
|
||||||
|
## Quick Validation Loop
|
||||||
|
```text
|
||||||
|
[TSE Signed Genesis] -> [Tx 2 (prev_hash = Genesis Signature)] -> [Tx 3] -> ...
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user