This commit is contained in:
seb
2026-09-30 17:35:20 +02:00
parent 82cc138ba6
commit caacbe4968
9 changed files with 366 additions and 0 deletions

View File

@@ -183,8 +183,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
| [`routes/unit/delete.md`](routes/unit/delete.md) | `DELETE /unit` | | [`routes/unit/delete.md`](routes/unit/delete.md) | `DELETE /unit` |
| [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` | | [`routes/unit/list.md`](routes/unit/list.md) | `GET /unit` |
| [`routes/unit/upsert.md`](routes/unit/upsert.md) | `PUT /unit` | | [`routes/unit/upsert.md`](routes/unit/upsert.md) | `PUT /unit` |
| [`routes/v1/category.md`](routes/v1/category.md) | `GET /v1/category` |
| [`routes/pos/client.md`](routes/pos/client.md) | `GET /v1/client` | | [`routes/pos/client.md`](routes/pos/client.md) | `GET /v1/client` |
| [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` | | [`routes/pos/init.md`](routes/pos/init.md) | `GET /v1/init` |
| [`routes/v1/product.md`](routes/v1/product.md) | `GET /v1/product` |
| [`routes/warehouse/list.md`](routes/warehouse/list.md) | `GET /warehouse` | | [`routes/warehouse/list.md`](routes/warehouse/list.md) | `GET /warehouse` |
| [`routes/warehouse/create.md`](routes/warehouse/create.md) | `POST /warehouse` | | [`routes/warehouse/create.md`](routes/warehouse/create.md) | `POST /warehouse` |
| [`routes/warehouse/order.md`](routes/warehouse/order.md) | `PUT /warehouse/order` | | [`routes/warehouse/order.md`](routes/warehouse/order.md) | `PUT /warehouse/order` |

View File

@@ -13576,6 +13576,57 @@
} }
} }
}, },
"/v1/product": {
"get": {
"summary": "JTL-POS product catalog synchronization",
"description": "Returns product catalog with prices, stock, variants, and image hashes modified after lastChangedProduct cursor.",
"tags": [
"POS Sync"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "lastChangedProduct",
"schema": {
"type": "string"
},
"description": "Unix timestamp cursor of last product sync"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Maximum number of products to return"
}
],
"responses": {
"200": {
"description": "Array of product objects for JTL-POS",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/v1/init": { "/v1/init": {
"get": { "get": {
"tags": [ "tags": [
@@ -13841,6 +13892,57 @@
} }
} }
} }
},
"/v1/category": {
"get": {
"summary": "JTL-POS category synchronization",
"description": "Returns category hierarchy modified after lastChangedCategory cursor for the authenticated POS register.",
"tags": [
"POS Sync"
],
"security": [
{
"BearerAuth": []
}
],
"parameters": [
{
"in": "query",
"name": "lastChangedCategory",
"schema": {
"type": "string"
},
"description": "Unix timestamp cursor of last category sync"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Maximum number of categories to return"
}
],
"responses": {
"200": {
"description": "Array of category objects for JTL-POS",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
}
}
} }
}, },
"components": { "components": {

View File

@@ -0,0 +1,34 @@
# `GET /v1/category`
Protected. Requires a valid session cookie (`credentials: 'include'`).
JTL-POS category synchronization
Returns category hierarchy modified after lastChangedCategory cursor for the authenticated POS register.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `lastChangedCategory` | no | string | Unix timestamp cursor of last category sync |
| `limit` | no | integer | Maximum number of categories to return |
## Response `200`
Array of category objects for JTL-POS
```json
[
{}
]
```
## Errors
| Status | Description |
|--------|-------------|
| 401 | Unauthorized |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -0,0 +1,34 @@
# `GET /v1/product`
Protected. Requires a valid session cookie (`credentials: 'include'`).
JTL-POS product catalog synchronization
Returns product catalog with prices, stock, variants, and image hashes modified after lastChangedProduct cursor.
## Query parameters
| Param | Required | Type | Description |
|-------|----------|------|-------------|
| `lastChangedProduct` | no | string | Unix timestamp cursor of last product sync |
| `limit` | no | integer | Maximum number of products to return |
## Response `200`
Array of product objects for JTL-POS
```json
[
{}
]
```
## Errors
| Status | Description |
|--------|-------------|
| 401 | Unauthorized |
---
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._

View File

@@ -38,6 +38,13 @@ function emitStockEvents(companyId, body, result) {
} }
} }
} }
if (Array.isArray(result?.lines)) {
for (const line of result.lines) {
if (line?.sku) {
emit(`article-${line.sku}`, { companyId });
}
}
}
} }
/** /**

View File

@@ -85,3 +85,13 @@ export async function getMaxOrderIdCount(params) {
return store.posterminal.getMaxOrderIdCount(params); return store.posterminal.getMaxOrderIdCount(params);
} }
export async function getCategoryList(params) {
const store = await getStore();
return store.posterminal.getCategoryList(params);
}
export async function getProductList(params) {
const store = await getStore();
return store.posterminal.getProductList(params);
}

View File

@@ -2,6 +2,8 @@ import { Router } from 'express';
import { posAuthMiddleware } from './middleware/posAuth.js'; import { posAuthMiddleware } from './middleware/posAuth.js';
import { handleClient } from './routes/client.js'; import { handleClient } from './routes/client.js';
import { handleInit } from './routes/init.js'; import { handleInit } from './routes/init.js';
import { handleCategory } from './routes/category.js';
import { handleProduct } from './routes/product.js';
const router = Router(); const router = Router();
@@ -11,5 +13,9 @@ router.get('/client', handleClient);
// POS sync initialization counts // POS sync initialization counts
router.get('/init', posAuthMiddleware, handleInit); router.get('/init', posAuthMiddleware, handleInit);
// POS master data synchronization
router.get('/category', posAuthMiddleware, handleCategory);
router.get('/product', posAuthMiddleware, handleProduct);
export default router; export default router;

67
src/v1/routes/category.js Normal file
View File

@@ -0,0 +1,67 @@
import { getCategoryList } from '../../db/posterminal.js';
function formatServerTimestamp() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
/**
* @openapi
* /v1/category:
* get:
* summary: JTL-POS category synchronization
* description: Returns category hierarchy modified after lastChangedCategory cursor for the authenticated POS register.
* tags:
* - POS Sync
* security:
* - BearerAuth: []
* parameters:
* - in: query
* name: lastChangedCategory
* schema:
* type: string
* description: Unix timestamp cursor of last category sync
* - in: query
* name: limit
* schema:
* type: integer
* default: 50
* description: Maximum number of categories to return
* responses:
* 200:
* description: Array of category objects for JTL-POS
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* 401:
* description: Unauthorized
*/
export async function handleCategory(req, res, next) {
try {
const posterminal = req.posterminal;
const cursor = Number(req.query.lastChangedCategory) || 0;
const limit = Number(req.query.limit) || 20;
const categories = await getCategoryList({
posterminalId: posterminal.id,
cursor,
limit,
});
const timestamp = formatServerTimestamp();
res.json(
categories.map((category) => ({
...category,
updated_at: timestamp,
created_at: timestamp,
}))
);
} catch (err) {
next(err);
}
}

