Commit graph

58 commits

Author SHA1 Message Date
c19637465b feat: Frappe ERP matching, pipeline model cache, ACL, stock field, specs by type
Frappe ERP:
- findExistingCustomer() on FrappeErpAdapter — two-step name+address lookup
- FrappeHttpClient: add put() method; switch invoice submit to PUT docstatus=1 (Frappe v16)
- buildItemDescription() uses specsText + inventory number + serial number
- Integration tests: find Simon Kühn, create real 1337€ invoice, cancel+delete in tearDown
- FRAPPE_GENERIC_ITEM_CODE=SKU002 added to .env.local and bin/test-integration

Pipeline — model cache:
- PhotoUploadHandler: after vision, check DB for existing article with same modelNumber
- On match: copy ebayTitle/ebayDescription/specsText/attributes, skip specs+JSON+eBay steps
- DraftArticleHandler: apply model_match data and mark job complete directly
- ArticleRepository: findCompletedByModelNumber() query

Pipeline — specs by article type:
- SpecsResearchAgent: accept attributeFields list, format as bullet list in {{fields}} var
- SpecsResearchHandler: derive attribute names from ArticleType, pass to agent
- SpecsResearchMessage: add attributeFields param
- Prompt migration: replace hardcoded laptop spec list with {{fields}} placeholder

Article:
- specsText field (nullable text column + migration)
- stock field visible on index and editable in CRUD form
- addAttributeValue()/removeAttributeValue() adder-remover pair for Symfony form binding
- AttributeValue::getArticle() getter
- AttributeValueFormType: detect required attributes from ArticleType assignments, set required=true
- ManualIngestType: add stock/quantity field (default 1, min 1)

Users / ACL:
- PermissionVoter: define named permission constants + allPermissions()
- User: getGrantedPermissions()/setGrantedPermissions() helpers
- UserCrudController: permissions checkbox group on edit form

UI / assets:
- public/css/admin/custom.css: red asterisk for required fields
- DashboardController: register custom CSS

