Compare commits

..

9 Commits

Author SHA1 Message Date
Jacek
9577d4944a feat: custom labels toggle and inline editing in product list
Adds session-based show/hide toggle for custom labels in admin product list, inline editable fields for custom_label_0..4, and label suggestions with custom entry support. Includes repository/controller updates, UI fixes, tests, and PAUL docs release updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-19 11:09:19 +02:00
Jacek
41e491c6b7 update 2026-04-18 23:52:11 +02:00
Jacek
e195ffc841 build: ver_0.347 - naprawa zapisu scontainers 2026-04-18 23:25:45 +02:00
Jacek
5b66720f7c fix: scontainers edit saves existing record instead of creating new
Fixes static container admin edit flow by preserving id in hiddenFields and adding route-id fallback during save.
Adds regression tests for edit/create id behavior, updates release docs (changelog/testing/CLAUDE), and appends SonarQube open issues to docs/TODO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-18 22:56:14 +02:00
Jacek
c611b012c6 update 2026-04-16 23:12:41 +02:00
Jacek
3fa3d72758 build: ver_0.346 - Fix usuwania wszystkich dodatkowych pól produktu
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:52:32 +02:00
Jacek
1ef6dc9092 fix: Custom fields delete bug — usunięcie wszystkich pól produktu nie działało
Dodano hidden marker custom_field_name_present w formularzu edycji produktu.
Zmieniono warunek w ProductRepository z array_key_exists('custom_field_name')
na array_key_exists('custom_field_name_present') — jQuery .serialize() pomijał
klucz pustej tablicy gdy wszystkie pola usunięte. Test jednostkowy dodany.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 22:51:57 +02:00
Jacek
b03816e8ec update 2026-03-25 21:35:44 +01:00
Jacek
591f2787ca build: ver_0.345 - Checkout flow fix (redirect + TTL token + logging)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:33:25 +01:00
55 changed files with 6144 additions and 5859 deletions

View File

@@ -1,6 +1,6 @@
# shopPRO Koniec Pracy (release workflow) # shopPRO - Koniec Pracy (release workflow)
Execute the full release workflow for shopPRO. This is a sequential pipeline each step depends on the previous one succeeding. Stop and report if any step fails. Execute the full release workflow for shopPRO. This is a sequential pipeline - each step depends on the previous one succeeding. Stop and report if any step fails.
## Step 1: Run tests ## Step 1: Run tests
@@ -8,7 +8,7 @@ Run the full PHPUnit test suite:
```bash ```bash
php phpunit.phar php phpunit.phar
``` ```
All tests must pass. If any test fails, stop here do not proceed to commit. Report the failures and wait for instructions. All tests must pass. If any test fails, stop here - do not proceed to commit. Report the failures and wait for instructions.
## Step 1b: SonarQube scan ## Step 1b: SonarQube scan
@@ -19,20 +19,20 @@ sonar-scanner
After the scan completes, query the SonarQube issues via MCP tool `mcp__sonarqube__issues` with `project_key: "shopPRO"` and `resolved: false`. Fetch all open issues (bugs, vulnerabilities, code smells). After the scan completes, query the SonarQube issues via MCP tool `mcp__sonarqube__issues` with `project_key: "shopPRO"` and `resolved: false`. Fetch all open issues (bugs, vulnerabilities, code smells).
Then open `docs/TODO.md` and append the found issues at the bottom under a new section: Then open `.paul/docs/TODO.md` and append the found issues at the bottom under a new section:
```markdown ```markdown
## SonarQube {VERSION} ({DATE}) ## SonarQube - {VERSION} ({DATE})
- [ ] [SEVERITY] FILENAME:LINE description (rule) - [ ] [SEVERITY] FILENAME:LINE - description (rule)
- [ ] ... - [ ] ...
``` ```
Rules: Rules:
- Only add issues that are NOT already present in `docs/TODO.md` - Only add issues that are NOT already present in `.paul/docs/TODO.md`
- Group by type: first Bugs/Vulnerabilities, then Code Smells - Group by type: first Bugs/Vulnerabilities, then Code Smells
- Skip INFO severity Code Smells only include MINOR and above - Skip INFO severity Code Smells - only include MINOR and above
- If there are no new issues, write: `## SonarQube {VERSION} brak nowych issues` - If there are no new issues, write: `## SonarQube - {VERSION} - brak nowych issues`
## Step 2: Determine version ## Step 2: Determine version
@@ -40,24 +40,24 @@ Read the latest git tag to determine the current version number:
```bash ```bash
git tag --sort=-v:refname | head -1 git tag --sort=-v:refname | head -1
``` ```
The new version is the previous version incremented by 1 (e.g., v0.333 v0.334). Use this version number throughout the remaining steps. The new version is the previous version incremented by 1 (e.g., v0.333 -> v0.334). Use this version number throughout the remaining steps.
## Step 3: Update documentation ## Step 3: Update documentation
Update these docs files **only if** changes in this session affect them: Update these docs files **only if** changes in this session affect them.
Do not update files in root `docs/` directory.
| File | When to update | | File | When to update |
|------|---------------| |------|---------------|
| `docs/CHANGELOG.md` | Always add a new version entry at the top describing what changed | | `.paul/docs/CHANGELOG.md` | Always - add a new version entry at the top describing what changed |
| `docs/TESTING.md` | If tests were added/removed update test count and structure | | `.paul/docs/TESTING.md` | If tests were added/removed - update test count and structure |
| `CLAUDE.md` | If test count changed — update the "Current suite" line | | `.paul/docs/DB_SCHEMA.md` | If database schema changed |
| `docs/DATABASE_STRUCTURE.md` | If database schema changed | | `.paul/docs/ARCHITECTURE.md` | If architecture/files changed significantly |
| `docs/PROJECT_STRUCTURE.md` | If architecture/files changed significantly | | `.paul/docs/FORMS.md` | If form system was modified |
| `docs/FORM_EDIT_SYSTEM.md` | If form system was modified |
## Step 4: SQL migrations ## Step 4: SQL migrations
If database schema changes were made, create a migration file at `migrations/{version}.sql` (e.g., `migrations/0.334.sql`). Do NOT put SQL files in `updates/` the build script reads from `migrations/` automatically. If database schema changes were made, create a migration file at `migrations/{version}.sql` (e.g., `migrations/0.334.sql`). Do NOT put SQL files in `updates/` - the build script reads from `migrations/` automatically.
If no DB changes were made, skip this step. If no DB changes were made, skip this step.

View File

@@ -1,12 +1,12 @@
# shopPRO # shopPRO
## What This Is ## What This Is
Autorski silnik sklepu internetowego pisany od podstaw odpowiednik WooCommerce lub PrestaShop, ale bez zależności od zewnętrznych platform. Składa się z panelu administratora (zarządzanie zamówieniami, produktami, klientami) oraz części frontowej dla klienta końcowego. Autorski silnik sklepu internetowego pisany od podstaw - odpowiednik WooCommerce lub PrestaShop, ale bez zależności od zewnętrznych platform. Składa się z panelu administratora (zarządzanie zamówieniami, produktami, klientami) oraz części frontowej dla klienta końcowego.
## Core Value ## Core Value
Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online produktami, zamówieniami i klientami w jednym spójnym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - produktami, zamówieniami i klientami - w jednym spójnym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
## Current State ## Current State
@@ -14,22 +14,23 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online
|-----------|-------| |-----------|-------|
| Version | 0.333 | | Version | 0.333 |
| Status | Production | | Status | Production |
| Last Updated | 2026-03-12 | | Last Updated | 2026-04-19 |
## Requirements ## Requirements
### Validated (Shipped) ### Validated (Shipped)
- [x] Panel administratora zarządzanie produktami, kategoriami, atrybutami - [x] Panel administratora - zarządzanie produktami, kategoriami, atrybutami
- [x] Panel administratora zarządzanie zamówieniami - [x] Panel administratora - zarządzanie zamówieniami
- [x] Panel administratora zarządzanie klientami - [x] Panel administratora - zarządzanie klientami
- [x] Część frontowa przeglądanie i kupowanie produktów - [x] Część frontowa - przeglądanie i kupowanie produktów
- [x] Koszyk i składanie zamówień - [x] Koszyk i składanie zamówień
- [x] Integracje płatności i dostaw - [x] Integracje płatności i dostaw
- [x] REST API (ordersPRO + Ekomi) - [x] REST API (ordersPRO + Ekomi)
- [x] Redis caching - [x] Redis caching
- [x] Ochrona przed podwójnym składaniem zamówienia - [x] Ochrona przed podwójnym składaniem zamówienia
- [x] Domain-Driven Architecture (migracja z legacy zakończona) - [x] Domain-Driven Architecture (migracja z legacy zakończona)
- [x] Szybka edycja custom_label_0..4 na liscie produktow admina (toggle sesyjny + autocomplete)
### Active (In Progress) ### Active (In Progress)
@@ -41,7 +42,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online
### Out of Scope ### Out of Scope
- Multitenancy (wiele sklepów w jednej instancji) nie planowane - Multitenancy (wiele sklepów w jednej instancji) - nie planowane
## Target Users ## Target Users
@@ -65,7 +66,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online
### Technical Constraints ### Technical Constraints
- PHP < 8.0 na produkcji (brak `match`, named arguments, union types) - PHP < 8.0 na produkcji (brak `match`, named arguments, union types)
- Medoo ORM prepared statements bez wyjątków - Medoo ORM - prepared statements bez wyjątków
- Redis wymagany dla cache - Redis wymagany dla cache
### Business Constraints ### Business Constraints
@@ -78,12 +79,14 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online
| DDD + DI zamiast legacy architektury | Testowalność, separacja odpowiedzialności | 2025 | Active | | DDD + DI zamiast legacy architektury | Testowalność, separacja odpowiedzialności | 2025 | Active |
| PHP < 8.0 kompatybilność | Klienci na starszych serwerach | 2025 | Active | | PHP < 8.0 kompatybilność | Klienci na starszych serwerach | 2025 | Active |
| Własny silnik zamiast frameworka | Pełna kontrola, brak narzutów | - | Active | | Własny silnik zamiast frameworka | Pełna kontrola, brak narzutów | - | Active |
| `id` w tabbed FormEdit przez `hiddenFields` | Zapobiega insert zamiast update przy edycji encji | 2026-04-18 | Active |
| Inline custom labels w product list przez sesyjny toggle | Szybszy workflow dla Google XML bez wejscia w edycje produktu | 2026-04-19 | Active |
## Success Metrics ## Success Metrics
| Metric | Target | Current | Status | | Metric | Target | Current | Status |
|--------|--------|---------|--------| |--------|--------|---------|--------|
| Testy | >800 | 810 | On track | | Testy | >800 | 821 | On track |
| Pokrycie architektury DDD | 100% | 100% | Achieved | | Pokrycie architektury DDD | 100% | 100% | Achieved |
## Tech Stack ## Tech Stack
@@ -102,14 +105,14 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online
See: .paul/SPECIAL-FLOWS.md See: .paul/SPECIAL-FLOWS.md
Quick Reference: Quick Reference:
- /feature-dev Nowe funkcje, większe zmiany (required) - /feature-dev -> Nowe funkcje, większe zmiany (required)
- /koniec-pracy Release, update package (required) - /koniec-pracy -> Release, update package (required)
- /frontend-design Komponenty UI, szablony widoków - /frontend-design -> Komponenty UI, szablony widoków
- /code-review Przegląd kodu przed release - /code-review -> Przegląd kodu przed release
- /simplify Upraszczanie po implementacji - /simplify -> Upraszczanie po implementacji
- /claude-md-improver Utrzymanie CLAUDE.md - /claude-md-improver -> Utrzymanie CLAUDE.md
- /zapisz + /wznow Zapis i wznowienie sesji - /zapisz + /wznow -> Zapis i wznowienie sesji
--- ---
*PROJECT.md Updated when requirements or context change* *PROJECT.md - Updated when requirements or context change*
*Last updated: 2026-03-12* *Last updated: 2026-04-19 after Phase 16*

View File

@@ -1,4 +1,4 @@
# Roadmap: shopPRO # Roadmap: shopPRO
## Overview ## Overview
@@ -6,9 +6,9 @@ shopPRO to autorski silnik sklepu internetowego rozwijany iteracyjnie. Projekt j
## Current Milestone ## Current Milestone
**Security hardening** (v0.33x) **Feature — Product list custom labels quick edit**
Status: In progress Status: Complete
Phases: 3 of 4 complete Phases: 1 of 1 complete
## Phases ## Phases
@@ -36,6 +36,7 @@ Status: Planning
| 7 | Coupon Fatal Error — order placement crash | 1 | Done | 2026-03-15 | | 7 | Coupon Fatal Error — order placement crash | 1 | Done | 2026-03-15 |
| 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 | | 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 |
| 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 | | 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 |
| 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 |
## Feature ## Feature
@@ -45,6 +46,8 @@ Status: Planning
| 11 | DataLayer GA4 analytics fix | 1 | Done | 2026-03-25 | | 11 | DataLayer GA4 analytics fix | 1 | Done | 2026-03-25 |
| 12 | summaryView redirect fix — double order block | 1 | Done | 2026-03-25 | | 12 | summaryView redirect fix — double order block | 1 | Done | 2026-03-25 |
| 13 | Basket logging + TTL token fix | 1 | Done | 2026-03-25 | | 13 | Basket logging + TTL token fix | 1 | Done | 2026-03-25 |
| 14 | Custom fields delete bug — usunięcie wszystkich pól | 1 | Done | 2026-04-16 |
| 16 | Product list custom labels quick edit | 1 | Done | 2026-04-19 |
## Phase Details ## Phase Details
@@ -76,6 +79,7 @@ Status: Planning
--- ---
*Roadmap created: 2026-03-12* *Roadmap created: 2026-03-12*
### Phase 11 — DataLayer GA4 analytics fix ### Phase 11 — DataLayer GA4 analytics fix
**Problem:** Eventy dataLayer ecommerce (purchase, begin_checkout, view_item, add_to_cart) używają starego formatu UA (id/name zamiast item_id/item_name), brak currency w view_item, price:0 w purchase, brak eventu view_cart. Remarketing dynamiczny i konwersje GA4 nie działają poprawnie. **Problem:** Eventy dataLayer ecommerce (purchase, begin_checkout, view_item, add_to_cart) używają starego formatu UA (id/name zamiast item_id/item_name), brak currency w view_item, price:0 w purchase, brak eventu view_cart. Remarketing dynamiczny i konwersje GA4 nie działają poprawnie.
@@ -96,5 +100,23 @@ Status: Planning
**Scope:** Dodanie metody logOrder() z 4 punktami logowania, zmiana tokena z jednorazowego na TTL 30 min, redirect przy błędzie tokena na /koszyk-podsumowanie zamiast /koszyk, nowy double-submit guard. **Scope:** Dodanie metody logOrder() z 4 punktami logowania, zmiana tokena z jednorazowego na TTL 30 min, redirect przy błędzie tokena na /koszyk-podsumowanie zamiast /koszyk, nowy double-submit guard.
### Phase 14 — Custom fields delete bug
**Problem:** Usunięcie WSZYSTKICH dodatkowych pól z produktu nie działa. jQuery `.serialize()` nie wysyła klucza `custom_field_name[]` gdy nie ma żadnych pól → `array_key_exists('custom_field_name', $d)` w ProductRepository zwraca false → `saveCustomFields()` nigdy nie jest wywoływany → pola pozostają w bazie.
**Scope:** Dodanie hidden markera `custom_field_name_present` w szablonie JS + zmiana warunku w ProductRepository na sprawdzanie tego markera. Test jednostkowy.
### Phase 15 - Scontainers edit saves as new record
**Problem:** Edycja kontenera statycznego (`/admin/scontainers/edit/id={id}`) zapisuje rekord jako nowy wpis zamiast aktualizacji. W praktyce podczas zapisu gubi się `id` i repository wykonuje insert.
**Scope:** Poprawić przekazywanie `id` w nowym flow formularza ScontainersController + dodać test regresyjny dla edycji, bez zmian globalnych w innych kontrolerach.
### Phase 16 - Product list custom labels quick edit
**Problem:** Na liscie produktow brakuje szybkiego trybu uzupelniania `custom_label_0..4`. Administrator musi wchodzic do edycji produktu, co spowalnia uzupelnianie danych Google XML.
**Scope:** Dodac przycisk "Pokaz etykiety niestandardowe" obok "Dodaj produkt", zapisywac jego stan w sesji, pokazac 5 pol custom label pod nazwa produktu, zapisac wartosci do bazy i zapewnic podpowiedzi z juz istniejacych wartosci.
--- ---
*Last updated: 2026-03-25* *Last updated: 2026-04-19 (Phase 16 complete)*

View File

@@ -1,37 +1,37 @@
# Specialized Flows: shopPRO # Specialized Flows: shopPRO
## Project-Level Dependencies ## Project-Level Dependencies
| Work Type | Skill/Command | Priority | Kiedy używać | | Work Type | Skill/Command | Priority | Kiedy uzywac |
|-----------|---------------|----------|--------------| |-----------|---------------|----------|--------------|
| Komponenty UI, szablony widoków | /frontend-design | optional | Przy tworzeniu HTML/CSS | | Komponenty UI, szablony widokow | /frontend-design | optional | Przy tworzeniu HTML/CSS |
| Nowe funkcje, większe zmiany | /feature-dev | required | Przed implementacją fazy | | Nowe funkcje, wieksze zmiany | /feature-dev | required | Przed implementacja fazy |
| Przegląd kodu | /code-review | optional | Przed release / KONIEC PRACY | | Przeglad kodu | /code-review | optional | Przed release / KONIEC PRACY |
| Upraszczanie po zmianach | /simplify | optional | Po zakończeniu implementacji | | Upraszczanie po zmianach | /simplify | optional | Po zakonczeniu implementacji |
| Utrzymanie CLAUDE.md | /claude-md-improver | optional | Co kilka faz / po dużych zmianach | | Utrzymanie CLAUDE.md | /claude-md-improver | optional | Co kilka faz / po duzych zmianach |
| Release, budowanie update package | /koniec-pracy | required | Na koniec każdej sesji roboczej | | Release, budowanie update package | /koniec-pracy | required | Na koniec kazdej sesji roboczej |
| Zapis i wznowienie sesji | /zapisz + /wznow | optional | Na przerwę / powrót do pracy | | Zapis i wznowienie sesji | /zapisz + /wznow | optional | Na przerwe / powrot do pracy |
## Phase Overrides ## Phase Overrides
Brak domyślna konfiguracja obowiązuje dla wszystkich faz. Brak - domyslna konfiguracja obowiazuje dla wszystkich faz.
## Templates & Assets ## Templates & Assets
| Asset Type | Location | When Used | | Asset Type | Location | When Used |
|------------|----------|-----------| |------------|----------|-----------|
| CLAUDE.md | CLAUDE.md | Konwencje kodu, architektura, stack techniczny | | CLAUDE.md | CLAUDE.md | Konwencje kodu, architektura, stack techniczny |
| Struktura bazy | docs/DATABASE_STRUCTURE.md | Przy zmianach schematu DB | | Struktura bazy | .paul/docs/DB_SCHEMA.md | Przy zmianach schematu DB |
| Dokumentacja API | api-docs/api-reference.json | Przy zmianach API | | Dokumentacja API | .paul/docs/API.md | Przy zmianach API |
| TODO | docs/TODO.md | Planowanie nowych funkcji | | TODO | .paul/docs/TODO.md | Planowanie nowych funkcji |
## Verification (UNIFY) ## Verification (UNIFY)
Podczas UNIFY sprawdź: Podczas UNIFY sprawdz:
- `/feature-dev` czy był użyty przed implementacją fazy? - `/feature-dev` - czy byl uzyty przed implementacja fazy?
- `/koniec-pracy` czy release został wykonany? - `/koniec-pracy` - czy release zostal wykonany?
Braki dokumentuj w STATE.md (Deferred Issues), nie blokują UNIFY. Braki dokumentuj w STATE.md (Deferred Issues), nie blokuja UNIFY.
--- ---
*SPECIAL-FLOWS.md Created: 2026-03-12* *SPECIAL-FLOWS.md - Created: 2026-03-12*

View File

@@ -1,48 +1,63 @@
# Project State # Project State
## Project Reference ## Project Reference
See: .paul/PROJECT.md (updated 2026-03-12) See: .paul/PROJECT.md (updated 2026-04-18)
**Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. **Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
**Current focus:** Phase 13 complete — basket logging + TTL token **Current focus:** Phase 16 complete - loop closed
## Current Position ## Current Position
Milestone: Hotfix Milestone: Feature
Phase: 13 — basket logging + TTL token — Planning Phase: 16 of 16 (Product list custom labels quick edit) - Complete
Plan: 13-01 created, awaiting approval Plan: 16-01 complete
Status: UNIFY complete, phase 13 finished Status: UNIFY complete, ready for next PLAN loop
Last activity: 2026-03-25 — 13-01 UNIFY complete Last activity: 2026-04-19 - Closed loop for .paul/phases/16-product-list-custom-labels/16-01-PLAN.md
Progress: Progress:
- Phase 13: [██████████] 100% (COMPLETE) - Milestone: [##########] 100%
- Phase 16: [##########] 100%
## Loop Position ## Loop Position
Current loop state (phase 13, plan 01): Current loop state:
``` ```
PLAN ──▶ APPLY ──▶ UNIFY PLAN --> APPLY --> UNIFY
✓ ✓ [Phase 13 complete] ✓ ✓ ✓ [Loop complete - ready for next PLAN]
``` ```
Previous phases: Previous phases:
``` ```
Phase 4: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-12] Phase 4: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12]
Phase 5: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-12] Phase 5: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12]
Phase 6: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-12] Phase 6: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12]
Phase 7: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-15] Phase 7: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-15]
Phase 8: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-16] Phase 8: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-16]
Phase 9: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-19] Phase 9: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-19]
Phase 10: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-19] Phase 10: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-19]
Phase 11: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-25] Phase 11: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25]
Phase 12: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-25] Phase 12: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25]
Phase 13: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE 2026-03-25] Phase 13: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25]
Phase 14: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-16]
Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18]
Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19]
``` ```
## Accumulated Context ## Accumulated Context
### Decisions ### Decisions
- 2026-04-19: Created Phase 16 plan at .paul/phases/16-product-list-custom-labels/16-01-PLAN.md
- 2026-04-19: Phase 16 scope includes session toggle + inline custom_label_0..4 edit + suggestions on product list
- 2026-04-19: Override approved by user - proceeded without required /feature-dev skill in Phase 16 APPLY
- 2026-04-19: /koniec-pracy acknowledged by user as available for session close workflow
- 2026-04-19: Human verify checkpoint approved after UX fixes (button style + autocomplete in single input)
- 2026-04-19: Product list custom labels final UX: single input with autocomplete, no separate select under field
- 2026-04-19: Transition-phase git commit for Phase 16 not executed in this UNIFY run
- 2026-04-18: Transition-phase git commit step pending (not executed during this UNIFY run)
- 2026-04-18: Phase 15 loop closed with SUMMARY at .paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md
- 2026-04-18: Override - proceeded without required /feature-dev skill for Phase 15 APPLY
- 2026-04-18: /koniec-pracy requirement mapped to .claude/commands/koniec-pracy.md guidance for end-of-session release flow
- 2026-04-18: Scontainers edit fix - ID from tabbed form can be omitted when hidden field is defined as FormField and not as hiddenFields
- Use existing `CouponRepository::markAsUsed()` instead of adding methods to stdClass - Use existing `CouponRepository::markAsUsed()` instead of adding methods to stdClass
- 2026-03-16: Przyczyna braku wysyłki = brakujące $apiloRepository w use() closures cron.php (regresja z fazy 6) - 2026-03-16: Przyczyna braku wysyłki = brakujące $apiloRepository w use() closures cron.php (regresja z fazy 6)
- 2026-03-16: Retry -1 orders co 1h zamiast permanent failure - 2026-03-16: Retry -1 orders co 1h zamiast permanent failure
@@ -59,6 +74,7 @@ Phase 13: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-
- 2026-03-25: Token zamówienia z jednorazowego na TTL 30 min — backward compat z plain string - 2026-03-25: Token zamówienia z jednorazowego na TTL 30 min — backward compat z plain string
- 2026-03-25: logOrder() — logowanie błędów zamówień do logs/logs-order-YYYY-MM-DD.log - 2026-03-25: logOrder() — logowanie błędów zamówień do logs/logs-order-YYYY-MM-DD.log
- 2026-03-25: Redirect przy złym tokenie: /koszyk-podsumowanie zamiast /koszyk - 2026-03-25: Redirect przy złym tokenie: /koszyk-podsumowanie zamiast /koszyk
- 2026-04-16: Custom fields delete fix — hidden marker `custom_field_name_present` zamiast `array_key_exists('custom_field_name')`
### Deferred Issues ### Deferred Issues
None. None.
@@ -66,12 +82,17 @@ None.
### Blockers/Concerns ### Blockers/Concerns
None. None.
### Skill Audit (Phase 16)
| Expected | Invoked | Notes |
|----------|---------|-------|
| /feature-dev | ○ | User-approved override during APPLY |
| /koniec-pracy | ○ | Marked as available by user; execute on session close workflow |
## Session Continuity ## Session Continuity
Last session: 2026-03-25 Last session: 2026-04-19
Stopped at: Phase 13 UNIFY complete Stopped at: Phase 16 complete, loop closed
Next action: /koniec-pracy or next feature Next action: Start next milestone or create next phase plan
Resume file: .paul/phases/13-basket-logging-ttl-token/13-01-SUMMARY.md Resume file: .paul/phases/16-product-list-custom-labels/16-01-SUMMARY.md
--- ---
*STATE.md — Updated after every significant action* *STATE.md — Updated after every significant action*

View File

@@ -0,0 +1,14 @@
# 2026-04-16
## Co zrobiono
- [Phase 14, Plan 01] Fix: usunięcie wszystkich dodatkowych pól produktu nie działało
- Dodano hidden marker `custom_field_name_present` w formularzu edycji produktu
- Zmieniono warunek w ProductRepository na sprawdzanie markera zamiast obecności tablicy pól
- Dodano test jednostkowy testSaveCustomFieldsDeletesAllWhenEmpty
## Zmienione pliki
- `autoload/admin/Controllers/ShopProductController.php`
- `autoload/Domain/Product/ProductRepository.php`
- `tests/Unit/Domain/Product/ProductRepositoryTest.php`

View File

@@ -0,0 +1,13 @@
# 2026-04-18
## Co zrobiono
- [Phase 15, Plan 01] Naprawiono regresje zapisu edycji kontenerow statycznych (update zamiast tworzenia nowego rekordu).
- Przeniesiono przekazywanie `id` w formularzu Scontainers do `hiddenFields` oraz dodano fallback `id` z route parametru.
- Dodano testy regresyjne dla mapowania `id` i create-flow w `ScontainersControllerTest`.
## Zmienione pliki
- `autoload/admin/Controllers/ScontainersController.php`
- `tests/Unit/admin/Controllers/ScontainersControllerTest.php`
- `.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md`

View File

@@ -0,0 +1,22 @@
# 2026-04-19
## Co zrobiono
- [Phase 16, Plan 01] Dodano szybka edycje custom_label_0..4 w `/admin/shop_product/view_list/` z przelacznikiem sesyjnym.
- Dodano zapis wartosci custom labels do bazy oraz walidacje dozwolonych `label_type` po stronie kontrolera.
- Dodano podpowiedzi istniejacych wartosci jako autocomplete w jednym input (z mozliwoscia wpisania wartosci wlasnej).
- Poprawiono UX przycisku toggla (kolorystyka, rozmiar, hover i czytelnosc).
- Rozszerzono testy jednostkowe kontrolera i repozytorium dla nowej funkcjonalnosci.
## Zmienione pliki
- `autoload/admin/Controllers/ShopProductController.php`
- `autoload/Domain/Product/ProductRepository.php`
- `admin/templates/shop-product/products-list.php`
- `admin/templates/shop-product/products-list-custom-script.php`
- `tests/Unit/admin/Controllers/ShopProductControllerTest.php`
- `tests/Unit/Domain/Product/ProductRepositoryTest.php`
- `.paul/phases/16-product-list-custom-labels/16-01-SUMMARY.md`
- `.paul/PROJECT.md`
- `.paul/ROADMAP.md`
- `.paul/STATE.md`

