Nyheter & Uppdateringar

v1.0.0
v1.0.0 Stabil release 2026-03-12 Aktuell

Svensk UI — Komplett översättning

  • Alla texter genom hela appen använder nu korrekta svenska tecken (å, ä, ö)
  • Fixat i: Inställningar, Aktivitetslogg, Mitt konto, Dashboard, Produktverkstad
  • Inställningsnamn och beskrivningar: "Avrundningsläge", "Prisändringsvarning", etc.
  • Aktivitetsetiketter: "Godkände grupp", "Döpte om produkt", "Ändrade inställning", etc.

Inloggning — Ny design

  • Modern split-screen layout — mörk gradient med logga till vänster, formulär till höger
  • Stöd för vita logotyper — syns tydligt mot mörk bakgrund
  • Visa/dölj lösenord — ögon-ikon i lösenordsfältet
  • Laddningsindikator — spinner vid inloggning
  • Mobilanpassad — logga i mörk rundad container på små skärmar

Buggfixar

  • Statistikräknare — "Att godkänna"-räknaren uppdateras korrekt vid batch-godkännande
  • Blocklista — retry-logik (2 försök med 2s fördröjning) löser timeout vid kallstart
  • HTTPS Mixed Content — ProxyHeadersMiddleware säkerställer korrekta URLs bakom Render-proxy
  • Apostrofer i produktnamnNature's Answer visas korrekt (inte Nature\x27s Answer)
  • Parentnamn i Produktverkstad — fullständigt namn visas med radbrytning istället för trunkering
  • Lösenordsåterställning — ny endpoint POST /api/auth/reset-password

---

v0.14.0 Mitt konto, Aktivitetslogg, Changelog & Auto-sync 2026-03-12

Mitt konto

  • Profilsida (/account) — visa och redigera namn, e-post, roll
  • Byt lösenord — krav på nuvarande lösenord för säkerhet
  • Senaste aktivitet — visa egna händelser på kontosidan
  • Klickbart användarnamn i header-baren länkar till Mitt konto

Aktivitetslogg — Spåra vem som gör vad

  • Ny ActivityLog-modell — sparar användare, händelse, objekt, IP-adress, detaljer (JSONB)
  • Instrumenterade endpoints: inloggning, gruppgodkännande, produktändringar, namnändring, inställningsändringar, förslag accept/ignore
  • Admin-vy (/activity) — filtrerbara loggar med paginering
  • Auto-rensning — Celery-task rensar loggar äldre än 90 dagar (konfigurerbart via inställningar)
  • Ny setting: log_retention_days (default 90, 7–365)

Nyheter / Changelog

  • Ny sida (/changelog) — renderar CHANGELOG.md som snygga versionskort
  • Versionslänken i sidebar-footer länkar till changelog
  • Aktuell version markerad med grön badge
  • Minimal Markdown-parser — hanterar rubriker, listor, tabeller, fetstil, kodblock

Auto-sync till Shopify

  • auto_sync_changed_products() — synkar alla ändrade fält (pris, beskrivning, SEO, tags, cost, namn) var 30 min
  • Brand-dash fix i diff-update — "SciTec Marine" → "SciTec - Marine" (samma logik som mapper.py)
  • Radering istället för arkivering — dubbletter raderas vid konsolidering, ingen fallback till arkivering
  • Lager exkluderat — hanteras av externt system

Inställningar

  • Ny Schema-flik — visuell tidslinje med dagliga tasks (06:00–09:00) och periodiska uppgifter
  • Blocklista förbättrad — sökfält + paginering (50 per sida)
  • Daglig pipeline kl 06:30 (steg 1–18, smart mode)
  • AI-förslag kl 09:00
  • Loggrensning kl 03:00

Alembic Migration

  • d4e5f6g7h8i9activity_logs tabell + users.last_login_at kolumn

---

v0.13.0 Workshop UX, Produktdetaljer & Branding 2026-03-11

Produktverkstad — Snabbare & Smartare

  • 10x snabbare grupplistning — ny get_group_list_fast() använder SQL-aggregation istället för ORM eager loading
  • Inga variant-objekt laddas — bara counts och preview-namn via array_agg()
  • Ny endpoint GET /api/variants/list ersätter tung /api/variants/data för listvy
  • Auto-load vid tom workspace — när alla grupper godkänts laddas automatiskt 5 nya ogodkända grupper
  • "Ladda fler grupper"-kort synligt i workspace för manuell laddning
  • Same-brand parallellt arbete — ny GET /api/variants/brand-groups API
  • Klick på "Redigera grupp" laddar huvudgruppen + upp till 5 grupper från samma varumärke
  • Parallell laddning via Promise.all() för snabb öppning
  • Rikare gruppkort — bild, lagerantal, Shopify-status och push-badge
  • Produktbild (12×12) med fallback-placeholder
  • Lagerantal, Shopify-variant count, push-status badge
  • Tydliga knappar: "Redigera grupp" (workspace) vs "Produktsida" (detaljvy)

