1.7.4
⚙️ Patch 1.7.4 — Repricer fixes, Tickets AI workflow, and CI overhaul¶
🚀 Summary¶
- Deleting a base repricer article now cascades to all attached channel details and Otto pricing history — no orphaned rows left behind.
- Channel detail status is now reconciled with base-article stock across repricer channels — fixes stale OOS on Otto (and others) when inventory is available again.
- Tickets service: n8n generates AI drafts; humans review in the UI; Send to Plenty posts directly via Plenty Messenger API (not n8n).
- n8n workflow settings moved from env vars to database + Credentials UI.
- Added 37 new repricer tests (detail CRUD, UVP, delete cascade, stock/status reconciliation); multi-repricer suite now 76 tests.
- GitHub CI rebuilt: faster parallel jobs, no full Docker stack for unit tests, fixed test discovery under
app/tests. - Invoice service: fixed confirmation endpoint using wrong
Invoicefield; stale API tests updated.
✨ Improvements¶
Repricer — cascade delete & tests¶
- Repricer cascade delete (
repricer_article.remove): - New helper
remove_all_channel_detailsruns before the base article is deleted. - Removes Shopify, Kaufland, Otto, FBA, and all FBM rows for the article (including multiple FBM entries per SKU).
- Clears
item_pricing_historyrows for the SKU. - Existing
DELETE /{sku}endpoint uses this path — no API change required. - Repricer test coverage (
service_multi-repricer/app/tests/): test_delete_article.py— cascade delete (all channels + pricing history + extra FBM row); unknown SKU no-op.test_details_crud.py— create / update / delete for Shopify, Kaufland, Otto, FBM, FBA; FBM multi-row behaviour; single-channel delete leaves base article and other channels intact.test_uvp.py—set_uvpfull/min-only/max-only updates, selective channel flags, propagation to bundle children,child_skusreferences, and variant SKU patterns;remove_uvpclears UVP fields, re-enablesuse_min_max_rules, selective remove flags, prices unchanged on remove.test_article_status.py— stock-based status reconciliation (oos↔active), multi-channel reconcile, monitoring skip rules.
Repricer — stock / status reconciliation¶
- New
services/article_status.pycentralises detail status from basestock: stock == 0→oos(exceptpaused).stock > 0and statusoos/unknown→active.- Preserves
paused,error, andupdate_neededwhen stock is available. reconcile_article_channel_detailsupdates Shopify, Kaufland, Otto, FBM (all rows), and FBA in one pass.- Applied during:
sync_base— after Plenty stock sync (replaces OOS-only marking).- Otto sync — refreshes Otto quantity for existing SKUs and reconciles status.
- Otto price monitor — reconciles before skip checks.
- FBM / FBA recalculate — reconciles before OOS skip.
Tickets — n8n generate, human review, Plenty send¶
- Workflow split:
| Step | Handler |
| -------------- | ---------------------------------------------------------- |
| Generate draft | n8n webhook (
POST /workflow/generate) | | Save draft | n8n callback (action: generate_complete) | | Human review | Tickets UI (edit content, recipient, reference, whisper) | | Send reply | Backend →POST /rest/messagesvia Plenty credentials | - AI draft storage on
ticketstable (one draft per ticket — no separateai_responsestable): ai_response_content,ai_response_message_uuid,ai_response_recipient,ai_response_reference,ai_response_whisper,ai_response_status.- API (ticket-scoped):
GET /tickets/{uuid}/ai-responses— 0 or 1 draft from ticket row.PUT /tickets/{uuid}/ai-response— update draft.POST /tickets/{uuid}/ai-response/send— send to Plenty.DELETE /tickets/{uuid}/ai-response— clear draft.- Frontend: Generate via n8n + Send to Plenty button; Credentials tab documents the split flow.
- Removed in-process OpenAI integration (credentials tab, endpoints,
openaidependency).
Tickets — n8n settings in UI¶
- New table
n8n_workflow_settings(singleton row) stores webhook URL, token, callback URL, and enabled flag. - Credentials → n8n Workflow tab: editable form with save (token never returned on GET; leave blank to keep existing).
GET/PUT /tickets/workflow/settings— load and persist configuration.- Env vars
N8N_TICKETS_*remain as fallback when DB fields are empty.
CI pipeline¶
- Split into three parallel jobs: backend tests, frontend lint, Docker Compose config validation.
- Concurrency — outdated runs cancelled on new push.
- Shared test env:
PYTHONPATH, in-memory SQLiteDATABASE_URL, test secrets. - Pip/npm caching across backend and service
requirements.txtfiles. - Webhooks only fire after all jobs succeed on
main/master. - Removed slow
docker compose up --buildfrom the test path (Compose is validated withdocker compose configonly). run_tests.sh: finds tests underapp/testsandtests/; runsbackend/app; setsPYTHONPATH; LF line endings via.gitattributes.- Backend deps:
pytest-asyncioadded tobackend/requirements.txt.
🐛 Bug Fixes¶
- Stale OOS on repricer channels (especially Otto): articles with
stock > 0could keepooson channel details and were skipped by price monitoring — status is now reconciled on sync and before monitor/recalc. - Otto monitor skip logic: no longer excludes items solely because detail status was stale
oos; reconciles from stock first. - Otto monitor quantity: uses scraped Otto quantity or base-article stock when deciding OOS vs active.
- Orphaned repricer details after delete: removing a base article left Shopify/Kaufland/Otto/FBM/FBA rows and pricing history in the DB — now deleted in the same transaction path.
- Tickets send path: previously triggered n8n or falsely marked sent without posting to Plenty — now sends via
Plenty_Manager.create_reply_message(). - Tickets AI storage: code assumed a separate
ai_responsestable that does not exist in production — draft data lives onticketsonly. - CI false green:
run_tests.shonly looked for a top-leveltests/folder, soservice_multi-repricer/app/tests(and similar layouts) were never executed in CI. - Invoice confirmation update:
update_invoice_confirmationpassedrow=instead ofsheet_row=to theInvoicemodel — caused 500 errors; HTTP 400 responses from the handler are no longer swallowed as 500. - Invoice tests: mocks updated for current OAuth callback response shape, reauthorize signature, and removed
/download/test-progressendpoint (2 tests skipped).
📁 Files changed¶
Backend (service_multi-repricer)
app/crud/repricer_article.py—remove_all_channel_details, cascade inremove().app/services/article_status.py— stock/status reconciliation helpers.app/api/v1/endpoints/repricer_articles.py—sync_basereconcile hook.app/api/v1/endpoints/otto_articles.py— sync quantity refresh, monitor reconcile, effective quantity.app/api/v1/endpoints/fbm_articles.py,fba_articles.py— reconcile before OOS skip.
Backend (service_tickets)
app/services/ticket_ai_draft.py— ticket-row draft mapping.app/services/plenty/plenty_manager.py—create_reply_message().app/services/n8n_workflow.py— DB-backed config with env fallback.app/schemas/n8n_workflow.py,app/crud/n8n_workflow.py— n8n settings persistence.app/api/v1/endpoints/tickets.py— generate/send/update AI draft on ticket.app/api/v1/endpoints/n8n_webhooks.py— generate callback writes to ticket.app/schemas/tickets.py— AI draft columns onTicketmodel.
Frontend
src/pages/Tickets/Tabs/TicketsTab.jsx— review UI, Send to Plenty.src/pages/Tickets/Credentials/WorkflowInfo.jsx— n8n settings form.src/pages/Tickets/Tabs/OverviewTab.jsx— updated workflow docs.src/services/api/tickets/tickets.js— ticket-scoped AI + workflow settings API.
Tests
service_multi-repricer/app/tests/test_delete_article.pyservice_multi-repricer/app/tests/test_details_crud.pyservice_multi-repricer/app/tests/test_uvp.pyservice_multi-repricer/app/tests/test_article_status.pyservice_multi-repricer/app/tests/test_recalculate_channels.py— FBM OOS test usesstock=0.
Backend (service_invoice)
app/api/v1/endpoints/sheets.py—sheet_rowfix, re-raiseHTTPExceptionon confirmation failure.app/tests/api/v1/test_sheets.py,test_google.py,test_download.py— mock and assertion updates.
CI / tooling
.github/workflows/ci.ymlrun_tests.sh.gitattributesbackend/requirements.txtdocker/postgres/migrations/add_ai_response_meta.sqldocker/postgres/migrations/add_n8n_workflow_settings.sqldocker/postgres/tables/n8n_workflow_settings.sqldocker/postgres/tables/tickets.sql— AI draft columns.
🔧 Database & SQL¶
Repricer
- No schema migration — cascade delete is at the application layer.
Tickets — run on server before deploy
-- AI draft fields on tickets (one draft per ticket)
ALTER TABLE public.tickets
ADD COLUMN IF NOT EXISTS ai_response_message_uuid text,
ADD COLUMN IF NOT EXISTS ai_response_recipient text,
ADD COLUMN IF NOT EXISTS ai_response_reference text,
ADD COLUMN IF NOT EXISTS ai_response_whisper boolean DEFAULT false,
ADD COLUMN IF NOT EXISTS ai_response_status text;
-- n8n workflow settings (singleton)
CREATE TABLE IF NOT EXISTS public.n8n_workflow_settings (
id integer PRIMARY KEY DEFAULT 1,
webhook_url text,
webhook_token text,
callback_url text,
is_active boolean DEFAULT true,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now(),
CONSTRAINT n8n_workflow_settings_singleton CHECK (id = 1)
);
INSERT INTO public.n8n_workflow_settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING;
See docker/postgres/migrations/add_ai_response_meta.sql and add_n8n_workflow_settings.sql.
Usage notes¶
Repricer
- Run repricer tests:
cd backend/service_multi-repricer python -m pytest app/tests - After deploy, run Plenty sync or Otto sync to fix existing articles with stale OOS status; the next monitor cycle will include them.
- Deleting a repricer article via the API now removes all channel details for that SKU.
Tickets
- Credentials → Plenty — active Plenty login with valid token/
user_id. - Credentials → n8n Workflow — webhook URL, token, callback URL (
/api/v1/tickets/webhooks/n8n). - Expand a ticket → Generate via n8n → edit draft → Send to Plenty.
- n8n callback example:
{ "action": "generate_complete", "ticket_uuid": "...", "message_uuid": "...", "content": "..." }.
CI
- Full backend suite from repo root:
chmod +x run_tests.sh ./run_tests.sh - Frontend lint:
cd frontend && npm run lint.