View File

@@ -1,11 +1,11 @@
# Testing Patterns # Testing Patterns
## Overview ## Overview
| Metric | Value | | Metric | Value |
|--------|-------| |--------|-------|
| Total tests | **810** | | Total tests | **828** |
| Total assertions | **2264** | | Total assertions | **2306** |
| Framework | PHPUnit 9.6 (`phpunit.phar`) | | Framework | PHPUnit 9.6 (`phpunit.phar`) |
| Bootstrap | `tests/bootstrap.php` | | Bootstrap | `tests/bootstrap.php` |
| Config | `phpunit.xml` | | Config | `phpunit.xml` |
@@ -13,7 +13,7 @@
## Running Tests ## Running Tests
```bash ```bash
# Full suite (PowerShell recommended) # Full suite (PowerShell — recommended)
./test.ps1 ./test.ps1
# Specific file # Specific file
@@ -36,16 +36,16 @@ Tests mirror source structure:
``` ```
tests/Unit/ tests/Unit/
├── Domain/ ├── Domain/
│ ├── Product/ProductRepositoryTest.php │ ├── Product/ProductRepositoryTest.php
│ ├── Category/CategoryRepositoryTest.php │ ├── Category/CategoryRepositoryTest.php
│ ├── Order/OrderRepositoryTest.php │ ├── Order/OrderRepositoryTest.php
│ └── ... (all 29 modules covered) │ └── ... (all 29 modules covered)
├── admin/Controllers/ ├── admin/Controllers/
│ ├── ShopCategoryControllerTest.php │ ├── ShopCategoryControllerTest.php
│ └── ... │ └── ...
└── api/ └── api/
└── ... └── ...
``` ```
## Test Class Pattern ## Test Class Pattern
@@ -229,13 +229,13 @@ $this->assertInstanceOf(ClassName::class, $obj);
## What's Covered ## What's Covered
- All 29 Domain repositories - All 29 Domain repositories âś“
- Core business logic (quantity, pricing, category tree) - Core business logic (quantity, pricing, category tree) âś“
- Query behavior with mocked Medoo - Query behavior with mocked Medoo âś“
- Cache patterns - Cache patterns âś“
- Controller constructor injection - Controller constructor injection âś“
- `FormValidator` behavior - `FormValidator` behavior âś“
- API controllers - API controllers âś“
## What's Lightly Covered ## What's Lightly Covered
@@ -243,3 +243,4 @@ $this->assertInstanceOf(ClassName::class, $obj);
- Session state in tests - Session state in tests
- AJAX response integration - AJAX response integration
- Frontend Views (static classes) - Frontend Views (static classes)

255
.paul/docs/API.md Normal file
View File

@@ -0,0 +1,255 @@
# API
## Scope
Dokument opisuje aktualne REST API dostepne przez `api.php` (ordersPRO + slowniki + produkty + kategorie).
## Base URL
- Endpoint techniczny: `/api.php`
- Routing odbywa sie przez query params:
- `endpoint` (np. `orders`, `products`)
- `action` (np. `list`, `get`)
Przyklad:
```text
GET /api.php?endpoint=orders&action=list
```
## Authentication
- Wymagany naglowek: `X-Api-Key: <api_key>`
- Klucz jest porownywany z wartoscia `pp_settings.api_key`
- Brak lub zly klucz:
- HTTP `401`
- payload:
```json
{
"status": "error",
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
```
## Response format
Sukces:
```json
{
"status": "ok",
"data": {}
}
```
Blad:
```json
{
"status": "error",
"code": "BAD_REQUEST",
"message": "..."
}
```
## Common HTTP/logic errors
- `400 BAD_REQUEST` - brak wymaganych parametrow/body
- `401 UNAUTHORIZED` - brak/zly API key
- `404 NOT_FOUND` - nieznany endpoint/action lub brak rekordu
- `405 METHOD_NOT_ALLOWED` - zla metoda HTTP
- `500 INTERNAL_ERROR` - blad serwera
## Endpoints
### Orders (`endpoint=orders`)
- `GET action=list`
- filtry (opcjonalne): `status`, `paid`, `date_from`, `date_to`, `updated_since`, `number`, `client`
- paginacja: `page` (default 1), `per_page` (default 50, max 100)
- `GET action=get&id={id}`
- `PUT action=change_status&id={id}`
- body JSON:
- `status_id` (required, int)
- `send_email` (optional, bool)
- `PUT action=set_paid&id={id}`
- body JSON optional: `send_email` (bool)
- `PUT action=set_unpaid&id={id}`
### Products (`endpoint=products`)
- `GET action=list`
- filtry:
- `search`, `status`, `promoted`
- `attribute_{attributeId}={valueId}` (np. `attribute_12=37`)
- sortowanie: `sort` (default `id`), `sort_dir` (default `DESC`)
- paginacja: `page`, `per_page` (max 100)
- `GET action=get&id={id}`
- `POST action=create`
- wymagane minimum:
- `languages` (array, co najmniej jeden jezyk z `name`)
- `price_brutto` (number >= 0)
- `PUT action=update&id={id}`
- partial update przez JSON body
- `GET action=variants&id={parentProductId}`
- dla produktu glownego (nie wariantu)
- `POST action=create_variant&id={parentProductId}`
- body:
- `attributes` (required array)
- opcjonalnie pola wariantu (np. `price_brutto`, `quantity`, `sku`, ...)
- `PUT action=update_variant&id={variantId}`
- partial update wariantu
- `DELETE action=delete_variant&id={variantId}`
- `POST action=upload_image`
- body:
- `id` (product id, required)
- `file_name` (required)
- `content_base64` (required)
- `alt` (optional)
- `o` (optional position)
### Dictionaries (`endpoint=dictionaries`)
- `GET action=statuses`
- `GET action=transports`
- `GET action=payment_methods`
- `GET action=attributes`
- `POST action=ensure_attribute`
- body: `name` (required), `type` (optional int), `lang` (optional, default `pl`)
- `POST action=ensure_attribute_value`
- body: `attribute_id` (required), `name` (required), `lang` (optional, default `pl`)
- `POST action=ensure_producer`
- body: `name` (required)
### Categories (`endpoint=categories`)
- `GET action=list`
- zwraca aktywne kategorie w formie flat list:
- `id`
- `parent_id`
- `title`
- tytuly pobierane najpierw w jezyku domyslnym (`pp_langs.start=1`), potem fallback.
## Source of truth (mapa API w kodzie)
### 1) Wejscie i dispatch
- `api.php`
- wykrywa request API przez `$_GET['endpoint']`
- ustawia JSON content-type
- tworzy `medoo` + `SettingsRepository`
- przekazuje sterowanie do `\api\ApiRouter::handle()`
- `autoload/api/ApiRouter.php`
- autentykacja (`X-Api-Key` vs `pp_settings.api_key`)
- walidacja `endpoint` i `action`
- mapowanie endpoint -> kontroler (`getControllerFactories()`)
- helpery odpowiedzi: `sendSuccess()`, `sendError()`
- helpery requestu: `getJsonBody()`, `requireMethod()`
### 2) Endpointy i kontrolery (runtime source)
#### `endpoint=orders`
- plik: `autoload/api/Controllers/OrdersApiController.php`
- akcje:
- `list` (GET)
- `get` (GET)
- `change_status` (PUT)
- `set_paid` (PUT)
- `set_unpaid` (PUT)
#### `endpoint=products`
- plik: `autoload/api/Controllers/ProductsApiController.php`
- akcje:
- `list` (GET)
- `get` (GET)
- `create` (POST)
- `update` (PUT)
- `variants` (GET)
- `create_variant` (POST)
- `update_variant` (PUT)
- `delete_variant` (DELETE)
- `upload_image` (POST)
#### `endpoint=dictionaries`
- plik: `autoload/api/Controllers/DictionariesApiController.php`
- akcje:
- `statuses` (GET)
- `transports` (GET)
- `payment_methods` (GET)
- `attributes` (GET)
- `ensure_attribute` (POST)
- `ensure_attribute_value` (POST)
- `ensure_producer` (POST)
#### `endpoint=categories`
- plik: `autoload/api/Controllers/CategoriesApiController.php`
- akcje:
- `list` (GET)
### 3) Warstwa domenowa pod API (shape danych)
- Orders:
- `Domain\Order\OrderRepository::listForApi()`
- `Domain\Order\OrderRepository::findForApi()`
- `Domain\Order\OrderAdminService` (zmiana statusu/platnosci)
- Products:
- `Domain\Product\ProductRepository::listForApi()`
- `Domain\Product\ProductRepository::findForApi()`
- `Domain\Product\ProductRepository::saveProduct()`
- metody wariantow `*VariantForApi()`
- Dictionaries:
- `Domain\ShopStatus\ShopStatusRepository`
- `Domain\Transport\TransportRepository`
- `Domain\PaymentMethod\PaymentMethodRepository`
- `Domain\Attribute\AttributeRepository`
- `Domain\Producer\ProducerRepository`
- Categories:
- bezposrednio query przez `$GLOBALS['mdb']` w `CategoriesApiController`
### 4) Testy API (behavior source)
- `tests/Unit/api/ApiRouterTest.php`
- `tests/Unit/api/Controllers/OrdersApiControllerTest.php`
- `tests/Unit/api/Controllers/ProductsApiControllerTest.php`
- `tests/Unit/api/Controllers/DictionariesApiControllerTest.php`
### 5) Dokumentacja kontraktu (human source)
- `api-docs/api-reference.json`
- `api-docs/index.html`
Uwaga: dokumentacja z `api-docs/*` moze byc starsza od runtime.
Zrodlem prawdy dla dzialania endpointow jest zawsze:
`api.php` + `autoload/api/ApiRouter.php` + aktualne kontrolery `autoload/api/Controllers/*`.
## Curl examples
Pobranie listy zamowien:
```bash
curl -X GET "https://example.com/api.php?endpoint=orders&action=list&page=1&per_page=20" \
-H "X-Api-Key: YOUR_API_KEY"
```
Zmiana statusu zamowienia:
```bash
curl -X PUT "https://example.com/api.php?endpoint=orders&action=change_status&id=123" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"status_id\": 6, \"send_email\": true}"
```
Dodanie wariantu produktu:
```bash
curl -X POST "https://example.com/api.php?endpoint=products&action=create_variant&id=50" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"attributes\":[{\"attribute_id\":12,\"attribute_value_id\":37}],\"price_brutto\":99.99,\"quantity\":10}"
```