Infra:
- PipelineJobFailureListener: mark job failed (with real error) when Messenger exhausts retries
- doctrine.yaml: exclude app.inventory_seq from schema diff
- ErpAdapterInterface: add findExistingCustomer()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 16:42:15 +00:00
cba8ebcf5e feat: Frappe customer integration tests + FrappeHttpClient get/delete
Adds GET and DELETE methods to FrappeHttpClient. Integration tests cover
create, find, not-found (wrong name), and delete against the live staging
ERPNext instance. Run with: bin/test-integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:47:20 +00:00
5927fa97c4 fix: article index row click goes to edit instead of detail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:27:23 +00:00
945f0479ca feat: collapsible attribute list on article detail via <details> element
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:22:07 +00:00
ad9cb279c9 fix: article detail — photos on top, no ID, eBay description without scrollbar
Photos field moved to first position. ID field removed entirely. eBay
description on detail uses a custom template that renders raw HTML in a
plain div (no span/title wrapper, no overflow constraints). Form view
keeps the textarea editor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:19:42 +00:00
a381ee6531 feat: strip --- delimiters from eBay text output, commercial listing prompt
Agent now extracts content between --- markers so LLM preamble/postamble
is discarded. ebay_description prompt updated for commercial listings:
no private-sale language, condition explicitly "gebraucht", 1 year
gesetzliche Gewährleistung. Device label now includes modelName.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:18:40 +00:00
14eab1ab5c fix: use Field for photos in article detail view (Collection can't be TextField)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:15:53 +00:00
ed0caea344 fix: use Field for attributeValues detail view, render ebayDescription as HTML
TextField rejects Collection values before formatValue runs. Switching to
the generic Field avoids the type check. ebayDescription now renders its
HTML tags in the detail view instead of showing raw markup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:15:32 +00:00
b908e44a6e fix: cascade delete attribute values when attribute definition is removed
FK on attribute_values.attribute_definition_id now uses ON DELETE CASCADE
so deleting an AttributeDefinition also clears its values from all
articles. Admin delete action shows a confirmation warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:02:46 +00:00
5b2a200fc2 feat: specs research agent reports corrected model number
When Tavily search results reveal that the OCR'd model number has a
character error, the specs_research prompt asks the LLM to output a
CORRECTED_MODEL_NUMBER line. The agent parses it out, stores it in the
job output, and DraftArticleHandler applies it to the article in
preference to the raw vision value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:00:41 +00:00
4515911b27 fix: combine modelName + modelNumber for specs search query
Model numbers from OCR often have character errors (G→6, O→0 etc).
Using both fields together lets the human-readable name anchor the
search even when the model number is slightly wrong.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:33:00 +00:00
32da9bb48f refactor: make prompt templates DB-only system prompts
Remove all hardcoded defaults from PromptTemplateService — the DB record
is now mandatory and render() throws if a key is missing. Admin UI
disables new/delete and makes the key field read-only so system prompts
cannot be renamed or removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:20:17 +00:00
974bd239a5 fix: prevent serial bleed into MODEL_NUMBER in vision output
Parser now strips any embedded field labels (e.g. "SERIAL: x") that the
LLM mistakenly appends to a field value. Prompt updated with a concrete
example showing MODEL_NUMBER as blank to reinforce leaving it empty when
no separate part code is visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:19:17 +00:00
fc628df42b fix: block pipeline at needs_review when no model detected
Without a model name/number specs research is pointless. Stays at
needs_review so the user can enter the model manually, then re-run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:17:25 +00:00
4d223c37c1 fix: relax vision prompt to prefer MODEL_NAME over strict name/number split
Splitting MODEL into MODEL_NAME and MODEL_NUMBER confused the vision
model into returning empty for both. Now MODEL_NAME accepts any visible
model identifier; MODEL_NUMBER is only filled when a separate part/
product code is explicitly shown on the label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:15:04 +00:00
321f6aaa05 fix: only block vision pipeline if nothing at all was readable
Previously blocked on needs_review when model name/number were empty,
even if manufacturer or serial were detected. Now proceeds to specs
research whenever any useful field was extracted, only blocking when the
nameplate was completely unreadable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:11:30 +00:00
525424a6a1 feat: extract modelName and modelNumber separately in vision pipeline
Vision prompt now distinguishes MODEL_NAME (human-readable product name,
e.g. "ThinkPad T490s") from MODEL_NUMBER (part/product code, e.g.
"20NXS0BA00"). Both fields flow through the pipeline and are written to
the article. Specs research uses model number as search subject when
available, falling back to model name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:09:26 +00:00
c10c306a5a feat: add optional modelName field to Article
Adds model_name VARCHAR(255) column to app.articles, exposes it in the
admin CRUD form (optional, hidden on index), and adds translations for
both EN and DE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:06:36 +00:00
6e17fb82a0 fix: mark pipeline complete only after eBay text is generated
DraftArticleHandler was calling markCompleted() before dispatching
EbayTextMessage, causing the SSE to fire "completed" while the article
title was still null. EbayTextHandler had no job tracking at all.

- DraftArticleHandler: recordStep('draft_article') instead of markCompleted()
- EbayTextHandler: call markCompleted() after setEbayTexts() so the job
  is only marked done once the title and description are actually written

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:01:54 +00:00
6241398390 feat: camera capture and multi-photo upload at article ingest
- Ingest page redesigned: camera modal (getUserMedia + capture fallback
  for mobile), mandatory search photo with client-side validation,
  optional extra photos grid with per-photo remove button
- Camera modal: live video preview, capture → preview → confirm/retake
  flow; stops stream on modal close; falls back to native file picker
  if getUserMedia is unavailable
- ManualIngestController: uploads extra photos via PhotoService::uploadRaw(),
  stores [{storagePathId, filename}] in job inputData as extraPhotos
- PhotoService::attachExtra(): attach already-stored file to an article
  by StoragePath ID + filename
- DraftArticleHandler: after creating the article, attaches extra photos
  in sort order; errors are best-effort (pipeline not aborted)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:16:04 +00:00
693e458e07 feat: photo gallery on article detail with upload, sort and set-main
- Add secured GET /admin/photos/{filename} to serve files from var/uploads/
- Add POST /api/articles/{id}/photos/sort for drag-and-drop reordering
- Add PhotoService::reorder() to persist new sort positions
- Add photo gallery field (onlyOnDetail) using custom Twig template:
  - Drag-and-drop reorder via SortableJS CDN
  - Click or drop to upload new photos
  - Set main (★) and delete per photo
  - Main photo highlighted with blue border + badge
- Add field.photos translation key (EN/DE)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:10:59 +00:00
0c278aefbf chore: hide UUID id column from all index/list views
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 09:04:39 +00:00
cfb5cc4ad0 feat: replace Mistral web_search with Tavily for specs research
- Add WebSearchInterface + TavilyWebSearch (POST /search, max 5 results)
- SpecsResearchAgent now fetches search results first, injects them as
  {{searchResults}} context into the prompt, then calls plain generate()
  — no dependency on model-specific web_search tool support
- Update specs_research prompt template (PHP default + DB migration) to
  use the new {{searchResults}} variable
- Wire TAVILY_API_KEY env var; register TavilyWebSearch in services.yaml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:35:52 +00:00
00dc232426 fix: fall back to plain generate() when web_search tool returns HTTP error
mistral-large-latest may not support the web_search tool type on all API
tiers; catch the exception and retry without web search so the pipeline
does not crash with needs_review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:18:54 +00:00
4739a0b1fe fix: allow Re-run AI for NeedsReview — pipeline may be stuck mid-step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 08:15:40 +00:00
6eeffadee9 fix: hide Re-run AI for NeedsReview articles — manual review required
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:57:31 +00:00
ef29c3c47f feat: disable Re-run AI when pipeline job is already active
Add hasActiveJobForArticle() to check for queued/processing jobs.
The displayIf closure hides the action while a job is running.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:56:00 +00:00
3928d29420 fix: use askConfirmation instead of setConfirmation (correct EasyAdmin API)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:54:32 +00:00
ec159d7b3a feat: DB-backed translations editable in admin
- Add Translation entity (locale/domain/key/value, unique on all three)
- Add TranslationRepositoryInterface + DoctrineTranslationRepository
- Add DatabaseTranslator decorator (#[AsDecorator]) that checks DB first
  and falls back to YAML files; clears per-request cache on setLocale()
- Add TranslationCrudController with locale/domain filters and read-only
  key/locale/domain on edit to prevent accidental renames
- Add "Übersetzungen / Translations" menu entry in DashboardController
- Migration 20260520000000: create app.translations table
- Migration 20260520010000: seed from admin.en.yaml + admin.de.yaml (204 rows)
- Flatten admin.de.yaml to dot-notation; add new keys for translation CRUD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:53:52 +00:00
a3984adbed feat: full DE/EN i18n with browser language detection and confirmation dialogs
- Add LocaleSubscriber: detects browser language, honours session override (priority 20)
- Add LocaleSwitchController: stores locale in session, linked from user menu
- Add admin.en.yaml / admin.de.yaml translation files (95 keys each)
- Wire translation fallback to EN in config/packages/translation.yaml
- Replace all hard-coded strings in CRUD controllers with TranslatableMessage
- Inject TranslatorInterface into DashboardController, ArticleCrudController,
  AIPipelineJobCrudController and PipelineStreamController; add locale switcher
  links (English / Deutsch) to the user menu
- Add confirmation dialog to "Re-run AI" and "Retry" pipeline actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:48:26 +00:00
4c16f8cd68 feat: live pipeline status notifications via SSE
Adds real-time toast notifications to all admin pages for AI pipeline
job progress. The browser subscribes to an SSE endpoint
(GET /admin/pipeline/events) which polls the DB every 2 seconds and
emits events whenever a job's step or status changes.

- AIPipelineJob gains updatedAt (migration 20260519020000), bumped on
  every state-change method, with an index for efficient polling
- AIPipelineJobRepositoryInterface/Doctrine get findUpdatedSince()
- PipelineStreamController streams SSE with per-connection dedup and
  auto-reconnect (retry: 3000); streams for 90 s then signals reconnect
- pipeline-notifications.js handles EventSource, shows colour-coded
  toasts (queued/processing/completed/failed/needs_review) and is loaded
  globally via DashboardController::configureAssets()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:36:03 +00:00
a38fe7e72d fix: use web search in SpecsResearchAgent to prevent spec hallucination
The agent was calling generate() — pure model memory — which caused Mistral
to hallucinate specs for older devices (e.g. i5-1135G7 instead of i3-3120M).
generateWithWebSearch() is now used so Mistral queries live sources.

OllamaClientInterface gains generateWithWebSearch(); OllamaClient falls back
to generate() since Ollama has no built-in search tool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:28:21 +00:00
8a22be7ba5 fix: record json_coding step and forward serialNumber in JsonCodingHandler
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:19:13 +00:00
49e36a0a06 feat: editable AI prompt templates and articleType context in specs research
All agent prompts are now stored in app.prompt_templates (migration 20260519000000)
and editable by admins via the new AI Prompts CRUD page. If no DB entry exists
for a key the hardcoded default is used automatically as fallback.

PromptTemplateService renders templates with {{variable}} substitution.
All four agents (SpecsResearch, JsonCoding, EbayText, OllamaVision) use the service.

SpecsResearchAgent now receives the articleType name (e.g. "Laptop") so the
specs prompt is scoped to the correct device category instead of being generic.
SpecsResearchHandler loads the ArticleType from the repository for this purpose.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:19:02 +00:00
b9907d6c63 feat: replace SerpApi web search with Mistral native web_search tool
MistralClient gains generateWithWebSearch() which uses Mistral's built-in
web_search tool, handling the multi-turn tool-call loop server-side.
SerpApiWebSearch and WebSearchInterface are removed — no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:49 +00:00
f3b018e048 feat: enhance Article and ArticleType admin CRUD
- ArticleCrudController: shows manufacturer, model number, photos, pipeline status;
  adds re-run pipeline action and inline attribute editing
- ArticleTypeCrudController: formatValue for required/optional attribute lists on detail page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:44 +00:00
020a5ddbc8 feat: add manual ingest form, AI status page and pipeline archive
- ManualIngestController: photo upload form that starts a new pipeline job
- AiStatusController: shows active backend config and runs live connectivity tests
- PipelineArchiveCrudController: read-only view of completed/failed jobs
- ManualIngestType / AttributeValueFormType: form types for ingest and attribute editing
- AiConfigService: encapsulates backend info and test methods for the status page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:39 +00:00
740c9a4e08 feat: enhance pipeline job admin with retry action and step detail view
- Retry button on index and detail pages for failed/needs-review/processing jobs
- Shows inventory number, current step, attempt count, created-at on index
- Detail page renders full AI step output (vision, specs, attributes)
- Filters active jobs by non-completed status via custom query builder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:33 +00:00
6d8a06f151 feat: add step tracking, retry and article lookup to AIPipelineJob
- currentStep column (migration 20260517230000) written per pipeline stage
- recordStep() stores per-step output data and updates currentStep
- resetForRetry() requeues a failed/needs-review job
- getInventoryNumber() / getStatusLabel() helpers for admin display
- markCompleted() now merges rather than replacing outputData
- findByArticleId() added to repository for re-run lookups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:29 +00:00
9e59123683 feat: add manufacturer/model fields to Article and propagate through pipeline
- Article entity gets manufacturer and modelNumber columns (migration 20260517240000)
- Vision output (manufacturer/model) is written to the article in DraftArticleHandler
- manufacturer is forwarded through SpecsResearchMessage so the specs prompt can use it
- serialNumber is now threaded through JsonCodingMessage and ValidationMessage
- EbayTextHandler pulls specsText from the job's stored output data
- DraftArticleHandler supports re-run mode (existing article reuse) and sets Draft status
- ArticleType and AttributeValue get __toString() for form/display use
- ArticleService exposes reserveInventoryNumber() publicly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 07:18:24 +00:00
2cfc5e8f17 feat: add console commands, remaining migrations and config wiring
Console commands: CreateUser (interactive), BackupCommand, RotateLogsCommand.
Migrations 20260514: initial schema for app/logs schemas.
Config: register new bundles, Doctrine schema filter, Kernel micro-kernel
adjustments, deleted unused api.yaml route file and www.conf override.
Application service and API controller updates for the full article lifecycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:44:11 +00:00
f310643064 feat: add EasyAdmin CRUD controllers, security controller and templates
CRUD controllers for Article, ArticleType, AttributeDefinition,
ArticleTypeAttribute, AIPipelineJob, Order, Customer, Invoice, User
and LogEntry. SecurityController handles login/logout; TotpSetupController
manages 2FA enrollment. API controllers for pipeline and orders.

Admin dashboard template and Twig base layout included.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:44:03 +00:00
487c7f8da1 feat: add security layer, domain repositories and infrastructure services
ApiKeyAuthenticator, PermissionVoter and UserProvider implement
Symfony Security for the API (Bearer token) and admin (session) flows.
Domain repository interfaces added for ApiKey, User, AIPipelineJob and
Invoice; Doctrine implementations provided.

Also adds DatabaseLogHandler for structured DB logging, SerpApiWebSearch,
and the LogEntry domain entity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:43:58 +00:00
46cff4553f feat: add eBay and Frappe channel adapters with order infrastructure
eBay adapter covers OAuth, inventory API, fulfillment API, taxonomy
service and webhook signature verification. Frappe ERP adapter wraps
the Frappe HTTP client for order/invoice sync.

Includes CustomerResolver, InvoiceMailer, and the EbayWebhookController
for inbound eBay marketplace notifications.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:43:52 +00:00
fddfd920f5 feat: add Symfony Messenger pipeline with AI agents and handlers
Messages and handlers for the full AI pipeline:
DraftArticle → Validation → SpecsResearch → PhotoUpload → EbayText →
JsonCoding → PublishToChannel / DeactivateListingMessage / TrackingPush /
UpdateStockOnChannels / OrderReceived.

OllamaClient and OllamaClientInterface provide the base LLM backend.
AI agents (EbayTextAgent, JsonCodingAgent, OllamaVisionAgent,
SpecsResearchAgent) wrap the client with task-specific prompts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:43:47 +00:00
838b96eb14 feat: required/optional attribute sections in ArticleType form
Promote article_type_attributes join table to ArticleTypeAttribute entity
with a required boolean flag. ArticleType gains virtual form properties
(requiredAttributeDefs / optionalAttributeDefs) reconciled via
applyAttributeAssignments() on persist/update.

Admin form shows two Tom Select multi-selects; a small JS module
(article-type-attr-sync.js) listens for ea.autocomplete.connect events
and keeps the two lists mutually exclusive in real time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 22:43:42 +00:00
f915bba966 feat: admin panel, Mistral client, attribute management, API key command
- Fix EasyAdmin 5 routing: #[AdminDashboard] attribute + easyadmin.routes loader
- Fix login: _username/_password field names, CSRF stateless token config,
  sessions directory, Opcache reload after cache:clear
- Add MistralClient behind OllamaClientInterface — switchable via services.yaml alias
- Add Attribute CRUD with EnumType form + ChoiceField display (enum-safe rendering)
- Add Article Type CRUD with AssociationField for attribute assignments
- Add app:api-keys:create console command (bcrypt-hashed, never stored as plaintext)
- Add redis ext to Docker image + symfony/redis-messenger, start workers
- Translate all UI strings to English
- Add tests: MistralClient, ApiKey, CreateApiKeyCommand, StringArrayType,
  ArticleTypeCrudController, AttributeDefinitionCrudController (82 tests total)
- Update design doc: tech stack, AI backend switching guide, ops section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:15:13 +00:00
0706fdad58 feat: add self-service password change page at /account/password
Validates current password, enforces 8-char minimum, links from
EasyAdmin user menu. Also fixes route loader (attribute scan instead
of broken easyadmin.routes type).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 18:41:45 +00:00
d51efa057b feat: add MistralClient as switchable alternative to OllamaClient
Implements OllamaClientInterface against the Mistral Cloud API
(/v1/chat/completions). Switch by toggling the alias in services.yaml
and pointing AI_TEXT_MODEL / AI_VISION_MODEL env vars at the MISTRAL_*
or OLLAMA_* counterparts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:03:16 +00:00
6bf001b0c0 feat: implement Plan 2 — Article Management API
- 6 new domain repository interfaces (StoragePath, ArticlePhoto, AttributeValue, ChannelField, ArticleTypePlatformConfig, AttributeMapping)
- 6 Doctrine repository implementations
- StorageManager with multi-path quota-aware file storage (LocalStorageManager)
- Application services: ArticleTypeService, ArticleService, ArticleValidator, PhotoService, PlatformService, MappingService
- REST controllers: ArticleType, Article, Photo, Platform, Mapping (all under /api prefix)
- inventory_number sequence migration
- 22 unit tests passing, PHPStan level 9 clean, CS Fixer clean
- Fixed phpdoc_to_comment CS Fixer rule (disabled) to preserve @var type annotations
- Fixed PHPStan: User::getUserIdentifier non-empty-string, AIPipelineJob nullable missingFields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 05:19:20 +00:00