fix(apicore-sync): keep internal notes out of the shop and protect edited drafts

- Harvest descriptions no longer include ApiCore's internal harvest notes.
  The description becomes public on "Veröffentlichen", so a draft published
  without a careful read could have carried operational notes into the shop.
- A repeated sync overwrote unpublished harvest drafts, undoing the admin's
  corrections (season instead of honey type, location, sales text). Drafts
  with an admin audit entry are now left alone and reported as protected.
- Every ApiCore request has a 20 s timeout; before, an ApiCore that accepted
  the connection but never answered left the admin action hanging.
- The sync panel shows the time of the last complete run.
- Client tests: timeout, failed fetch is never treated as an empty source,
  malformed responses are rejected as a whole.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-27 05:39:09 +02:00
1 parent e0b47faf45
commit 0482fd490e
9 files changed
+194 -17

No files matched your search

+5 -1
View File
@@ -1131,7 +1131,7 @@ export const en: Dictionary = {
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.",
"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 or edited in the admin are never overwritten; internal ApiCore notes are not copied.",
button: "Sync everything",
resultHarvests: (created, updated, skipped, failed) =>
`Harvests: ${created} created, ${updated} updated, ${skipped} skipped${failed > 0 ? `, ${failed} failed` : ""}.`,
@@ -1143,6 +1143,10 @@ export const en: Dictionary = {
`${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.`,
protectedDrafts: (count) =>
`${count} harvest draft${count === 1 ? " was" : "s were"} already edited in the admin and therefore not overwritten.`,
lastRun: (when) => `Last complete run: ${when}`,
neverRun: "No complete run yet.",
error: "Sync with ApiCore failed.",
batchLoadError: "Batches could not be loaded — without them no products are created either",
},