feat: ein Knopf synchronisiert Ernten, Chargen und Produkte zusammen
Bisher gab es zwei Knoepfe auf zwei Seiten, und ihre Reihenfolge war nicht beliebig: eine Charge braucht ihre Ernte als Pflicht-Fremdschluessel, ein Produkt entsteht je Abfuellung und braucht die zugehoerige Charge. Wer mit den Produkten anfing, bekam reihenweise "wartet auf den Ernten-Sync" und musste danach noch einmal klicken. Diese Reihenfolge gehoert in den Ablauf, nicht in den Kopf der bedienenden Person. syncApiCoreAction ersetzt beide Actions und laeuft Ernten -> Chargen -> Produkte in einem Durchgang. "Alles synchronisieren" steht auf beiden Seiten und springt danach dorthin zurueck, von wo er ausgeloest wurde. Das Ziel kommt aus einem Formularfeld und wird gegen eine feste Liste geprueft — ein ungeprueftes returnTo waere eine offene Weiterleitung. Nebeneffekt: die Abfuellungen werden nur noch EINMAL von ApiCore geladen. Vorher holten Chargen- und Produkt-Sync dieselbe Liste getrennt. Knopf und Ergebnisanzeige liegen jetzt in einer gemeinsamen Komponente (components/apicore-sync-panel.tsx) statt zweimal in zwei Seiten. Die Ergebnisse stehen als ein Block beieinander, je eine Zeile fuer Ernten, Chargen und Produkte, plus die beiden Hinweise (Abfuellung ohne Charge, ausgelaufener Artikel) als Hinweis statt als Fehler. Damit wurden zehn i18n-Schluessel ueberfluessig, die vorher die beiden getrennten Anzeigen bedienten; sie sind aus dem Vertrag und beiden Sprachdateien entfernt. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
8f0f06fd13
commit
92ac826baa
8 files changed
+246
-213
No files matched your search
+18
-21
@@ -903,16 +903,6 @@ export const de: Dictionary = {
|
||||
descriptionLabel: "Beschreibung",
|
||||
isPublishedLabel: "Sofort veröffentlichen",
|
||||
create: "Anlegen",
|
||||
syncHeading: "ApiCore-Synchronisierung",
|
||||
syncIntro:
|
||||
"Importiert Ernten UND Chargen aus ApiCore. Ernten werden als Entwürfe angelegt (nicht öffentlich sichtbar); bereits veröffentlichte, zuvor synchronisierte Ernten werden dabei nicht verändert. Chargen werden nur neu angelegt (nie überschrieben, damit echte Lagerbestände erhalten bleiben) und brauchen eine bereits vorhandene Ernte. Die Produkte selbst kommen über den eigenen Sync-Knopf unter „Produkte“.",
|
||||
syncButton: "Jetzt synchronisieren",
|
||||
syncResult: (created, updated, skipped, failed) =>
|
||||
`Ernten: ${created} neu angelegt, ${updated} aktualisiert, ${skipped} übersprungen (bereits veröffentlicht)${failed > 0 ? `, ${failed} fehlgeschlagen` : ""}.`,
|
||||
syncError: "Synchronisierung mit ApiCore fehlgeschlagen.",
|
||||
batchSyncResult: (created, skipped, failed) =>
|
||||
`Chargen: ${created} neu angelegt, ${skipped} übersprungen (bereits vorhanden)${failed > 0 ? `, ${failed} fehlgeschlagen (siehe Server-Log)` : ""}.`,
|
||||
batchSyncError: "Chargen konnten nicht von ApiCore geladen werden",
|
||||
},
|
||||
adminHarvestEditPage: {
|
||||
backToHarvests: "Zurück zu Ernten",
|
||||
@@ -931,6 +921,24 @@ export const de: Dictionary = {
|
||||
publishNow: "Jetzt veröffentlichen",
|
||||
save: "Speichern",
|
||||
},
|
||||
adminApiCoreSync: {
|
||||
heading: "ApiCore-Synchronisierung",
|
||||
intro:
|
||||
"Holt in einem Durchgang Ernten, Chargen und Produkte aus ApiCore — in dieser Reihenfolge, weil jede Stufe auf der vorherigen aufbaut. Ernten und Produkte entstehen als Entwürfe: Ernten unveröffentlicht, Produkte deaktiviert und ohne Preis, denn ApiCore führt keine Verkaufspreise. Je Abfüllung entsteht ein eigener Jahrgang. Bereits freigegebene Einträge werden nie überschrieben.",
|
||||
button: "Alles synchronisieren",
|
||||
resultHarvests: (created, updated, skipped, failed) =>
|
||||
`Ernten: ${created} neu, ${updated} aktualisiert, ${skipped} übersprungen${failed > 0 ? `, ${failed} fehlgeschlagen` : ""}.`,
|
||||
resultBatches: (created, skipped, failed) =>
|
||||
`Chargen: ${created} neu, ${skipped} übersprungen${failed > 0 ? `, ${failed} fehlgeschlagen (siehe Server-Log)` : ""}.`,
|
||||
resultProducts: (created, skipped, failed) =>
|
||||
`Produkte: ${created} neue${created === 1 ? "r Jahrgang" : " Jahrgänge"}, ${skipped} übersprungen${failed > 0 ? `, ${failed} fehlgeschlagen (siehe Server-Log)` : ""}.`,
|
||||
pending: (count) =>
|
||||
`${count} Abfüllung${count === 1 ? "" : "en"} ohne passende Charge — meist ein Hinweis darauf, dass die zugehörige Ernte in ApiCore noch fehlt.`,
|
||||
discontinued: (count) =>
|
||||
`${count} Abfüllung${count === 1 ? "" : "en"} gehör${count === 1 ? "t" : "en"} zu einem Artikel, den ApiCore nicht mehr führt — dafür wurde kein neuer Jahrgang angelegt. Bereits vorhandene bleiben im Verkauf.`,
|
||||
error: "Synchronisierung mit ApiCore fehlgeschlagen.",
|
||||
batchLoadError: "Chargen konnten nicht geladen werden — ohne sie entstehen auch keine Produkte",
|
||||
},
|
||||
adminProductsPage: {
|
||||
backToDashboard: "Zurück zum Dashboard",
|
||||
delete: "Löschen",
|
||||
@@ -951,17 +959,6 @@ export const de: Dictionary = {
|
||||
statusInactive: "Inaktiv",
|
||||
edit: "Bearbeiten",
|
||||
noProductsYet: "Noch keine Produkte angelegt.",
|
||||
syncHeading: "ApiCore-Synchronisierung",
|
||||
syncIntro:
|
||||
"Legt für jede Abfüllung aus ApiCore ein eigenes Produkt an — also einen Jahrgang. „Waldhonig 2026“ und „Waldhonig 2027“ sind damit getrennte Produkte mit eigenem Preis, eigenem Text und eigenen Bewertungen; die Adresse trägt die Jahreszahl. Importierte Produkte sind zunächst deaktiviert und ohne Preis, denn ApiCore führt keine Verkaufspreise. Erst wenn Preis, Gewicht und Beschreibung gepflegt sind, lässt sich ein Produkt aktiv schalten. Vorhandene Produkte werden nur übersprungen, nie überschrieben. Die zugehörige Charge muss schon vorhanden sein — also zuerst den Ernten-Sync ausführen.",
|
||||
syncButton: "Jetzt synchronisieren",
|
||||
syncResult: (created, skipped, failed) =>
|
||||
`Produkte: ${created} neue${created === 1 ? "r Jahrgang" : " Jahrgänge"} angelegt, ${skipped} übersprungen (bereits vorhanden)${failed > 0 ? `, ${failed} fehlgeschlagen (siehe Server-Log)` : ""}.`,
|
||||
syncDiscontinued: (count) =>
|
||||
`${count} Abfüllung${count === 1 ? "" : "en"} gehör${count === 1 ? "t" : "en"} zu einem Artikel, den ApiCore nicht mehr führt — dafür wurde kein neuer Jahrgang angelegt. Bereits vorhandene Jahrgänge bleiben unverändert im Verkauf, damit der Restbestand abverkauft werden kann.`,
|
||||
syncPending: (count) =>
|
||||
`${count} Abfüllung${count === 1 ? "" : "en"} wartet${count === 1 ? "" : "en"} noch auf die zugehörige Charge. Zuerst unter „Ernten“ synchronisieren, dann hier erneut.`,
|
||||
syncError: "Synchronisierung mit ApiCore fehlgeschlagen.",
|
||||
draftHint: "Entwurf: kein Preis gepflegt",
|
||||
},
|
||||
adminProductEditPage: {
|
||||
|
||||
Reference in new issue
Block a user