---
phase: 119-reimport-total-paid-protection
plan: 01
subsystem: orders
tags: [reimport, idempotency, payment, refund, delta-update]

requires:
  - phase: 112-reimport-data-protection
    provides: delta-only updateOrderDelta + identical-payload guard
  - phase: 111-payment-transition-event
    provides: paymentTransition detection (0/1 -> 2)

provides:
  - total_paid protection when payment_status unchanged
  - is_canceled_by_buyer cancel-propagation override at field level
  - dynamic SQL SET builder in updateOrderDelta

affects: [orders re-import, manual payment corrections, refund flow, accounting]

tech-stack:
  added: []
  patterns:
    - "Dynamic SET fragment builder for partial column updates (PHP array + implode)"
    - "Boolean-flag-driven conditional column inclusion in UPDATE"

key-files:
  created:
    - tests/Unit/OrderImportRepositoryTest.php
  modified:
    - src/Modules/Orders/OrderImportRepository.php
    - .paul/codebase/architecture.md
    - .paul/codebase/tech_changelog.md

key-decisions:
  - "total_paid chronione gdy oldPaymentStatus === newPaymentStatus (any value, not only 2->2)"
  - "is_canceled_by_buyer chronione analogicznie, ale cancelledBySource override wymusza wpis"
  - "Brak backfillu - fix forward (order #976 poprawiony recznie)"
  - "Test flat path tests/Unit/ zamiast nested - match konwencji"

patterns-established:
  - "Dynamic SET builder: $setFragments[] + implode(', ', ...) zamiast statycznego SQL z NULL-bindem (NULL nadpisuje, NIE pomija)"
  - "Conditional column inclusion poprzez flagi boolean wyliczane w upsertOrderAggregate i przekazywane do delta method"

duration: ~20min
started: 2026-05-12T14:00:00Z
completed: 2026-05-12T14:20:00Z
---

# Phase 119 Plan 01: Re-import total_paid Protection Summary

**`updateOrderDelta()` chroni `total_paid` (i pomocniczo `is_canceled_by_buyer`) przed nadpisaniem z payloadu zrodla gdy `payment_status` nie ulega zmianie - reczne korekty operatora (zwroty czesciowe) przezywaja re-import.**

## Performance

| Metric | Value |
|--------|-------|
| Duration | ~20 min |
| Started | 2026-05-12T14:00:00Z |
| Completed | 2026-05-12T14:20:00Z |
| Tasks | 3 completed |
| Files modified | 4 (1 source, 1 test, 2 docs) |

## Acceptance Criteria Results

| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: total_paid preserved when payment_status unchanged | Pass (code + test) | SQL builder pomija `total_paid` gdy `$paymentStatusUnchanged=true`. Test napisany. |
| AC-2: total_paid updated on payment transition | Pass (code + test) | Gdy `payment_status` rozni sie (np. 0->2), fragment SET dolaczany. Test napisany. |
| AC-3: is_canceled_by_buyer propagated on source cancel | Pass (code + test) | Drugi warunek (`!$paymentStatusUnchanged || $cancelledBySource`) wymusza wpis flagi przy cancel ze zrodla nawet przy stabilnym `payment_status`. |
| AC-4: Test coverage | Partial | 3 testy napisane, syntax-checked (`php -l`). PHPUnit run odroczony - `vendor/` nieobecny, composer poza PATH. |

## Accomplishments

- Order #976 (i przyszle przypadki recznych korekt `total_paid`) chronione przed nadpisaniem przy kolejnym sync shoppro.
- `updateOrderDelta()` przeszedl ze static SQL na czytelny dynamic builder - latwo dodawac kolejne warunkowe kolumny w przyszlosci.
- Cancel propagation ze zrodla pozostaje silnym kontraktem - flaga `is_canceled_by_buyer` zawsze trafia do bazy gdy zrodlo anuluje, niezaleznie od ruchu `payment_status`.

## Task Commits

Atomic commits TBD (commit po UNIFY zgodnie z transition workflow).