Produktdetalj — Förbättrad Information

  • Beskrivningsförhandsgranskning fixad — hanterar både HTML och plaintext korrekt
  • HTML renderas via DOMParser, plaintext med radbrytningar och styckeindelning
  • Null guards för produkter utan beskrivning
  • Aktiv leverantörsbar — visar leverantörsnamn, pris (EUR), senaste import, Shopify-butikslänk
  • EAN + Supplier SKU i erbjudandetabellen med klickbara leverantörslänkar (↗)

Branding & UI

  • Konfigurerbar app-logga — ny setting app_logo_url i Inställningar
  • Logga visas i sidebar (h-12) och login-sida (h-20)
  • App-namn + version flyttat till botten av sidebar i ljusgrå
  • Konfigurerbart appnamnapp_name setting, default "Nexus PIM"
  • Template globalsapp_name, app_logo_url, app_version tillgängliga i alla templates

Användarhantering

  • Ny endpoint PUT /api/auth/update-user — admin kan uppdatera e-post, namn och lösenord
  • Rollkontroll — kräver admin-roll för att uppdatera användare

---

v0.12.0 Generated RRP, Weight Pricing, Supplier Auto-Reprice & Produktverkstad 2026-03-11

Genererat Rek.pris (compare_at_price)

  • Ny generate_compare_at_price() (pricing_engine.py) — genererar rekommenderat pris för Shopify
  • 3-stegs prioritet: 1) Leverantörs-RRP om ≥15% över säljpris, 2) Genererat hash-baserat RRP, 3) Inget om hög marginal
  • Deterministisk variation — MD5-hash av produkt-ID ger unik markup per produkt (20-35%), ser naturligt ut
  • Psykologisk avrundning — alla priser slutar på 9 (229, 249, 299, 339...)
  • Marginalspärr — produkter med >55% marginal får inget RRP (redan "dyra" relativt inköp)
  • 3 konfigurerbara settings: rrp_min_markup, rrp_max_markup, rrp_high_margin_cap
  • Integrerat i alla push-flöden: mapper.py, safe_sync.py (update + push_all_prices)

Viktkonditioner i prisregler

  • _check_conditions() stödjer nu weight_min och weight_max (kg)
  • Pricing UI — nya fält "Min vikt" och "Max vikt" i regelmodalen
  • Användning: Skapa fixed_amount-regel med {"weight_min": 3.0} → +50 kr för tunga produkter
  • Visas i regellistan som "≥ 3 kg"

Auto-prisberäkning vid leverantörsbyte

  • update_active_supplier() — kör calculate_price(apply=True) automatiskt vid byte
  • select_all_best_suppliers() — batch-version: samlar ändrade produkter, beräknar om pris
  • Säkerställer att priset alltid matchar aktiv leverantörs regler

BioU RRP-valutafix (PLN → EUR)

  • Upptäckt: BioU:s retailPriceGross är i PLN, inte EUR — verifierat via BioU REST API
  • import_engine.py — ny rrp_currency-parameter, konverterar PLN→EUR vid import
  • xml_feed.py — skickar rrp_currency="PLN" för BioU
  • Ny setting: pln_to_eur_rate (default 0.233) i Inställningar
  • 3 926 BioU-produkter korrigerade retroaktivt

Shopify Variant-ID Synkronisering

  • Ny sync_shopify_variant_ids() (safe_sync.py) — matchar PIM-produkter till Shopify via EAN/barcode
  • Ny Celery-task sync_variant_ids (time_limit 3600s)
  • Resultat: 4 160 av 8 399 produkter matchade och länkade
  • Löser problem med produkter pushade från gamla PIM som saknade variant-ID

Shopify Bulk-prispush

  • Ny push_all_prices() (safe_sync.py) — lättviktig pris-push (price + compare_at + cost)
  • Grupperar per Shopify-produkt — en PUT per produkt istället för per variant
  • Resumableskip_processed-parameter för att fortsätta efter timeout
  • Ny Celery-task med time_limit 7200s + soft_time_limit 7000s
  • API-endpoint: POST /api/workshop/push-all-prices
  • 9 018 priser uppdaterade på Shopify (5 122 produkter)

