Add a market-focused seeding path so the catalogue can be built from
domestic products rather than the English-heavy global default:
- adapter.fetch_by_country(country): OFF search filtered by
countries_tags_en, sorted by unique_scans_n (most-scanned first),
de-duplicated across pages since OFF popularity ordering is unstable.
- is_cn_gs1(code): True for GS1-China company prefixes (690-699),
i.e. genuinely domestic items vs. imports merely sold in China.
- seed_off --country <slug> [--domestic-only] [--page-size/--max-pages]:
e.g. 'seed_off --country china --domestic-only' loads only 69x
barcodes.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Wire the multi-barcode store layer to HTTP and the operator console:
- adminhandler: add POST /products/{id}/barcodes, DELETE
/products/{id}/barcodes/{barcodeID}, and POST .../primary. A barcode
owned by another product returns 409 with the conflicting product
(gtin/product_id/product_name); an invalid GTIN returns 400.
- admin-frontend: BarcodesCard on the product detail page lists all
barcodes (primary starred), adds with type/pack-level/region, sets
primary, and deletes; audit labels for the new actions.
- public-frontend: product detail surfaces non-primary barcodes so a
case/region code resolves and is visible to consumers.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add retry/backoff (429 + 5xx, Retry-After aware) to the OFF adapter so
transient API errors no longer abort a run.
- Clamp bounded text fields (serving_size, net_content_unit,
country_of_origin) to their column widths in transform; long OFF values
previously raised StringDataRightTruncation and rolled back the batch.
- Load each record inside a savepoint (load_record_safe) so one malformed
source record is skipped instead of aborting the whole import; jobs now
report an errored count.
- Tests for retry behaviour, serving_size clamping, and per-record isolation.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>