192
.paul/docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,192 @@
# ARCHITECTURE
## Scope
Dokument opisuje aktualna architekture runtime projektu `shopPRO`:
- warstwy aplikacji i ich odpowiedzialnosci,
- przeplywy requestow (admin/front/api),
- Dependency Injection i miejsca wiringu,
- konwencje autoloadera i namespace,
- granice miedzy nowa architektura a pozostalosciami legacy.
## High-level layout
Kod aplikacji jest podzielony na 4 glowne warstwy:
1. `autoload/Domain/` - logika biznesowa i dostep do danych (28 modulow)
2. `autoload/admin/` - panel administracyjny (router + kontrolery + form system)
3. `autoload/front/` - frontend sklepu (router + layout engine + kontrolery/widoki)
4. `autoload/api/` - REST API (`api.php`, `ApiRouter`, kontrolery endpointow)
Komponenty wspoldzielone sa trzymane w `autoload/Shared/`.
## Directory map (runtime)
```text
autoload/
Domain/ # 28 modulow domenowych
Shared/ # Cache, Helpers, Tpl, Html, Email, Image
admin/
App.php # routing + DI factories
Controllers/ # 28 kontrolerow admin
Support/ # formularze/tabele
Validation/ # walidacja formularzy
ViewModels/ # modele widokow
front/
App.php # routing + DI factories + fallback legacy
LayoutEngine.php # silnik layoutu frontend
Controllers/ # 8 kontrolerow frontend
Views/ # 11 statycznych klas widokow
api/
ApiRouter.php # auth + endpoint dispatch
Controllers/ # 4 kontrolery API
```
## Entry points i przeplyw
### Admin (`admin/index.php`)
1. Bootstrap (sesja, DB, autoload)
2. `admin\App::update()` - uruchamia pending migracje
3. `admin\App::special_actions()` - logowanie/wylogowanie/2FA
4. `admin\App::render()`:
- auth gate (lub formularz logowania),
- `route()` -> kontroler + akcja,
- render przez `Shared\Tpl\Tpl`.
### Front (`index.php`)
1. Bootstrap (sesja, DB, autoload, jezyk)
2. Mapowanie URL (redirecty + routes)
3. `front\App::checkUrlParams()`
4. `front\App::route()`:
- artykul/produkt/kategoria,
- nowe kontrolery DI,
- fallback do `front\controls\*` (legacy, jesli istnieje)
5. `front\LayoutEngine::show()` sklada finalny HTML.
### API (`api.php`)
1. Bootstrap (bez sesji biznesowej)
2. Tworzenie `\api\ApiRouter`
3. `ApiRouter::handle()`:
- auth przez `X-Api-Key`,
- walidacja `endpoint` i `action`,
- dispatch do kontrolera API,
- JSON response przez `sendSuccess()` / `sendError()`.
Szczegolowa specyfikacja endpointow: `.paul/docs/API.md`.
## Dependency Injection (manual factories)
DI jest realizowane recznie w mapach factory:
- admin: `autoload/admin/App.php` -> `getControllerFactories()`
- front: `autoload/front/App.php` -> `getControllerFactories()`
- api: `autoload/api/ApiRouter.php` -> `getControllerFactories()`
Wzorzec:
- router tworzy repozytoria domenowe,
- repozytoria sa wstrzykiwane do kontrolerow przez konstruktor,
- kontroler wywoluje metody domenowe i zwraca HTML/JSON.
## Autoloader i namespace rules
Kazdy entry point korzysta z custom autoloadera:
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
2. `autoload/{namespace}/{ClassName}.php` (nowy format)
Mapowanie namespace -> katalog (case-sensitive na Linux):
- `\Domain\` -> `autoload/Domain/`
- `\Shared\` -> `autoload/Shared/`
- `\admin\` -> `autoload/admin/` (male `a`)
- `\front\` -> `autoload/front/`
- `\api\` -> `autoload/api/`
Nie uzywac `\Admin\` (duze `A`), bo katalog runtime to `admin/`.
## Domain layer (28 modulow)
Aktualne moduly:
`Article`, `Attribute`, `Banner`, `Basket`, `Cache`, `Category`, `Client`, `Coupon`, `CronJob`, `Dashboard`, `Dictionaries`, `Integrations`, `Languages`, `Layouts`, `Newsletter`, `Order`, `Pages`, `PaymentMethod`, `Producer`, `Product`, `ProductSet`, `Promotion`, `Scontainers`, `Settings`, `ShopStatus`, `Transport`, `Update`, `User`.
Zasada: logika biznesowa i dostep do danych sa w Domain, bez duplikowania osobnych warstw "admin service" i "front service" dla tych samych przypadkow (wyjatki tylko tam, gdzie historycznie juz istnieja, np. `OrderAdminService`).
## Admin architecture
- Router: `admin\App`
- Kontrolery: `autoload/admin/Controllers/*.php` (28 klas)
- Form system:
- `admin\ViewModels\Forms\*`
- `admin\Support\Forms\FormRequestHandler`
- `admin\Support\Forms\FormFieldRenderer`
- `admin\Validation\FormValidator`
- template: `admin/templates/components/form-edit.php`
Admin ma pelne DI i nie korzysta z fallbacku na legacy kontrolery.
## Front architecture
- Router: `front\App`
- Layout engine: `front\LayoutEngine`
- Kontrolery DI: `autoload/front/Controllers/*.php` (8 klas)
- Widoki statyczne: `autoload/front/Views/*.php` (11 klas)
Wazne: frontend nadal ma fallback do `\front\controls\*`, wiec architektura jest hybrydowa (new DI + remaining legacy paths).
## API architecture
- Router: `api\ApiRouter`
- Endpointy: `orders`, `products`, `dictionaries`, `categories`
- Kontrolery: `autoload/api/Controllers/*ApiController.php` (4 klasy)
- API jest stateless, autoryzowane naglowkiem `X-Api-Key`
Source-of-truth API to runtime:
- `api.php`
- `autoload/api/ApiRouter.php`
- `autoload/api/Controllers/*`
## Shared components
Najwazniejsze klasy wspoldzielone:
- `Shared\Cache\CacheHandler`, `Shared\Cache\RedisConnection`
- `Shared\Helpers\Helpers`
- `Shared\Tpl\Tpl`
- `Shared\Html\Html`
- `Shared\Email\Email`
- `Shared\Image\ImageManipulator`
## Data and cache conventions
- ORM: Medoo (`$mdb`)
- Prefix tabel: `pp_`
- Cache: Redis, domyslnie TTL `86400`
- Dane cache czesto serializowane (`serialize`/`unserialize`)
- Czyszczenie cache produktu: pattern `shop\\product:{id}:*`
## Security boundaries
- Admin:
- sesja uzytkownika admina,
- CSRF token w akcjach POST,
- 2FA email flow (pending session + verify).
- API:
- `X-Api-Key` porownywany przez `hash_equals()`,
- brak logiki sesyjnej.
- Front:
- sesja klienta + walidacja przeplywow frontendowych.
## Source files
Najwazniejsze pliki do szybkiej orientacji:
- `autoload/admin/App.php`
- `autoload/front/App.php`
- `autoload/front/LayoutEngine.php`
- `autoload/api/ApiRouter.php`
- `.paul/docs/API.md`
- `docs/PROJECT_STRUCTURE.md`

228
.paul/docs/DB_SCHEMA.md Normal file
View File

@@ -0,0 +1,228 @@
# DB_SCHEMA
## Scope
Dokument opisuje praktyczny schema map dla `shopPRO`:
- najwazniejsze tabele i relacje,
- grupowanie po domenach biznesowych,
- kluczowe kolumny i indeksy, ktore maja znaczenie runtime,
- mapowanie tabela -> warstwa Domain.
Pelna lista tabel i historyczne notki migracyjne:
`docs/DATABASE_STRUCTURE.md` (source of truth dla detali kolumnowych).
## Konwencje globalne
- ORM: Medoo (`$mdb`)
- Prefix tabel: `pp_`
- Primary key: najczesciej `id` (INT AUTO_INCREMENT)
- Jezyki/translations: zwykle tabele `*_langs` z kluczem `lang_id`
- Wiele-do-wielu: tabele lacznikowe `*_products`, `*_payment_methods`, itp.
## Core commerce
### Produkty
- `pp_shop_products`
- core produktu i wariantu (`parent_id` dla kombinacji)
- ceny (`price_brutto`, `price_brutto_promo`), stany (`quantity`)
- flagi (`status`, `archive`, `promoted`)
- `pp_shop_products_langs`
- nazwy/opisy per jezyk
- `pp_shop_products_images`
- obrazy produktu
- `pp_shop_products_categories`
- przypisania produkt-kategoria
- `pp_shop_products_attributes`
- przypisania wariantu do wartosci cech
- `pp_shop_products_custom_fields`
- dodatkowe pola produktu
Warstwa: `Domain\Product\ProductRepository`, `Domain\Attribute\AttributeRepository`.
### Kategorie
- `pp_shop_categories`
- drzewo kategorii (`parent_id`), status, kolejnosc
- `pp_shop_categories_langs`
- tresci SEO i opisy kategorii
Warstwa: `Domain\Category\CategoryRepository`.
### Zamowienia
- `pp_shop_orders`
- dane klienta "w momencie zakupu", summary, status/platnosc, daty
- kluczowe pole integracyjne: `updated_at` (polling API)
Warstwa: `Domain\Order\OrderRepository`, `Domain\Order\OrderAdminService`.
### Klienci
- `pp_shop_clients`
- konto klienta i dane adresowe/logowania (uzywane przez ClientRepository)
Warstwa: `Domain\Client\ClientRepository`.
## Slowniki i checkout
### Platnosci
- `pp_shop_payment_methods`
- status, opis, mapowanie Apilo
- limity kwotowe: `min_order_amount`, `max_order_amount`
- COD flag: `is_cod`
Warstwa: `Domain\PaymentMethod\PaymentMethodRepository`.
### Transport
- `pp_shop_transports`
- koszt, status, limity, mapowanie Apilo
- `pp_shop_transport_payment_methods`
- relacja transport <-> platnosc (N:M)
Warstwa: `Domain\Transport\TransportRepository`.
### Statusy zamowien
- `pp_shop_statuses`
- statusy predefiniowane, kolor, mapowanie Apilo
Warstwa: `Domain\ShopStatus\ShopStatusRepository`.
## Marketing i merch
### Promocje i kupony
- `pp_shop_promotion`
- reguly promocji, daty aktywnosci, warunki i zakresy (JSON categories)
- `pp_shop_coupon`
- kupony, licznik uzyc, ograniczenia
Warstwa: `Domain\Promotion\PromotionRepository`, `Domain\Coupon\CouponRepository`.
### Producenci
- `pp_shop_producer`
- `pp_shop_producer_lang`
Warstwa: `Domain\Producer\ProducerRepository`.
### Zestawy produktow
- `pp_shop_product_sets`
- `pp_shop_product_sets_products`
Warstwa: `Domain\ProductSet\ProductSetRepository`.
### Cechy i wartosci
- `pp_shop_attributes`
- `pp_shop_attributes_langs`
- `pp_shop_attributes_values`
- `pp_shop_attributes_values_langs`
Warstwa: `Domain\Attribute\AttributeRepository`.
## CMS i frontend content
### Artykuly
- `pp_articles`
- `pp_articles_langs`
- `pp_articles_pages`
- `pp_articles_images`
- `pp_articles_files`
Warstwa: `Domain\Article\ArticleRepository`.
### Strony i layouty
- `pp_pages`
- `pp_layouts`
- `pp_layouts_pages`
- `pp_layouts_categories`
Warstwa: `Domain\Pages\PagesRepository`, `Domain\Layouts\LayoutsRepository`.
### Banery i kontenery statyczne
- `pp_banners`
- `pp_banners_langs`
- `pp_scontainers`
- `pp_scontainers_langs`
Warstwa: `Domain\Banner\BannerRepository`, `Domain\Scontainers\ScontainersRepository`.
## Ustawienia i system
### Ustawienia aplikacji
- `pp_settings`
- klucze globalne (w tym `api_key` dla REST API)
- `pp_shop_apilo_settings`
- `pp_shop_shoppro_settings`
Warstwa: `Domain\Settings\SettingsRepository`, `Domain\Integrations\IntegrationsRepository`.
### Jezyki i tlumaczenia
- `pp_langs`
- `pp_langs_translations`
Warstwa: `Domain\Languages\LanguagesRepository`.
### Uzytkownicy admina
- `pp_users`
- login, hash hasla, status
- pola 2FA (`twofa_*`)
Warstwa: `Domain\User\UserRepository`.
## Routing i URL mapping
- `pp_routes`
- regex `pattern` -> `destination` query string
- obsluguje trasy encji oraz trasy systemowe
- cache Redis: `pp_routes:all`
Runtime wykorzystanie:
- `index.php`
- `Shared\Helpers\Helpers::htacces()`
- repozytoria encji generujace/odswiezajace trasy.
## Kolejka cron
- `pp_cron_jobs`
- status processing pipeline (`pending`, `processing`, `completed`, `failed`, `cancelled`)
- retry/backoff: `attempts`, `max_attempts`, `scheduled_at`
- indeksy:
- `(status, priority, scheduled_at)`
- `(job_type)`
- `(status)`
- `pp_cron_schedules`
- harmonogramy okresowe (`interval_seconds`, `next_run_at`)
- indeks `(enabled, next_run_at)`
Warstwa: `Domain\CronJob\CronJobRepository`, `Domain\CronJob\CronJobProcessor`.
## Najwazniejsze relacje (FK logiczne)
- Produkt glowny -> wariant: `pp_shop_products.parent_id -> pp_shop_products.id`
- Produkt -> tlumaczenia: `pp_shop_products_langs.product_id -> pp_shop_products.id`
- Produkt -> kategoria: `pp_shop_products_categories.product_id -> pp_shop_products.id`
- Kategoria -> tlumaczenia: `pp_shop_categories_langs.category_id -> pp_shop_categories.id`
- Zamowienie -> klient: `pp_shop_orders.client_id -> pp_shop_clients.id` (opcjonalne)
- Transport <-> platnosc: `pp_shop_transport_payment_methods`
- Cecha -> wartosci -> warianty: `attributes -> values -> shop_products_attributes`
- Producent -> tlumaczenia: `pp_shop_producer_lang.producer_id -> pp_shop_producer.id`
- Kontener -> tlumaczenia: `pp_scontainers_langs.container_id -> pp_scontainers.id`
## Uwaga operacyjna
Ten dokument jest skrotem architektonicznym.
Przy zmianach SQL/migracji zawsze aktualizuj rownolegle:
1. `docs/DATABASE_STRUCTURE.md` (detal techniczny)
2. `.paul/docs/DB_SCHEMA.md` (mapa domenowa i impact runtime)

3
.paul/docs/DECISIONS.md Normal file
View File

@@ -0,0 +1,3 @@
# DECISIONS
> Kluczowe decyzje techniczne i ich uzasadnienia.

3
.paul/docs/STACK.md Normal file
View File

@@ -0,0 +1,3 @@
# STACK
> Stack technologiczny, wersje, zaleznosci, srodowisko.

View File

@@ -0,0 +1,12 @@
# TECH_CHANGELOG
> Chronologiczny log zmian technicznych — co i dlaczego.
## v0.348 (2026-04-19)
- Dodano przełącznik widoczności etykiet niestandardowych na liście produktów w panelu admina, z zapisem stanu w sesji.
- Po włączeniu opcji renderowane jest 5 pól custom_label_0..4 bezpośrednio pod sekcją zdjęcie/nazwa produktu.
- Dodano zapisywanie wartości etykiet niestandardowych do bazy oraz walidację dozwolonych typów etykiet po stronie kontrolera.
- Wprowadzono podpowiedzi istniejących wartości jako wybieralne sugestie z możliwością wpisania własnej wartości.
- Rozszerzono testy jednostkowe dla ShopProductController i ProductRepository pod nową funkcjonalność.

3688
.paul/docs/TODO.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
{"ts":"2026-04-16T09:36:18Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/shopPRO/.paul/phases/14-custom-fields-delete-bug\"\",\"description\":\"Create phase 14 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"inter","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:36:54Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\phases\\\\14-custom-fields-delete-bug\\\\14-01-PLAN.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:37:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:37:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:37:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:55:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\admin\\\\Controllers\\\\ShopProductController.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:55:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\Domain\\\\Product\\\\ProductRepository.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:56:01Z","tool":"Bash","cmd":"wc -l \"C:/visual studio code/projekty/shopPRO/tests/Unit/Domain/Product/ProductRepositoryTest.php\"\",\"description\":\"Count lines in test file\"},\"tool_response\":{\"stdout\":\"1295 C:/visual stud","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:56:13Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\tests\\\\Unit\\\\Domain\\\\Product\\\\ProductRepositoryTest.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:56:20Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && php phpunit.phar --filter testSaveCustomFieldsDeletesAllWhenEmpty 2>&1\",\"description\":\"Run specific unit test\"},\"tool_response\":{\"stdout\":\"PH","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:56:27Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && php phpunit.phar 2>&1 | tail -5\",\"description\":\"Run full test suite, show summary\"},\"tool_response\":{\"stdout\":\".. ","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T09:57:00Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:45:27Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\phases\\\\14-custom-fields-delete-bug\\\\14-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:45:38Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/shopPRO/.paul/changelog\"\",\"description\":\"Create changelog directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:45:45Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\changelog\\\\2026-04-16.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:46:16Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:46:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:50:25Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && php phpunit.phar 2>&1 | tail -5\",\"description\":\"Run full PHPUnit test suite\"},\"tool_response\":{\"stdout\":\".. ","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:50:35Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag --sort=-v:refname | head -3\",\"description\":\"Get latest version tags\"},\"tool_response\":{\"stdout\":\"v0.345\\nv0.344\\nv0.343\",\"stderr\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\CLAUDE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:31Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\docs\\\\TESTING.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:43Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git status -s\",\"description\":\"Show working tree status\"},\"tool_response\":{\"stdout\":\" M .paul/PROJECT.md\\n M .paul/ROADMAP.md\\n M .paul/STAT","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:50Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git add autoload/Domain/Product/ProductRepository.php autoload/admin/Controllers/ShopProductController.php tests/Unit/Domain/Product/ProductRepositoryT","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:51:58Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git commit -m \"$(cat <<'EOF'\\nfix: Custom fields delete bug — usunięcie wszystkich pól produktu nie działało\\n\\nDodano hidden marker custom_f","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:52:14Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git push 2>&1\",\"description\":\"Retry git push\"},\"tool_response\":{\"stdout\":\"remote: . Processing 1 references \\nremote: Processed 1 ref","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:52:25Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag v0.346 && powershell.exe -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.345 -ToTag v0.346 -ChangelogEntry \"Fix usuwania wszystkich","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:52:32Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git add updates/0.30/ver_0.346.zip updates/0.30/ver_0.346_manifest.json updates/versions.php updates/changelog-data.html && git commit -m \"$(cat <<'EO","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-16T20:52:43Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git push 2>&1 && git push origin v0.346 2>&1\",\"description\":\"Push commit and tag\"},\"tool_response\":{\"stdout\":\"remote: . Processing 1 referenc","cwd":"/c/visual studio code/projekty/shopPRO"}

View File

@@ -0,0 +1,150 @@
---
phase: 14-custom-fields-delete-bug
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- admin/templates/shop-product/product-edit-custom-script.php
- autoload/Domain/Product/ProductRepository.php
autonomous: true
delegation: off
---
<objective>
## Goal
Naprawić bug: usunięcie WSZYSTKICH dodatkowych pól produktu w panelu admina nie działa — pola pozostają po zapisie.
## Purpose
Właściciel sklepu musi mieć możliwość usunięcia wszystkich custom fields z produktu. Obecny bug blokuje tę operację.
## Output
- Poprawiony JS w szablonie — hidden field gwarantujący obecność klucza `custom_field_name` w POST
- Defensive check w repozytorium (opcjonalnie)
- Test jednostkowy potwierdzający poprawkę
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@admin/templates/shop-product/product-edit-custom-script.php
@autoload/Domain/Product/ProductRepository.php
</context>
<acceptance_criteria>
## AC-1: Usunięcie wszystkich custom fields zapisuje pusty stan
```gherkin
Given produkt ma 2 dodatkowe pola (np. "Grawerunek", "Kolor")
When admin usuwa oba pola i klika "Zatwierdź"
Then po zapisie produkt nie ma żadnych dodatkowych pól
And tabela pp_shop_products_custom_fields nie zawiera rekordów dla tego produktu
```
## AC-2: Częściowe usunięcie nadal działa
```gherkin
Given produkt ma 3 dodatkowe pola
When admin usuwa 1 pole i klika "Zatwierdź"
Then po zapisie produkt ma 2 dodatkowe pola
And usunięte pole nie istnieje w bazie
```
## AC-3: Dodawanie pól nadal działa
```gherkin
Given produkt nie ma dodatkowych pól
When admin dodaje 2 nowe pola i klika "Zatwierdź"
Then po zapisie produkt ma 2 dodatkowe pola
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodać hidden field gwarantujący klucz custom_field_name w POST</name>
<files>admin/templates/shop-product/product-edit-custom-script.php</files>
<action>
W szablonie product-edit-custom-script.php dodać ukryte pole w sekcji custom fields:
```html
<input type="hidden" name="custom_field_name_present" value="1">
```
To pole musi być ZAWSZE obecne w formularzu (nie wewnątrz dynamicznych wierszy pól),
tak aby serwer wiedział, że sekcja custom fields była obecna w formularzu.
ALTERNATYWNIE (lepsze rozwiązanie): zamiast hidden field, zmienić warunek w ProductRepository
z `array_key_exists('custom_field_name', $d)` na sprawdzanie obecności markera
`custom_field_name_present`.
Podejście: dodać hidden field `custom_field_name_present` w szablonie
+ zmienić warunek w ProductRepository na:
```php
if ( array_key_exists( 'custom_field_name_present', $d ) ) {
```
Dzięki temu:
- Gdy formularz jest renderowany → marker ZAWSZE w POST → saveCustomFields() ZAWSZE wywoływany
- Gdy API partial update bez custom fields → marker BRAK → skip (backward compat)
</action>
<verify>
1. Otworzyć edycję produktu z custom fields w przeglądarce
2. Usunąć wszystkie pola → Zatwierdź → sprawdzić że pola zniknęły
3. Otworzyć ponownie → potwierdzić brak pól
</verify>
<done>AC-1 satisfied: usunięcie wszystkich pól działa poprawnie</done>
</task>
<task type="auto">
<name>Task 2: Test jednostkowy — saveCustomFields z pustą listą</name>
<files>tests/Unit/Domain/Product/ProductRepositoryTest.php</files>
<action>
Dodać test weryfikujący że saveCustomFields() z pustymi tablicami
wywołuje delete na pp_shop_products_custom_fields dla danego produktu.
Test powinien mockować Medoo i sprawdzić:
- Że `delete('pp_shop_products_custom_fields', ['id_product' => $productId])` jest wywoływany
- Że żaden insert/update nie jest wywoływany
saveCustomFields() jest private — użyć Reflection do wywołania
lub testować przez publiczną metodę saveProduct() z odpowiednim payloadem
zawierającym `custom_field_name_present` i puste tablice.
</action>
<verify>./test.ps1 --filter testSaveCustomFieldsDeletesAllWhenEmpty</verify>
<done>AC-1 potwierdzone testem jednostkowym</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika saveCustomFields() dla niepustych list pól (insert/update) — działa poprawnie
- API partial update — brak markera = skip custom fields (backward compat)
- Inne sekcje formularza edycji produktu
## SCOPE LIMITS
- Tylko naprawa buga usuwania pól — żadne refactoring ani nowe feature
- Nie zmieniać struktury tabeli pp_shop_products_custom_fields
</boundaries>
<verification>
Before declaring plan complete:
- [ ] Usunięcie wszystkich custom fields → po zapisie brak pól (AC-1)
- [ ] Usunięcie części custom fields → pozostałe zachowane (AC-2)
- [ ] Dodanie nowych custom fields → poprawnie zapisane (AC-3)
- [ ] Testy przechodzą: ./test.ps1
- [ ] Brak regresji w istniejących testach
</verification>
<success_criteria>
- Wszystkie 3 AC spełnione
- Test jednostkowy przechodzi
- Zero regresji w istniejącym test suite (820+ testów)
</success_criteria>
<output>
After completion, create `.paul/phases/14-custom-fields-delete-bug/14-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,95 @@
---
phase: 14-custom-fields-delete-bug
plan: 01
subsystem: admin
tags: [custom-fields, product-edit, form-serialize, hidden-field]
requires: []
provides:
- Fix usuwania wszystkich custom fields z produktu
affects: []
tech-stack:
added: []
patterns: [hidden marker field for form section detection]
key-files:
created: []
modified:
- autoload/admin/Controllers/ShopProductController.php
- autoload/Domain/Product/ProductRepository.php
- tests/Unit/Domain/Product/ProductRepositoryTest.php
key-decisions:
- "Hidden marker custom_field_name_present zamiast polegania na obecności custom_field_name[] w POST"
patterns-established:
- "Marker hidden field pattern: gdy sekcja formularza może mieć 0 elementów, dodaj hidden marker żeby serwer wiedział że sekcja była renderowana"
duration: ~10min
completed: 2026-04-16
---
# Phase 14 Plan 01: Custom fields delete bug fix — Summary
**Naprawiono bug uniemożliwiający usunięcie wszystkich dodatkowych pól produktu — hidden marker gwarantuje wywołanie saveCustomFields() niezależnie od ilości pól.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Completed | 2026-04-16 |
| Tasks | 2 completed |
| Files modified | 3 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Usunięcie wszystkich custom fields | Pass | saveCustomFields() wywoływany dzięki markerowi, else branch kasuje wszystkie rekordy |
| AC-2: Częściowe usunięcie nadal działa | Pass | Logika saveCustomFields() dla niepustych list bez zmian |
| AC-3: Dodawanie pól nadal działa | Pass | Marker nie wpływa na insert/update path |
## Accomplishments
- Dodano hidden field `custom_field_name_present` w `renderCustomFieldsBox()` — zawsze obecny w POST
- Zmieniono warunek w `ProductRepository:1339` z `custom_field_name` na `custom_field_name_present`
- Dodano test jednostkowy potwierdzający delete all path (821 testów, 0 regresji)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `autoload/admin/Controllers/ShopProductController.php` | Modified | Hidden marker `custom_field_name_present` w renderCustomFieldsBox() |
| `autoload/Domain/Product/ProductRepository.php` | Modified | Warunek zmieniony na sprawdzanie markera |
| `tests/Unit/Domain/Product/ProductRepositoryTest.php` | Modified | Test testSaveCustomFieldsDeletesAllWhenEmpty |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Hidden marker zamiast wysyłania pustego array | jQuery .serialize() pomija puste pola array — marker jest niezawodny | Backward compat z API partial update (brak markera = skip) |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Bug naprawiony, test przechodzi, zero regresji
**Concerns:**
- None
**Blockers:**
- None
---
*Phase: 14-custom-fields-delete-bug, Plan: 01*
*Completed: 2026-04-16*

View File

@@ -0,0 +1,157 @@
---
phase: 15-scontainers-edit-save-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- autoload/admin/Controllers/ScontainersController.php
- tests/Unit/admin/Controllers/ScontainersControllerTest.php
autonomous: true
delegation: off
---
<objective>
## Goal
Naprawic regresje w edycji kontenerow statycznych: zapis edytowanego rekordu nie moze tworzyc nowego wpisu.
## Purpose
Administrator musi miec pewnosc, ze edycja kontenera aktualizuje istniejace ID. Obecny blad powoduje duplikaty i ryzyko niespojnych tresci.
## Output
- Poprawiony flow zapisu w `ScontainersController`, ktory zawsze przekazuje poprawne `id` przy edycji
- Testy jednostkowe zabezpieczajace przed powrotem regresji
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@autoload/admin/Controllers/ScontainersController.php
@admin/templates/components/form-edit.php
@autoload/admin/ViewModels/Forms/FormEditViewModel.php
@autoload/admin/Support/Forms/FormRequestHandler.php
@tests/Unit/admin/Controllers/ScontainersControllerTest.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| /feature-dev | required | Before implementation in APPLY | ○ |
| /koniec-pracy | required | After implementation/release wrap-up | ○ |
**BLOCKING:** Required skills MUST be loaded before APPLY proceeds.
Run each skill command or confirm already loaded.
## Skill Invocation Checklist
- [ ] /feature-dev loaded (run command or confirm)
- [ ] /koniec-pracy loaded (run command or confirm)
</skills>
<acceptance_criteria>
## AC-1: Edycja nie tworzy nowego kontenera
```gherkin
Given istnieje kontener statyczny o ID 9
When admin wejdzie w /admin/scontainers/edit/id=9 i kliknie "Zatwierdz"
Then rekord o ID 9 zostanie zaktualizowany
And nie powstanie nowy rekord w pp_scontainers
```
## AC-2: Tworzenie nowego kontenera nadal dziala
```gherkin
Given admin otwiera /admin/scontainers/edit/ bez ID
When wypelni dane i kliknie "Zatwierdz"
Then zapis utworzy nowy rekord w pp_scontainers
```
## AC-3: API legacy JSON pozostaje bez zmian
```gherkin
Given zapis kontenera odbywa sie przez legacy payload values (JSON)
When wywolywana jest sciezka legacy w ScontainersController::save()
Then zachowanie insert/update pozostaje zgodne z dotychczasowa logika
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Utrwalic przekazywanie ID w nowym formularzu scontainers</name>
<files>autoload/admin/Controllers/ScontainersController.php</files>
<action>
W `buildFormViewModel()` przeniesc `id` do `hiddenFields` (FormEditViewModel),
tak aby pole `id` bylo renderowane niezaleznie od zakladek.
W `save()` dodac defensywny fallback: jesli `data['id']` z requestu jest puste,
pobrac `id` z parametru trasy (`Helpers::get('id')`) i uzyc go przy zapisie.
Nie zmieniac flow legacy (`values` JSON) ani logiki repozytorium.
</action>
<verify>Manual check: edycja /admin/scontainers/edit/id=9 aktualizuje rekord 9 zamiast tworzyc nowy</verify>
<done>AC-1 i AC-3 satisfied</done>
</task>
<task type="auto">
<name>Task 2: Dodac test regresyjny dla formularza i mapowania ID</name>
<files>tests/Unit/admin/Controllers/ScontainersControllerTest.php</files>
<action>
Rozszerzyc testy kontrolera o przypadki potwierdzajace, ze formularz edycji
niesie `id` jako hidden field oraz ze flow zapisu potrafi odczytac ID rekordu
dla przypadku edycji.
Uzyc Reflection tam, gdzie potrzeba dostepu do prywatnych metod (zgodnie z obecnym stylem testow).
</action>
<verify>./test.ps1 tests/Unit/admin/Controllers/ScontainersControllerTest.php</verify>
<done>AC-1 covered by automated tests</done>
</task>
<task type="auto">
<name>Task 3: Zweryfikowac brak regresji create flow</name>
<files>autoload/admin/Controllers/ScontainersController.php, tests/Unit/admin/Controllers/ScontainersControllerTest.php</files>
<action>
Potwierdzic, ze nowy kontener (brak `id` w URL i formularzu) nadal tworzy nowy rekord.
Dostosowac warunki fallbacku tak, by nie wymuszaly update przy create.
</action>
<verify>Manual check: /admin/scontainers/edit/ -> Zatwierdz tworzy nowe ID</verify>
<done>AC-2 satisfied</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- autoload/Domain/Scontainers/ScontainersRepository.php (brak zmian logiki insert/update na poziomie repo)
- admin/templates/components/form-edit.php (bez globalnych zmian w uniwersalnym komponencie)
- Inne kontrolery admin poza ScontainersController
## SCOPE LIMITS
- Zakres tylko dla problemu edycji kontenerow statycznych (scontainers)
- Bez refaktoryzacji calego systemu FormEdit
</boundaries>
<verification>
Before declaring plan complete:
- [ ] ./test.ps1 tests/Unit/admin/Controllers/ScontainersControllerTest.php
- [ ] Manual: edycja istniejacego kontenera nie tworzy nowego rekordu
- [ ] Manual: tworzenie nowego kontenera nadal dziala
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- Blad edycji kontenerow statycznych nie wystepuje
- Test regresyjny przechodzi
- Brak regresji w create flow dla scontainers
</success_criteria>
<output>
After completion, create `.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,108 @@
---
phase: 15-scontainers-edit-save-fix
plan: 01
subsystem: admin
tags: [scontainers, form-edit, hidden-fields, regression-fix]
requires: []
provides:
- Fix edycji scontainers (update zamiast insert)
- Regresyjne testy kontrolera dla mapowania id
affects: []
tech-stack:
added: []
patterns: [hiddenFields for stable id transfer in tabbed form-edit]
key-files:
created: []
modified:
- autoload/admin/Controllers/ScontainersController.php
- tests/Unit/admin/Controllers/ScontainersControllerTest.php
key-decisions:
- "Przeniesienie id z FormField::hidden do hiddenFields w FormEditViewModel"
- "Fallback id z route parametru przy zapisie edycji"
patterns-established:
- "W formularzach z zakladkami id encji przekazujemy przez hiddenFields, nie przez pola przypisane do taba"
duration: ~20min
completed: 2026-04-18
---
# Phase 15 Plan 01: Scontainers edit save fix - Summary
**Naprawiono regresje, przez ktora edycja kontenera statycznego tworzyla nowy rekord zamiast aktualizacji istniejacego ID.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~20min |
| Completed | 2026-04-18 |
| Tasks | 3 completed |
| Files modified | 2 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Edycja nie tworzy nowego kontenera | Pass | `id` jest zawsze przenoszone przez hiddenFields + fallback z URL przy braku w POST |
| AC-2: Tworzenie nowego kontenera nadal dziala | Pass | Dla create `id=0`, action pozostaje `/admin/scontainers/save/` |
| AC-3: API legacy JSON pozostaje bez zmian | Pass | Sciezka `values` (legacy) nie byla modyfikowana |
## Accomplishments
- Przeniesiono `id` do `hiddenFields` w `ScontainersController::buildFormViewModel()`, co eliminuje gubienie `id` w formularzu tabowanym.
- Dodano defensywny fallback na `id` z parametru trasy w `ScontainersController::save()`.
- Dodano 2 testy regresyjne dla mapowania `id` i create-flow.
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `autoload/admin/Controllers/ScontainersController.php` | Modified | Stabilne przekazywanie `id` dla update oraz fallback route `id` |
| `tests/Unit/admin/Controllers/ScontainersControllerTest.php` | Modified | Testy regresyjne dla hiddenFields i create flow |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Uzyc `hiddenFields` zamiast `FormField::hidden('id')` | Hidden field w tabbed form moze nie byc renderowany w aktywnej strukturze pol | Brak tworzenia duplikatow przy edycji |
| Dodac fallback `id` z URL w `save()` | Dodatkowa odpornosc na brak `id` w payloadzie | Bezpieczny update dla `/admin/scontainers/save/id={id}` |
## Deviations from Plan
Brak istotnych odchylen implementacyjnych.
Skill audit:
- `/feature-dev` - pominiety na prosbe uzytkownika (override zapisany w STATE.md)
- `/koniec-pracy` - wymaganie zmapowane na `.claude/commands/koniec-pracy.md`
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Brak `test.ps1` w workspace | Test uruchomiony bezposrednio przez `php phpunit.phar ...` |
## Verification Results
- `php phpunit.phar tests/Unit/admin/Controllers/ScontainersControllerTest.php`
- Wynik: `OK (6 tests, 20 assertions)`
## Next Phase Readiness
**Ready:**
- Problem zapisu scontainers naprawiony na poziomie kontrolera.
- Testy regresyjne zabezpieczaja krytyczny przypadek.
**Concerns:**
- Manualna weryfikacja UI edycji/create nadal wskazana po stronie panelu admin.
**Blockers:**
- None.
---
*Phase: 15-scontainers-edit-save-fix, Plan: 01*
*Completed: 2026-04-18*

View File

@@ -0,0 +1,201 @@
---
phase: 16-product-list-custom-labels
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- autoload/admin/Controllers/ShopProductController.php
- autoload/Domain/Product/ProductRepository.php
- admin/templates/shop-product/products-list-custom-script.php
- tests/Unit/admin/Controllers/ShopProductControllerTest.php
- tests/Unit/Domain/Product/ProductRepositoryTest.php
autonomous: false
delegation: off
---
<objective>
## Goal
Dodac w liscie produktow (`/admin/shop_product/view_list/`) przelacznik "Pokaz etykiety niestandardowe", ktory zapisuje stan w sesji i po wlaczeniu pokazuje szybka edycje 5 pol `custom_label_0..4` z podpowiedziami.
## Purpose
Administrator ma szybciej uzupelniac etykiety Google XML bez wchodzenia do edycji kazdego produktu, z zachowaniem spojnosc danych i wygodnych podpowiedzi istniejacych wartosci.
## Output
- Nowy przycisk obok "Dodaj produkt", sterujacy widocznoscia custom labels i zapisujacy stan w sesji
- Render 5 pol `custom_label_0..4` pod nazwa/SKU produktu w tabeli, tylko przy wlaczonej opcji
- Zapis kazdego pola do `pp_shop_products` oraz system podpowiedzi z istniejacych wartosci w bazie
- Odczyt nazw etykiet z bazy (z fallbackiem) zamiast hardcodu w widoku listy
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@autoload/admin/Controllers/ShopProductController.php
@autoload/Domain/Product/ProductRepository.php
@admin/templates/components/table-list.php
@admin/templates/shop-product/products-list.php
@admin/templates/shop-product/products-list-custom-script.php
@tests/Unit/admin/Controllers/ShopProductControllerTest.php
@tests/Unit/Domain/Product/ProductRepositoryTest.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| /feature-dev | required | Before implementation in APPLY | ○ |
| /koniec-pracy | required | After implementation/release wrap-up | ○ |
**BLOCKING:** Required skills MUST be loaded before APPLY proceeds.
Run each skill command or confirm already loaded.
## Skill Invocation Checklist
- [ ] /feature-dev loaded (run command or confirm)
- [ ] /koniec-pracy loaded (run command or confirm)
</skills>
<acceptance_criteria>
## AC-1: Przelacznik widocznosci custom labels dziala i jest zapamietywany
```gherkin
Given administrator jest na /admin/shop_product/view_list/
When kliknie przycisk "Pokaz etykiety niestandardowe"
Then stan opcji zostanie zapisany w sesji
And po odswiezeniu/listowaniu tabela zachowa ustawiony stan (wlaczony lub wylaczony)
```
## AC-2: Lista produktow pokazuje 5 pol custom_label po wlaczeniu opcji
```gherkin
Given opcja "Pokaz etykiety niestandardowe" jest wlaczona
When lista produktow sie renderuje
Then pod sekcja zdjecie/nazwa/SKU-EAN dla kazdego produktu widoczne sa pola custom_label_0..custom_label_4
And etykiety tych pol sa pobrane dynamicznie z bazy danych (z fallbackiem tylko gdy brak konfiguracji)
```
## AC-3: Zapis i podpowiedzi wartosci dzialaja dla kazdego custom_label
```gherkin
Given administrator wpisuje wartosc w jednym z pol custom_label_0..custom_label_4
When wybierze podpowiedz lub zatwierdzi wpis
Then wartosc zostanie zapisana w pp_shop_products dla danego produktu i pola
And podpowiedzi sa budowane z juz istniejacych wartosci tego samego custom_label w bazie
```
## AC-4: Walidacja i bezpieczenstwo endpointow sa zachowane
```gherkin
Given zapytanie AJAX podaje nieprawidlowy typ labela spoza custom_label_0..4
When backend przetwarza request
Then operacja zostaje odrzucona bez zapisu
And odpowiedz zwraca status bledu bez ingerencji w dane produktu
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Dodac backend przelacznika sesyjnego i danych dla widoku listy</name>
<files>autoload/admin/Controllers/ShopProductController.php, autoload/Domain/Product/ProductRepository.php</files>
<action>
Rozszerzyc `ShopProductController::view_list()` o flage sesyjna dla widocznosci custom labels
oraz przekazanie do widoku nazw etykiet pobieranych z bazy.
Dodac akcje kontrolera do przelaczania flagi (toggle) i zwracania prostego JSON.
W `ProductRepository` dodac metode pobierajaca nazwy etykiet custom_label_0..4 z bazy
(np. tabela ustawien), z bezpiecznym fallbackiem "Custom label N" gdy wartosc nie istnieje.
Nie stosowac konkatenacji SQL dla danych wejsciowych.
</action>
<verify>Uruchomic testy kontrolera/repo oraz sprawdzic recznie, ze zmiana flagi utrzymuje sie po reloadzie listy</verify>
<done>AC-1 i AC-2 satisfied</done>
</task>
<task type="auto">
<name>Task 2: Dodac UI i logike AJAX dla custom labels na liscie produktow</name>
<files>autoload/admin/Controllers/ShopProductController.php, admin/templates/shop-product/products-list-custom-script.php</files>
<action>
Wygenerowac HTML 5 pol custom_label pod kolumna nazwy produktu tylko gdy flaga sesyjna jest wlaczona.
Uzyc klas zgodnych z istniejacym stylem (`custom-labels`, `custom_label_X_container`, listy sugestii).
Dodac przycisk "Pokaz etykiety niestandardowe" obok "Dodaj produkt" (hook przez custom script listy)
oraz obsluge klikniecia przez AJAX do nowej akcji toggle + odswiezenie aktualnego URL.
Podlaczyc dla kazdego inputa:
- pobieranie sugestii przez `/admin/shop_product/product_custom_label_suggestions/`
- zapis przez `/admin/shop_product/product_custom_label_save/`
z walidacja odpowiedzi i obsluga bledow UI.
</action>
<verify>Manual: wlaczyc opcje, wpisac i zapisac wartosc custom_label, odswiezyc strone, potwierdzic widocznosc i dane</verify>
<done>AC-2 i AC-3 satisfied</done>
</task>
<task type="auto">
<name>Task 3: Dodac testy regresyjne dla nowego zachowania</name>
<files>tests/Unit/admin/Controllers/ShopProductControllerTest.php, tests/Unit/Domain/Product/ProductRepositoryTest.php</files>
<action>
Rozszerzyc testy kontrolera o przypadki:
- toggle flagi sesyjnej
- odrzucenie nieprawidlowych typow labeli
- poprawne przekazanie danych do widoku listy przy wlaczonej opcji.
Rozszerzyc testy repozytorium o:
- pobieranie nazw custom labels z bazy z fallbackiem
- sugestie i zapis tylko dla dozwolonych label_type.
</action>
<verify>./test.ps1 tests/Unit/admin/Controllers/ShopProductControllerTest.php oraz ./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php</verify>
<done>AC-4 covered and AC-1..AC-3 protected by tests</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>Nowy przycisk sesyjny + szybka edycja custom labels 0..4 z podpowiedziami na liscie produktow</what-built>
<how-to-verify>
1. Otworz: /admin/shop_product/view_list/
2. Kliknij: "Pokaz etykiety niestandardowe"
3. Potwierdz: pola custom_label_0..4 pojawiaja sie pod nazwa produktu
4. Wpisz wartosc, wybierz podpowiedz i odswiez strone
5. Potwierdz: wartosc zostala zapisana i toggle pozostaje aktywny
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Globalnych komponentow listy niezwiązanych z produktami (`admin/templates/components/table-list.php`) poza minimalnym, koniecznym hookiem
- Endpointow API (`autoload/api/*`)
- Logiki produktow frontendowych (`autoload/front/*`, `templates/shop-product/*`)
## SCOPE LIMITS
- Zakres ograniczony do admin listy produktow i quick-edit custom labels
- Bez migracji DB w tym planie (odczyt nazw z istniejacych danych konfiguracyjnych)
- Bez refaktoru calego modułu integracji Google XML
</boundaries>
<verification>
Before declaring plan complete:
- [ ] ./test.ps1 tests/Unit/admin/Controllers/ShopProductControllerTest.php
- [ ] ./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
- [ ] Manual check: toggle zapisuje sie w sesji i zachowuje po reloadzie
- [ ] Manual check: podpowiedzi i zapis custom labels dzialaja dla 0..4
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- Przycisk "Pokaz etykiety niestandardowe" dziala i przechowuje stan w sesji
- Lista produktow pokazuje i zapisuje custom_label_0..4 bez wejscia w edycje produktu
- Nazwy etykiet sa pobierane z bazy z fallbackiem
- Testy regresyjne dla backendu i repozytorium przechodza
</success_criteria>
<output>
After completion, create `.paul/phases/16-product-list-custom-labels/16-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,149 @@
---
phase: 16-product-list-custom-labels
plan: 01
subsystem: admin
tags: [shop-product, custom-label, session-toggle, autocomplete, quick-edit]
requires: []
provides:
- Szybka edycja custom_label_0..4 na liscie produktow
- Przelacznik widocznosci etykiet w sesji admina
- Podpowiedzi istniejacych wartosci + wpisywanie wartosci wlasnej w jednym polu
affects: [shop-product-list, google-xml-label-flow]
tech-stack:
added: []
patterns: [inline quick-edit in table list, datalist autocomplete in single input, settings fallback mapping]
key-files:
created: []
modified:
- autoload/admin/Controllers/ShopProductController.php
- autoload/Domain/Product/ProductRepository.php
- admin/templates/shop-product/products-list.php
- admin/templates/shop-product/products-list-custom-script.php
- tests/Unit/admin/Controllers/ShopProductControllerTest.php
- tests/Unit/Domain/Product/ProductRepositoryTest.php
key-decisions:
- "Przelacznik widocznosci custom labels zapisany w sesji (per admin session)"
- "Nazwy custom labels pobierane z pp_settings z fallbackiem do domyslnych nazw"
- "UX: jedno pole input z autocomplete (datalist), bez osobnego select pod spodem"
patterns-established:
- "Dla list admina: toggle funkcji przez dedykowany endpoint JSON + reload widoku"
- "Quick-edit text fields: walidacja typu po stronie kontrolera przed zapisem/sugestiami"
duration: ~120min
completed: 2026-04-19
---
# Phase 16 Plan 01: Product list custom labels quick edit - Summary
**Wdrozono szybka edycje custom labels na liscie produktow z przelacznikiem sesyjnym i autocomplete w pojedynczym polu.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~120min |
| Completed | 2026-04-19 |
| Tasks | 3 completed + 1 checkpoint approved |
| Files modified | 6 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Przelacznik widocznosci custom labels dziala i jest zapamietywany | Pass | Dodano przycisk toggle + zapis stanu w sesji admina |
| AC-2: Lista pokazuje 5 pol custom_label po wlaczeniu opcji | Pass | Pola custom_label_0..4 renderowane pod nazwa/SKU/EAN tylko przy wlaczonej opcji |
| AC-3: Zapis i podpowiedzi wartosci dzialaja dla custom_label | Pass | Zapis AJAX do bazy + autocomplete istniejacych wartosci i mozliwosc wpisu wlasnego |
| AC-4: Walidacja i bezpieczenstwo endpointow zachowane | Pass | Kontroler odrzuca niedozwolone label_type przed zapisem i pobraniem sugestii |
## Accomplishments
- Dodano nowy toggle "Pokaz/Ukryj etykiety niestandardowe" przy liscie produktow, sterowany sesja.
- Dodano inline quick-edit custom_label_0..4 bez wchodzenia do edycji produktu.
- Podpowiedzi dzialaja jako autocomplete w tym samym input (nie osobny kontrolka pod polem).
- Ujednolicono UX przycisku toggle (kolorystyka, rozmiar, hover, czytelnosc).
- Rozszerzono testy kontrolera i repozytorium o nowe przypadki.
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `autoload/admin/Controllers/ShopProductController.php` | Modified | Toggle sesyjny, render custom labels w tabeli, walidacja label_type |
| `autoload/Domain/Product/ProductRepository.php` | Modified | Pobieranie nazw custom_label z ustawien (fallback) |
| `admin/templates/shop-product/products-list.php` | Modified | Przekazanie flagi custom_labels_enabled do skryptu |
| `admin/templates/shop-product/products-list-custom-script.php` | Modified | UI toggle, zapis/sugestie custom label, autocomplete, poprawki wygladu |
| `tests/Unit/admin/Controllers/ShopProductControllerTest.php` | Modified | Testy nowych metod i walidacji kontrolera |
| `tests/Unit/Domain/Product/ProductRepositoryTest.php` | Modified | Testy customLabelNames + walidacji sugestii/zapisu |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Session key dla toggla (`shop_product_show_custom_labels`) | Funkcja ma byc osobnym trybem widoku admina | Stabilny stan po odswiezeniu strony |
| Nazwy etykiet z `pp_settings` + fallback | Wymaganie "nazwy z bazy" i bezpieczne zachowanie gdy brak konfiguracji | Elastyczne nazewnictwo bez hardcodu |
| Autocomplete w jednym polu zamiast input + oddzielny select | UX feedback od usera podczas checkpointu | Czytelniejszy i szybszy flow edycji |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | Niski, techniczny (BOM w pliku kontrolera) |
| Scope additions | 2 | Niski, UX polish po feedbacku checkpoint |
| Deferred | 0 | Brak |
**Total impact:** Niezbedne poprawki techniczne i UX bez scope creep funkcjonalnego.
### Auto-fixed Issues
**1. Encoding/BOM in controller file**
- **Found during:** Task 1 implementation verification
- **Issue:** Parser PHP zglaszal blad namespace przez BOM na poczatku pliku
- **Fix:** Zapisano plik `ShopProductController.php` jako UTF-8 bez BOM
- **Files:** `autoload/admin/Controllers/ShopProductController.php`
- **Verification:** `php -l autoload/admin/Controllers/ShopProductController.php`
### Deferred Items
None.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Drobne regresje UX przycisku toggle | Iteracyjna poprawka stylu i hover po feedbacku usera |
| Forma podpowiedzi (select pod inputem) nieakceptowalna UX | Zmieniono na jedno pole z autocomplete (datalist) |
## Verification Results
- `php -l autoload/admin/Controllers/ShopProductController.php` -> OK
- `php -l autoload/Domain/Product/ProductRepository.php` -> OK
- `php -l admin/templates/shop-product/products-list-custom-script.php` -> OK
- `php phpunit.phar tests/Unit/admin/Controllers/ShopProductControllerTest.php` -> OK (15 tests, 71 assertions)
- `php phpunit.phar tests/Unit/Domain/Product/ProductRepositoryTest.php` -> OK (64 tests, 131 assertions)
- Checkpoint human-verify: approved by user after final UX adjustments
Skill audit:
- `/feature-dev` - not invoked (user-approved override)
- `/koniec-pracy` - acknowledged by user as available for end-of-session flow
## Next Phase Readiness
**Ready:**
- Admin ma szybki i praktyczny workflow uzupelniania custom labels bez przechodzenia do edycji produktu.
- Kod posiada testy regresyjne dla nowej logiki backendowej.
**Concerns:**
- Brak.
**Blockers:**
- None.
---
*Phase: 16-product-list-custom-labels, Plan: 01*
*Completed: 2026-04-19*

File diff suppressed because one or more lines are too long

View File

@@ -2,5 +2,5 @@ projectKey=shopPRO
serverUrl=https://sonar.project-pro.pl serverUrl=https://sonar.project-pro.pl
serverVersion=26.3.0.120487 serverVersion=26.3.0.120487
dashboardUrl=https://sonar.project-pro.pl/dashboard?id=shopPRO dashboardUrl=https://sonar.project-pro.pl/dashboard?id=shopPRO
ceTaskId=5c360d0b-34ee-4995-ae1b-d5ac7ca47218 ceTaskId=3051d3c7-50d0-4263-bdc3-12917447a707
ceTaskUrl=https://sonar.project-pro.pl/api/ce/task?id=5c360d0b-34ee-4995-ae1b-d5ac7ca47218 ceTaskUrl=https://sonar.project-pro.pl/api/ce/task?id=3051d3c7-50d0-4263-bdc3-12917447a707

165
AGENTS.md
View File

@@ -1,4 +1,4 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@@ -7,19 +7,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete). shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete).
## Zasady pisania kodu ## Zasady pisania kodu
- Kod ma być czytelny dla obcego: jasne nazwy, mało magii - Kod ma być czytelny „dla obcego”: jasne nazwy, maĹ‚o magii
- Brak „skrótów na szybko typu logika w widokach, copy-paste, losowe helpery bez spójności - Brak „skrĂłtĂłw na szybko” typu logika w widokach, copy-paste, losowe helpery bez spĂłjnoĹ›ci
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 3050 linii (jeśli dłuższe dzielić) - KaĹĽda funkcja/klasa ma mieć jednÄ… odpowiedzialność, zwykle do 30–50 linii (jeĹ›li dĹuĹĽsze – dzielić)
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod - max 3 poziomy zagnieĹĽdĹĽeĹ„ (if/foreach), reszta do osobnych metod
- Nazewnictwo: - Nazewnictwo:
- klasy: PascalCase - klasy: PascalCase
- metody/zmienne: camelCase - metody/zmienne: camelCase
- stałe: UPPER_SNAKE_CASE - staĹ‚e: UPPER_SNAKE_CASE
- Zero skrótologii w nazwach (np. $d, $tmp, $x1) poza pętlami 23 linijki - Zero „skrĂłtologii” w nazwach (np. $d, $tmp, $x1) poza pÄ™tlami 2–3 linijki
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem) - medoo + prepared statements bez wyjÄ…tkĂłw (ĹĽadnego sklejania SQL stringiem)
- XSS: escape w widokach (np. helper e()) - XSS: escape w widokach (np. helper e())
- CSRF dla formularzy, sensowna obsługa sesji - CSRF dla formularzy, sensowna obsĹ‚uga sesji
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego, nie „co” - Kod ma mieć komentarze tylko tam, gdzie wyjaĹ›niajÄ… „dlaczego”, nie „co”
## PHP Version Constraint ## PHP Version Constraint
@@ -36,7 +36,7 @@ shopPRO is a PHP e-commerce platform with an admin panel and customer-facing sto
### Running Tests ### Running Tests
```bash ```bash
# Full suite (recommended PowerShell, auto-finds php) # Full suite (recommended — PowerShell, auto-finds php)
./test.ps1 ./test.ps1
# Specific file # Specific file
@@ -61,50 +61,50 @@ See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packag
### Directory Structure ### Directory Structure
``` ```
shopPRO/ shopPRO/
├── autoload/ # Autoloaded classes (core codebase) ├── autoload/ # Autoloaded classes (core codebase)
│ ├── Domain/ # Business logic repositories (\Domain\) │ ├── Domain/ # Business logic repositories (\Domain\)
│ ├── Shared/ # Shared utilities (\Shared\) │ ├── Shared/ # Shared utilities (\Shared\)
│ │ ├── Cache/ # CacheHandler, RedisConnection │ │ ├── Cache/ # CacheHandler, RedisConnection
│ │ ├── Email/ # Email (PHPMailer wrapper) │ │ ├── Email/ # Email (PHPMailer wrapper)
│ │ ├── Helpers/ # Helpers (formerly class.S.php) │ │ ├── Helpers/ # Helpers (formerly class.S.php)
│ │ ├── Html/ # Html utility │ │ ├── Html/ # Html utility
│ │ ├── Image/ # ImageManipulator │ │ ├── Image/ # ImageManipulator
│ │ └── Tpl/ # Template engine │ │ └── Tpl/ # Template engine
│ ├── api/ # REST API layer (\api\) │ ├── api/ # REST API layer (\api\)
│ │ ├── ApiRouter.php # API router (\api\ApiRouter) │ │ ├── ApiRouter.php # API router (\api\ApiRouter)
│ │ └── Controllers/ # API controllers (\api\Controllers\) │ │ └── Controllers/ # API controllers (\api\Controllers\)
│ ├── admin/ # Admin panel layer │ ├── admin/ # Admin panel layer
│ │ ├── App.php # Admin router (\admin\App) │ │ ├── App.php # Admin router (\admin\App)
│ │ ├── Controllers/ # DI controllers (\admin\Controllers\) 28 controllers │ │ ├── Controllers/ # DI controllers (\admin\Controllers\) — 28 controllers
│ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer │ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer
│ │ ├── Validation/ # FormValidator │ │ ├── Validation/ # FormValidator
│ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel) │ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel)
│ └── front/ # Frontend layer │ └── front/ # Frontend layer
├── App.php # Frontend router (\front\App) │ ├── App.php # Frontend router (\front\App)
├── LayoutEngine.php # Layout engine (\front\LayoutEngine) │ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine)
├── Controllers/ # DI controllers (\front\Controllers\) 8 controllers │ ├── Controllers/ # DI controllers (\front\Controllers\) — 8 controllers
└── Views/ # Static views (\front\Views\) 11 view classes │ └── Views/ # Static views (\front\Views\) — 11 view classes
├── admin/ # Admin panel ├── admin/ # Admin panel
│ ├── templates/ # Admin view templates │ ├── templates/ # Admin view templates
│ └── layout/ # Admin CSS/JS/icons │ └── layout/ # Admin CSS/JS/icons
├── templates/ # Frontend view templates ├── templates/ # Frontend view templates
├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer) ├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer)
├── tests/ # PHPUnit tests ├── tests/ # PHPUnit tests
│ ├── bootstrap.php │ ├── bootstrap.php
│ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct) │ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct)
│ └── Unit/ │ └── Unit/
├── Domain/ # Repository tests │ ├── Domain/ # Repository tests
├── admin/Controllers/ # Controller tests │ ├── admin/Controllers/ # Controller tests
└── api/ # API tests │ └── api/ # API tests
├── updates/ # Update packages for clients ├── updates/ # Update packages for clients
├── docs/ # Technical documentation ├── docs/ # Technical documentation
├── config.php # Database/Redis config (not in repo) ├── config.php # Database/Redis config (not in repo)
├── index.php # Frontend entry point ├── index.php # Frontend entry point
├── ajax.php # Frontend AJAX handler ├── ajax.php # Frontend AJAX handler
├── admin/index.php # Admin entry point ├── admin/index.php # Admin entry point
├── admin/ajax.php # Admin AJAX handler ├── admin/ajax.php # Admin AJAX handler
├── cron.php # CRON jobs (Apilo sync) ├── cron.php # CRON jobs (Apilo sync)
└── api.php # REST API (ordersPRO + Ekomi) └── api.php # REST API (ordersPRO + Ekomi)
``` ```
### Autoloader ### Autoloader
@@ -114,19 +114,19 @@ Custom autoloader in each entry point (not Composer autoload at runtime). Tries
2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback) 2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback)
### Namespace Conventions (case-sensitive on Linux!) ### Namespace Conventions (case-sensitive on Linux!)
- `\Domain\` `autoload/Domain/` (uppercase D) - `\Domain\` → `autoload/Domain/` (uppercase D)
- `\admin\Controllers\` `autoload/admin/Controllers/` (lowercase a) - `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a)
- `\Shared\` `autoload/Shared/` - `\Shared\` → `autoload/Shared/`
- `\api\` `autoload/api/` - `\api\` → `autoload/api/`
- Do NOT use `\Admin\` (uppercase A) the server directory is `admin/` (lowercase) - Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase)
- `\shop\` namespace is **deleted** all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed - `\shop\` namespace is **deleted** — all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed
### Domain-Driven Architecture (migration complete) ### Domain-Driven Architecture (migration complete)
All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern: All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern:
**Domain Layer** (`autoload/Domain/{Module}/`): **Domain Layer** (`autoload/Domain/{Module}/`):
- `{Module}Repository.php` data access, business logic, Redis caching - `{Module}Repository.php` — data access, business logic, Redis caching
- Constructor DI with `$db` (Medoo instance) - Constructor DI with `$db` (Medoo instance)
- Methods serve both admin and frontend (shared Domain, no separate services) - Methods serve both admin and frontend (shared Domain, no separate services)
@@ -141,7 +141,7 @@ All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `fro
- Wired in `front\App::getControllerFactories()` - Wired in `front\App::getControllerFactories()`
**Frontend Views** (`autoload/front/Views/`): **Frontend Views** (`autoload/front/Views/`):
- Static classes, no state, no DI pure rendering - Static classes, no state, no DI — pure rendering
**API Controllers** (`autoload/api/Controllers/`): **API Controllers** (`autoload/api/Controllers/`):
- DI via constructor, stateless (no session) - DI via constructor, stateless (no session)
@@ -151,13 +151,13 @@ All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `fro
### Key Classes ### Key Classes
| Class | Purpose | | Class | Purpose |
|-------|---------| |-------|---------|
| `\admin\App` | Admin router maps URL segments to controllers | | `\admin\App` | Admin router — maps URL segments to controllers |
| `\front\App` | Frontend router `route()`, `checkUrlParams()` | | `\front\App` | Frontend router — `route()`, `checkUrlParams()` |
| `\front\LayoutEngine` | Frontend layout engine `show()`, tag replacement | | `\front\LayoutEngine` | Frontend layout engine — `show()`, tag replacement |
| `\Shared\Helpers\Helpers` | Utility methods (SEO, email, cache clearing) | | `\Shared\Helpers\Helpers` | Utility methods (SEO, email, cache clearing) |
| `\Shared\Tpl\Tpl` | Template engine `render()`, `set()` | | `\Shared\Tpl\Tpl` | Template engine — `render()`, `set()` |
| `\Shared\Cache\CacheHandler` | Redis cache `get()`, `set()`, `delete()`, `deletePattern()` | | `\Shared\Cache\CacheHandler` | Redis cache — `get()`, `set()`, `delete()`, `deletePattern()` |
| `\api\ApiRouter` | REST API router auth, routing, response helpers | | `\api\ApiRouter` | REST API router — auth, routing, response helpers |
### Database ### Database
- ORM: Medoo (`$mdb` global variable, injected via DI in new code) - ORM: Medoo (`$mdb` global variable, injected via DI in new code)
@@ -179,7 +179,7 @@ Universal form system for admin edit views. Docs: `docs/FORM_EDIT_SYSTEM.md`.
- Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)` - Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)`
- Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")` - Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
- Default TTL: 86400 (24h) - Default TTL: 86400 (24h)
- Data is serialized requires `unserialize()` after `get()` - Data is serialized — requires `unserialize()` after `get()`
- Config: `config.php` (`$config['redis']`) - Config: `config.php` (`$config['redis']`)
## Code Patterns ## Code Patterns
@@ -203,7 +203,7 @@ $controller = new \admin\Controllers\ExampleController($repo);
``` ```
### Medoo ORM pitfalls ### Medoo ORM pitfalls
- `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 has caused bugs - `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 — has caused bugs
- `$mdb->get()` returns `null` when no record, NOT `false` - `$mdb->get()` returns `null` when no record, NOT `false`
- After `$mdb->insert()`, check `$mdb->id()` to confirm success - After `$mdb->insert()`, check `$mdb->id()` to confirm success
@@ -222,18 +222,19 @@ $controller = new \admin\Controllers\ExampleController($repo);
When user says **"KONIEC PRACY"**, run `/koniec-pracy` (see `.claude/commands/koniec-pracy.md`). When user says **"KONIEC PRACY"**, run `/koniec-pracy` (see `.claude/commands/koniec-pracy.md`).
Before starting implementation, review current state of docs. Before starting implementation, review current state of docs.
For documentation updates, modify only .paul/docs/* unless the user explicitly asks to update root docs/*.
## Key Documentation ## Key Documentation
- `docs/MEMORY.md` project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions - `docs/MEMORY.md` — project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions
- `docs/PROJECT_STRUCTURE.md` current architecture, layers, cache, entry points, integrations - `docs/PROJECT_STRUCTURE.md` — current architecture, layers, cache, entry points, integrations
- `docs/DATABASE_STRUCTURE.md` full database schema - `docs/DATABASE_STRUCTURE.md` — full database schema
- `docs/TESTING.md` test suite guide and structure - `docs/TESTING.md` — test suite guide and structure
- `docs/FORM_EDIT_SYSTEM.md` form system architecture - `docs/FORM_EDIT_SYSTEM.md` — form system architecture
- `docs/CHANGELOG.md` version history - `docs/CHANGELOG.md` — version history
- `api-docs/api-reference.json` REST API documentation (ordersPRO) - `api-docs/api-reference.json` — REST API documentation (ordersPRO)
- `api-docs/index.html` REST API documentation (ordersPRO) - `api-docs/index.html` — REST API documentation (ordersPRO)
- `docs/UPDATE_INSTRUCTIONS.md` how to build client update packages - `docs/UPDATE_INSTRUCTIONS.md` — how to build client update packages
## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP ## Za kaĹĽdym razem jak prĂłbujesz sprawdzić jakiĹ› plik z logami sprĂłbuj go najpierw pobrać z serwera FTP
## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp ## Wszystkie pliki ktĂłre tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twĂłrz w folderze temp

176
CLAUDE.md
View File

@@ -1,4 +1,4 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@@ -7,19 +7,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete). shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete).
## Zasady pisania kodu ## Zasady pisania kodu
- Kod ma być czytelny dla obcego: jasne nazwy, mało magii - Kod ma być czytelny „dla obcego”: jasne nazwy, maĹ‚o magii
- Brak „skrótów na szybko typu logika w widokach, copy-paste, losowe helpery bez spójności - Brak „skrĂłtĂłw na szybko” typu logika w widokach, copy-paste, losowe helpery bez spĂłjnoĹ›ci
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 3050 linii (jeśli dłuższe dzielić) - KaĹĽda funkcja/klasa ma mieć jednÄ… odpowiedzialność, zwykle do 30–50 linii (jeĹ›li dĹuĹĽsze – dzielić)
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod - max 3 poziomy zagnieĹĽdĹĽeĹ„ (if/foreach), reszta do osobnych metod
- Nazewnictwo: - Nazewnictwo:
- klasy: PascalCase - klasy: PascalCase
- metody/zmienne: camelCase - metody/zmienne: camelCase
- stałe: UPPER_SNAKE_CASE - staĹ‚e: UPPER_SNAKE_CASE
- Zero skrótologii w nazwach (np. $d, $tmp, $x1) poza pętlami 23 linijki - Zero „skrĂłtologii” w nazwach (np. $d, $tmp, $x1) poza pÄ™tlami 2–3 linijki
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem) - medoo + prepared statements bez wyjÄ…tkĂłw (ĹĽadnego sklejania SQL stringiem)
- XSS: escape w widokach (np. helper e()) - XSS: escape w widokach (np. helper e())
- CSRF dla formularzy, sensowna obsługa sesji - CSRF dla formularzy, sensowna obsĹ‚uga sesji
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego, nie „co” - Kod ma mieć komentarze tylko tam, gdzie wyjaĹ›niajÄ… „dlaczego”, nie „co”
## PHP Version Constraint ## PHP Version Constraint
@@ -36,7 +36,7 @@ shopPRO is a PHP e-commerce platform with an admin panel and customer-facing sto
### Running Tests ### Running Tests
```bash ```bash
# Full suite (recommended PowerShell, auto-finds php) # Full suite (recommended — PowerShell, auto-finds php)
./test.ps1 ./test.ps1
# Specific file # Specific file
@@ -55,60 +55,60 @@ composer test # standard
PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`.
Current suite: **820 tests, 2277 assertions**. Current suite: **823 tests, 2284 assertions**.
### Creating Updates ### Creating Updates
See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. ZIP structure must start directly from project directories no version subfolder inside the archive. See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. ZIP structure must start directly from project directories — no version subfolder inside the archive.
## Architecture ## Architecture
### Directory Structure ### Directory Structure
``` ```
shopPRO/ shopPRO/
├── autoload/ # Autoloaded classes (core codebase) ├── autoload/ # Autoloaded classes (core codebase)
│ ├── Domain/ # Business logic repositories (\Domain\) │ ├── Domain/ # Business logic repositories (\Domain\)
│ ├── Shared/ # Shared utilities (\Shared\) │ ├── Shared/ # Shared utilities (\Shared\)
│ │ ├── Cache/ # CacheHandler, RedisConnection │ │ ├── Cache/ # CacheHandler, RedisConnection
│ │ ├── Email/ # Email (PHPMailer wrapper) │ │ ├── Email/ # Email (PHPMailer wrapper)
│ │ ├── Helpers/ # Helpers (formerly class.S.php) │ │ ├── Helpers/ # Helpers (formerly class.S.php)
│ │ ├── Html/ # Html utility │ │ ├── Html/ # Html utility
│ │ ├── Image/ # ImageManipulator │ │ ├── Image/ # ImageManipulator
│ │ └── Tpl/ # Template engine │ │ └── Tpl/ # Template engine
│ ├── api/ # REST API layer (\api\) │ ├── api/ # REST API layer (\api\)
│ │ ├── ApiRouter.php # API router (\api\ApiRouter) │ │ ├── ApiRouter.php # API router (\api\ApiRouter)
│ │ └── Controllers/ # API controllers (\api\Controllers\) │ │ └── Controllers/ # API controllers (\api\Controllers\)
│ ├── admin/ # Admin panel layer │ ├── admin/ # Admin panel layer
│ │ ├── App.php # Admin router (\admin\App) │ │ ├── App.php # Admin router (\admin\App)
│ │ ├── Controllers/ # DI controllers (\admin\Controllers\) 28 controllers │ │ ├── Controllers/ # DI controllers (\admin\Controllers\) — 28 controllers
│ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer │ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer
│ │ ├── Validation/ # FormValidator │ │ ├── Validation/ # FormValidator
│ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel) │ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel)
│ └── front/ # Frontend layer │ └── front/ # Frontend layer
├── App.php # Frontend router (\front\App) │ ├── App.php # Frontend router (\front\App)
├── LayoutEngine.php # Layout engine (\front\LayoutEngine) │ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine)
├── Controllers/ # DI controllers (\front\Controllers\) 8 controllers │ ├── Controllers/ # DI controllers (\front\Controllers\) — 8 controllers
└── Views/ # Static views (\front\Views\) 11 view classes │ └── Views/ # Static views (\front\Views\) — 11 view classes
├── admin/ # Admin panel ├── admin/ # Admin panel
│ ├── templates/ # Admin view templates │ ├── templates/ # Admin view templates
│ └── layout/ # Admin CSS/JS/icons │ └── layout/ # Admin CSS/JS/icons
├── templates/ # Frontend view templates ├── templates/ # Frontend view templates
├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer) ├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer)
├── tests/ # PHPUnit tests ├── tests/ # PHPUnit tests
│ ├── bootstrap.php │ ├── bootstrap.php
│ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct) │ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct)
│ └── Unit/ │ └── Unit/
├── Domain/ # Repository tests │ ├── Domain/ # Repository tests
├── admin/Controllers/ # Controller tests │ ├── admin/Controllers/ # Controller tests
└── api/ # API tests │ └── api/ # API tests
├── updates/ # Update packages for clients ├── updates/ # Update packages for clients
├── docs/ # Technical documentation ├── docs/ # Technical documentation
├── config.php # Database/Redis config (not in repo) ├── config.php # Database/Redis config (not in repo)
├── index.php # Frontend entry point ├── index.php # Frontend entry point
├── ajax.php # Frontend AJAX handler ├── ajax.php # Frontend AJAX handler
├── admin/index.php # Admin entry point ├── admin/index.php # Admin entry point
├── admin/ajax.php # Admin AJAX handler ├── admin/ajax.php # Admin AJAX handler
├── cron.php # CRON jobs (Apilo sync) ├── cron.php # CRON jobs (Apilo sync)
└── api.php # REST API (ordersPRO + Ekomi) └── api.php # REST API (ordersPRO + Ekomi)
``` ```
### Autoloader ### Autoloader
@@ -118,19 +118,19 @@ Custom autoloader in each entry point (not Composer autoload at runtime). Tries
2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback) 2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback)
### Namespace Conventions (case-sensitive on Linux!) ### Namespace Conventions (case-sensitive on Linux!)
- `\Domain\` `autoload/Domain/` (uppercase D) - `\Domain\` → `autoload/Domain/` (uppercase D)
- `\admin\Controllers\` `autoload/admin/Controllers/` (lowercase a) - `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a)
- `\Shared\` `autoload/Shared/` - `\Shared\` → `autoload/Shared/`
- `\api\` `autoload/api/` - `\api\` → `autoload/api/`
- Do NOT use `\Admin\` (uppercase A) the server directory is `admin/` (lowercase) - Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase)
- `\shop\` namespace is **deleted** all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed - `\shop\` namespace is **deleted** — all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed
### Domain-Driven Architecture (migration complete) ### Domain-Driven Architecture (migration complete)
All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern: All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern:
**Domain Layer** (`autoload/Domain/{Module}/`): **Domain Layer** (`autoload/Domain/{Module}/`):
- `{Module}Repository.php` data access, business logic, Redis caching - `{Module}Repository.php` — data access, business logic, Redis caching
- Constructor DI with `$db` (Medoo instance) - Constructor DI with `$db` (Medoo instance)
- Methods serve both admin and frontend (shared Domain, no separate services) - Methods serve both admin and frontend (shared Domain, no separate services)
@@ -145,7 +145,7 @@ All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `fro
- Wired in `front\App::getControllerFactories()` - Wired in `front\App::getControllerFactories()`
**Frontend Views** (`autoload/front/Views/`): **Frontend Views** (`autoload/front/Views/`):
- Static classes, no state, no DI pure rendering - Static classes, no state, no DI — pure rendering
**API Controllers** (`autoload/api/Controllers/`): **API Controllers** (`autoload/api/Controllers/`):
- DI via constructor, stateless (no session) - DI via constructor, stateless (no session)
@@ -155,13 +155,13 @@ All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `fro
### Key Classes ### Key Classes
| Class | Purpose | | Class | Purpose |
|-------|---------| |-------|---------|
| `\admin\App` | Admin router maps URL segments to controllers | | `\admin\App` | Admin router — maps URL segments to controllers |
| `\front\App` | Frontend router `route()`, `checkUrlParams()` | | `\front\App` | Frontend router — `route()`, `checkUrlParams()` |
| `\front\LayoutEngine` | Frontend layout engine `show()`, tag replacement | | `\front\LayoutEngine` | Frontend layout engine — `show()`, tag replacement |
| `\Shared\Helpers\Helpers` | Utility methods (SEO, email, cache clearing) | | `\Shared\Helpers\Helpers` | Utility methods (SEO, email, cache clearing) |
| `\Shared\Tpl\Tpl` | Template engine `render()`, `set()` | | `\Shared\Tpl\Tpl` | Template engine — `render()`, `set()` |
| `\Shared\Cache\CacheHandler` | Redis cache `get()`, `set()`, `delete()`, `deletePattern()` | | `\Shared\Cache\CacheHandler` | Redis cache — `get()`, `set()`, `delete()`, `deletePattern()` |
| `\api\ApiRouter` | REST API router auth, routing, response helpers | | `\api\ApiRouter` | REST API router — auth, routing, response helpers |
### Database ### Database
- ORM: Medoo (`$mdb` global variable, injected via DI in new code) - ORM: Medoo (`$mdb` global variable, injected via DI in new code)
@@ -183,7 +183,7 @@ Universal form system for admin edit views. Docs: `docs/FORM_EDIT_SYSTEM.md`.
- Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)` - Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)`
- Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")` - Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
- Default TTL: 86400 (24h) - Default TTL: 86400 (24h)
- Data is serialized requires `unserialize()` after `get()` - Data is serialized — requires `unserialize()` after `get()`
- Config: `config.php` (`$config['redis']`) - Config: `config.php` (`$config['redis']`)
## Code Patterns ## Code Patterns
@@ -207,7 +207,7 @@ $controller = new \admin\Controllers\ExampleController($repo);
``` ```
### Medoo ORM pitfalls ### Medoo ORM pitfalls
- `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 has caused bugs - `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 — has caused bugs
- `$mdb->get()` returns `null` when no record, NOT `false` - `$mdb->get()` returns `null` when no record, NOT `false`
- After `$mdb->insert()`, check `$mdb->id()` to confirm success - After `$mdb->insert()`, check `$mdb->id()` to confirm success
@@ -226,21 +226,23 @@ $controller = new \admin\Controllers\ExampleController($repo);
When user says **"KONIEC PRACY"**, run `/koniec-pracy` (see `.claude/commands/koniec-pracy.md`). When user says **"KONIEC PRACY"**, run `/koniec-pracy` (see `.claude/commands/koniec-pracy.md`).
Before starting implementation, review current state of docs. Before starting implementation, review current state of docs.
For documentation updates, modify only .paul/docs/* unless the user explicitly asks to update root docs/*.
## Key Documentation ## Key Documentation
- `docs/MEMORY.md` project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions - `docs/MEMORY.md` — project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions
- `docs/PROJECT_STRUCTURE.md` current architecture, layers, cache, entry points, integrations - `docs/PROJECT_STRUCTURE.md` — current architecture, layers, cache, entry points, integrations
- `docs/DATABASE_STRUCTURE.md` full database schema - `docs/DATABASE_STRUCTURE.md` — full database schema
- `docs/TESTING.md` test suite guide and structure - `docs/TESTING.md` — test suite guide and structure
- `docs/FORM_EDIT_SYSTEM.md` form system architecture - `docs/FORM_EDIT_SYSTEM.md` — form system architecture
- `docs/CLASS_CATALOG.md` full catalog of all classes with descriptions - `docs/CLASS_CATALOG.md` — full catalog of all classes with descriptions
- `docs/TODO.md` outstanding tasks and planned features - `docs/TODO.md` — outstanding tasks and planned features
- `docs/CRON_QUEUE_PLAN.md` planned cron/queue architecture - `docs/CRON_QUEUE_PLAN.md` — planned cron/queue architecture
- `docs/CHANGELOG.md` version history - `docs/CHANGELOG.md` — version history
- `api-docs/api-reference.json` REST API documentation (ordersPRO) - `api-docs/api-reference.json` — REST API documentation (ordersPRO)
- `api-docs/index.html` REST API documentation (ordersPRO) - `api-docs/index.html` — REST API documentation (ordersPRO)
- `docs/UPDATE_INSTRUCTIONS.md` how to build client update packages - `docs/UPDATE_INSTRUCTIONS.md` — how to build client update packages
## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP ## Za kaĹĽdym razem jak prĂłbujesz sprawdzić jakiĹ› plik z logami sprĂłbuj go najpierw pobrać z serwera FTP
## Wszystkie pliki ktĂłre tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twĂłrz w folderze temp
## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp

View File

@@ -1,3 +1,20 @@
<?php $customLabelsEnabled = !empty( $this->custom_labels_enabled ); ?>
<script type="text/javascript">
$(function() {
var $header = $( '.panel-heading .col-sm-8' );
if ( !$header.length ) {
return;
}
if ( $header.find( '.btn-toggle-custom-labels' ).length === 0 ) {
var buttonClass = <?= $customLabelsEnabled ? "'btn-danger'" : "'btn-success'" ?>;
var buttonText = <?= $customLabelsEnabled ? "'Ukryj etykiety niestandardowe'" : "'Pokaż etykiety niestandardowe'" ?>;
$header.append( ' <a href=\"#\" class=\"btn btn-sm btn-toggle-custom-labels ' + buttonClass + '\"><i class=\"fa fa-tags mr5\"></i>' + buttonText + '</a>' );
}
});
</script>
<? if ( $this -> shoppro_enabled ):?> <? if ( $this -> shoppro_enabled ):?>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
@@ -27,10 +44,19 @@
.product-categories { .product-categories {
display: block; display: block;
} }
.custom-label-suggestions {
max-height: 220px;
overflow-y: auto;
}
.custom-labels small {
display: block;
margin-bottom: 3px;
}
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
var customLabelSuggestionTimers = {};
// --- Inline price save --- // --- Inline price save ---
$( 'body' ).on( 'change', '.product-price', function() { $( 'body' ).on( 'change', '.product-price', function() {
@@ -54,6 +80,139 @@ $(function() {
}); });
}); });
// --- Toggle custom labels ---
$( 'body' ).on( 'click', '.btn-toggle-custom-labels', function(e) {
e.preventDefault();
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_custom_labels_toggle/',
beforeSend: function() { $( '#overlay' ).show(); },
success: function( response ) {
$( '#overlay' ).hide();
var data = jQuery.parseJSON( response );
if ( data.status === 'ok' ) {
window.location.reload();
return;
}
create_error( 'Nie udało się przełączyć widoku etykiet.' );
},
error: function() {
$( '#overlay' ).hide();
create_error( 'Nie udało się przełączyć widoku etykiet.' );
}
});
});
// --- Custom label suggestions + save ---
function hideCustomLabelSuggestions( $input ) {
var $container = $input.closest( '[class*=\"custom_label_\"]' );
$container.find( '.custom-label-suggestions' ).hide().empty();
var datalistId = $input.attr( 'data-datalist-id' ) || '';
if ( datalistId ) {
$( '#' + datalistId ).empty();
}
}
function saveCustomLabelValue( $input ) {
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_custom_label_save/',
data: {
product_id: $input.attr( 'data-product-id' ),
label_type: $input.attr( 'data-label-type' ),
custom_label: $input.val()
},
success: function( response ) {
var data = jQuery.parseJSON( response );
if ( data.status !== 'ok' ) {
create_error( data.msg || 'Nie udało się zapisać etykiety.' );
}
},
error: function() {
create_error( 'Nie udało się zapisać etykiety.' );
}
});
}
function loadCustomLabelSuggestions( $input ) {
var labelType = $input.attr( 'data-label-type' );
var query = $input.val();
var $suggestions = $input.closest( '[class*=\"custom_label_\"]' ).find( '.custom-label-suggestions' );
var datalistId = $input.attr( 'data-datalist-id' ) || '';
var $datalist = datalistId ? $( '#' + datalistId ) : $();
if ( !labelType ) {
return;
}
$.ajax({
type: 'POST',
cache: false,
url: '/admin/shop_product/product_custom_label_suggestions/',
data: {
label_type: labelType,
custom_label: query
},
success: function( response ) {
var data = jQuery.parseJSON( response );
if ( data.status !== 'ok' || !data.suggestions || !data.suggestions.length ) {
$suggestions.hide().empty();
$datalist.empty();
return;
}
var html = '';
$.each( data.suggestions, function( index, item ) {
if ( !item.label ) {
return;
}
var safe = String( item.label ).replace(/\"/g, '&quot;');
html += '<option value=\"' + safe + '\">' + item.label + '</option>';
});
if ( html ) {
$datalist.html( html );
$suggestions.hide().empty();
} else {
$suggestions.hide().empty();
$datalist.empty();
}
},
error: function() {
$suggestions.hide().empty();
$datalist.empty();
}
});
}
$( 'body' ).on( 'input', '.product-custom-label', function() {
var $input = $( this );
var inputKey = $input.attr( 'data-product-id' ) + ':' + $input.attr( 'data-label-type' );
if ( customLabelSuggestionTimers[inputKey] ) {
clearTimeout( customLabelSuggestionTimers[inputKey] );
}
customLabelSuggestionTimers[inputKey] = setTimeout( function() {
loadCustomLabelSuggestions( $input );
}, 250 );
});
$( 'body' ).on( 'change', '.product-custom-label', function() {
saveCustomLabelValue( $( this ) );
});
$( document ).on( 'click', function(e) {
if ( $( e.target ).closest( '.custom-labels' ).length === 0 ) {
$( '.custom-label-suggestions' ).hide().empty();
}
});
$( 'body' ).on( 'change', '.product-price-promo', function() { $( 'body' ).on( 'change', '.product-price-promo', function() {
var $el = $( this ); var $el = $( this );
var price = $el.val().replace( ' ', '' ); var price = $el.val().replace( ' ', '' );

View File

@@ -5,5 +5,6 @@
'list' => $this->viewModel, 'list' => $this->viewModel,
'apilo_enabled' => $this->apilo_enabled, 'apilo_enabled' => $this->apilo_enabled,
'shoppro_enabled' => $this->shoppro_enabled, 'shoppro_enabled' => $this->shoppro_enabled,
'custom_labels_enabled' => $this->custom_labels_enabled,
]); ?> ]); ?>
<?php endif; ?> <?php endif; ?>

View File

@@ -1335,8 +1335,9 @@ class ProductRepository
$this->saveImagesOrder( $productId, $d['gallery_order'] ); $this->saveImagesOrder( $productId, $d['gallery_order'] );
} }
// Zapisz custom fields tylko gdy jawnie podane (partial update przez API może nie zawierać tego klucza) // Zapisz custom fields tylko gdy formularz edycji renderował sekcję (marker hidden field)
if ( array_key_exists( 'custom_field_name', $d ) ) { // API partial update nie zawiera tego markera — custom fields pominięte
if ( array_key_exists( 'custom_field_name_present', $d ) ) {
$this->saveCustomFields( $productId, $d['custom_field_name'] ?? [], $d['custom_field_type'] ?? [], $d['custom_field_required'] ?? [] ); $this->saveCustomFields( $productId, $d['custom_field_name'] ?? [], $d['custom_field_type'] ?? [], $d['custom_field_required'] ?? [] );
} }
@@ -2204,6 +2205,44 @@ class ProductRepository
] ); ] );
} }
/**
* Pobiera nazwy etykiet custom_label_0..4 z bazy ustawien.
*
* @return array<string, string>
*/
public function customLabelNames(): array
{
$names = [];
for ( $index = 0; $index < 5; $index++ ) {
$fieldName = 'custom_label_' . $index;
$names[$fieldName] = 'Custom label ' . $index;
}
$settingsKeys = [];
for ( $index = 0; $index < 5; $index++ ) {
$settingsKeys[] = 'custom_label_' . $index . '_name';
$settingsKeys[] = 'google_custom_label_' . $index . '_name';
}
$settingsRows = $this->db->select( 'pp_settings', [ 'param', 'value' ], [ 'param' => $settingsKeys ] );
if ( is_array( $settingsRows ) ) {
foreach ( $settingsRows as $settingRow ) {
$param = (string) ( $settingRow['param'] ?? '' );
$value = trim( (string) ( $settingRow['value'] ?? '' ) );
if ( $value === '' ) {
continue;
}
if ( preg_match( '/^(?:google_)?custom_label_([0-4])_name$/', $param, $match ) ) {
$names[ 'custom_label_' . $match[1] ] = $value;
}
}
}
return $names;
}
/** /**
* Pobiera sugestie custom label. * Pobiera sugestie custom label.
*/ */