Produktverkstad — Workshop Förbättringar

  • Exakta DB-stats — ny get_group_stats() med direkta SQL-queries istället för att räkna laddade grupper
  • Ny API: GET /api/variants/stats — snabb stats utan produktladdning
  • 4 statboxar: "Att godkänna", "Godkända", "Totalt grupper", "Totalt varianter"
  • Godkända grupper filtreras bort — visar bara grupper som behöver godkännas
  • AI-förslag tab-count fix — räknar ner från server-total vid accept, inte sidans längd
  • Fulla produktnamn — borttagen truncate CSS, namn visas på egen rad under brand

Integrationer & API

  • Claude API-nyckel — ny setting i Integrationer (med env_fallback)
  • Claude-modell — konfigurerbar dropdown (Haiku/Sonnet/Opus)

Worker-skalning

  • Celery concurrency ökad från 4 till 8 workers (-c 8)

---

v0.11.0 Pricing Engine Port & Shopify Push Fixes 2026-03-10

Pricing Engine — Fullständig port från gamla PIM

  • Ny prismotor (nexus/services/pricing_engine.py) — portad 1:1 från gamla PIM:s beprövade logik
  • Arbetar i SEKbase_price-regel konverterar EUR→SEK först, sedan alla beräkningar i SEK
  • Regelkedja — regler appliceras i prioritetsordning, varje regels output matas till nästa
  • stop_processing-flagga — kan stoppa regelkedjan efter en specifik regel
  • 5 regeltyper: base_price (EUR→SEK), percentage_markup (×faktor), fixed_amount (+belopp), margin, currency_conversion
  • Prisflöde: Inköp EUR → ×11.50 (SEK) → ×1.575 markup → +10 kr om <150 SEK → ×1.12 moms → avrundning uppåt
  • Brand overrides: exkludering (OLY, Hairtamin) och custom margin (margin_multiplier)
  • BioU Yango/Ostrovit: ×1.89 istället för ×1.575 (via brand-specifika regler med conditions)

Nya modellfält (SupplierPricingRule)

  • name (String 200) — regelnamn
  • description (Text) — regelbeskrivning
  • percentage_value (Float) — t.ex. 1.575 för 57.5% markup
  • fixed_amount (Float) — t.ex. 10 kr lågpristillägg
  • currency_from / currency_to (String 10) — valutakonvertering (EUR→SEK)
  • stop_processing (Boolean) — stoppa regelkedjan efter denna regel
  • BrandPricingOverride: nytt fält margin_multiplier (Float)

Shopify Push Fixes

  • Priser i SEK direktcalculated_sell_price_incl lagras nu i SEK (inte EUR), ingen konvertering vid push
  • _sell_price_to_sek() borttagen — heuristiken för EUR/SEK-detektering behövs inte längre
  • mapper.py — förenklad _variant_pricing(), använder pris direkt
  • safe_sync.py — borttagen eur_to_sek()-konvertering vid diff-update och variant-push
  • push_readiness.py — förenklad prisvalidering, ingen EUR/SEK-heuristik

Shopify Content & Metadata

  • Metafield-push via POST — separata POST-anrop per metafield (Shopify REST API ignorerar metafields i PUT)
  • Korrekta namespaces: c_f/ingrediensforteckning, next_cart/short_description
  • Compare-at price — RRP som compare-at, bara om ≥15% högre än sellpris
  • Taxonomi-kategori — statisk mappning via taxonomy_mapping.json (60+ kategorier)
  • Collection-matchning — ordöverlappning istället för substring, min 3 tecken, exkluderar "P"/"All"/"Rabatterade produkter"

Alembic Migration

  • b2c3d4e5f6g7 — alla nya kolumner för pricing engine port

Batch-omprissättning

  • 18 364 produkter omprissatta med nya regler
  • 638 utan inköpspris (förväntat)
  • Verifierade beräkningar matchar gamla PIM exakt

---

v0.10.0 Dashboard Redesign & User-Configurable Settings 2026-03-09

Dashboard Redesign

  • Ny startsida med 4 rader: nyckeltal, pipeline-funnel + feed health, leverantörer + content + kvalitet, snabbnavigering
  • 6 nyckelkort (rad 1): Aktiva produkter, Shopify-pushade, Redo att pusha, Prissatta, Grupper, Leverantörer
  • Shopify pipeline-funnel (rad 2): visuell tratt från Ej pushade → Redo → Pushade med antal per steg
  • Feed health-panel (rad 2): produkter i grace period, försvunna offers, stock=0, prisändringar flaggade
  • Leverantörsstatus (rad 3): aktiva/inaktiva leverantörer, offers per leverantör
  • Content-komplettering (rad 3): beskrivning, SEO, ingredienser med procent
  • Kvalitetsöversikt (rad 3): medelpoäng, issues-fördelning
  • get_product_stats() — utökad från 7 till ~20 nyckeltal

