services
This commit is contained in:
90
scripts/getCategories.js
Normal file
90
scripts/getCategories.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useState } from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import chalk from 'chalk';
|
||||
import { getCategories } from '../src/services/misc/getCategories.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
const VISIBLE_ROWS = 20;
|
||||
|
||||
function flattenTree(nodes, depth = 0, out = []) {
|
||||
for (const node of nodes) {
|
||||
out.push({ ...node, depth });
|
||||
flattenTree(node.children, depth + 1, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function formatRow(row) {
|
||||
const indent = ' '.repeat(row.depth);
|
||||
const count = chalk.gray(` (${row.count})`);
|
||||
return `${indent}${row.cName ?? '—'}${count} ${chalk.dim(String(row.kKategorie))}`;
|
||||
}
|
||||
|
||||
const CategoryBrowser = ({ rows }) => {
|
||||
const { exit } = useApp();
|
||||
const [selected, setSelected] = useState(0);
|
||||
const windowStart = Math.max(0, Math.min(selected - Math.floor(VISIBLE_ROWS / 2), rows.length - VISIBLE_ROWS));
|
||||
const visible = rows.slice(windowStart, windowStart + VISIBLE_ROWS);
|
||||
const current = rows[selected];
|
||||
|
||||
const quit = () => {
|
||||
closePool()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
exit();
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
useInput((input, key) => {
|
||||
if (input === 'q' || (key.ctrl && input === 'c')) {
|
||||
quit();
|
||||
return;
|
||||
}
|
||||
if (key.upArrow) {
|
||||
setSelected((s) => Math.max(0, s - 1));
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setSelected((s) => Math.min(rows.length - 1, s + 1));
|
||||
}
|
||||
});
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.cyan.bold(`=== Categories (${rows.length}) ===`)),
|
||||
e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
visible.map((row, index) => {
|
||||
const absoluteIndex = windowStart + index;
|
||||
const prefix = absoluteIndex === selected ? chalk.cyan('> ') : ' ';
|
||||
const label = absoluteIndex === selected
|
||||
? chalk.cyan.bold(formatRow(row))
|
||||
: formatRow(row);
|
||||
return e(Text, { key: row.kKategorie }, prefix, label);
|
||||
})
|
||||
),
|
||||
rows.length > VISIBLE_ROWS
|
||||
? e(Text, { dimColor: true }, `Showing ${windowStart + 1}-${windowStart + visible.length} of ${rows.length}`)
|
||||
: null,
|
||||
current
|
||||
? e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
e(Text, { dimColor: true },
|
||||
`kKategorie: ${current.kKategorie} parent: ${current.kOberKategorie} sort: ${current.nSort ?? '—'} articles: ${current.count}`
|
||||
)
|
||||
)
|
||||
: null,
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('↑↓ navigate, q exit')))
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const { tree } = await getCategories();
|
||||
const rows = flattenTree(tree);
|
||||
render(e(CategoryBrowser, { rows }));
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
await closePool().catch(() => {});
|
||||
process.exit(1);
|
||||
}
|
||||
52
scripts/getSteuer.js
Normal file
52
scripts/getSteuer.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { render, Box, Text, useApp, useInput } from 'ink';
|
||||
import chalk from 'chalk';
|
||||
import { getSteuer } from '../src/services/misc/getSteuer.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
function SteuerTable({ steuerklassen }) {
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(Text, null, chalk.cyan.bold('=== Steuerklassen ===')),
|
||||
e(Box, { marginTop: 1, flexDirection: 'column' },
|
||||
e(Text, { dimColor: true }, ' ID Rate Name'),
|
||||
...steuerklassen.map((row) =>
|
||||
e(Text, { key: row.kSteuerklasse },
|
||||
' ',
|
||||
chalk.white(String(row.kSteuerklasse).padEnd(4)),
|
||||
chalk.yellow(`${String(row.fSteuersatz).padStart(3)}%`.padEnd(8)),
|
||||
row.cName ?? '—',
|
||||
row.nStandard === 1 ? chalk.gray(' (standard)') : ''
|
||||
)
|
||||
)
|
||||
),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray('Press any key to exit')))
|
||||
);
|
||||
}
|
||||
|
||||
const App = ({ steuerklassen }) => {
|
||||
const { exit } = useApp();
|
||||
|
||||
useInput(() => {
|
||||
closePool()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
exit();
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
return e(SteuerTable, { steuerklassen });
|
||||
};
|
||||
|
||||
try {
|
||||
const { steuerklassen } = await getSteuer();
|
||||
render(e(App, { steuerklassen }));
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
await closePool().catch(() => {});
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import chalk from 'chalk';
|
||||
import { getArticle } from '../src/services/article/getArticle.js';
|
||||
import { searchArticles } from '../src/services/article/searchArticles.js';
|
||||
import { closePool } from '../src/db/mssqlClient.js';
|
||||
import { getBrutto } from '../src/utils/tax.js';
|
||||
import { bruttoFromNetto, loadSteuerklassen } from '../src/services/misc/steuerklassen.js';
|
||||
|
||||
import '../src/config/env.js';
|
||||
|
||||
@@ -28,11 +28,12 @@ const eur = (value) => {
|
||||
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(value);
|
||||
};
|
||||
|
||||
function formatPrice(row) {
|
||||
function formatPrice(row, steuerklassen) {
|
||||
if (row.fVKNEtto == null) {
|
||||
return '—';
|
||||
}
|
||||
return eur(getBrutto(row.fVKNEtto, row.kSteuerklasse));
|
||||
const brutto = bruttoFromNetto(row.fVKNEtto, row.kSteuerklasse, steuerklassen);
|
||||
return brutto == null ? '—' : eur(brutto);
|
||||
}
|
||||
|
||||
function hasValue(value) {
|
||||
@@ -67,10 +68,8 @@ function Section({ title, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ArticleDetail({ article }) {
|
||||
const brutto = article.fVKNEtto != null
|
||||
? getBrutto(article.fVKNEtto, article.kSteuerklasse)
|
||||
: null;
|
||||
function ArticleDetail({ article, steuerklassen }) {
|
||||
const brutto = bruttoFromNetto(article.fVKNEtto, article.kSteuerklasse, steuerklassen);
|
||||
const aktiv = article.cAktiv === 'Y'
|
||||
? chalk.green('active')
|
||||
: article.cAktiv === 'N'
|
||||
@@ -194,14 +193,14 @@ function truncateForDisplay(value, max = 80) {
|
||||
return `${value.slice(0, max)}… (${value.length} chars)`;
|
||||
}
|
||||
|
||||
function formatListRow(row) {
|
||||
function formatListRow(row, steuerklassen) {
|
||||
const stock = row.fBestand ?? '—';
|
||||
const price = formatPrice(row);
|
||||
const price = formatPrice(row, steuerklassen);
|
||||
const name = row.cName ?? '—';
|
||||
return `${row.cArtNr ?? '—'} ${stock} ${price} ${name}`;
|
||||
}
|
||||
|
||||
const ResultList = ({ rows, onSelect, onBack }) => {
|
||||
const ResultList = ({ rows, onSelect, onBack, steuerklassen }) => {
|
||||
const [selected, setSelected] = useState(0);
|
||||
const windowStart = Math.max(0, Math.min(selected - Math.floor(VISIBLE_ROWS / 2), rows.length - VISIBLE_ROWS));
|
||||
const visible = rows.slice(windowStart, windowStart + VISIBLE_ROWS);
|
||||
@@ -228,8 +227,8 @@ const ResultList = ({ rows, onSelect, onBack }) => {
|
||||
const absoluteIndex = windowStart + index;
|
||||
const prefix = absoluteIndex === selected ? chalk.cyan('> ') : ' ';
|
||||
const label = absoluteIndex === selected
|
||||
? chalk.cyan.bold(formatListRow(row))
|
||||
: formatListRow(row);
|
||||
? chalk.cyan.bold(formatListRow(row, steuerklassen))
|
||||
: formatListRow(row, steuerklassen);
|
||||
return e(Text, { key: row.kArtikel ?? absoluteIndex }, prefix, label);
|
||||
}),
|
||||
rows.length > VISIBLE_ROWS
|
||||
@@ -238,7 +237,7 @@ const ResultList = ({ rows, onSelect, onBack }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const App = ({ steuerklassen }) => {
|
||||
const { exit } = useApp();
|
||||
const [mode, setMode] = useState(MODES.SEARCH);
|
||||
const [target, setTarget] = useState('');
|
||||
@@ -337,6 +336,7 @@ const App = () => {
|
||||
rows,
|
||||
onSelect: (row) => loadArticle(row.cArtNr),
|
||||
onBack: backToSearch,
|
||||
steuerklassen,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ const App = () => {
|
||||
: 'Enter = new search, q = quit';
|
||||
|
||||
return e(Box, { flexDirection: 'column' },
|
||||
e(ArticleDetail, { article }),
|
||||
e(ArticleDetail, { article, steuerklassen }),
|
||||
e(Box, { marginTop: 1 }, e(Text, null, chalk.gray(footer))),
|
||||
e(ResultInput, {
|
||||
onAgain: resetSearch,
|
||||
@@ -398,4 +398,5 @@ const ExitOnKey = ({ onExit }) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
render(e(App));
|
||||
const steuerklassen = await loadSteuerklassen();
|
||||
render(e(App, { steuerklassen }));
|
||||
|
||||
Reference in New Issue
Block a user