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 en: Dictionary = {
|
||||
descriptionLabel: "Description",
|
||||
isPublishedLabel: "Publish immediately",
|
||||
create: "Create",
|
||||
syncHeading: "ApiCore sync",
|
||||
syncIntro:
|
||||
"Imports harvests AND batches from ApiCore. Harvests are created as drafts (not publicly visible); previously synced harvests that are already published are left untouched. Batches are only ever created, never updated (so real stock levels are never overwritten), and need an already-existing harvest. The products themselves have their own sync button under “Products”.",
|
||||
syncButton: "Sync now",
|
||||
syncResult: (created, updated, skipped, failed) =>
|
||||
`Harvests: ${created} created, ${updated} updated, ${skipped} skipped (already published)${failed > 0 ? `, ${failed} failed` : ""}.`,
|
||||
syncError: "Sync with ApiCore failed.",
|
||||
batchSyncResult: (created, skipped, failed) =>
|
||||
`Batches: ${created} created, ${skipped} skipped (already exist)${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||||
batchSyncError: "Batches could not be loaded from ApiCore",
|
||||
},
|
||||
adminHarvestEditPage: {
|
||||
backToHarvests: "Back to harvests",
|
||||
@@ -931,6 +921,24 @@ export const en: Dictionary = {
|
||||
publishNow: "Publish now",
|
||||
save: "Save",
|
||||
},
|
||||
adminApiCoreSync: {
|
||||
heading: "ApiCore sync",
|
||||
intro:
|
||||
"Fetches harvests, batches and products from ApiCore in one run — in that order, because each step builds on the previous one. Harvests and products are created as drafts: harvests unpublished, products inactive and without a price, because ApiCore does not hold sales prices. Each bottling run becomes its own vintage. Entries already released are never overwritten.",
|
||||
button: "Sync everything",
|
||||
resultHarvests: (created, updated, skipped, failed) =>
|
||||
`Harvests: ${created} created, ${updated} updated, ${skipped} skipped${failed > 0 ? `, ${failed} failed` : ""}.`,
|
||||
resultBatches: (created, skipped, failed) =>
|
||||
`Batches: ${created} created, ${skipped} skipped${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||||
resultProducts: (created, skipped, failed) =>
|
||||
`Products: ${created} new vintage${created === 1 ? "" : "s"}, ${skipped} skipped${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||||
pending: (count) =>
|
||||
`${count} bottling run${count === 1 ? "" : "s"} without a matching batch — usually a sign that the corresponding harvest is still missing in ApiCore.`,
|
||||
discontinued: (count) =>
|
||||
`${count} bottling run${count === 1 ? "" : "s"} belong to an article ApiCore no longer carries, so no new vintage was created. Existing ones stay on sale.`,
|
||||
error: "Sync with ApiCore failed.",
|
||||
batchLoadError: "Batches could not be loaded — without them no products are created either",
|
||||
},
|
||||
adminProductsPage: {
|
||||
backToDashboard: "Back to dashboard",
|
||||
delete: "Delete",
|
||||
@@ -950,17 +958,6 @@ export const en: Dictionary = {
|
||||
statusInactive: "Inactive",
|
||||
edit: "Edit",
|
||||
noProductsYet: "No products created yet.",
|
||||
syncHeading: "ApiCore sync",
|
||||
syncIntro:
|
||||
"Creates one product per bottling run from ApiCore — that is, per vintage. “Waldhonig 2026” and “Waldhonig 2027” are separate products with their own price, copy and reviews; the address carries the year. Imported products start out inactive and without a price, because ApiCore does not hold sales prices. A product can only be activated once price, weight and description have been filled in. Existing products are only ever skipped, never overwritten. The matching batch has to exist already — so run the harvest sync first.",
|
||||
syncButton: "Sync now",
|
||||
syncResult: (created, skipped, failed) =>
|
||||
`Products: ${created} new vintage${created === 1 ? "" : "s"} created, ${skipped} skipped (already exist)${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||||
syncDiscontinued: (count) =>
|
||||
`${count} bottling run${count === 1 ? "" : "s"} belong${count === 1 ? "s" : ""} to an article ApiCore no longer carries, so no new vintage was created for ${count === 1 ? "it" : "them"}. Vintages already in the shop stay on sale so remaining stock can be sold through.`,
|
||||
syncPending: (count) =>
|
||||
`${count} bottling run${count === 1 ? " is" : "s are"} still waiting for the matching batch. Sync under “Harvests” first, then run this again.`,
|
||||
syncError: "Sync with ApiCore failed.",
|
||||
draftHint: "Draft: no price set",
|
||||
},
|
||||
adminProductEditPage: {
|
||||
|
||||
Reference in new issue
Block a user