User-Configurable Settings System

  • Ny nexus/services/settings_service.py — central tjänst med SETTING_DEFINITIONS dict
  • 15 inställningar i 5 grupper: Prissättning, Import, Feed & Lager, AI & Pipeline, Shopify
  • Auto-renderad UI — nya inställningar syns automatiskt på settings-sidan bara genom att lägga till i SETTING_DEFINITIONS
  • Stöd för: number (med min/max/step), select (dropdown), toggle (boolean)
  • API: GET /settings/ (lista med definitioner), GET /settings/definitions, PUT /settings/{key}, PUT /settings/ (bulk)
  • Ersätter hårdkodade värden i pricing_engine.py (max_sell_price_sek) och import_engine.py (price bounds)
  • Designprincip: alla nya features exponerar sina viktigaste parametrar som inställningar

Settings-sida Redesign

  • 3 flikar: Konfiguration, Blocklista, Brand Mappings
  • Konfigurationsfliken renderar grupper med färgkodade sektioner automatiskt från SETTING_DEFINITIONS
  • Bulk-sparning — sparar alla ändrade inställningar i ett anrop

---

v0.9.0 Feed Presence Management & Production Hardening 2026-03-09

Feed Presence Management — Ny Service

  • Ny nexus/services/feed_presence.py (~280 rader) — central logik för produkter som försvinner/återkommer
  • deactivate_missing_offers() — deaktiverar offers som inte synts i senaste import
  • Safety check: skippar om import_count < 50% av senaste lyckade (skydd mot partiella feeds)
  • Extra safety: skippar alltid om import_count == 0
  • _handle_offer_loss() — byter till billigaste aktiva leverantör eller startar grace period
  • detect_and_reprice_changed() — auto-omprisa vid små prisändringar (<20%), flagga stora (>20%)
  • check_grace_periods() — daglig task: nolla stock direkt, draft efter grace period
  • reactivate_product() — återaktivera produkt, köa Shopify-återställning

8 Scenarion Hanterade

#ScenarioÅtgärd
11 av N leverantörer tappar produktDeaktivera offer → byt leverantör → omprisa
2Flera (inte alla) tapparCascading offer-förlust
3ALLA tapparGrace period → stock=0 → draft efter grace
4Återkommer under graceAvbryt grace → återaktivera → omprisa
5Återkommer efter deaktiveringÅteraktivera → omprisa → Shopify active (om PUSHED)
6Inprisändring<20%: omprisa, >20%: flagga
7Stock=0 men i feedUppdatera stock → grace i DAGAR
8Partiell/tom feedSafety check → skippa deaktivering

Nya Modellfält

  • Supplier: grace_period_days, min_feed_count_ratio, last_successful_import_count, price_change_alert_pct
  • SupplierOffer: disappeared_at, stock_zero_since, previous_wholesale_euro, price_change_flagged
  • Product: grace_period_started, shopify_stock_zeroed_at, shopify_drafted_at
  • ImportResult: seen_eans: set[str]

Import Engine — Prisändringsdetektering & Stock-spårning

  • import_engine.pyoffer.previous_wholesale_euro sparas INNAN nytt pris sätts
  • Stock-spårning: offer.stock_zero_since = now vid stock → 0, rensas vid stock > 0
  • result.seen_eans.add(ean) — spårar alla EAN som synts i importen
  • Reaktivering rensas: grace_period_started, shopify_stock_zeroed_at

Import Task Integration

  • Fas 1.5deactivate_missing_offers() körs efter import, innan commit
  • Fas 3detect_and_reprice_changed() körs efter best supplier selection
  • Daglig taskdaily-grace-period-check via Celery Beat (kl 08:00)

ShopifyClient — 4 nya metoder

  • set_product_status(product_id, status) — PUT active/draft/archived
  • get_inventory_item_id(variant_id) — GET variant inventory_item_id
  • get_location_id() — GET primary location (cachad)
  • set_inventory_level(inventory_item_id, location_id, quantity) — POST inventory_levels/set

Centraliserat Exception-system

  • Ny nexus/exceptions.pyPIMError (400), NotFoundError (404), ConflictError (409), ShopifyError (502), ValidationError (422)
  • Exception handler i app.py — returnerar JSON {"detail": ...} med rätt statuskod
  • Ersätter inkonsekvent felhantering (ValueError, bare 500:or)

Redis Lock — Fail Closed

  • safe_sync.py_acquire_group_lock() kastar nu redis.ConnectionError istället för att fortsätta utan lås
  • Förhindrar duplicerade Shopify-produkter vid Redis-avbrott