| Task | Type | Description |
|------|------|-------------|
| Task 1: Dynamic SET builder | feat | `updateOrderDelta()` warunkowe `total_paid` + `is_canceled_by_buyer` |
| Task 2: Test PHPUnit | test | 3 testy via Reflection + sqlite in-memory |
| Task 3: Docs update | docs | `architecture.md` + `tech_changelog.md` Phase 119-01 |

## Files Created/Modified

| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Orders/OrderImportRepository.php` | Modified | `upsertOrderAggregate` wylicza `$paymentStatusUnchanged`; `updateOrderDelta()` dynamic SET builder z warunkowym `total_paid` i `is_canceled_by_buyer` |
| `tests/Unit/OrderImportRepositoryTest.php` | Created | 3 testy PHPUnit pokrywajace AC-1/2/3 (sqlite + ReflectionMethod) |
| `.paul/codebase/architecture.md` | Modified | Sekcja Re-import rozszerzona o akapit Phase 119-01 |
| `.paul/codebase/tech_changelog.md` | Modified | Nowy wpis 2026-05-12 Phase 119 Plan 01 |

## Decisions Made

| Decision | Rationale | Impact |
|----------|-----------|--------|
| `paymentStatusUnchanged` definiowany jako `oldPaymentStatus === newPaymentStatus` (any value) | Konserwatywne ograniczenie do `2 == 2` byloby surprising; "any value" jest spojne z intencja "nie ruszaj gdy nic sie nie zmienilo" | Operator moze recznie ustawic `total_paid` przy dowolnym `payment_status` (np. czesciowa platnosc 1) i recznie ja korygowac |
| `is_canceled_by_buyer` chronione **ale** override przez `cancelledBySource` | Bez override blokowalibysmy propagacje anulowania ze zrodla - to bylby regres wzgledem Phase 112-01 | Cancel ze zrodla nadal flaguje zamowienie i ustawia `status_code='anulowane'` |
| Brak CLI backfillu | Order #976 poprawiony recznie; inne przypadki ad-hoc; plan maly i skupiony | Mozliwe stare zamowienia z "uszkodzonym" `total_paid` - dopuszczalne ryzyko |
| Test flat path (`tests/Unit/`) zamiast nested (`tests/Unit/Modules/Orders/`) | Match istniejacej konwencji projektu (wszystkie testy plasko) | Spojnosc struktury testow |

## Deviations from Plan

### Summary

| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 0 | - |
| Scope additions | 0 | - |
| Deferred | 1 | PHPUnit run odroczony do srodowiska z `composer install` |

**Total impact:** Brak scope creep. Jedna deviation srodowiskowa (vendor/ gitignored).

### Deferred Items

- **PHPUnit run** - `vendor/` nieobecne, composer poza PATH. Test napisany i syntax-checked (`php -l`). Manual command po `composer install`: `vendor/bin/phpunit tests/Unit/OrderImportRepositoryTest.php`. Analogiczna sytuacja do gapow w Phase 116/117 (sonar-scanner).

## Issues Encountered

| Issue | Resolution |
|-------|------------|
| PHPUnit nieuruchamialny (brak vendor/) | Code + test napisany, syntax check przez `php -l`. Run odroczony. Pattern z Phase 117 (manual verification deferred). |
| Plan zakladal nested test path | Wybrano flat path - match konwencji projektu (wszystkie istniejace testy w `tests/Unit/` plasko, namespace `Tests\Unit`) |

## Next Phase Readiness

**Ready:**
- `OrderImportRepository::updateOrderDelta()` chroni reczne korekty `total_paid` przy kolejnych syncach.
- Pattern dynamic SET buildera dostepny dla przyszlych warunkowych UPDATE-ow.

**Concerns:**
- Brak realnego potwierdzenia testem zielonym (vendor unavailable). Operator powinien uruchomic `composer install` + phpunit przed produkcyjnym push.
- Brak manualnego smoke testu na zywym shoppro - re-sync order #976 powinien potwierdzic, ze `total_paid=91.00` przetrwa.

**Blockers:** None.

---
*Phase: 119-reimport-total-paid-protection, Plan: 01*
*Completed: 2026-05-12*
