fix(harvests): allow several harvests of the same type per year
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m15s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m15s
The ApiCore sync failed with "Unique constraint failed on the constraint:
harvests_year_type_key" as soon as ApiCore had two harvests of the same
honey type in one year, which is normal (several extractions per season).
Every bottling of that harvest was then skipped too.
- Drop the (year, type) unique index. The slug is now the only unique,
public identifier: /ernten/<slug>, e.g. bluetenhonig-2026, then
bluetenhonig-2026-07-14 for a further harvest of the same type.
- New Harvest.apicoreHarvestId as the sync key, backfilled from the old
"apicore-<id>" slugs; the sync gives those rows a readable slug.
- Slugs are generated by one shared slugify() that normalizes Unicode
(decomposed umlauts) and writes out umlauts. The migration repairs
manual slugs created by the old umlaut bug ("fr-htracht-2026").
- Slug collisions are resolved by the database unique index, so parallel
writers cannot produce duplicates; known slugs are skipped up front to
keep the logs free of handled P2002 errors.
- Old URLs /ernten/<year>/<type> answer with 308 to the new address.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
f1c0da4673
commit
641afa1ae4
15 files changed
+407
-83
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { isSellableStatus, type ApiCoreProduct, type ApiCoreHarvest } from "./apicore-client";
|
||||
import {
|
||||
slugForApiCoreSource,
|
||||
hasLegacyApiCoreSlug,
|
||||
buildHarvestDescription,
|
||||
buildProductDescription,
|
||||
deriveWeightGrams,
|
||||
@@ -40,25 +40,13 @@ function makeHarvest(overrides: Partial<ApiCoreHarvest> = {}): ApiCoreHarvest {
|
||||
};
|
||||
}
|
||||
|
||||
describe("slugForApiCoreSource", () => {
|
||||
test("leitet den Slug ausschliesslich aus der ApiCore-ID ab", () => {
|
||||
// Arrange
|
||||
const sourceId = "cm-product-1";
|
||||
|
||||
// Act
|
||||
const slug = slugForApiCoreSource(sourceId);
|
||||
|
||||
// Assert
|
||||
expect(slug).toBe("apicore-cm-product-1");
|
||||
describe("hasLegacyApiCoreSlug", () => {
|
||||
test("erkennt das alte Slug-Format 'apicore-<ID>'", () => {
|
||||
expect(hasLegacyApiCoreSlug("apicore-cmtx1ri7x001j012bvcqh9tpt")).toBe(true);
|
||||
});
|
||||
|
||||
test("bleibt stabil, wenn sich in ApiCore nur der Name aendert", () => {
|
||||
// Arrange: derselbe Datensatz, in ApiCore umbenannt.
|
||||
const before = makeProduct({ name: "Sommertracht 500 g" });
|
||||
const after = makeProduct({ name: "Sommertracht cremig, 500 g" });
|
||||
|
||||
// Act & Assert: sonst wuerde der Sync ein zweites Produkt anlegen.
|
||||
expect(slugForApiCoreSource(after.id)).toBe(slugForApiCoreSource(before.id));
|
||||
test("laesst lesbare Adressen in Ruhe", () => {
|
||||
expect(hasLegacyApiCoreSlug("bluetenhonig-2026")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user