Celery Task Timeouts

  • Alla tasks har nu time_limit: 300s (standard), 600s (batch), 1800s (pipeline), 3600s (full import/pipeline)
  • Förhindrar tasks som hänger i evighet

Säkerhet

  • config.pysecret_key kräver nu env-variabel (ingen osäker default)
  • .gitignore — ny fil, täcker .env, __pycache__, venv, IDE, OS, logs

Databasindex & Migration

  • ix_products_grace — partial index på grace_period_started
  • ix_offers_active_supplier — composite index (supplier_id, is_active)
  • ix_offers_disappeared — partial index på disappeared_at
  • Alembic migration a1b2c3d4e5f6 — alla nya kolumner + index

---

v0.8.0 Description Prompt & Move Conversion 2026-03-08

Description Prompt (portad från gamla PIM)

  • nexus/ai/prompts.pyENRICH_DESCRIPTION: inga generiska mall-exempel, hälsopåståenderegler
  • Minimum 1000 tecken enforced i step_description() — kortare avvisas och genereras om
  • Befintliga beskrivningar < 1000 tecken behandlas som "ej tillräckligt bra" → regenereras
  • Hälsopåståenden: NEJ till "botar/behandlar/förebygger", NEJ till "anti-aging/anti-inflammatorisk"
  • OK: EU-godkända påståenden som "bidrar till normal funktion av immunsystemet"

Move Parent-to-Variant Conversion (portad från gamla PIM)

  • move_variants() — beräknar variant_name via strip_parent_from_name() vid flytt
  • Rensar ärvd description (om inte manuellt redigerad) när produkt blir variant
  • Barn omparenteras till target med loggning

---

v0.7.1 Column Search Filter & Rename Edge Case Fix 2026-03-07

Column Search Filters Out Parent Products

  • search_products() — skips parent products without EAN that have active children
  • Parents are group headers, not draggable variants — they should never appear in per-column search results
  • Fetches 2x limit to compensate for filtered-out results

Rename Fix for Nested Parents (Both Child and Parent)

  • do_action("rename") — checks has_children before deciding what to update
  • Pure variants (child, no children): update variant_name only
  • Products with children (column headers): always update name
  • Products that are both child AND parent: update both name and variant_name
  • Fixes bug where renaming a column header that was also a child only updated variant_name, making it look like nothing changed

---

v0.7.0 Stale ID Validation, Auto-Link at Approve, Move Re-Parent & VO Fixes 2026-03-07

Stale Shopify ID Validation in push_group

  • _push_group_inner() — validates all Shopify product IDs via GET before using them
  • Stale IDs (404 / unreachable) are cleared from parent + variants automatically
  • _clear_stale_shopify_id() — helper that resets shopify_product_id, variant_id, handle and push_status
  • Prevents "Invalid Product" errors when PIM references deleted Shopify products

Approve Auto-Links Unlinked Variants

  • approve_group() accepts optional variant_ids parameter
  • Variants shown in VO workspace but not yet DB-linked are auto-linked to parent before approval
  • ApproveRequest schema updated with variant_ids: list[int] | None
  • Frontend sends all workspace variant IDs in approve request

Move Re-Parents Children

  • move_variants() — when moving a product that has children, re-parents children to target instead of leaving them orphaned
  • Prevents broken parent chains when reorganizing groups

Rename Updates variant_name for Child Products

  • do_action() rename — if product has parent_product_id, updates variant_name (not name)
  • Protects variant_name via ManualEditProtection
  • Parent products still update name as before

VO Workspace Cache Validation

  • validateWorkspaceCache() — on page load, validates each cached group exists via API
  • Stale groups (deleted/deactivated parents) are silently removed from workspace
  • Remaining groups are refreshed with latest DB data

VO Filtered List Index Fix

  • renderGroupsList() — uses allGroups.indexOf(group) instead of filtered array index
  • Fixes wrong group opening when clicking items in a filtered list view

---

v0.6.0 Handle Fix, Delete vs Archive, VO UX Improvements 2026-03-06

Handle Fix After Group Push

  • _create_group_product() — kollar om Shopify auto-genererade fel handle (t.ex. -1 suffix från arkiverad kollision)
  • Retry med delay (3 försök, 2s mellan), redirect från fel handle till rätt
  • Löser problem med handles som now-foods-cod-liver-oil-2 istället för now-foods-flax-oil

Delete Instead of Archive in Consolidation

  • _consolidate_group() — raderar nu gamla Shopify-produkter istället för att arkivera
  • Frigör handles omedelbart → inga -1/-2 suffix på nya produkter
  • Fallback till arkivering om radering misslyckas

