Files
getrankekarte/posClient.js

225 lines
6.7 KiB
JavaScript

const crypto = require('crypto');
const AdmZip = require('adm-zip');
const xml2js = require('xml2js');
const db = require('./db');
function getConfig() {
const rows = db.prepare('SELECT key, value FROM config').all();
const config = {};
for (const r of rows) {
config[r.key] = r.value;
}
return config;
}
function generateHeaders(appId, passId) {
const ts = new Date().toISOString();
const reqId = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2);
// POS Cloud standard hash: MD5/SHA256 über appId + passId + ts
const hash = crypto.createHash('sha256').update(`${appId}${passId || ''}${ts}`).digest('hex');
return {
'WWSVC-HASH': hash,
'WWSVC-TS': ts,
'WWSVC-REQID': reqId,
'User-Agent': 'Getraenkekarte-POS-Client/1.0'
};
}
async function performSync(syncType = 'ERSTEINRICHTUNG') {
const config = getConfig();
const { pos_url, pos_pass_id, pos_app_id, pos_cash_register } = config;
if (!pos_url) {
throw new Error('POS Cloud URL nicht konfiguriert');
}
const endpoint = new URL(pos_url);
endpoint.searchParams.set('ART', syncType);
if (pos_cash_register) {
endpoint.searchParams.set('MDCDEVICEID', pos_cash_register);
}
endpoint.searchParams.set('LAGER', '1');
endpoint.searchParams.set('LAGERORTE', '1');
endpoint.searchParams.set('VK1', '1');
const headers = generateHeaders(pos_app_id, pos_pass_id);
const response = await fetch(endpoint.toString(), {
method: 'GET',
headers: headers
});
if (!response.ok) {
throw new Error(`POS Cloud HTTP Error: ${response.status} ${response.statusText}`);
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
return parseAndStoreZip(buffer);
}
async function parseAndStoreZip(buffer) {
const zip = new AdmZip(buffer);
const zipEntries = zip.getEntries();
let productsCount = 0;
let groupsCount = 0;
const insertGroup = db.prepare(`
INSERT INTO product_groups (id, name, sort_order)
VALUES (?, ?, ?)
ON CONFLICT(id) DO UPDATE SET name=excluded.name
`);
const insertProduct = db.prepare(`
INSERT INTO products (id, text1, text2, group_id, preis1, unit, active)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(id) DO UPDATE SET
text1=excluded.text1,
text2=excluded.text2,
group_id=excluded.group_id,
preis1=excluded.preis1,
unit=excluded.unit,
active=1,
updated_at=CURRENT_TIMESTAMP
`);
const deactivateProduct = db.prepare(`
UPDATE products SET active = 0 WHERE id = ?
`);
const transaction = db.transaction(() => {
for (const entry of zipEntries) {
const fileName = entry.entryName.toLowerCase();
// Gruppen
if (fileName.includes('productgroup') || fileName.includes('warengruppe')) {
const content = entry.getData().toString('utf8');
let groups = [];
if (fileName.endsWith('.json')) {
try { groups = JSON.parse(content); } catch(e) {}
}
if (Array.isArray(groups)) {
for (const g of groups) {
const id = String(g.id || g.groupId || g.nr || '');
const name = g.name || g.text || g.description || 'Kategorie ' + id;
if (id) {
insertGroup.run(id, name, g.sortOrder || 0);
groupsCount++;
}
}
}
}
// Artikel
if (fileName.includes('product') && !fileName.includes('productgroup')) {
const content = entry.getData().toString('utf8');
let items = [];
if (fileName.endsWith('.json')) {
try { items = JSON.parse(content); } catch(e) {}
}
if (Array.isArray(items)) {
for (const item of items) {
const id = String(item.id || item.articleNo || item.barcode || '');
const text1 = item.text1 || item.name || item.title || 'Unbekannt';
const text2 = item.text2 || item.description || '';
const groupId = String(item.groupId || item.warengruppe || '');
// Preis 1 ermitteln
let preis1 = 0;
if (typeof item.prices === 'object' && item.prices !== null) {
preis1 = parseFloat(item.prices.vk1 || item.prices.price1 || item.prices.standard || 0);
} else if (item.preis1 !== undefined) {
preis1 = parseFloat(item.preis1);
} else if (item.price !== undefined) {
preis1 = parseFloat(item.price);
}
const unit = item.contentUnit || item.unit || '';
if (id) {
insertProduct.run(id, text1, text2, groupId, isNaN(preis1) ? 0 : preis1, unit);
productsCount++;
}
}
}
}
// Deletions
if (fileName.includes('deletion')) {
const content = entry.getData().toString('utf8');
try {
const deletions = JSON.parse(content);
if (Array.isArray(deletions)) {
for (const del of deletions) {
const id = String(del.id || del.articleNo || del);
deactivateProduct.run(id);
}
}
} catch(e) {}
}
}
db.prepare('INSERT INTO sync_logs (status, message, items_count) VALUES (?, ?, ?)')
.run('SUCCESS', `Importiert: ${productsCount} Artikel, ${groupsCount} Gruppen`, productsCount);
});
transaction();
return { productsCount, groupsCount };
}
// Manueller/Mock Import für Testzwecke & manuelle Pflege
function importJsonData(data) {
const insertProduct = db.prepare(`
INSERT INTO products (id, text1, text2, group_id, preis1, unit, active)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON CONFLICT(id) DO UPDATE SET
text1=excluded.text1,
text2=excluded.text2,
group_id=excluded.group_id,
preis1=excluded.preis1,
unit=excluded.unit,
active=1,
updated_at=CURRENT_TIMESTAMP
`);
const insertGroup = db.prepare(`
INSERT INTO product_groups (id, name, sort_order)
VALUES (?, ?, ?)
ON CONFLICT(id) DO UPDATE SET name=excluded.name
`);
let count = 0;
const tx = db.transaction(() => {
if (data.groups && Array.isArray(data.groups)) {
for (const g of data.groups) {
insertGroup.run(String(g.id), g.name, g.sortOrder || 0);
}
}
if (data.products && Array.isArray(data.products)) {
for (const p of data.products) {
insertProduct.run(
String(p.id || Math.random().toString(36).substring(2)),
p.text1 || 'Unbekannt',
p.text2 || '',
String(p.groupId || ''),
parseFloat(p.preis1 || p.price || 0),
p.unit || ''
);
count++;
}
}
});
tx();
return count;
}
module.exports = {
performSync,
parseAndStoreZip,
importJsonData,
getConfig
};