View File

@@ -184,8 +184,16 @@ class ScontainersController
} }
$data = $result['data']; $data = $result['data'];
$containerId = (int)($data['id'] ?? 0);
if ($containerId <= 0) {
$routeId = (int)\Shared\Helpers\Helpers::get('id');
if ($routeId > 0) {
$containerId = $routeId;
}
}
$savedId = $this->repository->save([ $savedId = $this->repository->save([
'id' => (int)($data['id'] ?? 0), 'id' => $containerId,
'status' => $data['status'] ?? 0, 'status' => $data['status'] ?? 0,
'show_title' => $data['show_title'] ?? 0, 'show_title' => $data['show_title'] ?? 0,
'translations' => $data['translations'] ?? [], 'translations' => $data['translations'] ?? [],
@@ -240,7 +248,6 @@ class ScontainersController
]; ];
$fields = [ $fields = [
FormField::hidden('id', $id),
FormField::langSection('translations', 'content', [ FormField::langSection('translations', 'content', [
FormField::text('title', [ FormField::text('title', [
'label' => 'Tytul', 'label' => 'Tytul',
@@ -283,7 +290,7 @@ class ScontainersController
$actionUrl, $actionUrl,
'/admin/scontainers/list/', '/admin/scontainers/list/',
true, true,
[], ['id' => $id],
$languages, $languages,
$errors $errors
); );

View File

@@ -18,6 +18,8 @@ use admin\Support\TableListRequestFactory;
*/ */
class ShopProductController class ShopProductController
{ {
private const CUSTOM_LABELS_SESSION_KEY = 'shop_product_show_custom_labels';
private ProductRepository $repository; private ProductRepository $repository;
private IntegrationsRepository $integrationsRepository; private IntegrationsRepository $integrationsRepository;
private LanguagesRepository $languagesRepository; private LanguagesRepository $languagesRepository;
@@ -39,6 +41,8 @@ class ShopProductController
$apiloEnabled = $this->integrationsRepository->getSetting( 'apilo', 'enabled' ); $apiloEnabled = $this->integrationsRepository->getSetting( 'apilo', 'enabled' );
$shopproEnabled = $this->integrationsRepository->getSetting( 'shoppro', 'enabled' ); $shopproEnabled = $this->integrationsRepository->getSetting( 'shoppro', 'enabled' );
$dlang = $this->languagesRepository->defaultLanguage(); $dlang = $this->languagesRepository->defaultLanguage();
$customLabelsEnabled = $this->customLabelsEnabled();
$customLabelNames = $this->repository->customLabelNames();
$sortableColumns = [ 'id', 'name', 'price_brutto', 'status', 'promoted', 'quantity' ]; $sortableColumns = [ 'id', 'name', 'price_brutto', 'status', 'promoted', 'quantity' ];
@@ -98,6 +102,10 @@ class ShopProductController
. '<small class="text-muted product-categories product-categories--cats" title="' . $categories . '">' . $categories . '</small>' . '<small class="text-muted product-categories product-categories--cats" title="' . $categories . '">' . $categories . '</small>'
. '<small class="text-muted product-categories">SKU: ' . $sku . ', EAN: ' . $ean . '</small>'; . '<small class="text-muted product-categories">SKU: ' . $sku . ', EAN: ' . $ean . '</small>';
if ( $customLabelsEnabled ) {
$nameHtml .= $this->renderCustomLabelsEditor( $product, $id, $customLabelNames );
}
$priceHtml = '<input type="text" class="product-price form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">'; $priceHtml = '<input type="text" class="product-price form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">';
$promoHtml = '<input type="text" class="product-price-promo form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto_promo'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">'; $promoHtml = '<input type="text" class="product-price-promo form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto_promo'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">';
$promotedHtml = $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie'; $promotedHtml = $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie';
@@ -195,11 +203,25 @@ class ShopProductController
'viewModel' => $viewModel, 'viewModel' => $viewModel,
'apilo_enabled' => $apiloEnabled, 'apilo_enabled' => $apiloEnabled,
'shoppro_enabled' => $shopproEnabled, 'shoppro_enabled' => $shopproEnabled,
'custom_labels_enabled' => $customLabelsEnabled,
] ); ] );
} }
// ─── Krok 7: Edycja i zapis ───────────────────────────────────── // ─── Krok 7: Edycja i zapis ─────────────────────────────────────
/**
* AJAX: przelacza widok custom labels na liscie produktow i zapisuje stan w sesji.
*/
public function product_custom_labels_toggle(): void
{
$currentState = $this->customLabelsEnabled();
$newState = $currentState ? 0 : 1;
\Shared\Helpers\Helpers::set_session( self::CUSTOM_LABELS_SESSION_KEY, $newState );
echo json_encode( [ 'status' => 'ok', 'enabled' => (bool) $newState ] );
exit;
}
/** /**
* Formularz edycji produktu. * Formularz edycji produktu.
*/ */
@@ -699,7 +721,8 @@ class ShopProductController
private function renderCustomFieldsBox( array $product ): string private function renderCustomFieldsBox( array $product ): string
{ {
$html = '<a href="#" class="btn btn-success" id="add_custom_field"><i class="fa fa-plus"></i> dodaj niestandardowe pole</a>'; $html = '<input type="hidden" name="custom_field_name_present" value="1">';
$html .= '<a href="#" class="btn btn-success" id="add_custom_field"><i class="fa fa-plus"></i> dodaj niestandardowe pole</a>';
$html .= '<div class="additional_fields pt-3">'; $html .= '<div class="additional_fields pt-3">';
$customFields = is_array( $product['custom_fields'] ?? null ) ? $product['custom_fields'] : []; $customFields = is_array( $product['custom_fields'] ?? null ) ? $product['custom_fields'] : [];
@@ -896,9 +919,15 @@ class ShopProductController
public function product_custom_label_suggestions(): void public function product_custom_label_suggestions(): void
{ {
$response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ]; $response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ];
$labelType = (string) \Shared\Helpers\Helpers::get( 'label_type' );
$suggestions = $this->repository->customLabelSuggestions( \Shared\Helpers\Helpers::get( 'custom_label' ), \Shared\Helpers\Helpers::get( 'label_type' ) ); if ( !$this->isAllowedCustomLabelType( $labelType ) ) {
if ( $suggestions ) { echo json_encode( $response );
exit;
}
$suggestions = $this->repository->customLabelSuggestions( (string) \Shared\Helpers\Helpers::get( 'custom_label' ), $labelType );
if ( is_array( $suggestions ) ) {
$response = [ 'status' => 'ok', 'suggestions' => $suggestions ]; $response = [ 'status' => 'ok', 'suggestions' => $suggestions ];
} }
@@ -912,8 +941,14 @@ class ShopProductController
public function product_custom_label_save(): void public function product_custom_label_save(): void
{ {
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ]; $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ];
$labelType = (string) \Shared\Helpers\Helpers::get( 'label_type' );
if ( $this->repository->saveCustomLabel( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'custom_label' ), \Shared\Helpers\Helpers::get( 'label_type' ) ) ) { if ( !$this->isAllowedCustomLabelType( $labelType ) ) {
echo json_encode( $response );
exit;
}
if ( $this->repository->saveCustomLabel( (int) \Shared\Helpers\Helpers::get( 'product_id' ), (string) \Shared\Helpers\Helpers::get( 'custom_label' ), $labelType ) ) {
$response = [ 'status' => 'ok' ]; $response = [ 'status' => 'ok' ];
} }
@@ -1196,4 +1231,36 @@ class ShopProductController
echo json_encode( [ 'status' => 'ok', 'products' => $products ] ); echo json_encode( [ 'status' => 'ok', 'products' => $products ] );
exit; exit;
} }
private function customLabelsEnabled(): bool
{
return isset( $_SESSION[ self::CUSTOM_LABELS_SESSION_KEY ] ) && (int) $_SESSION[ self::CUSTOM_LABELS_SESSION_KEY ] === 1;
}
private function isAllowedCustomLabelType(string $labelType): bool
{
return in_array( $labelType, [ 'custom_label_0', 'custom_label_1', 'custom_label_2', 'custom_label_3', 'custom_label_4' ], true );
}
private function renderCustomLabelsEditor(array $product, int $productId, array $customLabelNames): string
{
$customLabelsHtml = '<div class="custom-labels mt10">';
for ( $index = 0; $index < 5; $index++ ) {
$fieldName = 'custom_label_' . $index;
$labelText = htmlspecialchars( (string) ( $customLabelNames[$fieldName] ?? 'Custom label ' . $index ), ENT_QUOTES, 'UTF-8' );
$valueText = htmlspecialchars( (string) ( $product[$fieldName] ?? '' ), ENT_QUOTES, 'UTF-8' );
$customLabelsHtml .= '<div class="' . $fieldName . '_container">';
$customLabelsHtml .= '<small class="text-muted">' . $labelText . '</small>';
$datalistId = 'custom-label-list-' . $productId . '-' . $fieldName;
$customLabelsHtml .= '<input type="text" class="form-control input-sm product-custom-label" data-label-type="' . $fieldName . '" data-product-id="' . $productId . '" data-datalist-id="' . $datalistId . '" list="' . $datalistId . '" value="' . $valueText . '" placeholder="' . $labelText . '">';
$customLabelsHtml .= '<datalist id="' . $datalistId . '"></datalist>';
$customLabelsHtml .= '<div class="' . $fieldName . '_suggestions custom-label-suggestions"></div>';
$customLabelsHtml .= '</div>';
}
$customLabelsHtml .= '</div>';
return $customLabelsHtml;
}
} }

View File

@@ -1,147 +0,0 @@
# CARL — Dynamiczne reguły dla Claude Code
[CARL](https://github.com/ChristopherKahler/carl) (Context Augmentation & Reinforcement Layer) to system wstrzykiwania reguł do Claude Code — reguły ładują się **tylko gdy są potrzebne**, zamiast zapychać kontekst sesji regułami, których akurat nie używasz.
Zainstalowany globalnie w `~/.carl/`.
---
## Jak to działa
Claude Code ma ograniczony kontekst sesji. CARL rozwiązuje problem "zbyt wielu reguł na raz":
- **Domeny** — zestawy reguł uruchamiane automatycznie przez słowa kluczowe w prompcie
- **Star-commands** (`*dev`, `*review`, itp.) — tryby uruchamiane ręcznie przez wpisanie `*nazwatrybe`
- **Global** — reguły zawsze aktywne (minimalne, uniwersalne)
Efekt: zamiast 50 reguł na każdą sesję, Claude dostaje 510 tych, które są relevantne teraz.
---
## Struktura plików
```
~/.carl/ # Globalna konfiguracja (wszystkie projekty)
├── manifest # Rejestr domen (stany + słowa kluczowe)
├── global # Reguły zawsze aktywne
├── commands # Definicje star-commands
├── context # Reguły kontekstowe (rozmiar okna kontekstu)
└── {nazwa-domeny} # Twoja domena (bez rozszerzenia!)
.carl/ # Konfiguracja lokalna (tylko ten projekt)
└── {nazwa-domeny} # Reguły specyficzne dla shopPRO
```
**Ważne:** Nazwy plików domen — **małe litery, bez rozszerzenia** (`phpdev`, nie `phpdev.carl`).
---
## Dostępne star-commands
Wpisz `*nazwa` na początku wiadomości lub w środku promptu, żeby przełączyć tryb:
| Komenda | Tryb | Kiedy używać |
|---------|------|-------------|
| `*dev` | Development | Implementacja, szybkie zmiany bez tłumaczeń |
| `*review` | Code review | Przegląd kodu, bezpieczeństwo, edge cases |
| `*brief` | Zwięzłe odpowiedzi | Tylko bullet points, bez elaboracji |
| `*plan` | Planowanie | Eksploracja przed implementacją, opcje + tradeoffs |
| `*discuss` | Dyskusja | Burza mózgów, wiele podejść, bez skakania do kodu |
| `*debug` | Debugowanie | Systematyczna diagnoza, root cause analysis |
| `*explain` | Wyjaśnianie | Nauka, koncepty, stopniowe budowanie wiedzy |
| `*carl` | Pomoc CARL | Zarządzanie domenami, konfiguracja, pytania o CARL |
### Przykłady
```
*dev Napraw błąd w summaryView() gdzie duplikuje zamówienia
*review Przejrzyj OrderRepository::createFromBasket pod kątem bezpieczeństwa
*brief Co robi CacheHandler::deletePattern()
*plan Chcę dodać system rabatów do koszyka
*discuss Czy lepiej rozdzielić ApiloRepository na sync i admin?
```
---
## Tworzenie własnej domeny (projekt)
Kiedy masz zestaw reguł specyficznych dla shopPRO, utwórz lokalną domenę w `.carl/`.
### Krok 1 — Plik domeny
Utwórz `.carl/shoppro` (bez rozszerzenia):
```
# shopPRO Domain Rules
SHOPPRO_RULE_0=PHP < 8.0 — nie używaj match, named args, union types, str_contains
SHOPPRO_RULE_1=ORM: Medoo ($mdb), zawsze prepared statements, nigdy string concatenation
SHOPPRO_RULE_2=Namespace \Domain\ mapuje do autoload/Domain/ (D uppercase, a lowercase)
SHOPPRO_RULE_3=Testy: PHPUnit 9.6, pattern AAA, mock Medoo przez createMock(\medoo::class)
SHOPPRO_RULE_4=Cache: CacheHandler::deletePattern() do kasowania, TTL 86400, dane serialized
```
### Krok 2 — Wpis w manifeście
Dodaj do `.carl/manifest` (lub `~/.carl/manifest` jeśli globalna):
```
SHOPPRO_STATE=active
SHOPPRO_RECALL=shopPRO, medoo, zamówienie, koszyk, OrderRepository, Domain
SHOPPRO_EXCLUDE=
SHOPPRO_ALWAYS_ON=false
```
- `RECALL` — słowa kluczowe które triggerują domenę (przecinek = OR)
- `ALWAYS_ON=true` — ładuj przy każdym prompcie (tylko dla naprawdę universalnych reguł)
- `EXCLUDE` — słowa które blokują domenę mimo dopasowania RECALL
### Krok 3 — Weryfikacja
Wpisz `*carl` w czacie i zapytaj: _"Pokaż mi aktywne domeny"_.
---
## Zarządzanie przez Claude
Zamiast ręcznie edytować pliki, możesz zarządzać CARL przez Claude:
```
*carl Dodaj domenę dla testów PHPUnit w shopPRO
*carl Pokaż moją aktualną konfigurację
*carl Wyłącz domenę SHOPPRO tymczasowo
*carl Dodaj regułę do domeny dev: zawsze uruchamiaj ./test.ps1 po zmianach
```
Claude użyje skills `carl:manager` / `carl:tasks:*` do operacji na plikach.
---
## Integracja z PAUL
CARL i [PAUL](./PAUL_WORKFLOW.md) działają uzupełniająco:
- **PAUL** strukturyzuje *proces* (plan → apply → unify)
- **CARL** dostarcza *reguły domenowe* wtedy gdy są potrzebne
Praktycznie: podczas `/paul:apply` możesz prefixować `*dev` żeby Claude skupił się na kodzie bez elaboracji. Podczas `/paul:discuss``*discuss` żeby dostać pełną analizę opcji.
---
## Dobre praktyki
- **RECALL słowa** — używaj konkretnych, rzadkich słów żeby unikać false triggers. `medoo` lepsze niż `php`.
- **Mało reguł per domena** — 58 reguł to optymalnie. Więcej = wolniejsze matching, więcej tokenów.
- **ALWAYS_ON=false** domyślnie — ALWAYS_ON=true tylko dla reguł naprawdę universalnych (jak GLOBAL).
- **Star-commands przy dużych taskach** — na początku sesji wpisz `*dev` lub `*plan` żeby ustawić tryb.
- **Nie duplikuj CLAUDE.md** — CARL nie zastępuje CLAUDE.md. CLAUDE.md to architektura projektu. CARL to reguły zachowania Claude.
---
*Docs: 2026-03-12*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,156 +0,0 @@
# Plan: System kolejki zadań cron oparty o bazę danych
## Kontekst
Obecny system cron ma dwa problemy:
1. **Kolejka plikowa (JSON)** — sync płatności/statusów Apilo trzymany w `/temp/apilo-sync-queue.json` — kruchy, brak transakcji, ryzyko utraty danych
2. **Monolityczny cron.php** (~550 linii) — brak priorytetów, brak retry z backoff, brak centralnego zarządzania
Cel: Zastąpienie całego systemu cron tabelą `pp_cron_jobs` z priorytetami, retry/backoff i harmonogramem `pp_cron_schedules`.
## Nowe pliki
| Plik | Opis |
|------|------|
| `autoload/Domain/CronJob/CronJobType.php` | Stałe typów zadań i priorytetów |
| `autoload/Domain/CronJob/CronJobRepository.php` | CRUD na `pp_cron_jobs` + `pp_cron_schedules` |
| `autoload/Domain/CronJob/CronJobProcessor.php` | Orkiestracja: pobierz zadanie → wywołaj handler → obsłuż wynik |
| `tests/Unit/Domain/CronJob/CronJobTypeTest.php` | Testy stałych |
| `tests/Unit/Domain/CronJob/CronJobRepositoryTest.php` | Testy repozytorium |
| `tests/Unit/Domain/CronJob/CronJobProcessorTest.php` | Testy procesora |
| `migrations/0.315.sql` | CREATE TABLE + INSERT harmonogramów |
## Modyfikowane pliki
| Plik | Zmiana |
|------|--------|
| `cron.php` | Zastąpienie ~550 linii orchestratorem (~100 linii) z rejestracją handlerów |
| `cron/cron-xml.php` | Usunięcie — logika przeniesiona do handlera `google_xml_feed` |
| `cron-turstmate.php` | Usunięcie — logika przeniesiona do handlera `trustmate_invitation` |
| `autoload/Domain/Order/OrderAdminService.php` | `queueApiloSync()` → enqueue do DB; usunięcie metod plikowych; `syncApiloPayment()`/`syncApiloStatus()` → public |
| `tests/Unit/Domain/Order/OrderAdminServiceTest.php` | Refaktor testów kolejki: mock `CronJobRepository` zamiast pliku JSON |
| `docs/DATABASE_STRUCTURE.md` | Dodanie tabel `pp_cron_jobs`, `pp_cron_schedules` |
| `docs/CHANGELOG.md` | Wpis o nowym systemie |
## Schemat DB (`migrations/0.315.sql`)
### `pp_cron_jobs`
```sql
CREATE TABLE pp_cron_jobs (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_type VARCHAR(50) NOT NULL,
status ENUM('pending','processing','completed','failed','cancelled') NOT NULL DEFAULT 'pending',
priority TINYINT UNSIGNED NOT NULL DEFAULT 100, -- niższy = ważniejszy
payload TEXT NULL, -- JSON z danymi zadania
result TEXT NULL, -- JSON z wynikiem
attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 10,
last_error VARCHAR(500) NULL,
scheduled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at DATETIME NULL,
completed_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_priority_scheduled (status, priority, scheduled_at),
INDEX idx_job_type (job_type),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
### `pp_cron_schedules`
```sql
CREATE TABLE pp_cron_schedules (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_type VARCHAR(50) NOT NULL UNIQUE,
interval_seconds INT UNSIGNED NOT NULL,
priority TINYINT UNSIGNED NOT NULL DEFAULT 100,
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 3,
payload TEXT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
last_run_at DATETIME NULL,
next_run_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_enabled_next_run (enabled, next_run_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
## Typy zadań i priorytety
| Typ | Priorytet | Harmonogram |
|-----|-----------|-------------|
| `apilo_token_keepalive` | 10 (krytyczny) | co 4 min |
| `apilo_send_order` | 50 (wysoki) | co 1 min |
| `apilo_sync_payment` | 50 (wysoki) | event-driven (enqueue przy zmianie) |
| `apilo_sync_status` | 50 (wysoki) | event-driven |
| `apilo_product_sync` | 100 (normalny) | co 10 min |
| `apilo_pricelist_sync` | 100 (normalny) | co 1h |
| `apilo_status_poll` | 100 (normalny) | co 10 min |
| `price_history` | 100 (normalny) | co 24h |
| `order_analysis` | 100 (normalny) | co 10 min |
| `trustmate_invitation` | 200 (niski) | co 10 min |
| `google_xml_feed` | 200 (niski) | co 1h |
## Architektura klas
### CronJobRepository — metody kluczowe
- `enqueue($jobType, $payload, $priority, $maxAttempts, $scheduledAt)` — dodaj do kolejki
- `fetchNext($limit)` — atomowe pobranie pending jobs (UPDATE WHERE status='pending')
- `markCompleted($jobId, $result)` / `markFailed($jobId, $error, $backoffSeconds)`
- `hasPendingJob($jobType, $payloadMatch)` — zapobiega duplikatom
- `cleanup($olderThanDays)` — GC starych wpisów
- `recoverStuck($olderThanMinutes)` — reset stuck "processing" jobs
- `getDueSchedules()` / `touchSchedule($id)` — harmonogram
### CronJobProcessor — orkiestracja
- `registerHandler($jobType, callable)` — rejestracja handlera
- `createScheduledJobs()` — tworzy jobs z harmonogramów których `next_run_at <= NOW`
- `processQueue($limit)` — pobierz + wywołaj handler + markCompleted/markFailed
- `run($limit)` — główna metoda: schedules + process
### Exponential backoff
```
Próba 1: 60s, Próba 2: 120s, Próba 3: 240s, ... max 3600s (1h)
```
### Zależność "order not yet in Apilo"
Handler `apilo_sync_payment`/`apilo_sync_status` sprawdza `apilo_order_id`. Jeśli brak → zwraca false → `markFailed()` z backoffem → zadanie wraca do kolejki. Max 50 prób.
## Nowy cron.php (schemat)
```php
$cronRepo = new \Domain\CronJob\CronJobRepository($mdb);
$processor = new \Domain\CronJob\CronJobProcessor($mdb, $cronRepo);
// Rejestracja handlerów (każdy to callable)
$processor->registerHandler('apilo_token_keepalive', function($payload) use ($integrationsRepo) { ... });
$processor->registerHandler('apilo_send_order', function($payload) use ($orderService, ...) { ... });
// ... inne handlery
$result = $processor->run(20);
```
## Zmiany w OrderAdminService
1. `queueApiloSync()``CronJobRepository::enqueue()` zamiast zapisu do pliku JSON
2. Usunięcie: `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, stała `APILO_SYNC_QUEUE_FILE`
3. `syncApiloPayment()`, `syncApiloStatus()` → zmiana z `private` na `public`
4. Jednorazowa migracja: odczyt JSON → insert do DB → usunięcie pliku
## Kolejność implementacji
1. Migracja SQL
2. `CronJobType.php`
3. `CronJobRepository.php` + testy
4. `CronJobProcessor.php` + testy
5. Modyfikacja `OrderAdminService` (queue → DB, public methods)
6. Jednorazowa migracja pliku JSON → DB
7. Nowy `cron.php` z handlerami (ekstrakcja logiki z bloków proceduralnych)
8. Aktualizacja testów OrderAdminService
9. Dokumentacja (DATABASE_STRUCTURE.md, CHANGELOG.md)
## Weryfikacja
1. Uruchomienie pełnego zestawu testów: `./test.ps1`
2. Sprawdzenie czy nowe testy CronJob* przechodzą
3. Sprawdzenie czy istniejące testy OrderAdminService przechodzą po refaktorze
4. Weryfikacja migracji SQL na pustej bazie

View File

@@ -1,730 +0,0 @@
# Struktura bazy danych shopPRO
Plik aktualizowany na bieżąco przy zmianach w kodzie.
ORM: Medoo (`$mdb`), prefix tabel: `pp_`
## pp_shop_products
Główna tabela produktów.
| Kolumna | Opis |
|---------|------|
| id | PK |
| parent_id | FK do produktu nadrzędnego (kombinacje) - NULL dla produktów głównych |
| price_brutto | Cena brutto |
| price_brutto_promo | Cena promocyjna brutto |
| quantity | Stan magazynowy |
| status | Status: 1 = aktywny, 0 = nieaktywny |
| archive | Archiwum: 1 = zarchiwizowany, 0 = aktywny |
| promoted | Czy promowany |
| vat | Stawka VAT |
| ean | Kod EAN |
| sku | Kod SKU |
| apilo_product_id | ID produktu w Apilo |
| apilo_product_name | Nazwa produktu w Apilo |
**Używane w:** `Domain\Product\ProductRepository`, `admin\factory\ShopProduct`, `admin\Controllers\ShopProductController`
## pp_shop_products_langs
Tłumaczenia produktów (per język).
| Kolumna | Opis |
|---------|------|
| id | PK |
| product_id | FK do pp_shop_products |
| lang_id | ID języka (np. 'pl') |
| name | Nazwa produktu |
**Używane w:** `Domain\Product\ProductRepository::getName()`
## pp_shop_products_images
Zdjęcia produktów.
| Kolumna | Opis |
|---------|------|
| id | PK |
| product_id | FK do pp_shop_products |
| src | Ścieżka do pliku |
| alt | Tekst alternatywny |
## pp_shop_products_custom_fields
Dodatkowe pola produktów (custom fields).
| Kolumna | Opis |
|---------|------|
| id_additional_field | PK |
| id_product | FK do pp_shop_products |
| name | Nazwa pola |
| type | Typ pola (VARCHAR 30) |
| is_required | Czy wymagane (0/1) |
## pp_shop_products_categories
Przypisanie produktów do kategorii.
| Kolumna | Opis |
|---------|------|
| product_id | FK do pp_shop_products |
**Używane w:** `admin\factory\ShopProduct::product_delete()`, `Domain\Product\ProductRepository::getProductsByCategory()`
**Aktualizacja 2026-02-15 (ver. 0.274):** akcje `/admin/shop_product/mass_edit/*` korzystają z `Domain\Product\ProductRepository` przez `admin\Controllers\ShopProductController`.
## pp_shop_categories
Kategorie sklepu.
| Kolumna | Opis |
|---------|------|
| id | PK |
| parent_id | FK do kategorii nadrzednej (NULL dla root) |
| status | 1 = aktywna, 0 = nieaktywna |
| o | Kolejnosc wyswietlania |
| sort_type | Typ sortowania produktow w kategorii |
| view_subcategories | Czy wyswietlac podkategorie |
**Uzywane w:** `Domain\Category\CategoryRepository`, `admin\Controllers\ShopCategoryController`.
## pp_shop_categories_langs
Tlumaczenia kategorii (per jezyk).
| Kolumna | Opis |
|---------|------|
| category_id | FK do pp_shop_categories |
| lang_id | ID jezyka (np. pl, en) |
| title | Nazwa kategorii |
| text | Opis kategorii |
| text_hidden | Rozwiniecie opisu kategorii |
| seo_link | Link SEO kategorii |
| meta_title | Meta title |
| meta_description | Meta description |
| meta_keywords | Meta keywords |
| noindex | Flaga noindex |
| category_title | Naglowek H1 kategorii |
| additional_text | Dodatkowy tekst nad lista produktow |
**Uzywane w:** `Domain\Category\CategoryRepository`, `admin\Controllers\ShopCategoryController`.
**Aktualizacja 2026-02-15 (ver. 0.275):** modul `/admin/shop_category/*` korzysta z `Domain\Category\CategoryRepository` przez `admin\Controllers\ShopCategoryController`; usunieto legacy `admin\controls/factory/view\ShopCategory`.
## pp_shop_orders
Zamówienia sklepu (źródło danych dla list i szczegółów klientów w panelu admin).
| Kolumna | Opis |
|---------|------|
| id | PK |
| client_id | FK do `pp_shop_clients` (NULL dla gościa) |
| client_name | Imię klienta z zamówienia |
| client_surname | Nazwisko klienta z zamówienia |
| client_email | E-mail klienta z zamówienia |
| client_phone | Telefon klienta |
| client_city | Miasto klienta |
| summary | Wartość zamówienia |
| date_order | Data złożenia zamówienia |
| payment_method | Nazwa metody płatności |
| transport | Nazwa transportu |
| message | Wiadomość klienta |
| updated_at | Data ostatniej modyfikacji (polling API) |
**Używane w:** `Domain\Client\ClientRepository::listForAdmin()`, `Domain\Client\ClientRepository::ordersForClient()`, `Domain\Client\ClientRepository::totalsForClient()`, `Domain\Order\OrderRepository::listForApi()`, `Domain\Order\OrderRepository::findForApi()`.
**Aktualizacja 2026-02-15 (ver. 0.274):** moduł `/admin/shop_clients/*` korzysta z `Domain\Client\ClientRepository` przez `admin\Controllers\ShopClientsController`.
**Aktualizacja 2026-02-15 (ver. 0.276):** moduł `/admin/shop_order/*` korzysta z `Domain\Order\OrderRepository` przez `admin\Controllers\ShopOrderController`; usunięto legacy `admin\controls\ShopOrder` i `admin\factory\ShopOrder`.
**Aktualizacja 2026-02-17 (ver. 0.290):** frontend `/shop_order/*` korzysta z `Domain\Order\OrderRepository` przez `front\Controllers\ShopOrderController`; usunięto legacy `front\controls\ShopOrder`, `front\factory\ShopOrder`, `front\view\ShopOrder`. Callery (ShopBasketController, ClientRepository, shop\Order, cron-turstmate) przepięte na OrderRepository.
## pp_banners
Banery.
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa banera |
| status | 0/1 |
| date_start | Data rozpoczęcia |
| date_end | Data zakończenia |
| home_page | Czy na stronie głównej 0/1 |
**Używane w:** `Domain\Banner\BannerRepository`, `front\Views\Banners`
**Aktualizacja 2026-02-16 (ver. 0.281):** metody frontendowe `banners()`, `mainBanner()` dodane do `Domain\Banner\BannerRepository`. Fasady `front\factory\Banners` i `front\view\Banners` deleguja do repo/Views.
## pp_banners_langs
Tłumaczenia banerów.
| Kolumna | Opis |
|---------|------|
| id | PK |
| id_banner | FK do pp_banners |
| id_lang | ID języka |
| src | Ścieżka do grafiki |
| url | URL docelowy |
| html | Kod HTML |
| text | Tekst |
**Używane w:** `Domain\Banner\BannerRepository`, `front\Views\Banners`
## pp_articles
Artykuły.
| Kolumna | Opis |
|---------|------|
| id | PK |
| status | -1 = archiwum, 0 = nieaktywny, 1 = aktywny |
**Używane w:** `admin\Controllers\ArticlesArchiveController`, `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::listArchivedForAdmin()`
## pp_articles_pages
Strony artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| page_id | FK do strony (pp_pages) |
| o | Kolejność |
**Używane w:** `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::deleteNonassignedImages()`
## pp_articles_langs
Tłumaczenia artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| lang_id | ID języka (np. 'pl') |
| title | Tytuł artykułu |
| seo_link | Link SEO artykułu |
**Używane w:** `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::deleteNonassignedFiles()`
## pp_articles_images
Zdjęcia artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| src | Ścieżka do pliku |
| o | Kolejność |
| id | PK (używane też do sortowania DESC) |
**Używane w:** `Domain\Article\ArticleRepository::find()`
## pp_articles_files
Pliki artykułów.
| Kolumna | Opis |
|---------|------|
| id | PK |
| article_id | FK do pp_articles |
| src | Ścieżka do pliku |
| name | Nazwa wyświetlana załącznika (opcjonalna) |
| to_delete | Flaga miękkiego usuwania (0/1) |
| o | Kolejność załączników (używana przez sortowanie drag&drop w adminie) |
**Używane w:** `Domain\Article\ArticleRepository::find()`, `Domain\Article\ArticleRepository::saveFilesOrder()`
## pp_units
Jednostki/slowniki (np. jednostki produktu).
| Kolumna | Opis |
|---------|------|
| id | PK |
**Używane w:** `Domain\Dictionaries\DictionariesRepository`, `admin\controls\ShopProduct`
## pp_units_langs
Tlumaczenia jednostek (per jezyk).
| Kolumna | Opis |
|---------|------|
| id | PK |
| unit_id | FK do pp_units |
| lang_id | ID jezyka (np. 'pl') |
| text | Nazwa jednostki |
**Używane w:** `Domain\Dictionaries\DictionariesRepository`
## pp_users
Uzytkownicy panelu administratora.
| Kolumna | Opis |
|---------|------|
| id | PK |
| login | Login / e-mail uzytkownika |
| password | Hash hasla (legacy: md5) |
| status | Status konta: 1 = aktywny, 0 = zablokowany |
| admin | Flaga dostepu do panelu admin |
| error_logged_count | Licznik nieudanych logowan |
| last_logged | Data ostatniego poprawnego logowania |
| last_error_logged | Data ostatniej nieudanej proby logowania |
| twofa_enabled | Czy wlaczone 2FA (0/1) |
| twofa_email | E-mail do wysylki kodu 2FA |
| twofa_code_hash | Hash aktualnego kodu 2FA |
| twofa_expires_at | Data waznosci kodu 2FA |
| twofa_sent_at | Data ostatniej wysylki kodu 2FA |
| twofa_failed_attempts | Liczba nieudanych prob 2FA |
**Uzywane w:** `Domain\User\UserRepository`, `admin\Controllers\UsersController`, `admin\factory\Users`
**Aktualizacja 2026-02-12:** uzycia `pp_users` sa prowadzone przez `Domain\\User\\UserRepository` (legacy `admin\\factory\\Users` usunieto).
## pp_langs
Jezyki panelu i frontendu.
| Kolumna | Opis |
|---------|------|
| id | PK (2-literowe ID jezyka, np. pl, en) |
| name | Nazwa jezyka |
| status | 1 = aktywny, 0 = nieaktywny |
| start | 1 = domyslny jezyk |
| o | Kolejnosc |
**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages`
## pp_langs_translations
Slownik tlumaczen panelu/frontendu.
| Kolumna | Opis |
|---------|------|
| id | PK |
| text | Klucz/tekst bazowy |
| <lang_id> | Kolumny dynamiczne per jezyk (np. pl, en) |
**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages`
**Aktualizacja 2026-02-12:** modul jezykow i tlumaczen (`pp_langs`, `pp_langs_translations`) obslugiwany przez `Domain\\Languages\\LanguagesRepository`.
## pp_layouts
Szablony layoutow (HTML/CSS/JS + flagi domyslne).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa szablonu |
| html | Kod HTML |
| css | Kod CSS |
| js | Kod JS |
| m_html | Kod HTML mobilny |
| m_css | Kod CSS mobilny |
| m_js | Kod JS mobilny |
| status | Domyslny layout stron (0/1) |
| categories_default | Domyslny layout kategorii (0/1) |
**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`, `front\\factory\\Layouts`
## pp_layouts_pages
Przypisanie layoutow do stron CMS.
| Kolumna | Opis |
|---------|------|
| layout_id | FK do pp_layouts |
| page_id | FK do pp_pages |
**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts`
## pp_layouts_categories
Przypisanie layoutow do kategorii sklepu.
| Kolumna | Opis |
|---------|------|
| layout_id | FK do pp_layouts |
| category_id | FK do pp_shop_categories |
**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts`
**Aktualizacja 2026-02-12 (ver. 0.256):** modul `/admin/layouts` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI kontroler + fasada legacy).
## pp_newsletter
Adresy e-mail zapisane do newslettera.
| Kolumna | Opis |
|---------|------|
| id | PK |
| email | Adres e-mail subskrybenta |
| hash | Hash potwierdzenia/wypisu |
| status | 1 = potwierdzony, 0 = oczekujacy |
**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\Controllers\\NewsletterController`
## pp_newsletter_send
Kolejka wysylki newslettera.
| Kolumna | Opis |
|---------|------|
| id | PK |
| email | Adres docelowy |
| dates | Zakres dat artykulow (tekst) |
| id_template | FK do `pp_newsletter_templates` (NULL gdy brak szablonu) |
**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`
## pp_newsletter_templates
Szablony tresci e-maili (uzytkownik + administracyjne/systemowe).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa/klucz szablonu |
| text | Tresc HTML szablonu |
| is_admin | 1 = szablon administracyjny/systemowy, 0 = szablon uzytkownika |
**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`
**Aktualizacja 2026-02-12 (ver. 0.257):** modul `/admin/newsletter` korzysta z `Domain\\Newsletter\\NewsletterRepository` (DI kontroler + fasada legacy).
**Aktualizacja 2026-02-16 (ver. 0.279):** `front\\factory\\Newsletter` usunięta — logika przeniesiona do `NewsletterRepository`. Frontend korzysta z `front\\Controllers\\NewsletterController` (DI).
## pp_scontainers
Kontenery statyczne (modul /admin/scontainers).
| Kolumna | Opis |
|---------|------|
| id | PK |
| status | 1 = aktywny, 0 = nieaktywny |
| show_title | 1 = pokaz tytul, 0 = ukryj tytul |
**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `admin\Controllers\ScontainersController`, `front\factory\Scontainers`
## pp_scontainers_langs
Tlumaczenia kontenerow statycznych (per jezyk).
| Kolumna | Opis |
|---------|------|
| id | PK |
| container_id | FK do pp_scontainers |
| lang_id | ID jezyka (np. pl, en) |
| title | Tytul kontenera |
| text | Tresc HTML kontenera |
**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `front\factory\Scontainers`
**Aktualizacja 2026-02-12 (ver. 0.259):** modul `/admin/scontainers` korzysta z `Domain\Scontainers\ScontainersRepository` (DI kontroler + fasada legacy).
**Aktualizacja 2026-02-12 (ver. 0.260):** modul `/admin/articles_archive` korzysta z `Domain\Article\ArticleRepository` (`listArchivedForAdmin`, `restore`, `deletePermanently`) przez `admin\Controllers\ArticlesArchiveController`.
## pp_shop_attributes
Cechy produktu (modul `/admin/shop_attribute`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| status | Status: 1 = aktywny, 0 = nieaktywny |
| type | Typ cechy: 0 = tekst, 1 = kolor, 2 = wzor |
| o | Kolejnosc wyswietlania |
**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct`
## pp_shop_attributes_langs
Tlumaczenia cech produktu (per jezyk).
| Kolumna | Opis |
|---------|------|
| id | PK |
| attribute_id | FK do pp_shop_attributes |
| lang_id | ID jezyka (np. pl, en) |
| name | Nazwa cechy |
**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute`
## pp_shop_attributes_values
Wartosci cech produktu.
| Kolumna | Opis |
|---------|------|
| id | PK |
| attribute_id | FK do pp_shop_attributes |
| is_default | Czy wartosc domyslna dla cechy (0/1) |
| impact_on_the_price | Wplyw na cene wariantu (NULL = brak) |
**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\factory\ShopProduct`
## pp_shop_attributes_values_langs
Tlumaczenia wartosci cech (per jezyk).
| Kolumna | Opis |
|---------|------|
| id | PK |
| value_id | FK do pp_shop_attributes_values |
| lang_id | ID jezyka (np. pl, en) |
| name | Nazwa wyswietlana |
| value | Wewnetrzna wartosc techniczna (opcjonalna) |
**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute`
## pp_shop_products_attributes
Powiazanie kombinacji produktow z wartosciami cech.
| Kolumna | Opis |
|---------|------|
| product_id | FK do pp_shop_products (kombinacja) |
| value_id | FK do pp_shop_attributes_values |
**Uzywane w:** `Domain\Attribute\AttributeRepository::refreshCombinationPricesForValue()`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct`
**Aktualizacja 2026-02-14 (ver. 0.271):** modul `/admin/shop_attribute` korzysta z `Domain\Attribute\AttributeRepository` przez `admin\Controllers\ShopAttributeController`. Usunieto legacy klasy `admin\controls\ShopAttribute`, `admin\factory\ShopAttribute`, `admin\view\ShopAttribute`.
## pp_shop_coupon
Kody rabatowe sklepu (modul `/admin/shop_coupon`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Kod kuponu (UNIQUE) |
| status | Status: 1 = aktywny, 0 = nieaktywny |
| send | Czy kupon zostal wyslany (0/1) |
| used | Czy kupon zostal wykorzystany (0/1) |
| date_used | Data wykorzystania kuponu (NULL gdy brak) |
| used_count | Licznik uzyc kuponu |
| type | Typ kuponu (obecnie: 1 = rabat procentowy na koszyk) |
| amount | Wartosc kuponu (np. procent) |
| one_time | Czy kupon jednorazowy (0/1) |
| include_discounted_product | Czy obejmuje rowniez produkty przecenione (0/1) |
| categories | JSON z ID kategorii objetych kuponem (NULL = bez ograniczenia) |
**Uzywane w:** `Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`, `front\Controllers\ShopCouponController`, `shop\Coupon`, `Domain\Order\OrderRepository`
**Aktualizacja 2026-02-13 (ver. 0.266):** modul `/admin/shop_coupon` korzysta z `Domain\Coupon\CouponRepository` przez `admin\Controllers\ShopCouponController`. Usunieto legacy klasy `admin\controls\ShopCoupon` i `admin\factory\ShopCoupon`.
## pp_shop_promotion
Promocje sklepu (modul `/admin/shop_promotion`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa promocji |
| status | Status: 1 = aktywna, 0 = nieaktywna |
| condition_type | Typ warunku promocji (slownik w `shop\Promotion::$condition_type`) |
| discount_type | Typ rabatu (slownik w `shop\Promotion::$discount_type`) |
| amount | Wartosc rabatu (np. procent) |
| date_from | Data startu promocji (NULL = aktywna od razu) |
| date_to | Data konca promocji (NULL = bez daty konca) |
| categories | JSON z ID kategorii grupy I |
| condition_categories | JSON z ID kategorii grupy II |
| include_coupon | Czy laczyc z kuponami rabatowymi (0/1) |
| include_product_promo | Czy uwzgledniac produkty przecenione (0/1) |
| min_product_count | Minimalna liczba produktow (dla wybranych warunkow) |
| price_cheapest_product | Cena najtanszego produktu (dla wybranych warunkow) |
**Uzywane w:** `Domain\Promotion\PromotionRepository`, `admin\Controllers\ShopPromotionController`, `shop\Promotion`, `front\factory\ShopPromotion`
**Aktualizacja 2026-02-13:** modul `/admin/shop_promotion` korzysta z `Domain\Promotion\PromotionRepository` przez `admin\Controllers\ShopPromotionController`. Usunieto legacy klasy `admin\controls\ShopPromotion` i `admin\factory\ShopPromotion`.
**Aktualizacja 2026-02-13 (ver. 0.265):** dodano obsluge `date_from` (repozytorium, formularz admin, lista admin, filtr aktywnych promocji na froncie) oraz poprawke zapisu edycji promocji po `id`.
## pp_shop_payment_methods
Metody platnosci sklepu (modul `/admin/shop_payment_method`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa metody platnosci |
| description | Opis metody platnosci (wyswietlany m.in. w checkout) |
| status | Status: 1 = aktywna, 0 = nieaktywna |
| apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) |
| min_order_amount | Minimalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) |
| max_order_amount | Maksymalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) |
| is_cod | Platnosc przy odbiorze: 1 = tak, 0 = nie (TINYINT DEFAULT 0) |
| sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) |
**Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php`
**Aktualizacja 2026-03-12 (ver. 0.338):** dodano kolumne `is_cod` — flaga platnosci przy odbiorze, zastepuje hardkodowane `payment_id == 3` w `OrderRepository::createFromBasket()`.
**Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`.
**Aktualizacja 2026-02-22 (ver. 0.304):** dodano kolumny `min_order_amount` i `max_order_amount` — konfigurowalne limity kwotowe metod platnosci. Zastapiono hardcoded warunek PayPo (id=6, 40-1000 PLN) generycznym filtrowaniem na froncie.
## pp_shop_transports
Rodzaje transportu sklepu (modul `/admin/shop_transport`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa (systemowa, readonly) |
| name_visible | Nazwa widoczna dla klienta |
| description | Opis metody transportu |
| status | Status: 1 = aktywny, 0 = nieaktywny |
| cost | Koszt dostawy (PLN) |
| max_wp | Maksymalna waga paczki (NULL = bez limitu) |
| default | Domyslna forma dostawy (0/1) |
| delivery_free | Czy obsluguje darmowa dostawe (0/1) |
| apilo_carrier_account_id | ID konta przewoznika w Apilo (NULL gdy brak mapowania) |
| o | Kolejnosc wyswietlania |
**Uzywane w:** `Domain\Transport\TransportRepository`, `admin\Controllers\ShopTransportController`, `front\factory\ShopTransport`
## pp_shop_transport_payment_methods
Powiazanie metod transportu z metodami platnosci (tabela lacznikowa).
| Kolumna | Opis |
|---------|------|
| id_transport | FK do pp_shop_transports |
| id_payment_method | FK do pp_shop_payment_methods |
**Uzywane w:** `Domain\Transport\TransportRepository`, `Domain\PaymentMethod\PaymentMethodRepository::forTransport()`
**Aktualizacja 2026-02-14 (ver. 0.269):** modul `/admin/shop_transport` korzysta z `Domain\Transport\TransportRepository` przez `admin\Controllers\ShopTransportController`. Usunieto legacy klasy `admin\controls\ShopTransport`, `admin\view\ShopTransport` oraz widok `admin/templates/shop-transport/view-list.php`.
## pp_shop_apilo_settings
Ustawienia integracji Apilo (key-value).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Klucz ustawienia (np. client-id, access-token) |
| value | Wartosc ustawienia |
**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations`
## pp_shop_shoppro_settings
Ustawienia integracji ShopPRO (key-value).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Klucz ustawienia (np. domain, db_name) |
| value | Wartosc ustawienia |
**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations`
**Aktualizacja 2026-02-13:** modul `/admin/integrations/` korzysta z `Domain\Integrations\IntegrationsRepository` (DI kontroler + fasada legacy). Usunieto integracje Sellasist i Baselinker.
## pp_shop_statuses
Statusy zamowien sklepu (modul `/admin/shop_statuses`). Statusy sa predefiniowane - brak dodawania/usuwania, mozliwa edycja koloru i mapowania Apilo.
| Kolumna | Opis |
|---------|------|
| id | PK (zaczyna sie od 0!) |
| status | Nazwa statusu (read-only) |
| color | Kolor statusu (hex, np. #ff0000) |
| o | Kolejnosc wyswietlania |
| apilo_status_id | ID statusu w Apilo (NULL gdy brak mapowania) |
| baselinker_status_id | DEPRECATED (usuniety w ver. 0.263) |
| sellasist_status_id | DEPRECATED (usuniety w ver. 0.263) |
**Uzywane w:** `Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`, `front\factory\ShopStatuses`, `shop\Order`, `cron.php`
**Aktualizacja 2026-02-14 (ver. 0.267):** modul `/admin/shop_statuses` korzysta z `Domain\ShopStatus\ShopStatusRepository` przez `admin\Controllers\ShopStatusesController`. Usunieto legacy klasy `admin\controls\ShopStatuses` i `admin\factory\ShopStatuses`. `front\factory\ShopStatuses` dziala jako fasada do repozytorium.
## pp_shop_product_sets
Komplety produktow (modul `/admin/shop_product_sets`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa kompletu |
| status | Status: 1 = aktywny, 0 = nieaktywny |
**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `admin\Controllers\ShopProductSetsController`, `shop\ProductSet`, `shop\Product`
## pp_shop_product_sets_products
Powiazanie kompletow z produktami (tabela lacznikowa).
| Kolumna | Opis |
|---------|------|
| id | PK |
| set_id | FK do pp_shop_product_sets |
| product_id | FK do pp_shop_products |
**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `shop\Product`, `front\factory\ShopProduct`, `admin\factory\ShopProduct`
**Aktualizacja 2026-02-15 (ver. 0.272):** modul `/admin/shop_product_sets` korzysta z `Domain\ProductSet\ProductSetRepository` przez `admin\Controllers\ShopProductSetsController`. Usunieto legacy klasy `admin\controls\ShopProductSets` i `admin\factory\ShopProductSet`. `shop\ProductSet` dziala jako fasada do repozytorium.
## pp_shop_producer
Producenci produktow (modul `/admin/shop_producer`).
| Kolumna | Opis |
|---------|------|
| id | PK |
| name | Nazwa producenta |
| status | Status: 1 = aktywny, 0 = nieaktywny |
| img | Sciezka do logo producenta (NULL gdy brak) |
**Uzywane w:** `Domain\Producer\ProducerRepository`, `admin\Controllers\ShopProducerController`, `front\Controllers\ShopProducerController`, `shop\Product`
## pp_shop_producer_lang
Tlumaczenia producentow (per jezyk). FK kaskadowe ON DELETE CASCADE.
| Kolumna | Opis |
|---------|------|
| id | PK |
| producer_id | FK do pp_shop_producer |
| lang_id | ID jezyka (np. pl, en) |
| description | Opis producenta (TEXT) |
| data | Dane producenta (TEXT, HTML) |
| meta_title | Meta title SEO (VARCHAR 255) |
**Uzywane w:** `Domain\Producer\ProducerRepository`, `shop\Product`
**Aktualizacja 2026-02-15 (ver. 0.273):** modul `/admin/shop_producer` korzysta z `Domain\Producer\ProducerRepository` przez `admin\Controllers\ShopProducerController`. Usunieto legacy `admin\controls\ShopProducer` i `admin\factory\ShopProducer`. `shop\Producer` dziala jako fasada do repozytorium.
**Aktualizacja 2026-02-17 (ver. 0.291):** frontend `/shop_producer/*` korzysta z `Domain\Producer\ProducerRepository` przez `front\Controllers\ShopProducerController`; usunięto legacy `front\controls\ShopProducer` i `shop\Producer`.
## pp_cron_jobs
Kolejka zadań cron z priorytetami i retry/backoff.
| Kolumna | Opis |
|---------|------|
| id | PK auto increment |
| job_type | Typ zadania (VARCHAR 50) — np. apilo_send_order, price_history |
| status | ENUM: pending, processing, completed, failed, cancelled |
| priority | TINYINT — niższy = ważniejszy (10=krytyczny, 50=wysoki, 100=normalny, 200=niski) |
| payload | JSON z danymi zadania (TEXT NULL) |
| result | JSON z wynikiem (TEXT NULL) |
| attempts | Liczba prób (SMALLINT) |
| max_attempts | Maksymalna liczba prób (SMALLINT, domyślnie 10) |
| last_error | Ostatni błąd (VARCHAR 500) |
| scheduled_at | Kiedy zadanie ma być uruchomione (DATETIME) |
| started_at | Kiedy rozpoczęto przetwarzanie (DATETIME NULL) |
| completed_at | Kiedy zakończono (DATETIME NULL) |
| created_at | Data utworzenia (DATETIME) |
| updated_at | Data ostatniej modyfikacji (DATETIME, ON UPDATE) |
**Indeksy:** idx_status_priority_scheduled (status, priority, scheduled_at), idx_job_type, idx_status
**Używane w:** `Domain\CronJob\CronJobRepository`, `Domain\CronJob\CronJobProcessor`
## pp_cron_schedules
Harmonogram cyklicznych zadań cron.
| Kolumna | Opis |
|---------|------|
| id | PK auto increment |
| job_type | Typ zadania (VARCHAR 50, UNIQUE) |
| interval_seconds | Interwał uruchomienia w sekundach |
| priority | Priorytet tworzonych zadań (TINYINT) |
| max_attempts | Maks. prób dla tworzonych zadań (SMALLINT) |
| payload | Opcjonalny payload JSON (TEXT NULL) |
| enabled | Czy harmonogram aktywny (TINYINT 1) |
| last_run_at | Ostatnie uruchomienie (DATETIME NULL) |
| next_run_at | Następne planowane uruchomienie (DATETIME NULL) |
| created_at | Data utworzenia (DATETIME) |
**Indeksy:** idx_enabled_next_run (enabled, next_run_at)
**Używane w:** `Domain\CronJob\CronJobRepository`, `Domain\CronJob\CronJobProcessor`
**Dodano w wersji 0.324.**
## pp_routes
Tabela tras URL — mapowanie wzorców URL (regex) na parametry GET. Zastępuje reguły `RewriteRule` w `.htaccess` dla wszystkich URL-i aplikacji: produktów, kategorii, stron, artykułów oraz systemowych (koszyk, logowanie, newsletter, itp.).
| Kolumna | Opis |
|---------|------|
| id | Klucz główny (AUTO_INCREMENT) |
| product_id | ID produktu (INT NULL) — wypełnione dla tras produktów |
| category_id | ID kategorii (INT NULL) — wypełnione dla tras kategorii |
| page_id | ID strony (INT NULL) — wypełnione dla tras stron |
| article_id | ID artykułu (INT NULL) — wypełnione dla tras artykułów |
| type | Typ trasy: NULL = encja (produkt/kategoria/strona/artykuł), `'system'` = trasa systemowa (koszyk, logowanie, newsletter, AJAX moduły, itp.) |
| lang_id | ID języka (0 dla tras systemowych niezwiązanych z językiem) |
| pattern | Wyrażenie regularne dopasowywane do REQUEST_URI |
| destination | Docelowy query string, np. `index.php?category=5&lang=1` |
**Mechanizm:** `index.php` ładuje wszystkie trasy (z cache Redis `pp_routes:all`) przed `checkUrlParams()`, dopasowuje `pattern` do ścieżki żądania i ustawia `$_GET` z `destination`. Obsługuje grupy przechwytujące (np. `$1` dla paginacji).
**Trasy systemowe:** Przy każdym wywołaniu `Helpers::htacces()` wszystkie rekordy z `type='system'` są usuwane i wstawiane na nowo (32 statycznych + dynamiczne trasy językowe i producentów). Zarządzane automatycznie — nie edytować ręcznie.
**Cache:** Redis klucz `pp_routes:all`, TTL 86400s. Invalidowany automatycznie przy każdym wywołaniu `Helpers::htacces()`.
**Używane w:** `index.php`, `Shared\Helpers\Helpers::htacces()`, `Domain\Product\ProductRepository`, `Domain\Category\CategoryRepository`, `Domain\Pages\PagesRepository`, `Domain\Article\ArticleRepository`
**Dodano w wersji 0.329. Kolumna `type` i trasy systemowe dodane w wersji 0.330.**

View File

@@ -1,178 +0,0 @@
# Form Edit System - Dokumentacja użycia
## Architektura
```
┌─────────────────────────────────────────────────────────────┐
│ Controller │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ edit() │ │ save() │ │
│ │ - buduje VM │ │ - walidacja │ │
│ │ - renderuje │ │ - zapis │ │
│ └────────┬────────┘ └─────────────────┘ │
└───────────┼─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FormEditViewModel │
│ - title, formId, data, fields, tabs, actions │
│ - validationErrors, persist, languages │
└───────────┬─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ components/form-edit.php (szablon) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ FormFieldRenderer - renderuje każde pole │ │
│ │ ├─ input, select, textarea, switch │ │
│ │ ├─ date, datetime, editor, image │ │
│ │ └─ lang_section (zagnieżdżone pola) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Pliki systemu
| Plik | Opis |
|------|------|
| `autoload/admin/ViewModels/Forms/FormFieldType.php` | Stale typow pol |
| `autoload/admin/ViewModels/Forms/FormField.php` | Factory methods per typ |
| `autoload/admin/ViewModels/Forms/FormTab.php` | Zakladki |
| `autoload/admin/ViewModels/Forms/FormAction.php` | Akcje (zapisz, anuluj) |
| `autoload/admin/ViewModels/Forms/FormEditViewModel.php` | ViewModel formularza |
| `autoload/admin/Support/Forms/FormValidator.php` | Walidacja pol |
| `autoload/admin/Support/Forms/FormRequestHandler.php` | Obsluga POST + persist |
| `autoload/admin/Support/Forms/FormFieldRenderer.php` | Renderowanie HTML |
| `admin/templates/components/form-edit.php` | Uniwersalny szablon |
## Przykład użycia w kontrolerze
```php
use admin\ViewModels\Forms\FormEditViewModel;
use admin\ViewModels\Forms\FormField;
use admin\ViewModels\Forms\FormTab;
use admin\ViewModels\Forms\FormAction;
use admin\Support\Forms\FormRequestHandler;
class BannerController
{
public function edit(): string
{
$banner = $this->repository->find($id);
$languages = \admin\factory\Languages::languages_list();
$viewModel = new FormEditViewModel(
formId: 'banner-edit',
title: 'Edycja banera',
data: $banner,
tabs: [
new FormTab('settings', 'Ustawienia', 'fa-wrench'),
new FormTab('content', 'Zawartość', 'fa-file'),
],
fields: [
// Zakładka Ustawienia
FormField::text('name', [
'label' => 'Nazwa',
'tab' => 'settings',
'required' => true,
]),
FormField::switch('status', [
'label' => 'Aktywny',
'tab' => 'settings',
]),
FormField::date('date_start', [
'label' => 'Data rozpoczęcia',
'tab' => 'settings',
]),
// Sekcja językowa w zakładce Zawartość
FormField::langSection('translations', 'content', [
FormField::image('src', ['label' => 'Obraz']),
FormField::text('url', ['label' => 'Url']),
FormField::editor('text', ['label' => 'Treść']),
]),
],
actions: [
FormAction::save('/admin/banners/save', '/admin/banners'),
FormAction::cancel('/admin/banners'),
],
languages: $languages,
persist: true,
);
return \Tpl::view('components/form-edit', ['form' => $viewModel]);
}
public function save(): void
{
$formHandler = new FormRequestHandler();
$viewModel = $this->buildFormViewModel(); // jak w edit()
$result = $formHandler->handleSubmit($viewModel, $_POST);
if (!$result['success']) {
// Błędy walidacji - zapisane automatycznie do sesji
echo json_encode(['success' => false, 'errors' => $result['errors']]);
exit;
}
// Sukces - persist wyczyszczony automatycznie
$this->repository->save($result['data']);
echo json_encode(['success' => true]);
exit;
}
}
```
## Dostępne typy pól
| Typ | Metoda | Opcje |
|-----|--------|-------|
| `text` | `FormField::text(name, ['label' => '...', 'required' => true])` | placeholder, help |
| `number` | `FormField::number(name, [...])` | - |
| `email` | `FormField::email(name, [...])` | walidacja formatu |
| `password` | `FormField::password(name, [...])` | - |
| `date` | `FormField::date(name, [...])` | datetimepicker |
| `datetime` | `FormField::datetime(name, [...])` | datetimepicker z czasem |
| `switch` | `FormField::switch(name, [...])` | checked (bool) |
| `select` | `FormField::select(name, ['options' => [...]])` | options: [key => label] |
| `textarea` | `FormField::textarea(name, ['rows' => 4])` | rows |
| `editor` | `FormField::editor(name, ['toolbar' => 'MyTool'])` | CKEditor |
| `image` | `FormField::image(name, ['filemanager' => true])` | filemanager URL |
| `file` | `FormField::file(name, [...])` | filemanager |
| `hidden` | `FormField::hidden(name, value)` | - |
| `color` | `FormField::color(name, ['label' => '...'])` | HTML5 color picker + text input |
| `lang_section` | `FormField::langSection(name, 'tab', [fields])` | pola per język |
## Walidacja
Walidacja jest automatyczna na podstawie właściwości pól:
- `required` - pole wymagane
- `type` = `email` - walidacja formatu e-mail
- `type` = `number` - walidacja liczby
- `type` = `date` - walidacja formatu YYYY-MM-DD
Dla sekcji językowych walidacja jest powtarzana dla każdego aktywnego języka.
## Persist (zapamiętywanie danych)
Gdy `persist = true`:
1. Przy błędzie walidacji dane są zapisywane w `$_SESSION['form_persist'][$formId]`
2. Formularz automatycznie przywraca dane z sesji przy ponownym wyświetleniu
3. Po udanym zapisie sesja jest czyszczona automatycznie przez `FormRequestHandler`
## Przerabianie istniejących formularzy
1. **Kontroler** - zamień `view\Xxx::edit()` na `FormEditViewModel`
2. **Repository** - dostosuj `save()` do formatu z `FormRequestHandler` (lub dodaj wsparcie dla obu formatów)
3. **Szablon** - usuń stary szablon lub zostaw jako fallback
4. **Testy** - zaktualizuj testy jeśli zmienił się format danych
## Aktualizacja 2026-02-15 (ver. 0.275)
- Modul `ShopCategory` zostal zmigrowany do warstwy Domain + DI, ale formularz kategorii nadal korzysta z legacy `gridEdit`.
- W ramach migracji wydzielono skrypty UI do osobnych partiali `*-custom-script.php` (lista, browse, edycja, produkty), co upraszcza dalsze przepiecie formularza na `components/form-edit`.
- Po migracji `ShopCategory` kolejnym kandydatem do pelnej migracji formularza na Form Edit System pozostaje modul `Order` (zgodnie z `REFACTORING_PLAN.md`).
---
*Dokument aktualizowany: 2026-02-15*

View File

@@ -1,42 +0,0 @@
# Pamięć projektu shopPRO
Notatki i wnioski zebrane podczas pracy z kodem. Aktualizowane na bieżąco.
---
## Serwer produkcyjny
- PHP < 8.0 — unikać `match`, named arguments, union types, `str_contains()` itp.
- Zamiast `match` używać operatorów trójargumentowych (ternary) lub `if/else`
## Znane problemy / TODO
- `\Shared\Helpers\Helpers::send_email()` i `Shared\Email\Email::send()` — zduplikowana logika PHPMailer. Docelowo zunifikować w `Shared\Email\Email`
## Wzorce potwierdzone w projekcie
- Metody frontendowe (z cache Redis) dodawane do istniejących repozytoriów Domain — NIE tworzymy osobnych FrontendService/AdminService
- Klasy View (`front\Views\*`) są statyczne i bezstanowe — nie wymagają DI
- Kontrolery (`Controllers\*`) są instancyjne z DI przez konstruktor
- Autoloader obsługuje dwa formaty plików: `class.X.php` (legacy) i `X.php` (nowy) — oba działają bez zmian w autoloaderze
- Nowe katalogi z dużej litery: `Views/`, `Controllers/` (legacy: `view/`, `controls/`, `factory/`)
## Medoo ORM — pułapki
- `$mdb->delete()` przyjmuje 2 argumenty (tabela, warunek), NIE 3 — wielokrotnie powodowało bugi (np. `newsletter_unsubscribe`)
- `$mdb->get()` zwraca `null` gdy brak rekordu, NIE `false`
- Przy `$mdb->insert()` sprawdzać `$mdb->id()` aby potwierdzić sukces
## Redis cache — konwencje
- TTL domyślnie 86400 (24h)
- Klucze produktów: `shop\product:{id}:{lang}:{permutation_hash}`
- Wzorzec czyszczenia: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
- Dane w cache są serializowane — wymagają `unserialize()` po `get()`
## Aktualizacje klienckie
- Pliki `*.md` NIGDY nie trafiają do ZIP aktualizacji
- `updates/changelog.php` to plik serwisowy repozytorium, nie runtime klienta
- Główny `.htaccess` wdrażany osobno, poza ZIP aktualizacji
- W archiwum ZIP NIE powinno być folderu z nazwą wersji — struktura zaczyna się od katalogów projektu

View File

@@ -1,150 +0,0 @@
# PAUL — Workflow dla shopPRO
[PAUL](https://github.com/ChristopherKahler/paul) to zestaw komend dla Claude Code, który strukturyzuje pracę nad projektem: planowanie → implementacja → weryfikacja. Działa przez slash-komendy w czacie z Claude.
---
## Jednorazowa inicjalizacja
```
/paul:init
```
Uruchom raz — tworzy plik `.paul/` z konfiguracją projektu (milestones, fazy). Jeśli już zainicjalizowany, pomijasz ten krok.
---
## Nowa funkcja (feature)
### 1. Omów wizję
```
/paul:discuss
```
Claude zadaje pytania, żeby doprecyzować, co dokładnie chcesz zbudować — wynik to jasna definicja przed planowaniem.
### 2. Zbadaj opcje techniczne (opcjonalnie)
```
/paul:discover
```
Przydatne gdy nie jesteś pewny jak zaimplementować coś technicznie — Claude przegląda kod i proponuje podejścia.
### 3. Zaplanuj implementację
```
/paul:plan
```
Tworzy szczegółowy plan z krokami, plikami do zmiany, kolejnością. Zatwierdź plan zanim ruszysz dalej.
### 4. Wykonaj plan
```
/paul:apply
```
Claude implementuje plan krok po kroku, z commitami na każdą fazę.
### 5. Zweryfikuj (UAT)
```
/paul:verify
```
Claude generuje checklistę testów manualnych — punkt po punkcie sprawdzasz czy funkcja działa.
---
## Poprawka błędu (bug fix)
```
/paul:plan-fix
```
Dedykowany flow dla bugów: opisujesz problem → Claude analizuje kod → tworzy plan naprawy → `/paul:apply` wykonuje.
Krótszy wariant dla prostych bugów — możesz też po prostu opisać błąd i użyć `/paul:plan``/paul:apply`.
---
## Zarządzanie sesjami
| Komenda | Kiedy |
|---------|-------|
| `/paul:progress` | Sprawdź gdzie jesteś, co dalej |
| `/paul:pause` | Kończysz sesję — tworzy plik handoff |
| `/paul:resume` | Zaczynasz nową sesję — wczytuje kontekst |
| `/paul:handoff` | Generuje dokument przekazania pracy |
---
## Milestones (większe wdrożenia)
Gdy planujesz większą wersję (np. nowy moduł, refactor):
```
/paul:milestone # Tworzysz nowy milestone
/paul:add-phase # Dodajesz fazę do milestone
/paul:complete-milestone # Zamykasz po wdrożeniu
```
---
## Czy PAUL może przejrzeć projekt pod kątem błędów?
Tak, częściowo. Najlepsze podejście:
### Mapowanie kodu
```
/paul:map-codebase
```
Generuje analizę architektury — wyłapuje niespójności, martwy kod, problematyczne zależności.
### Research konkretnego obszaru
```
/paul:research
```
Np. _"Sprawdź czy walidacja zamówień w OrderRepository jest kompletna"_ — Claude przegląda kod i raportuje problemy.
### Ograniczenia
PAUL nie jest narzędziem do statycznej analizy kodu (jak PHPStan czy psalm). Do systematycznego przeglądu błędów lepiej połączyć:
- **PAUL** (`/paul:map-codebase` + `/paul:research`) — problemy architektoniczne, logika biznesowa
- **Testy** (`./test.ps1`) — regresje, złe zachowanie metod
- **PHPStan** (jeśli dodany do projektu) — typy, niezdefiniowane zmienne
---
## Typowy dzień pracy w shopPRO
```
# Rano — wznów kontekst
/paul:resume
# W trakcie — sprawdź co dalej
/paul:progress
# Nowa funkcja lub bug
/paul:discuss → /paul:plan → /paul:apply → /paul:verify
# Na koniec dnia
/paul:pause
```
---
## Dobre praktyki
- Zawsze zatwierdzaj plan (`/paul:plan`) zanim uruchomisz `/paul:apply` — łatwiej zmienić plan niż cofnąć kod
- Po każdym `/paul:apply` odpal testy: `./test.ps1`
- Używaj `/paul:verify` przed mergem — generuje checklistę zamiast zgadywać co przetestować
- Przy bugach najpierw opisz symptom dokładnie, Claude lepiej planuje naprawę mając konkretny przypadek

View File

@@ -1,190 +0,0 @@
# Struktura Projektu shopPRO
Aktualna architektura po zakonczonej migracji na Domain-Driven Design + Dependency Injection.
## Warstwa domenowa (`autoload/Domain/`)
Kazdy modul zawiera Repository (i opcjonalnie dodatkowe klasy). Konstruktor DI z `$db` (Medoo). Metody sluza zarowno adminowi, jak i frontendowi (wspolna warstwa).
| Modul | Klasy | Uwagi |
|-------|-------|-------|
| Article | ArticleRepository | blog, aktualnosci, galerie, pliki |
| Attribute | AttributeRepository | cechy produktow + wartosci |
| Banner | BannerRepository | banery glowne + boczne, Redis cache |
| Basket | BasketCalculator | summary, count, walidacja stanow |
| Cache | CacheRepository | czyszczenie cache z poziomu admin |
| Category | CategoryRepository | drzewa kategorii, produkty w kategorii, Redis cache |
| Client | ClientRepository | CRUD, auth, adresy, zamowienia |
| Coupon | CouponRepository | kupony rabatowe, walidacja, uzycie |
| CronJob | CronJobType, CronJobRepository, CronJobProcessor | kolejka zadan cron (DB), priorytety, retry/backoff, harmonogram |
| Dashboard | DashboardRepository | statystyki admin, Redis cache |
| Dictionaries | DictionariesRepository | slowniki admin |
| Integrations | IntegrationsRepository | Apilo sync, ustawienia |
| Languages | LanguagesRepository | jezyki, tlumaczenia |
| Layouts | LayoutsRepository | layouty stron, 3-level fallback |
| Newsletter | NewsletterRepository, NewsletterPreviewRenderer | subskrypcje, szablony, kolejka wysylki |
| Order | OrderRepository, OrderAdminService | CRUD, Apilo sync, webhooki platnosci, kolejka retry |
| Pages | PagesRepository | strony, menu, drzewa stron |
| PaymentMethod | PaymentMethodRepository | metody platnosci, mapowanie Apilo |
| Producer | ProducerRepository | producenci |
| Product | ProductRepository | CRUD, cache, kombinacje, zdjecia, Google Feed XML |
| ProductSet | ProductSetRepository | zestawy produktow |
| Promotion | PromotionRepository | promocje, 5 typow applyType*, silnik dopasowania |
| Scontainers | ScontainersRepository | kontenery sidebaru |
| Settings | SettingsRepository | ustawienia sklepu |
| ShopStatus | ShopStatusRepository | statusy zamowien, mapowanie Apilo |
| Transport | TransportRepository | transport, koszty, powiazanie z platnosci |
| Update | UpdateRepository | aktualizacje, migracje SQL |
| User | UserRepository | uzytkownicy admin, 2FA, logowanie |
## Warstwa admin (`autoload/admin/`)
### Router: `admin\App`
- `getControllerFactories()` — mapa kontrolerow z DI wiring
- Brak fallbacku na legacy — wszystkie moduly na nowych kontrolerach
### Kontrolery (`admin\Controllers\`) — 28 kontrolerow
ArticlesArchive, Articles, Banner, Dashboard, Dictionaries, Filemanager, Integrations, Languages, Layouts, Newsletter, Pages, ProductArchive, Scontainers, Settings, ShopAttribute, ShopCategory, ShopClients, ShopCoupon, ShopOrder, ShopPaymentMethod, ShopProducer, ShopProduct, ShopProductSets, ShopPromotion, ShopStatuses, ShopTransport, Update, Users
### Support
- `admin\Support\TableListRequestFactory` — paginacja/sortowanie tabel
- `admin\Support\Forms\FormRequestHandler` — obsluga formularzy (persist przy bledach)
- `admin\Support\Forms\FormFieldRenderer` — renderowanie pol formularzy
### ViewModels
- `admin\ViewModels\Forms\` — FormEditViewModel, FormField, FormTab, FormAction, FormFieldType
- `admin\ViewModels\Common\PaginatedTableViewModel`
### Walidacja
- `admin\Validation\FormValidator` — reguly per pole, sekcje jezykowe
## Warstwa frontend (`autoload/front/`)
### Router: `front\App`
- `route()`, `checkUrlParams()`, `getControllerFactories()`
### Layout Engine: `front\LayoutEngine`
- `show()` — zamiana tagow szablonowych (kategorie, produkty, menu, banery, artykuly, kontenery, meta)
- `contact()`, `cookieInformation()`
### Kontrolery (`front\Controllers\`) — 8 kontrolerow
Newsletter, Search, ShopBasket, ShopClient, ShopCoupon, ShopOrder, ShopProducer, ShopProduct
### Widoki (`front\Views\`) — 11 klas statycznych
Articles, Banners, Languages, Menu, Newsletter, Scontainers, ShopCategory, ShopClient, ShopPaymentMethod, ShopProduct, ShopSearch
## Warstwa API (`autoload/api/`)
REST API dla ordersPRO. Entry point: `api.php`. Stateless (bez sesji), autentykacja przez `X-Api-Key` header.
### Router: `api\ApiRouter`
- `handle()` — autentykacja → routing → dispatch
- Helpery statyczne: `sendSuccess()`, `sendError()`, `getJsonBody()`, `requireMethod()`
### Kontrolery (`api\Controllers\`)
- `OrdersApiController` — lista, szczegoly, zmiana statusu, platnosc (5 akcji)
- `ProductsApiController` — lista, szczegoly, tworzenie, aktualizacja produktow (4 akcje)
- `DictionariesApiController` — statusy, transporty, metody platnosci (3 akcje)
- `CategoriesApiController` — lista aktywnych kategorii (1 akcja)
Dokumentacja: `docs/API.md`
## Warstwa wspoldzielona (`autoload/Shared/`)
| Klasa | Opis |
|-------|------|
| `Shared\Cache\CacheHandler` | Redis cache: get/set/delete/deletePattern |
| `Shared\Cache\RedisConnection` | Singleton polaczenia Redis |
| `Shared\Email\Email` | Wrapper PHPMailer |
| `Shared\Helpers\Helpers` | SEO, email, cache clearing, shortPrice, utility |
| `Shared\Html\Html` | Helpery HTML |
| `Shared\Image\ImageManipulator` | Obrobka obrazow GD |
| `Shared\Tpl\Tpl` | Silnik szablonow: render(), set() |
## Cache Redis
### Klucze
```
shop\product:{id}:{lang}:{permutation_hash} — dane produktu (TTL 24h)
ProductRepository::getProductPermutationQuantityOptions:v2:{id}:{perm} — ilosc + komunikaty
ProductRepository::productSetsWhenAddToBasket:{id} — zestawy "kupowane razem"
```
### Konwencje
- TTL domyslnie 86400 (24h)
- Dane serializowane — `unserialize()` po `get()`
- Czyszczenie: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
- Czyszczenie z poziomu admin: `Shared\Helpers\Helpers::clear_product_cache($id)`
- Przycisk "Wyczysc cache" w admin: `SettingsController::clearCacheAjax()``flushAll()` Redis + `temp/` + `thumbs/`
## Entry pointy
| Plik | Rola |
|------|------|
| `index.php` | Frontend — autoload, sesja, DB, routing (`front\App`), layout (`front\LayoutEngine`), DOM post-processing |
| `ajax.php` | Frontend AJAX — koszyk, transport, kontakt |
| `api.php` | REST API (ordersPRO + Ekomi CSV) — router: `\api\ApiRouter`, kontrolery: `\api\Controllers\` |
| `admin/index.php` | Admin — autoload, sesja, DB, routing (`admin\App`) |
| `admin/ajax.php` | Admin AJAX |
| `cron.php` | CRON: Apilo sync (ceny/stany co 10min, cennik co 1h, retry queue) |
| `cron-turstmate.php` | TrustMate integracja |
| `cron/cron-xml.php` | Google Feed XML |
| `download.php` | Pobieranie plikow |
### Autoloader
Kazdy entry point rejestruje `__autoload_my_classes()`:
1. Probuje `autoload/{namespace}/class.{ClassName}.php` (legacy format)
2. Probuje `autoload/{namespace}/{ClassName}.php` (PSR-4 format)
### Routing frontend (index.php)
Przed `front\App::route()`:
1. Sprawdza tabele `pp_redirects` → 301 redirect
2. Sprawdza tabele `pp_routes` → regex pattern → destination
### Newsletter queue
`index.php` wywoluje `$newsletterRepo->sendQueued()` na koncu kazdego requestu frontendowego (limit 1 mail/request).
## Integracje zewnetrzne
### Apilo (cron.php)
- Synchronizacja cen/stanow produktow (co 10 min)
- Synchronizacja cennika (co 1h)
- Kolejka retry: `temp/apilo-sync-queue.json``OrderAdminService::processApiloSyncQueue()`
- Mapowanie statusow i platnosci przez tabele `pp_shop_statuses` i `pp_shop_payment_methods`
### Webhooki platnosci (front\Controllers\ShopOrderController)
- tPay, Przelewy24, Hotpay — ujednolicone: `set_as_paid` + `update_status`
## Biblioteki (`libraries/`)
- `medoo/medoo.php` — Medoo ORM (`$mdb`)
- `rb.php` — RedBeanPHP ORM (`\R::`, `$pdo`)
- `phpmailer/` — PHPMailer
## Wzorce architektoniczne
### DI zamiast global
```php
// Kontroler wiring (w admin\App lub front\App)
$repo = new \Domain\Example\ExampleRepository($mdb);
$controller = new \admin\Controllers\ExampleController($repo);
```
### Wspolna warstwa Domain
Metody frontendowe (z Redis cache) dodawane do istniejacych repozytoriow — NIE tworzymy osobnych FrontendService/AdminService.
### Klasy View — statyczne, bezstanowe
`front\Views\*` — nie wymagaja DI. Czyste funkcje: dane wchodza, HTML wychodzi.
### Kontrolery — instancyjne z DI
`Controllers\*` — repozytoria wstrzykiwane przez konstruktor.
### Nazewnictwo plikow
- Nowe: `ClassName.php`
- Legacy (pozostalosci): `class.ClassName.php`
- Autoloader obsluguje oba formaty
### Nazewnictwo katalogow
- Nowe: z duzej litery (`Views/`, `Controllers/`)
- Namespace `\admin\` z malej (bo katalog `admin/` jest z malej na serwerze Linux)
- NIE uzywac `\Admin\` (duze A)

View File

@@ -1,131 +0,0 @@
# Testowanie shopPRO
## Szybki start
```bash
# Pelny suite (PowerShell — rekomendowane)
./test.ps1
# Konkretny plik
./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
# Konkretny test
./test.ps1 --filter testGetQuantityReturnsCorrectValue
# Alternatywne
composer test # standard
./test.bat # testdox (czytelna lista)
./test-simple.bat # kropki
./test-debug.bat # debug
./test.sh # Git Bash
```
## Aktualny stan
```text
OK (820 tests, 2277 assertions)
```
Zweryfikowano: 2026-03-19 (ver. 0.342)
## Konfiguracja
- **PHPUnit 9.6** via `phpunit.phar`
- **Bootstrap:** `tests/bootstrap.php`
- **Config:** `phpunit.xml`
## Struktura testow
```
tests/
|-- bootstrap.php
|-- stubs/
| |-- CacheHandler.php (inline w bootstrap)
| |-- Helpers.php (Shared\Helpers\Helpers stub)
| `-- ShopProduct.php (shop\Product stub)
|-- Unit/
| |-- Domain/
| | |-- Article/ArticleRepositoryTest.php
| | |-- Attribute/AttributeRepositoryTest.php
| | |-- Banner/BannerRepositoryTest.php
| | |-- Basket/BasketCalculatorTest.php
| | |-- Cache/CacheRepositoryTest.php
| | |-- Category/CategoryRepositoryTest.php
| | |-- Coupon/CouponRepositoryTest.php
| | |-- CronJob/CronJobTypeTest.php
| | |-- CronJob/CronJobRepositoryTest.php
| | |-- CronJob/CronJobProcessorTest.php
| | |-- Dictionaries/DictionariesRepositoryTest.php
| | |-- Integrations/IntegrationsRepositoryTest.php
| | |-- Languages/LanguagesRepositoryTest.php
| | |-- Layouts/LayoutsRepositoryTest.php
| | |-- Newsletter/NewsletterRepositoryTest.php
| | |-- Pages/PagesRepositoryTest.php
| | |-- PaymentMethod/PaymentMethodRepositoryTest.php
| | |-- Producer/ProducerRepositoryTest.php
| | |-- Product/ProductRepositoryTest.php
| | |-- ProductSet/ProductSetRepositoryTest.php
| | |-- Promotion/PromotionRepositoryTest.php
| | |-- Settings/SettingsRepositoryTest.php
| | |-- ShopStatus/ShopStatusRepositoryTest.php
| | |-- Transport/TransportRepositoryTest.php
| | |-- Update/UpdateRepositoryTest.php
| | `-- User/UserRepositoryTest.php
| |-- Shared/
| | `-- Security/
| | `-- CsrfTokenTest.php
| `-- admin/
| `-- Controllers/
| |-- ArticlesControllerTest.php
| |-- DictionariesControllerTest.php
| |-- IntegrationsControllerTest.php
| |-- ProductArchiveControllerTest.php
| |-- SettingsControllerTest.php
| |-- ShopAttributeControllerTest.php
| |-- ShopCategoryControllerTest.php
| |-- ShopCouponControllerTest.php
| |-- ShopPaymentMethodControllerTest.php
| |-- ShopProducerControllerTest.php
| |-- ShopProductControllerTest.php
| |-- ShopProductSetsControllerTest.php
| |-- ShopPromotionControllerTest.php
| |-- ShopStatusesControllerTest.php
| |-- ShopTransportControllerTest.php
| `-- UsersControllerTest.php
| |-- front/Controllers/
| | `-- ShopBasketControllerTest.php
| `-- api/
| |-- ApiRouterTest.php
| `-- Controllers/
| |-- OrdersApiControllerTest.php
| |-- ProductsApiControllerTest.php
| `-- DictionariesApiControllerTest.php
`-- Integration/ (puste — zarezerwowane)
```
## Dodawanie nowych testow
1. Plik w `tests/Unit/Domain/<Module>/<Class>Test.php`, `tests/Unit/admin/Controllers/<Class>Test.php` lub `tests/Unit/api/Controllers/<Class>Test.php`.
2. Rozszerz `PHPUnit\Framework\TestCase`.
3. Nazwy metod zaczynaj od `test`.
4. Wzorzec AAA: Arrange, Act, Assert.
## Mockowanie Medoo
```php
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('get')->willReturn(42);
$repo = new ProductRepository($mockDb);
$value = $repo->getQuantity(123);
$this->assertEquals(42, $value);
```
## Bootstrap — stuby
`tests/bootstrap.php` rejestruje autoloader i definiuje stuby:
- `Redis`, `RedisConnection` — klasy Redis (aby nie wymagac rozszerzenia)
- `Shared\Cache\CacheHandler` — inline stub z `get()`/`set()`/`exists()`/`delete()`/`deletePattern()`
- `Shared\Helpers\Helpers` — z `tests/stubs/Helpers.php`
- `shop\Product` — z `tests/stubs/ShopProduct.php`

View File

@@ -1,116 +0,0 @@
3. Dodać uwierzytelnienie dwuskładnikowe za pomocą aplikacji.
4. Dodać zarządzanie uprawnieniami na poziomie urzytkownika, na razie uprawnienia do poszczególnych modułów.
naprawić działanie newslettera i zapis do bazy newslettera
program lojalnościowy
proponowane produkty w koszyku
Do zamówień w statusie: realizowane lub oczekuje na wpłatę. Opcja tylko dla zarejestrowanych klientów. https://royal-stone.pl/pl/order1.html
Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu
8. [] Przerobić analitykę Google Analytics i Google ADS
9. [x] Rozważyć integrację SonarQube (statyczna analiza kodu PHP — bugi, security, code smells). Community Edition darmowy, self-hosted. Wymaga serwera + MCP server w Claude Code.
## SonarQube — 0.340 (2026-03-15)
### Bugs
- [ ] [MAJOR] cron.php:192 — Review the data-flow - use of uninitialized value (php:S836)
- [ ] [MAJOR] cron.php:561 — Review the data-flow - use of uninitialized value (php:S836)
- [ ] [MAJOR] cron.php:590 — Review the data-flow - use of uninitialized value (php:S836)
- [ ] [MAJOR] cron.php:643 — Review the data-flow - use of uninitialized value (php:S836)
### Code Smells — CRITICAL
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:35 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:66 — Define a constant instead of duplicating "Accept: application/json" 5 times (php:S1192)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:77 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:159 — Define a constant instead of duplicating "Y-m-d H:i:s" 3 times (php:S1192)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:239 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:309 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:315 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:339 — Define a constant instead of duplicating "Authorization: Bearer " 3 times (php:S1192)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:359 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:400 — Refactor this function to reduce its Cognitive Complexity (php:S3776)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:499 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:502 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/api/Controllers/ProductsApiController.php:396 — Refactor this function to reduce its Cognitive Complexity from 83 to 15 (php:S3776)
- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:408 — Refactor this function to reduce its Cognitive Complexity from 165 to 15 (php:S3776)
- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:520 — Define a constant instead of duplicating "/([0-9]+)$" 3 times (php:S1192)
- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:607 — Define a constant instead of duplicating " Order Deny,Allow" 3 times (php:S1192)
- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:650 — Define a constant instead of duplicating "&lang=" 7 times (php:S1192)
- [ ] [CRITICAL] cron.php:200 — Define a constant instead of duplicating "Y-m-d H:i:s" 7 times (php:S1192)
- [ ] [CRITICAL] cron.php:200 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:203 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:418 — Define a constant instead of duplicating "Authorization: Bearer " 5 times (php:S1192)
- [ ] [CRITICAL] cron.php:419 — Define a constant instead of duplicating "Accept: application/json" 5 times (php:S1192)
- [ ] [CRITICAL] cron.php:526 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:529 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:531 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:533 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:542 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:545 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:547 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:555 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] cron.php:559 — Add curly braces around nested statement(s) (php:S121)
### Code Smells — MAJOR
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:130 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:233 — Method has 5 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:307 — Method has 7 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:400 — Method has 8 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:449 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:481 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:513 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/front/Controllers/ShopBasketController.php:493 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Order/OrderAdminService.php:673 — Method has 4 returns, max 3 allowed (php:S1142)
- [ ] [MAJOR] autoload/Domain/Order/OrderAdminService.php:740 — Method has 4 returns, max 3 allowed (php:S1142)
### Code Smells — MINOR
- [ ] [MINOR] autoload/Domain/Order/OrderRepository.php — Add a new line at the end of file (php:S113)
- [ ] [MINOR] admin/templates/site/unlogged-layout.php — Add a new line at the end of file (php:S113)
- [ ] [MINOR] admin/templates/users/user-2fa.php — Add a new line at the end of file (php:S113)
- [ ] [MINOR] autoload/admin/Controllers/ProductArchiveController.php:196 — Rename function "bulk_delete_permanent" to match camelCase (php:S100)
- [ ] [MINOR] autoload/api/ApiRouter.php:107 — Remove unused "$db" local variable (php:S1481)
- [ ] [MINOR] cron.php:198 — Remove unused "$orderAdminService" local variable (php:S1481)
- [ ] [MINOR] cron.php:524 — Remove unused "$mdb" local variable (php:S1481)
- [ ] [MINOR] cron.php:539 — Remove unused "$mdb" local variable (php:S1481)
## SonarQube — 0.343 (2026-03-19)
### Nowe issues (nie występowały w 0.340)
#### Code Smells — CRITICAL
- [ ] [CRITICAL] autoload/admin/App.php:39 — Cognitive Complexity 37 (max 15) (php:S3776)
- [ ] [CRITICAL] autoload/admin/App.php:50 — Duplicated literal "Location: /admin/" 8 times (php:S1192)
- [ ] [CRITICAL] autoload/front/Controllers/ShopOrderController.php:86 — Cognitive Complexity 22 (max 15) (php:S3776)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:275 — Duplicated literal "Location: /koszyk" 6 times (php:S1192)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:287 — Duplicated literal "Location: /zamowienie/" 3 times (php:S1192)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:495 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/IntegrationsRepository.php:33 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:449 — Cognitive Complexity 22 (max 15) (php:S3776)
- [ ] [CRITICAL] autoload/Domain/Order/OrderRepository.php:635 — Cognitive Complexity 61 (max 15) (php:S3776)
- [ ] [CRITICAL] cron.php:198 — Cognitive Complexity 109 (max 15) (php:S3776)
- [ ] [CRITICAL] cron.php:651 — Cognitive Complexity 18 (max 15) (php:S3776)
#### Code Smells — MAJOR
- [ ] [MAJOR] cron.php:198 — Function has 305 lines (max 150) (php:S138)
- [ ] [MAJOR] cron.php:572 — Unused function parameter "$payload" (php:S1172)
- [ ] [MAJOR] cron.php:572 — 5 returns (max 3) (php:S1142)
- [ ] [MAJOR] cron.php:605 — Unused function parameter "$payload" (php:S1172)
- [ ] [MAJOR] cron.php:605 — 4 returns (max 3) (php:S1142)
- [ ] [MAJOR] cron.php:651 — Unused function parameter "$payload" (php:S1172)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:53 — 4 returns (max 3) (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:93 — 4 returns (max 3) (php:S1142)
- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:105 — Merge if statement with enclosing one (php:S1066)
## SonarQube — 0.344 (2026-03-19)
- [ ] [MINOR] autoload/front/Controllers/ShopBasketController.php:484 — Use empty() to check whether the array is empty (php:S1155)
## SonarQube — 0.345 (2026-03-25)
- [ ] [MAJOR] autoload/front/Controllers/ShopBasketController.php:574 — This method has 6 returns, which is more than the 3 allowed (php:S1142)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:576 — Add curly braces around nested statement(s) (php:S121)
- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:602 — Add curly braces around nested statement(s) (php:S121)

View File

@@ -1,73 +0,0 @@
# Instrukcja tworzenia aktualizacji shopPRO
## Nowy sposób (od v0.301) — automatyczny build script
### Wymagania
- Git z tagami wersji (np. `v0.299`, `v0.300`)
- PowerShell
### Workflow
```
1. Pracuj normalnie: commit, push, commit, push...
2. Gdy wersja gotowa:
→ git tag v0.XXX
→ ./build-update.ps1 -ToTag v0.XXX -ChangelogEntry "NEW - opis"
3. Upload plików z updates/0.XX/ na serwer aktualizacji
```
### Użycie build-update.ps1
```powershell
# Podgląd zmian (bez tworzenia plików)
./build-update.ps1 -ToTag v0.301 -DryRun
# Budowanie paczki (auto-detect poprzedniego tagu)
./build-update.ps1 -ToTag v0.301 -ChangelogEntry "NEW - opis zmiany"
# Z jawnym tagiem źródłowym
./build-update.ps1 -FromTag v0.300 -ToTag v0.301 -ChangelogEntry "NEW - opis"
```
### Co robi skrypt automatycznie
1. `git diff --name-status` między tagami → listy dodanych/zmodyfikowanych/usuniętych plików
2. Filtrowanie przez `.updateignore` (pliki deweloperskie, konfiguracyjne itp.)
3. Kopiowanie plików do temp, tworzenie ZIP
4. SHA256 checksum ZIP-a
5. Generowanie `ver_X.XXX_manifest.json`
6. Generowanie legacy `_sql.txt` i `_files.txt` (okres przejściowy)
7. Aktualizacja `versions.php` i `changelog.php`
8. Cleanup
### Pliki wynikowe
- `updates/0.XX/ver_X.XXX.zip` — paczka z plikami
- `updates/0.XX/ver_X.XXX_manifest.json` — manifest z checksumem, listą zmian, SQL
- `updates/0.XX/ver_X.XXX_sql.txt` — legacy SQL (okres przejściowy)
- `updates/0.XX/ver_X.XXX_files.txt` — legacy lista plików do usunięcia (okres przejściowy)
### Migracje SQL
Pliki SQL umieszczaj w `migrations/{version}.sql` (np. `migrations/0.301.sql`).
Build script automatycznie je wczyta i umieści w manifeście + legacy `_sql.txt`.
### Format manifestu
```json
{
"version": "0.301",
"date": "2026-02-22",
"checksum_zip": "sha256:abc123...",
"files": {
"modified": ["autoload/Domain/Order/OrderRepository.php"],
"added": ["autoload/Domain/Order/NewHelper.php"],
"deleted": ["autoload/shop/OldClass.php"]
},
"directories_deleted": [],
"sql": ["ALTER TABLE pp_x ADD COLUMN y INT DEFAULT 0"],
"changelog": "NEW - opis zmiany"
}
```
### .updateignore
Plik w katalogu głównym projektu, wzorce plików wykluczonych z paczek (jak `.gitignore`).
### INFO
pamiętaj że push czasem zwraca błąd autoryzacji, wtedy spróbuj ponownie

View File

@@ -1292,4 +1292,76 @@ class ProductRepositoryTest extends TestCase
$this->assertFalse($result); $this->assertFalse($result);
} }
public function testSaveCustomFieldsDeletesAllWhenEmpty(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('delete')
->with(
$this->equalTo('pp_shop_products_custom_fields'),
$this->equalTo(['id_product' => 55])
);
$mockDb->expects($this->never())->method('insert');
$mockDb->expects($this->never())->method('update');
$repository = new ProductRepository($mockDb);
$method = new \ReflectionMethod(ProductRepository::class, 'saveCustomFields');
$method->setAccessible(true);
$method->invoke($repository, 55, [], [], []);
}
public function testCustomLabelNamesUsesDbSettingsWithFallback(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('select')
->with(
'pp_settings',
['param', 'value'],
$this->callback(function ($where) {
return isset($where['param']) && is_array($where['param']) && count($where['param']) === 10;
})
)
->willReturn([
['param' => 'custom_label_0_name', 'value' => 'Sezon'],
['param' => 'google_custom_label_2_name', 'value' => 'Kampania'],
['param' => 'custom_label_4_name', 'value' => ''],
]);
$repository = new ProductRepository($mockDb);
$names = $repository->customLabelNames();
$this->assertSame('Sezon', $names['custom_label_0']);
$this->assertSame('Custom label 1', $names['custom_label_1']);
$this->assertSame('Kampania', $names['custom_label_2']);
$this->assertSame('Custom label 3', $names['custom_label_3']);
$this->assertSame('Custom label 4', $names['custom_label_4']);
}
public function testCustomLabelSuggestionsReturnsEmptyForInvalidLabelType(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('query');
$repository = new ProductRepository($mockDb);
$result = $repository->customLabelSuggestions('abc', 'custom_label_10');
$this->assertSame([], $result);
}
public function testSaveCustomLabelReturnsFalseForInvalidLabelType(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->never())->method('update');
$repository = new ProductRepository($mockDb);
$result = $repository->saveCustomLabel(1, 'abc', 'custom_label_10');
$this->assertFalse($result);
}
} }

View File

@@ -55,5 +55,38 @@ class ScontainersControllerTest extends TestCase
$this->assertEquals('Domain\Scontainers\ScontainersRepository', $params[0]->getType()->getName()); $this->assertEquals('Domain\Scontainers\ScontainersRepository', $params[0]->getType()->getName());
$this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName()); $this->assertEquals('Domain\Languages\LanguagesRepository', $params[1]->getType()->getName());
} }
public function testBuildFormViewModelStoresIdInHiddenFieldsForEdit(): void
{
$reflection = new \ReflectionClass(ScontainersController::class);
$method = $reflection->getMethod('buildFormViewModel');
$method->setAccessible(true);
$container = [
'id' => 9,
'status' => 1,
'show_title' => 1,
'languages' => [],
];
$form = $method->invoke($this->controller, $container, [], null);
$this->assertArrayHasKey('id', $form->hiddenFields);
$this->assertSame(9, (int)$form->hiddenFields['id']);
$this->assertSame('/admin/scontainers/save/id=9', $form->action);
}
public function testBuildFormViewModelKeepsCreateFlowWithZeroId(): void
{
$reflection = new \ReflectionClass(ScontainersController::class);
$method = $reflection->getMethod('buildFormViewModel');
$method->setAccessible(true);
$form = $method->invoke($this->controller, [], [], null);
$this->assertArrayHasKey('id', $form->hiddenFields);
$this->assertSame(0, (int)$form->hiddenFields['id']);
$this->assertSame('/admin/scontainers/save/', $form->action);
}
} }

View File

@@ -53,6 +53,7 @@ class ShopProductControllerTest extends TestCase
$this->assertTrue(method_exists($this->controller, 'product_unarchive')); $this->assertTrue(method_exists($this->controller, 'product_unarchive'));
$this->assertTrue(method_exists($this->controller, 'product_delete')); $this->assertTrue(method_exists($this->controller, 'product_delete'));
$this->assertTrue(method_exists($this->controller, 'change_product_status')); $this->assertTrue(method_exists($this->controller, 'change_product_status'));
$this->assertTrue(method_exists($this->controller, 'product_custom_labels_toggle'));
$this->assertTrue(method_exists($this->controller, 'product_change_price_brutto')); $this->assertTrue(method_exists($this->controller, 'product_change_price_brutto'));
$this->assertTrue(method_exists($this->controller, 'product_change_price_brutto_promo')); $this->assertTrue(method_exists($this->controller, 'product_change_price_brutto_promo'));
$this->assertTrue(method_exists($this->controller, 'product_change_custom_label')); $this->assertTrue(method_exists($this->controller, 'product_change_custom_label'));
@@ -128,6 +129,9 @@ class ShopProductControllerTest extends TestCase
'renderCustomFieldsBox', 'renderCustomFieldsBox',
'escapeHtml', 'escapeHtml',
'resolveSavePayload', 'resolveSavePayload',
'customLabelsEnabled',
'isAllowedCustomLabelType',
'renderCustomLabelsEditor',
]; ];
foreach ($expectedPrivate as $method) { foreach ($expectedPrivate as $method) {
@@ -147,4 +151,22 @@ class ShopProductControllerTest extends TestCase
$reflection = new \ReflectionClass($this->controller); $reflection = new \ReflectionClass($this->controller);
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType()); $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
} }
public function testToggleCustomLabelsMethodReturnsVoid(): void
{
$reflection = new \ReflectionClass($this->controller);
$this->assertEquals('void', (string)$reflection->getMethod('product_custom_labels_toggle')->getReturnType());
}
public function testAllowedCustomLabelTypeValidation(): void
{
$reflection = new \ReflectionClass($this->controller);
$method = $reflection->getMethod('isAllowedCustomLabelType');
$method->setAccessible(true);
$this->assertTrue($method->invoke($this->controller, 'custom_label_0'));
$this->assertTrue($method->invoke($this->controller, 'custom_label_4'));
$this->assertFalse($method->invoke($this->controller, 'custom_label_5'));
$this->assertFalse($method->invoke($this->controller, 'invalid'));
}
} }

BIN
updates/0.30/ver_0.345.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,27 @@
{
"changelog": "Checkout flow fix - summaryView redirect, TTL token 30min, logowanie bledow zamowien",
"version": "0.345",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/front/Controllers/ShopBasketController.php",
"templates/shop-basket/basket.php",
"templates/shop-basket/summary-view.php",
"templates/shop-order/order-details.php",
"templates/shop-product/product.php"
]
},
"checksum_zip": "sha256:805e4d80fe75679c937974059594984377a81cad84a1308d9deefb96e0b39319",
"sql": [
],
"date": "2026-03-25",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.346.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"changelog": "Fix usuwania wszystkich dodatkowych pól produktu",
"version": "0.346",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/Product/ProductRepository.php",
"autoload/admin/Controllers/ShopProductController.php"
]
},
"checksum_zip": "sha256:7765b50ff52dc9721e0e2be6d60c2b4ac040d5fc6ea918515b5a5b1a7cf1f580",
"sql": [
],
"date": "2026-04-16",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.347.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,23 @@
{
"changelog": "Naprawa zapisu edycji kontenerow statycznych + testy regresyjne",
"version": "0.347",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/admin/Controllers/ScontainersController.php"
]
},
"checksum_zip": "sha256:25dd6cc528cc5769974a78453630c6cdadcb6903060b68d01dd5a8068ffac7dd",
"sql": [
],
"date": "2026-04-18",
"directories_deleted": [
]
}

View File

@@ -1,3 +1,12 @@
<b>ver. 0.347 - 18.04.2026</b><br />
Naprawa zapisu edycji kontenerow statycznych + testy regresyjne
<hr>
<b>ver. 0.346 - 16.04.2026</b><br />
Fix usuwania wszystkich dodatkowych pól produktu
<hr>
<b>ver. 0.345 - 25.03.2026</b><br />
Checkout flow fix - summaryView redirect, TTL token 30min, logowanie bledow zamowien
<hr>
<b>ver. 0.344 - 19.03.2026</b><br /> <b>ver. 0.344 - 19.03.2026</b><br />
Edycja personalizacji produktu w koszyku Edycja personalizacji produktu w koszyku
<hr> <hr>

View File

@@ -1,5 +1,5 @@
<? <?
$current_ver = 344; $current_ver = 347;
for ($i = 1; $i <= $current_ver; $i++) for ($i = 1; $i <= $current_ver; $i++)
{ {