VO UX — Info Button & EAN Layout

  • Info-knapp (ℹ) alltid synlig bredvid leverantörslänk och rename (inte längre i ⋮-menyn)
  • EAN-kod visas på egen rad under ikonerna i monospace — ger mer plats för variantnamn
  • Drag-and-drop ghost fix — varianten tas bort från källgruppens data omedelbart vid flytt

---

v0.5.0 Duplicate Prevention, EAN Conflict Check & Per-Parent Locking 2026-03-06

EAN Conflict Check at VO Approve

  • _check_ean_conflicts() — kontrollerar om varianters EAN redan finns på andra Shopify-produkter innan godkännande
  • Blockerar approve med detaljerad info om vilka varianter som krockar och vilken Shopify-produkt de pekar på
  • force=True-parameter — användaren kan tvinga godkännande, rensar gamla Shopify-länkar först
  • Frontend confirm-dialog — visar konflikter med EAN + produktnamn, frågar om force

Duplicate Variant Name Dedup

  • group_to_shopify_payload() — detekterar duplicerade variantnamn och lägger till [EAN]-suffix
  • Shopify avvisar varianter med samma namn — denna fix hanterar t.ex. "Hazelnut - 908g" som finns i flera storlekar

Per-Parent Redis Lock

  • _acquire_group_lock() / _release_group_lock() — Redis-baserat lås per parent_id (TTL 120s)
  • push_group() — tar lås innan push, hoppar över om lås redan finns (parallell push pågår)
  • Förhindrar att parallella Celery-tasks skapar duplicerade Shopify-produkter
  • Fail-open: om Redis är nere, fortsätter utan lås

Fix: "All Variants on Same Shopify Product" Case

  • push_group() — nytt scenario: alla varianter pekar på samma Shopify-produkt men parent saknar shopify_product_id
  • Länkar parent istället för att skapa ny Shopify-produkt (undviker dubblett)

---

v0.4.0 Shopify Consolidation, Re-Approval Logic & VO UX 2026-03-06

Re-Approval Logic (smart content preservation)

  • _has_good_content() — kollar om parent redan har bra AI-genererat content (description_is_ai + description + short_description + seo_title)
  • _clear_stale_content() skippar nu rensning om parent redan har bra content — hanterar "lägg till ny variant i befintlig godkänd grupp" utan att radera befintligt content
  • force=True-parameter — tvinga rensning vid strukturella ändringar

Shopify Group Push (batch — 1 API-anrop)

  • _create_group_product() — skapar 1 Shopify-produkt med ALLA varianter i ETT POST-anrop (istället för N separata)
  • group_to_shopify_payload() i mapper.py — bygger payload med alla varianter, options, SEO, ingredienser
  • _detect_option_name() — auto-detekterar "Smak", "Storlek" eller "Variant" från variantnamn
  • _upload_variant_images() — laddar upp bilder post-creation kopplade till specifika Shopify-varianter via variant_ids
  • push_group() hanterar 3 scenarion: ny batch-push, lägg till variant, konsolidering

Shopify Group Consolidation

  • _consolidate_group() — upptäcker varianter spridda på flera Shopify-produkter och konsoliderar till EN

1. Samlar gamla handles för redirects

2. Skapar ny grupperad produkt (batch, alla varianter i 1 anrop)

3. Arkiverar gamla Shopify-produkter

4. Skapar 301-redirects från gamla handles till ny

5. Fixar handle med retry + delay (Shopify behöver tid att frigöra handle)

6. Redirect från auto-genererad fel-handle till korrekt handle

  • Nya ShopifyClient-metoder: archive_product(), create_redirect(), update_product_handle(), delete()

Auto-Push Bugfixes (from old PIM)

  • _try_auto_push_group() använde push_products (individuell push) istället för push_group → skapade separata Shopify-produkter istället för en grupperad
  • Ny Celery-task push_group_task — dedikerad task för grupp-push, anropas av _try_auto_push_group()
  • enrich_then_push Phase 0 — respekterar nu _has_good_content(), skippar content-rensning vid re-approval
  • enrich_then_push Phase 2 — återställer APPROVED-status före push (push readiness recalc kunde sätta READY istället)

VO Design — Quick Actions

  • Leverantörslänk (↗) och redigera (✏) alltid synliga — inte längre dolda i kebab-menyn
  • Sökresultat stannar öppna — vid sökning kan användaren lägga till flera grupper utan att sökresultaten stängs
  • Tillagda grupper markeras med ✓ och gråas ut i sökresultaten

---

v0.3.0 VO Auto-Push, Content Enrichment & Shopify Duplicate Prevention 2026-03-06

