feat(erp-export): nur lesender Inventar-Spiegel für ERPNext (T8)
Datenquelle für den nächtlichen Inventar-Abgleich in ERPNext (ERPNext- Ausbau T8, Entscheidungen E2/E3). Neues Recht erp-inventory:read, auch als API-Schlüssel-Scope; es öffnet nur diese vier GET-Endpunkte: - /api/v1/export/inventar/produkte: Artikel (ohne Beschreibung) - .../chargen: Abfüllungen und Herstellungschargen mit Losnummer, MHD, Ausgangsmenge, Rest aus dem Bewegungsbuch, Füllmenge g und Sorte - .../bewegungen?seit=&nach=&limit=: Shop-Verkauf, Retoure, Entnahmen mit Grund (HOFLADEN = Vertrauensschrank, EIGENBEDARF, BRUCH, PROBE, SONSTIGES) und Inventur; Cursor über (createdAt, id), Bewegungen erst nach 5 Minuten Wartezeit, damit der Cursor keine offene Buchung überholt - .../ernten?jahr=: Nettogewicht kg je Kalenderjahr, wie die Statistik Alle Listen stabil sortiert, seitenweise (1-500), Parameter mit class-validator geprüft, Cursor je Endpunkt streng validiert. Feld- Allowlists: keine Notizen, Benutzer, Standorte, Rohstoffe. Die Gründe gab es im Datenmodell schon - keine Migration; das Recht legt der Katalog- Sync beim Start an und gibt es der Rolle Administrator. Vertrag mit Feldern, Einheiten, Fehlercodes und Beispielen: docs/erp-inventar-export.md. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
1a1e70c94b
commit
68f81fabd4
17 files changed
+2569
-4
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import { summarizeHarvestYears } from './harvest-years';
|
||||
|
||||
function harvest(date: string, netWeight: number | null) {
|
||||
return { date: new Date(date), netWeight };
|
||||
}
|
||||
|
||||
describe('summarizeHarvestYears', () => {
|
||||
it('summiert das Nettogewicht je Kalenderjahr (UTC), älteste Jahre zuerst', () => {
|
||||
const years = summarizeHarvestYears([
|
||||
harvest('2026-07-14T00:00:00.000Z', 41),
|
||||
harvest('2025-06-01T00:00:00.000Z', 12.5),
|
||||
harvest('2026-05-20T00:00:00.000Z', 18.25),
|
||||
]);
|
||||
|
||||
expect(years).toEqual([
|
||||
{
|
||||
year: 2025,
|
||||
netWeightKg: 12.5,
|
||||
harvestCount: 1,
|
||||
harvestsWithoutNetWeight: 0,
|
||||
},
|
||||
{
|
||||
year: 2026,
|
||||
netWeightKg: 59.25,
|
||||
harvestCount: 2,
|
||||
harvestsWithoutNetWeight: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('zählt Ernten ohne Nettogewicht mit, rechnet sie aber nicht ein', () => {
|
||||
const [year] = summarizeHarvestYears([
|
||||
harvest('2026-07-14T00:00:00.000Z', 20),
|
||||
harvest('2026-08-01T00:00:00.000Z', null),
|
||||
]);
|
||||
expect(year).toEqual({
|
||||
year: 2026,
|
||||
netWeightKg: 20,
|
||||
harvestCount: 2,
|
||||
harvestsWithoutNetWeight: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('rundet auf Gramm, damit Gleitkomma-Reste nicht im Vertrag landen', () => {
|
||||
const [year] = summarizeHarvestYears([
|
||||
harvest('2026-07-14T00:00:00.000Z', 0.1),
|
||||
harvest('2026-07-15T00:00:00.000Z', 0.2),
|
||||
harvest('2026-07-16T00:00:00.000Z', 12.3456),
|
||||
]);
|
||||
expect(year.netWeightKg).toBe(12.646);
|
||||
});
|
||||
|
||||
it('ordnet Silvester und Neujahr nach UTC zu', () => {
|
||||
const years = summarizeHarvestYears([
|
||||
harvest('2025-12-31T23:59:59.999Z', 1),
|
||||
harvest('2026-01-01T00:00:00.000Z', 2),
|
||||
]);
|
||||
expect(years.map((y) => [y.year, y.netWeightKg])).toEqual([
|
||||
[2025, 1],
|
||||
[2026, 2],
|
||||
]);
|
||||
});
|
||||
|
||||
it('liefert für ein angefragtes Jahr ohne Ernten eine Nullzeile statt nichts', () => {
|
||||
expect(summarizeHarvestYears([], 2024)).toEqual([
|
||||
{
|
||||
year: 2024,
|
||||
netWeightKg: 0,
|
||||
harvestCount: 0,
|
||||
harvestsWithoutNetWeight: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('beschränkt sich auf das angefragte Jahr', () => {
|
||||
const years = summarizeHarvestYears(
|
||||
[
|
||||
harvest('2025-06-01T00:00:00.000Z', 5),
|
||||
harvest('2026-06-01T00:00:00.000Z', 7),
|
||||
],
|
||||
2026,
|
||||
);
|
||||
expect(years).toEqual([
|
||||
{
|
||||
year: 2026,
|
||||
netWeightKg: 7,
|
||||
harvestCount: 1,
|
||||
harvestsWithoutNetWeight: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('liefert ohne Ernten und ohne Jahr eine leere Liste', () => {
|
||||
expect(summarizeHarvestYears([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user