104
src/v1/routes/product.js Normal file
View File

@@ -0,0 +1,104 @@
import { getProductList } from '../../db/posterminal.js';
const STATIC_DEFAULTS = {
sort: '0',
p_price: '0.00',
discountable: '0',
deposit: '0',
discount: '',
d_price: '0.0',
tax_rate2: '',
use_in_out_tax: '0',
barcode: null,
use_stock: '0',
q_div: '0',
quantity: '0',
unit: null,
single_bookable: '0',
annotation: '',
status: '0',
tags: '',
is_parent: '0',
parent: '0',
variants: '',
print_kitchen_receipt: '0',
deposit_name: '',
updated_at: '0001-01-01 00:00:00',
isCompositeProduct: '0',
attributes: [],
configurationGroups: '',
options: null,
hasBestBeforeDate: '0',
hasLotNumber: '0',
hasSerialNumber: '0',
PLU: '',
short_description: '',
minStock: '0',
container: [],
reservedQuantity: '0.00',
deliveryDetails: [],
isbn: '',
manufacturerName: null,
han: null,
productType: '0',
voucherData: null,
inputPrice: '0',
inputQuantity: '0',
};
/**
* @openapi
* /v1/product:
* get:
* summary: JTL-POS product catalog synchronization
* description: Returns product catalog with prices, stock, variants, and image hashes modified after lastChangedProduct cursor.
* tags:
* - POS Sync
* security:
* - BearerAuth: []
* parameters:
* - in: query
* name: lastChangedProduct
* schema:
* type: string
* description: Unix timestamp cursor of last product sync
* - in: query
* name: limit
* schema:
* type: integer
* default: 50
* description: Maximum number of products to return
* responses:
* 200:
* description: Array of product objects for JTL-POS
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* 401:
* description: Unauthorized
*/
export async function handleProduct(req, res, next) {
try {
const posterminal = req.posterminal;
const cursor = Number(req.query.lastChangedProduct) || 0;
const limit = Number(req.query.limit) || 20;
const products = await getProductList({
posterminalId: posterminal.id,
cursor,
limit,
});
res.json(
products.map((product) => ({
...STATIC_DEFAULTS,
...product,
}))
);
} catch (err) {
next(err);
}
}