VO Approve → Enrich → Push (automatiskt flöde)

  • approve_group() rensar nu gammal content (beskrivning, ingredienser, SEO, tags, handle) på parent innan push
  • Ny _clear_stale_content() — rensar content-fält utan att röra namn/gruppering (skyddat av ManualEditProtection)
  • _try_auto_push_group() och _try_auto_push_standalone() triggar nu enrich_then_push Celery-task om content saknas, istället för att blockera
  • Ny Celery-task enrich_then_push (shopify_tasks.py):

1. Kör enrichment pipeline (steg 8-15: beskrivning, ingredienser, SEO, kategori, pris)

2. Räknar om push readiness

3. Pushar till Shopify

  • approve_variants() gör samma sak för enskilda varianter och standalone-produkter
  • FIELD_TO_PIPELINE_STEP-mappning (push_readiness.py) används för att bestämma vilka pipeline-steg som behövs

Shopify Duplicate Prevention

  • Ny _find_family_shopify_id() — kollar parent + syskons shopify_product_id innan ny Shopify-produkt skapas
  • Ny _add_variant_to_shopify_product() — lägger till PIM-produkt som variant på befintlig Shopify-produkt (via REST API)
  • Stale cache retry — vid tomt API-svar: rensa cache + försök igen
  • push_group() inkluderar "added_variant" i success-count

is_variant-bugg på parents

  • Parents med is_variant=True — produkter som promotas till parent via VO drag-and-drop behöll is_variant=True, vilket fick enrichment-steg 8-14 att skippa dem
  • Fix i link_variant() och move_variants() — sätter target.is_variant = False vid länkning
  • Fix i approve_group() — sätter parent.is_variant = False vid godkännande
  • Ny _fix_variant_flag_on_parents() (pipeline.py) — hittar och fixar parents med is_variant=True automatiskt vid pipeline-start

Nested Parent Cleanup

  • Ny _fix_nested_parents() (pipeline.py) — hittar produkter som har barn men även egen parent_product_id (dubbelroll) och bryter parent-länken
  • Körs automatiskt i pipeline-start via _fix_orphan_parents()

VO Design

  • Actions visas vid hover — knappar (detaljer, byt namn, ny grupp, avlänka, avaktivera) dolda som standard, visas vid hover
  • Mer plats för variantnamn — actions flyttade från vc-top till egen rad under kortet

---

v0.2.0 Pipeline Pause/Resume, Prisskydd & Kvalitetshöjning 2026-03-05

Pipeline Pause & Resume

  • Ny modell PipelineRun (nexus/db/models/task.py) — persistent körningshistorik med status, checkpoint och progress
  • Pause/resume/cancel — Redis pause-flagga + DB-checkpoint gör att pipeline kan pausas mellan steg och återupptas
  • Nya API-endpoints: POST /pause/{run_id}, POST /resume/{run_id}, POST /cancel/{run_id}, GET /runs
  • Import pause — samma mekanism för import-körningar (pausar mellan leverantörer)
  • UI: Paus/Återuppta/Avbryt-knappar i pipeline-sidan, körningshistorik-tabell
  • Page refresh bevarar statecheckActiveRun() återställer progress och polling vid sidladdning

Prisskydd (tre nivåer)

  • Import (import_engine.py):
  • wholesale ≤ 0 → produkt avvisas
  • wholesale < 0.50 EUR → pris stripps (produkt behålls)
  • wholesale > 500 EUR → pris stripps
  • RRP > 2 000 EUR → stripps
  • Pricing engine (pricing_engine.py):
  • Beräknat pris > 15 000 SEK → blockerar med felmeddelande
  • Beräknat pris < 20 SEK → loggar varning
  • Push readiness (push_readiness.py):
  • Saknat/noll sell price → price_no_sell blockerar push
  • Saknat/noll wholesale → price_no_wholesale blockerar push
  • Sell price < 20 SEK → price_too_low
  • Sell price > 15 000 SEK → price_too_high
  • Quality audit (quality_audit.py):
  • 5 nya priskontroller: price_absurd_wholesale, price_suspicious_low_wholesale, price_absurd_sell_high, price_absurd_sell_low, price_absurd_rrp

Beskrivningskvalitet & AI-flagga

  • Nya produktfält: description_is_ai (boolean), description_rating (0-100)
  • Steg 8 sätter description_is_ai = True när AI-beskrivning genereras
  • _rate_description() — poängsätter beskrivningar (0-100) baserat på AI-status, sektioner, ordantal
  • Push readiness kräver description_is_ai = True för att tillåta push
  • Quality audit returnerar 3-tuple: (issues, score, description_rating)

Variant-hantering

  • Import: varianter får aldrig egen description (skippas vid import)
  • Steg 5 (gruppering): rensar description + short_description när produkt blir variant
  • Quality audit: flaggar varianter som har egen description (variant_has_description)

