This commit is contained in:
seb
2026-06-24 09:50:08 +02:00
parent 185b68fe52
commit 9b11a8a24c
18 changed files with 716 additions and 34 deletions

View File

@@ -0,0 +1,20 @@
import { getCategories } from '../../../services/misc/getCategories.js';
/**
* @param {import('express').Application} app
* @param {string} [basePath='']
*/
export function register(app, basePath = '') {
/**
* @route GET /misc/getCategories
* @returns {Object} 200 - `{ categories, tree }`
*/
app.get(`${basePath}/misc/getCategories`, async (_req, res) => {
try {
const result = await getCategories();
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
}

View File

@@ -0,0 +1,19 @@
import { getSteuer } from '../../../services/misc/getSteuer.js';
/**
* @param {import('express').Application} app
* @param {string} [basePath='']
*/
export function register(app, basePath = '') {
/**
* @route GET /misc/getSteuer
* @returns {Object} 200 - `{ steuerklassen }`
*/
app.get(`${basePath}/misc/getSteuer`, async (_req, res) => {
try {
res.status(200).json(await getSteuer());
} catch (error) {
res.status(500).json({ error: error.message });
}
});
}

View File

@@ -2,3 +2,27 @@ export const LOAD_KAT_MAP = `
SELECT kKategorie, kOberKategorie
FROM dbo.tkategorie
`;
export const LOAD_CATEGORIES = `
SELECT
kKategorie,
kOberKategorie,
nSort,
(
SELECT cName
FROM dbo.tKategorieSprache
WHERE kSprache = 1
AND kPlattform = 1
AND kShop = 0
AND dbo.tkategorie.kKategorie = dbo.tKategorieSprache.kKategorie
) AS cName
FROM dbo.tkategorie
ORDER BY nSort, cName
`;
export const CATEGORY_ARTICLE_COUNTS = `
SELECT ka.kKategorie, COUNT(*) AS count
FROM tkategorieartikel ka
INNER JOIN tArtikel a ON a.kArtikel = ka.kArtikel AND a.cAktiv = 'Y'
GROUP BY ka.kKategorie
`;

11
src/db/mssql/steuer.js Normal file
View File

@@ -0,0 +1,11 @@
export const LOAD_STEUERKLASSEN = `
SELECT
sk.kSteuerklasse,
sk.cName,
sk.nStandard,
sz.fSteuersatz
FROM dbo.tSteuerklasse sk
INNER JOIN dbo.tSteuersatz sz ON sz.kSteuerklasse = sk.kSteuerklasse
WHERE sz.kSteuerzone = 1
ORDER BY sk.kSteuerklasse
`;

View File

@@ -1,7 +1,7 @@
import env from '../../config/env.js';
import { getPool } from '../../db/mssqlClient.js';
import { getBrutto } from '../../utils/tax.js';
import { enrichSupplierAnmerkungRow } from './enrichSupplier.js';
import { bruttoFromNetto, loadSteuerklassen } from '../misc/steuerklassen.js';
import * as Q from '../../db/mssql/getArticle.js';
async function query(pool, sql, inputs = {}) {
@@ -71,9 +71,10 @@ async function loadAttributes(pool, kArtikel) {
async function loadStaffel(pool, kArtikel, kSteuerklasse) {
const rows = await query(pool, Q.STAFFEL, { kArtikel });
const steuerklassen = await loadSteuerklassen();
return rows.map((item) => ({
menge: item.nAnzahlAb.toString(),
fVKBrutto: getBrutto(item.fNEttoPreis, kSteuerklasse).toFixed(2),
fVKBrutto: bruttoFromNetto(item.fNEttoPreis, kSteuerklasse, steuerklassen)?.toFixed(2),
}));
}

View File

@@ -0,0 +1,73 @@
import { getPool } from '../../db/mssqlClient.js';
import {
CATEGORY_ARTICLE_COUNTS,
LOAD_CATEGORIES,
} from '../../db/mssql/categories.js';
function sortCategories(list) {
list.sort((a, b) => {
const sortDiff = (a.nSort ?? 0) - (b.nSort ?? 0);
if (sortDiff !== 0) {
return sortDiff;
}
return String(a.cName ?? '').localeCompare(String(b.cName ?? ''), 'de');
});
for (const node of list) {
sortCategories(node.children);
}
}
function buildCategoryTree(rows) {
const nodes = new Map();
for (const row of rows) {
nodes.set(row.kKategorie, {
kKategorie: row.kKategorie,
kOberKategorie: row.kOberKategorie,
nSort: row.nSort,
cName: row.cName ?? null,
count: row.count ?? 0,
children: [],
});
}
const roots = [];
for (const node of nodes.values()) {
const parent = nodes.get(node.kOberKategorie);
if (parent && node.kOberKategorie !== node.kKategorie) {
parent.children.push(node);
} else {
roots.push(node);
}
}
sortCategories(roots);
return roots;
}
export async function getCategories() {
const pool = await getPool();
const [categoriesResult, countsResult] = await Promise.all([
pool.request().query(LOAD_CATEGORIES),
pool.request().query(CATEGORY_ARTICLE_COUNTS),
]);
const countByCategory = new Map(
countsResult.recordset.map((row) => [row.kKategorie, row.count])
);
const categories = categoriesResult.recordset.map((row) => ({
kKategorie: row.kKategorie,
kOberKategorie: row.kOberKategorie,
nSort: row.nSort,
cName: row.cName ?? null,
count: countByCategory.get(row.kKategorie) ?? 0,
}));
return {
categories,
tree: buildCategoryTree(categories),
};
}

View File

@@ -0,0 +1,6 @@
import { loadSteuerklassen } from './steuerklassen.js';
export async function getSteuer() {
const steuerklassen = await loadSteuerklassen();
return { steuerklassen };
}

View File

@@ -0,0 +1,28 @@
import { getPool } from '../../db/mssqlClient.js';
import { LOAD_STEUERKLASSEN } from '../../db/mssql/steuer.js';
let cache;
export async function loadSteuerklassen() {
if (cache) {
return cache;
}
const pool = await getPool();
const result = await pool.request().query(LOAD_STEUERKLASSEN);
cache = result.recordset.map((row) => ({
kSteuerklasse: row.kSteuerklasse,
cName: row.cName,
nStandard: row.nStandard,
fSteuersatz: row.fSteuersatz,
}));
return cache;
}
export function bruttoFromNetto(netto, kSteuerklasse, steuerklassen) {
const row = steuerklassen.find((item) => item.kSteuerklasse === kSteuerklasse);
if (row == null || netto == null) {
return null;
}
return netto * (1 + row.fSteuersatz / 100);
}

View File

@@ -1,14 +0,0 @@
const STEUER_RATES = {
1: 19,
2: 7,
3: 0,
};
export function getSteuer(kSteuerklasse) {
return STEUER_RATES[kSteuerklasse] ?? 19;
}
export function getBrutto(netto, kSteuerklasse) {
const rate = getSteuer(kSteuerklasse);
return netto * (1 + rate / 100);
}