feat: Produkte aus ApiCore synchronisieren
Der Shop konnte Ernten und Chargen bereits aus ApiCore uebernehmen, den Produktkatalog aber nicht - jedes Glas musste im Admin ein zweites Mal von Hand angelegt werden. Neuer Knopf unter /admin/produkte zieht die Produkte und traegt anschliessend die passende Charge am Produkt nach, wo ApiCore die Abfuellung einem Produkt zugeordnet hat. Genau diese Verknuepfung fuettert die Herkunftsseite hinter dem QR-Code auf dem Glas. ApiCore fuehrt ueberhaupt keine Verkaufspreise. Importierte Produkte entstehen deshalb als deaktivierte Entwuerfe mit Preis 0, den nur ein Mensch setzen kann. Damit daraus nie eine Gratis-Bestellung wird, lehnen die Aktivierung im Admin und der Checkout ein Produkt ohne Preis jetzt ausdruecklich ab, und die Produktliste kennzeichnet solche Entwuerfe. Ein bereits vorhandenes Produkt wird uebersprungen, nie ueberschrieben: genau die drei Felder, die ApiCore liefert (Name, Beschreibung, Gewicht), zieht der Admin vor der Freischaltung von Hand nach - ein Update wuerde diese Arbeit bei jedem weiteren Lauf wegwerfen. Ein in ApiCore ausgelaufenes Produkt wird nicht automatisch deaktiviert, sondern nur gemeldet, damit der Restbestand abverkauft werden kann. Nebenbei behoben: die beiden Produktformulare und die Bestelluebersicht haben den ?error=-Parameter aus fail() nie ausgegeben - abgelehnte Eingaben landeten wortlos wieder auf dem Formular. Der Login gegen ApiCore ist jetzt von den Abfragen getrennt (openApiCoreSession), damit ein Sync-Lauf mit einer einzigen Anmeldung auskommt statt mit dreien. Die Feld-Abbildung liegt neu in lib/apicore-mapping.ts und ist damit testbar, statt hinter "use server" zu verschwinden. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c61ae92390
commit
78c19f1b73
15 files changed
+908
-226
No files matched your search
@@ -903,7 +903,7 @@ export const en: Dictionary = {
|
||||
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.",
|
||||
"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` : ""}.`,
|
||||
@@ -945,6 +945,18 @@ export const en: Dictionary = {
|
||||
statusInactive: "Inactive",
|
||||
edit: "Edit",
|
||||
noProductsYet: "No products created yet.",
|
||||
syncHeading: "ApiCore sync",
|
||||
syncIntro:
|
||||
"Imports the product catalogue from ApiCore. Imported products start out inactive and without a price — ApiCore does not hold sales prices. A product can only be activated and sold once price, weight and description have been filled in. Existing products are only ever skipped, never overwritten, so no manual work is lost. Where ApiCore assigns a bottling run to a product, the matching batch is linked as well (provided it has already been imported by the harvest sync).",
|
||||
syncButton: "Sync now",
|
||||
syncResult: (created, skipped, failed) =>
|
||||
`Products: ${created} created, ${skipped} skipped (already exist)${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||||
syncDiscontinued: (count) =>
|
||||
`${count} active product${count === 1 ? " is" : "s are"} discontinued in ApiCore. They deliberately stay on sale so remaining stock can be sold through — decide yourself whether to deactivate them.`,
|
||||
syncLinked: (count) => `${count} product${count === 1 ? "" : "s"} linked to the matching batch.`,
|
||||
syncError: "Sync with ApiCore failed.",
|
||||
linkError: "Batches could not be loaded, the products were imported anyway",
|
||||
draftHint: "Draft: no price set",
|
||||
},
|
||||
adminProductEditPage: {
|
||||
backToProducts: "Back to products",
|
||||
|
||||
Reference in new issue
Block a user