Quality Audit — utökade kontroller

  • 18 nya BAD_PATTERNS (tom HTML, platshållare, AI-läckage på engelska)
  • 13 nya JUNK_VARIANT_NAMES
  • Nya kontroller: desc_not_ai, desc_ai_incomplete, desc_is_name, desc_empty_html, desc_sku_dump, vn_is_ean, vn_is_url, vn_too_long, vn_bad_pattern

Migreringar

  • scripts/create_pipeline_runs_table.sql — PipelineRun-tabell
  • scripts/add_description_fields.sql — description_is_ai + description_rating kolumner

---

v0.1.0 Initial Build 2026-03-05

Komplett nybygge av PIM-systemet. Ersätter gamla PIM (290,000 rader, 668 filer) med modern arkitektur.

Tech Stack

  • Backend: FastAPI (async) → ersätter Flask
  • Databas: PostgreSQL 16 (asyncpg) → ersätter SQLite
  • ORM: SQLAlchemy 2.0 (async mapped_column) → ersätter SQLAlchemy 1.x
  • Migrering: Alembic → ersätter manuella ALTER TABLE
  • Jobbkö: Celery + Redis → ersätter threading + globala dicts
  • Cache: Redis → ersätter filbaserad JSON-cache
  • Frontend: HTMX + Jinja2 + Tailwind CSS → ersätter inline JS/CSS + jQuery
  • AI: GPT-4o-mini (samma)
  • Auth: JWT + bcrypt → ersätter ingen auth
  • Deploy: Docker Compose → ersätter lokal python app.py

Struktur (117 filer, ~6,700 rader)

  • nexus/db/models/ — 10 modeller (Product med 45 kolumner, JSONB manual-edit)
  • nexus/api/ — 12 API-routers (max 250 rader/fil)
  • nexus/services/ — 8 tjänster (pricing, quality, push_readiness, etc.)
  • nexus/importers/ — 5 filer (base, field_mapping, import_engine, powerbody, xml_feed)
  • nexus/ai/ — 10 filer (client, prompts, pipeline, smart_naming, 6 steg-filer)
  • nexus/shopify/ — 7 filer (auth, client, cache, mapper, safe_sync, diff, backfill)
  • nexus/tasks/ — 6 Celery-tasks (import, pipeline, pricing, shopify, quality, scheduled)
  • nexus/utils/ — 5 verktyg (ean, currency, text, auto_grouping, pagination)
  • nexus/templates/ — 10 templates (base + Tailwind sidebar + HTMX)
  • scripts/ — 2 (migrate_from_legacy, run_pipeline)

Nyckelförbättringar vs gamla PIM

ProblemLösning
app.py = 7,468 rader, 118 routes12 router-filer, max 250 rader/fil
26 boolean *_manually_editedEN JSONB-kolumn manually_edited_fields
EAN-normalisering på 3 ställenutils/ean.py (1 ställe)
SupplierOffer upsert på 3 ställenimport_engine.py (1 ställe)
_call_ai() på 3 ställenai/client.py (1 ställe)
338 inline script-blockapp.js + HTMX
Ingen authJWT + bcrypt
SQLite concurrency-problemPostgreSQL
Bakgrundstrådar + globala dictsCelery + Redis
Ingen migreringAlembic
debug=True i produktionPydantic Settings + .env

Datamodell

  • Product: ~45 kolumner (ned från ~95), JSONB manually_edited_fields, PushStatus enum
  • SupplierOffer: 1 EAN = 1 Product, N offers (UNIQUE product_id+supplier_id)
  • User: JWT auth med email, bcrypt-hash, roller (admin/viewer)
  • Nya index: parent_product_id, push_status, brand, active_supplier_id
  • CheckConstraint: id != parent_product_id

Pipeline (18 steg)

1-4: Namnhantering (normalisera, bracket, brand, variant-extraktion)

5-7: Gruppering (AI auto-gruppering, variant-namngivning, Shopify-länkning)

8-14: AI-berikning (beskrivning, ingredienser, SEO, taggar, kategorisering)

15: Auto-prissättning

16-17: Namnkomplettering + titel-normalisering

18: Kvalitetsgranskning (36+ regelbaserade kontroller)

Events

  • product.created → pipeline steg 1-7 (Celery)
  • product.price_changed → auto-prisberäkning
  • product.enriched → push readiness check
  • product.push_ready → auto-push om approved

Kommandon

  • docker compose up — starta alla containers
  • make migrate — kör Alembic-migrering
  • make test — kör tester
  • python -m scripts.migrate_from_legacy — migrera från gamla PIM
  • python -m scripts.run_pipeline --steps 1-18 — kör pipeline