From 541e61bf7ded5e032e62965c7e13f8f69ae3ebc4 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Wed, 13 May 2026 17:38:57 +0200 Subject: [PATCH 1/7] update --- .paul/governance/governance_2026-05-13.jsonl | 56 ++++++ .../_pudelko_komunia_core.cpython-312.pyc | Bin 5888 -> 5888 bytes ...a_statuetka_podziekowanie_rodzice_wzor3.py | 174 ++++++++++++++++++ ...a_statuetka_podziekowanie_rodzice_wzor5.py | 173 +++++++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 .paul/governance/governance_2026-05-13.jsonl create mode 100644 tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor3.py create mode 100644 tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor5.py diff --git a/.paul/governance/governance_2026-05-13.jsonl b/.paul/governance/governance_2026-05-13.jsonl new file mode 100644 index 0000000..bdfa1b7 --- /dev/null +++ b/.paul/governance/governance_2026-05-13.jsonl @@ -0,0 +1,56 @@ +{"ts":"2026-05-13T07:17:55Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T07:18:08Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T07:18:38Z","tool":"Bash","cmd":"sed -n '80,120p' \"tools/generowanie/pudelko_komunia_kielich.py\"\",\"description\":\"Check remaining args of pudelko_komunia_kielich.py\"},\"tool_response\":{\"stdout\":\" parser.add_argument(\"--","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T07:19:38Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop\",\"description\":\"Check if Photoshop is running\"},\"tool_response\":{\"stdout\":\"Binary file (standard input) matches\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T07:19:52Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/pudelko_komunia_kielich.py --klient \"Dorota Mackiewicz\" --imie \"Karoliny\" --data \"17.05.2026\" --","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T07:20:00Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:07:14Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:07:33Z","tool":"Bash","cmd":"python tools/generowanie/email_message_fetcher.py --email \"kingawisniewskafotografia@gmail.com\" --days 60 --json 2>&1\",\"description\":\"Check client emails for Kinga Wiśniewska\"},\"tool_response","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:08:22Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/pudelko_komunia_dziewczynka.py --klient \"Kinga Wiśniewska\" --imie \"Kornelki\" --data \"17.05.2026\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:08:30Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:14:07Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:15:03Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/pudelko_komunia_dziewczynka.py \\\\\\n --klient \"Marzena Socha\" \\\\\\n --imie \"Igi\" \\\\\\n --","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:15:12Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:50:43Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:50:54Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:51:03Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT o.internal_order_","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T09:51:12Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT oi.id, oi.origina","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:10:41Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:11:42Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/pudelko_komunia_kielich.py \\\\\\n --klient \"Paulina Lewandowska\" \\\\\\n --imie \"Antosia\" \\\\\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:11:50Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:44:25Z","tool":"Bash","cmd":"tasklist /FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop || echo \"NIE ZNALEZIONO\"\",\"description\":\"Sprawdź czy Photoshop jest uruchomiony\"},\"tool_response\":{\"stdout\":\"NI","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:45:49Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\_explore_layers.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:48:20Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\_explore_layers.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:51:14Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\_prep_akrylowa_statuetka_rodzice_wzor3.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:52:31Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\akrylowa_statuetka_podziekowanie_rodzice_wzor3.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:55:42Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\_insert_mapping_wzor3.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T10:56:02Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\_insert_mapping_wzor3.sql","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:09:04Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\\\\akrylowa_statuetka_podziekowanie_rodzice_wzor5.py","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:10:17Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\orderPRO\" && git ls-files | head -30\"},\"tool_response\":{\"stdout\":\".claude/agent_credentials.md\\n.claude/commands/wygeneruj-projekty.md\\n.claude/co","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:10:23Z","tool":"Bash","cmd":"cd \"C:\\\\visual studio code\\\\projekty\\\\orderPRO\" && find . -name \"*.php\" -o -name \"*.js\" -o -name \"*.json\" -o -name \"*.env\" | grep -E \"(config|model|controller|migration)\" | head -40\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:10:49Z","tool":"Bash","cmd":"dir \"C:\\\\visual studio code\\\\projekty\\\\orderPRO\\\\tools\\\\generowanie\"\"},\"tool_response\":{\"stdout\":\"__pycache__\\n_assets\\n_batch_log.txt\\n_batch_run.sh\\n_explore_buteleczki_klaudia","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:13:26Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:13:41Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:13:57Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/email_message_fetcher.py --email \"nattalia.skrzypek@gmail.com\" --days 60 --json 2>/dev/null\",\"description\":\"Sprawdź ma","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:14:01Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/email_photo_fetcher.py --email \"nattalia.skrzypek@gmail.com\" --out \"C:/tmp/orderpro_photos\" --days 365 2>/dev/null\",\"de","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:14:57Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop || echo \"BRAK\"\",\"description\":\"Sprawdź czy Photoshop jest uruchomiony\"},\"tool_response\":{\"stdout\":\"Binary file","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:15:12Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/zaproszenie_slub_rodzice_zdjecie_w5.py --klient \"Natalia Skrzypek\" --imiona-pary \"Natalia i Mariusz","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:15:20Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:49:47Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:49:57Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT o.internal_order_","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:50:12Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:50:24Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE project_mappings SET","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:50:42Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:50:52Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT oi.id, oi.origina","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:00Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:13Z","tool":"Bash","cmd":"printf 'SET NAMES utf8mb4;\\\\nUPDATE project_mappings SET product_name_pattern = '\"'\"'Akrylowa statuetka podzi\\\\xc4\\\\x99kowanie dla rodzic\\\\xc3\\\\xb3w ze zdj\\\\xc4\\\\x99ciem - Wz\\\\xc3\\\","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:19Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro < /tmp/fix_mapping.sql 2>/dev/nu","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:26Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT id, product_name_pat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:35Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"SELECT oa.email FROM order_","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:43Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/email_message_fetcher.py --email \"haa1122@wp.pl\" --days 60 --json 2>/dev/null\",\"description\":\"Sprawdź maile od klientk","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:51:48Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && python tools/generowanie/email_photo_fetcher.py --email \"haa1122@wp.pl\" --out \"C:/tmp/orderpro_photos\" --days 365 2>/dev/null\",\"description\":\"","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:52:14Z","tool":"Bash","cmd":"tasklist //FI \"IMAGENAME eq Photoshop.exe\" 2>/dev/null | grep -i photoshop || echo \"BRAK\"\",\"description\":\"Sprawdź Photoshop\"},\"tool_response\":{\"stdout\":\"Binary file (standard input) mat","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:52:35Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/orderPRO\" && PYTHONIOENCODING=utf-8 python tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor3.py --klient \"Hanna Kopp-Ostrowska\" --para \"Hania i P","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T11:52:48Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"UPDATE order_items SET proj","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T12:50:29Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT \\n oi.id AS i","cwd":"/c/visual studio code/projekty/orderPRO"} +{"ts":"2026-05-13T12:50:39Z","tool":"Bash","cmd":"\"C:/xampp/mysql/bin/mysql.exe\" --default-character-set=utf8mb4 -h host700513.hostido.net.pl -P 3306 -u host700513_orderpro -phrDNtUBg9grwZ7syN77S host700513_orderpro -e \"\\nSELECT o.internal_order_","cwd":"/c/visual studio code/projekty/orderPRO"} diff --git a/tools/generowanie/__pycache__/_pudelko_komunia_core.cpython-312.pyc b/tools/generowanie/__pycache__/_pudelko_komunia_core.cpython-312.pyc index 8f195218e9181b9ec16ea6a3a16a75b942560719..d96b32224bc82011c72e638f057468357f9cc188 100644 GIT binary patch delta 19 ZcmZqBYtZ95&CAQh00cf^8@axT0RS!R1it_P delta 19 ZcmZqBYtZ95&CAQh00hN zostaje wstawione, + przeskalowane "cover" do oryginalnych boundsow placeholdera i wycentrowane +- jesli zdjecie nie zostalo podane lub plik nie istnieje -> wstawiamy + zielony prostokat (#00FF00) jako sygnal, ze klient nie przeslal foto + +Wymaga: uruchomiony Adobe Photoshop, photoshop-python-api, Pillow. +""" + +import argparse +import os +import photoshop.api as ps + + +PROJEKT_DIR = os.path.join( + r"d:\pomysloweprezenty.pl\projekty\ślub - podziękowania", + "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 3", +) +SZABLON_PATH = os.path.join( + PROJEKT_DIR, + "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 3.psd", +) +GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") + +ASSETS_DIR = os.path.join(os.path.dirname(__file__), "_assets") +GREEN_PLACEHOLDER_PATH = os.path.join(ASSETS_DIR, "green_placeholder.png") + +GROUP_NAME = "Warstwa 1" +PHOTO_SLOT = "zdjecie" + +NAGLOWEK_DEFAULT = "Kochani Rodzice!" + + +def ensure_green_placeholder(): + if os.path.exists(GREEN_PLACEHOLDER_PATH): + return GREEN_PLACEHOLDER_PATH + os.makedirs(ASSETS_DIR, exist_ok=True) + from PIL import Image + Image.new("RGB", (1000, 1000), (0, 255, 0)).save(GREEN_PLACEHOLDER_PATH) + return GREEN_PLACEHOLDER_PATH + + +def set_text(layer, new_text): + """Zmienia tekst zachowujac srodek bounding boxa.""" + b = [float(x) for x in layer.bounds] + cx, cy = (b[0] + b[2]) / 2, (b[1] + b[3]) / 2 + layer.textItem.contents = new_text + b2 = [float(x) for x in layer.bounds] + cx2, cy2 = (b2[0] + b2[2]) / 2, (b2[1] + b2[3]) / 2 + dx, dy = cx - cx2, cy - cy2 + if dx or dy: + layer.translate(dx, dy) + + +def set_layer_text(container, layer_name, new_text): + if new_text is None: + return + layer = container.artLayers[layer_name] + old = layer.textItem.contents + set_text(layer, new_text) + print(f' {layer_name}: "{old[:50]}" -> "{new_text[:50]}"') + + +def replace_smart_object_contents(app, file_path): + desc = ps.ActionDescriptor() + desc.putPath(app.charIDToTypeID("null"), file_path) + desc.putInteger(app.charIDToTypeID("PgNm"), 1) + app.executeAction(app.stringIDToTypeID("placedLayerReplaceContents"), desc) + + +def replace_photo_cover(app, so_layer, photo_path): + """Podmienia zawartosc Smart Object i skaluje 'cover' do oryginalnych boundsow.""" + target = [float(x) for x in so_layer.bounds] + target_w = target[2] - target[0] + target_h = target[3] - target[1] + target_cx = (target[0] + target[2]) / 2 + target_cy = (target[1] + target[3]) / 2 + + app.activeDocument.activeLayer = so_layer + replace_smart_object_contents(app, photo_path) + + new_b = [float(x) for x in so_layer.bounds] + new_w = new_b[2] - new_b[0] + new_h = new_b[3] - new_b[1] + if new_w <= 0 or new_h <= 0: + return + + scale = max(target_w / new_w, target_h / new_h) * 100.0 + so_layer.resize(scale, scale, ps.AnchorPosition.MiddleCenter) + + cur = [float(x) for x in so_layer.bounds] + cur_cx = (cur[0] + cur[2]) / 2 + cur_cy = (cur[1] + cur[3]) / 2 + so_layer.translate(target_cx - cur_cx, target_cy - cur_cy) + + +def swap_photo_slot(app, container, slot_name, photo_path): + so_layer = container.artLayers[slot_name] + if photo_path and os.path.isfile(photo_path): + print(f" {slot_name}: {photo_path}") + replace_photo_cover(app, so_layer, photo_path) + else: + if photo_path: + print(f" {slot_name}: UWAGA plik nie istnieje ({photo_path}) - zielony placeholder") + else: + print(f" {slot_name}: brak - zielony placeholder") + replace_photo_cover(app, so_layer, ensure_green_placeholder()) + + +def generate(klient, para, data, tresc=None, naglowek=None, zdjecie=None): + os.makedirs(GOTOWE_DIR, exist_ok=True) + output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") + + para_i_data = f"{para}\r{data}" + + app = ps.Application() + doc = app.open(SZABLON_PATH) + print(f"Otwarto szablon: {doc.name}") + + try: + group = doc.layerSets[GROUP_NAME] + + print(" Podmiana tekstow:") + set_layer_text(group, "naglowek", naglowek or NAGLOWEK_DEFAULT) + set_layer_text(group, "para_i_data", para_i_data) + set_layer_text(group, "tresc", tresc) + + print(" Podmiana zdjecia:") + swap_photo_slot(app, group, PHOTO_SLOT, zdjecie) + + psd_opts = ps.PhotoshopSaveOptions() + doc.saveAs(output_path, psd_opts, True) + print(f"Zapisano: {output_path}") + finally: + try: + doc.close(ps.SaveOptions.DoNotSaveChanges) + except Exception: + pass + + print("Gotowe!") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Generator PSD - Akrylowa statuetka podziekowanie dla Rodzicow Wzor 3" + ) + parser.add_argument("--klient", required=True, help="Nazwa pliku wyjsciowego (bez rozszerzenia)") + parser.add_argument("--para", required=True, help="Imiona pary mlodej, np. 'Karolina i Kamil'") + parser.add_argument("--data", required=True, help="Data uroczystosci, np. '06.06.2026'") + parser.add_argument("--naglowek", default=None, help="Naglowek (opcjonalnie, domyslnie 'Kochani Rodzice!')") + parser.add_argument("--tresc", default=None, help="Tresc podziekowania (opcjonalnie, domyslnie z szablonu)") + parser.add_argument("--zdjecie", default=None, help="Sciezka do zdjecia (opcjonalnie; brak = zielony placeholder)") + + args = parser.parse_args() + generate( + klient=args.klient, + para=args.para, + data=args.data, + tresc=args.tresc, + naglowek=args.naglowek, + zdjecie=args.zdjecie, + ) + + +if __name__ == "__main__": + main() diff --git a/tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor5.py b/tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor5.py new file mode 100644 index 0000000..4fc7016 --- /dev/null +++ b/tools/generowanie/akrylowa_statuetka_podziekowanie_rodzice_wzor5.py @@ -0,0 +1,173 @@ +""" +Generator PSD - Akrylowa statuetka podziekowanie dla Rodzicow ze zdjeciem (Wzor 5). + +Struktura PSD: w grupie "Warstwa 1" znajduja sie: +- warstwy tekstowe: naglowek, para, data, tresc +- Smart Object zdjecia: zdjecie + +Podmiana zdjecia: +- jesli --zdjecie wskazuje na istniejacy plik -> zostaje wstawione, + przeskalowane "cover" do oryginalnych boundsow placeholdera i wycentrowane +- jesli zdjecie nie zostalo podane lub plik nie istnieje -> wstawiamy + zielony prostokat (#00FF00) jako sygnal, ze klient nie przeslal foto + +Wymaga: uruchomiony Adobe Photoshop, photoshop-python-api, Pillow. +""" + +import argparse +import os +import photoshop.api as ps + + +PROJEKT_DIR = os.path.join( + r"d:\pomysloweprezenty.pl\projekty\ślub - podziękowania", + "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 5", +) +SZABLON_PATH = os.path.join( + PROJEKT_DIR, + "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 5.psd", +) +GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") + +ASSETS_DIR = os.path.join(os.path.dirname(__file__), "_assets") +GREEN_PLACEHOLDER_PATH = os.path.join(ASSETS_DIR, "green_placeholder.png") + +GROUP_NAME = "Warstwa 1" +PHOTO_SLOT = "zdjecie" + +NAGLOWEK_DEFAULT = "Kochani Rodzice" + + +def ensure_green_placeholder(): + if os.path.exists(GREEN_PLACEHOLDER_PATH): + return GREEN_PLACEHOLDER_PATH + os.makedirs(ASSETS_DIR, exist_ok=True) + from PIL import Image + Image.new("RGB", (1000, 1000), (0, 255, 0)).save(GREEN_PLACEHOLDER_PATH) + return GREEN_PLACEHOLDER_PATH + + +def set_text(layer, new_text): + """Zmienia tekst zachowujac srodek bounding boxa.""" + b = [float(x) for x in layer.bounds] + cx, cy = (b[0] + b[2]) / 2, (b[1] + b[3]) / 2 + layer.textItem.contents = new_text + b2 = [float(x) for x in layer.bounds] + cx2, cy2 = (b2[0] + b2[2]) / 2, (b2[1] + b2[3]) / 2 + dx, dy = cx - cx2, cy - cy2 + if dx or dy: + layer.translate(dx, dy) + + +def set_layer_text(container, layer_name, new_text): + if new_text is None: + return + layer = container.artLayers[layer_name] + old = layer.textItem.contents + set_text(layer, new_text) + print(f' {layer_name}: "{old[:50]}" -> "{new_text[:50]}"') + + +def replace_smart_object_contents(app, file_path): + desc = ps.ActionDescriptor() + desc.putPath(app.charIDToTypeID("null"), file_path) + desc.putInteger(app.charIDToTypeID("PgNm"), 1) + app.executeAction(app.stringIDToTypeID("placedLayerReplaceContents"), desc) + + +def replace_photo_cover(app, so_layer, photo_path): + """Podmienia zawartosc Smart Object i skaluje 'cover' do oryginalnych boundsow.""" + target = [float(x) for x in so_layer.bounds] + target_w = target[2] - target[0] + target_h = target[3] - target[1] + target_cx = (target[0] + target[2]) / 2 + target_cy = (target[1] + target[3]) / 2 + + app.activeDocument.activeLayer = so_layer + replace_smart_object_contents(app, photo_path) + + new_b = [float(x) for x in so_layer.bounds] + new_w = new_b[2] - new_b[0] + new_h = new_b[3] - new_b[1] + if new_w <= 0 or new_h <= 0: + return + + scale = max(target_w / new_w, target_h / new_h) * 100.0 + so_layer.resize(scale, scale, ps.AnchorPosition.MiddleCenter) + + cur = [float(x) for x in so_layer.bounds] + cur_cx = (cur[0] + cur[2]) / 2 + cur_cy = (cur[1] + cur[3]) / 2 + so_layer.translate(target_cx - cur_cx, target_cy - cur_cy) + + +def swap_photo_slot(app, container, slot_name, photo_path): + so_layer = container.artLayers[slot_name] + if photo_path and os.path.isfile(photo_path): + print(f" {slot_name}: {photo_path}") + replace_photo_cover(app, so_layer, photo_path) + else: + if photo_path: + print(f" {slot_name}: UWAGA plik nie istnieje ({photo_path}) - zielony placeholder") + else: + print(f" {slot_name}: brak - zielony placeholder") + replace_photo_cover(app, so_layer, ensure_green_placeholder()) + + +def generate(klient, para, data, tresc=None, naglowek=None, zdjecie=None): + os.makedirs(GOTOWE_DIR, exist_ok=True) + output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") + + app = ps.Application() + doc = app.open(SZABLON_PATH) + print(f"Otwarto szablon: {doc.name}") + + try: + group = doc.layerSets[GROUP_NAME] + + print(" Podmiana tekstow:") + set_layer_text(group, "naglowek", naglowek or NAGLOWEK_DEFAULT) + set_layer_text(group, "para", para) + set_layer_text(group, "data", data) + set_layer_text(group, "tresc", tresc) + + print(" Podmiana zdjecia:") + swap_photo_slot(app, group, PHOTO_SLOT, zdjecie) + + psd_opts = ps.PhotoshopSaveOptions() + doc.saveAs(output_path, psd_opts, True) + print(f"Zapisano: {output_path}") + finally: + try: + doc.close(ps.SaveOptions.DoNotSaveChanges) + except Exception: + pass + + print("Gotowe!") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Generator PSD - Akrylowa statuetka podziekowanie dla Rodzicow ze zdjeciem Wzor 5" + ) + parser.add_argument("--klient", required=True, help="Nazwa pliku wyjsciowego (bez rozszerzenia)") + parser.add_argument("--para", required=True, help="Imiona pary mlodej, np. 'Magdalena i Robert'") + parser.add_argument("--data", required=True, help="Data uroczystosci, np. '27.06.2026'") + parser.add_argument("--naglowek", default=None, help="Naglowek (opcjonalnie, domyslnie 'Kochani Rodzice')") + parser.add_argument("--tresc", default=None, help="Tresc podziekowania (opcjonalnie, domyslnie z szablonu)") + parser.add_argument("--zdjecie", default=None, help="Sciezka do zdjecia (opcjonalnie; brak = zielony placeholder)") + + args = parser.parse_args() + generate( + klient=args.klient, + para=args.para, + data=args.data, + tresc=args.tresc, + naglowek=args.naglowek, + zdjecie=args.zdjecie, + ) + + +if __name__ == "__main__": + main() From 3443879f5928b4046aa98d647cbf2b08cc00f65b Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 14 May 2026 11:43:11 +0200 Subject: [PATCH 2/7] feat(127): polkurier integration foundation Single-instance globalna konfiguracja polkurier.pl jako alternatywa dla Apaczki: szyfrowany login + Token API, karta w hubie integracji i realny test polaczenia przez apimetod=test_auth_api zweryfikowany na zywym koncie operatora (Autoryzacja: 1). ShipmentProviderRegistry netkniety - PolkurierShipmentService/ TrackingService w kolejnych fazach. Kluczowe ustalenia kontraktu API (z SDK polkurier-sdk): - POST https://api.polkurier.pl/ (jeden endpoint) - JSON body: {authorization:{login,token}, apimetod, data} - Sukces: top-level status === 'success' (nie 'ok') - Blad: tresc w polu 'response' envelope'a - Content-Type: application/json (strict, bez charset suffix) Co-Authored-By: Claude Sonnet 4.6 --- .paul/PROJECT.md | 12 +- .paul/ROADMAP.md | 6 +- .paul/STATE.md | 23 +- .paul/changelog/2026-05-14.md | 28 ++ .paul/codebase/architecture.md | 36 +++ .paul/codebase/db_schema.md | 17 +- .paul/codebase/tech_changelog.md | 25 ++ .../127-01-PLAN.md | 292 ++++++++++++++++++ .../127-01-SUMMARY.md | 201 ++++++++++++ ..._create_polkurier_integration_settings.sql | 29 ++ resources/lang/pl.php | 42 +++ resources/views/settings/polkurier.php | 110 +++++++ routes/web.php | 21 +- .../Settings/IntegrationsHubController.php | 28 +- src/Modules/Settings/PolkurierApiClient.php | 197 ++++++++++++ .../PolkurierIntegrationController.php | 123 ++++++++ .../PolkurierIntegrationRepository.php | 218 +++++++++++++ 17 files changed, 1391 insertions(+), 17 deletions(-) create mode 100644 .paul/changelog/2026-05-14.md create mode 100644 .paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md create mode 100644 .paul/phases/127-polkurier-integration-foundation/127-01-SUMMARY.md create mode 100644 database/migrations/20260514_000114_create_polkurier_integration_settings.sql create mode 100644 resources/views/settings/polkurier.php create mode 100644 src/Modules/Settings/PolkurierApiClient.php create mode 100644 src/Modules/Settings/PolkurierIntegrationController.php create mode 100644 src/Modules/Settings/PolkurierIntegrationRepository.php diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index ab4ceee..072b694 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów | Attribute | Value | |-----------|-------| | Version | 3.7.0-dev | -| Status | v3.7 in progress — Phases 113-125 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix) | -| Last Updated | 2026-05-13 (Phase 125 closed) | +| Status | v3.7 in progress — Phases 113-127 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix + invoice GUS mapping + polkurier foundation) | +| Last Updated | 2026-05-14 (Phase 127 closed) | ## Requirements @@ -126,6 +126,7 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [x] Eksport XLSX paragonow w `/accounting`: nowe naglowki (Numer | Data wystawienia | Kwota brutto | Kwota netto | Stawka VAT | Kwota VAT) z osobnym wierszem per stawka VAT; `items_json` snapshot rozszerzony o `vat` per pozycja (z `order_items.tax_rate`, fallback 23.0); legacy fallback `net = brutto/1.23` — Phase 123 - [x] Szablony SMS: CRUD w `/settings/sms-templates` (name + body + is_active), wspolny `SmsVariableResolver` wydzielony z Email\\VariableResolver (placeholdery `{{zamowienie.*|kupujacy.*|adres.*|firma.*|przesylka.*}}`), dropdown "Wybierz szablon" w zakladce SMS na `/orders/{id}` wstawia rozwiniete zmienne do textarea (z `OrderProAlerts.confirm` przy nadpisaniu); stopka SMSPLANET dalej doklejana wylacznie przez `SmsConversationService::buildFinalOutboundBody()` (Phase 122 contract preserved) — Phase 124 - [x] Bugfix detekcji faktury przy imporcie: shopPRO order z `firm_nip` ustawia `invoice_requested=1` (mapper jako jedyne zrodlo heurystyki, sync service propaguje `aggregate['invoice_detected']`); Allegro rozszerzony o `naturalPerson=false`/`address.taxId`/`companyName` (wczesniej tylko `invoice.required`); usunieta legacy kolumna `orders.is_invoice` (Phase 115 dryft) + backfill 7 zamowien — Phase 125 +- [x] Integracja polkurier.pl (fundament): pojedyncza globalna konfiguracja w `/settings/integrations/polkurier`, szyfrowany Token API + login, karta w hubie integracji obok Apaczki i realny test polaczenia przez `apimetod=test_auth_api` zweryfikowany na zywym koncie operatora; `ShipmentProviderRegistry` netkniety — `PolkurierShipmentService/TrackingService` w kolejnych fazach — Phase 127 ### Deferred @@ -237,6 +238,11 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API | Flash dual API: `Flash::push(type, message)` (preferred, typed) + `Flash::set/get(key, value)` (BC) | Phase 120: layouty (app/auth/public) iterują `Flash::all()` automatycznie. Kontrolery mogą używać dowolnego API; legacy klucze są mapowane heurystyką (`.save/.created/.deleted/.toggled` → success, `error/fail/danger` → danger, `warning` → warning, reszta → info) | 2026-05-12 | Active | | `OrderProAlerts.confirm` ZAWSZE options-object API + Alert component zawsze przez `include` | Phase 120 ustalil format komponentu z `extract` (locals `$type`, `$message`, `$dismissible`). Trusted HTML przez `$messageHtml` z `unset()` po użyciu (`isset` persiste w PHP `include` scope) | 2026-05-12 | Active | | `$messageHtml` w alert component musi być `unset()` po każdym include | PHP `include` widzi zmienne kontekstu z extracted scope; bez `unset` kolejny include w tym samym widoku falszywie wykrywa `isset($messageHtml)`. Pattern dla wszystkich miejsc używających `$messageHtml` (4 widoki: invoice_form, receipt-create, printing, statistics/orders) | 2026-05-12 | Active | +| polkurier startuje jako jedna globalna konfiguracja (single-instance, mirror Apaczka/HostedSMS/SMSPLANET) z realnym testowym wywolaniem `apimetod=test_auth_api` | Operator ma jedno konto polkurier; fundament musi byc zweryfikowany na zywym API zanim dolozymy `PolkurierShipmentService` | 2026-05-14 | Active | +| polkurier wymaga `login + token` razem w body `authorization` (nie samego tokena) | Zweryfikowane w SDK polkurier-sdk (`Auth.php`/`Request.php`); kolumna `login VARCHAR(190)` w `polkurier_integration_settings` mimo ze PLAN tego nie wymagal — kontrakt API to dyktuje | 2026-05-14 | Active | +| polkurier API: top-level `status` === `'success'` (nie `'ok'`), tresc bledu w polu `response` envelope'a | `ResponseStatus::SUCCESS = 'success'` z `src/Type/ResponseStatus.php` SDK; bledy rzucane przez `ErrorException($response->get('response'))` w `PolkurierWebService.php`. Pattern dla wszystkich przyszlych metod polkurier API (`createShipment`, `getLabel`, `getStatus`, `cancelOrder`, etc.) | 2026-05-14 | Active | +| polkurier API odrzuca `Content-Type` z parametrem (`application/json; charset=UTF-8`) — wymagany dokladnie `application/json` | Strict equality check po stronie polkuriera; pattern do reuse jezeli inne integracje sa rownie strict | 2026-05-14 | Active | +| polkurier dziala obok Apaczki (nie zamiast) | Decyzja operatora — oba dostawcy zyja niezaleznie, `ShipmentProviderRegistry` rejestruje obu (Apaczka netknieta w Phase 127; polkurier dodany w nastepnej fazie razem z `PolkurierShipmentService`) | 2026-05-14 | Active | ## Success Metrics @@ -268,6 +274,6 @@ Quick Reference: --- *PROJECT.md — Updated when requirements or context change* -*Last updated: 2026-05-12 after Phase 120 (Alert Component Unification) closure; v3.7 milestone in progress* +*Last updated: 2026-05-14 after Phase 127 (polkurier Integration Foundation) closure; v3.7 milestone in progress* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 26e7500..0556d90 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -26,8 +26,12 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt | 124 | SMS Templates | 1/1 | Complete (2026-05-13; migration + manual SMS smoke pending operator) | | 125 | invoice_requested Import Fix (shopPRO+Allegro NIP detection, drop legacy is_invoice column) | 1/1 | Complete (2026-05-13; migration + manual smoke pending operator) | | 126 | Invoice GUS Field Mapping Fix (KRS-based heuristic: JDG → name do "Imię i nazwisko", spółka → "Nazwa firmy") | 1/1 | Complete (2026-05-13; manual smoke pending operator) | +| 127 | polkurier Integration Foundation (single-instance settings + Token API + realny test polaczenia; obok Apaczki) | 1/1 | Complete (2026-05-14; live API verified — `Autoryzacja: 1`) | Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania): +- polkurier ShipmentService (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety) — fundament 127 zweryfikowany +- polkurier TrackingService + `delivery_status_mappings` (provider='polkurier') +- polkurier paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` z SDK polkuriera) - Eksport XLSX listy wystawionych faktur (analogicznie do paragonow) - Idempotencja podwojnego POST do Fakturowni (INVOICE-IDEMP-115) - Event automatyzacji `invoice.created` (jezeli operator chce wysylac faktury mailem) @@ -508,4 +512,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md` --- *Roadmap created: 2026-03-12* -*Last updated: 2026-05-13 - Phase 126 UNIFY closed* +*Last updated: 2026-05-14 - Phase 127 UNIFY closed (live API verified)* diff --git a/.paul/STATE.md b/.paul/STATE.md index f15744e..04edbdb 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,19 +5,19 @@ See: .paul/PROJECT.md (updated 2026-05-07) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** v3.7 Invoices + operational integrations - Phase 126 GUS field mapping fix complete (UNIFY closed). +**Current focus:** v3.7 Invoices + operational integrations - Phase 127 polkurier foundation UNIFY zakonczony, transition (commit + ROADMAP/PROJECT update) pending. ## Current Position Milestone: v3.7 Invoices (Fakturownia integration) - In progress -Phase: 126 of TBD (Invoice GUS field mapping fix) - Complete -Plan: 126-01 complete (SUMMARY.md created) -Status: UNIFY complete, transition pending (commit + ROADMAP update) -Last activity: 2026-05-13 - Phase 126-01 UNIFY zakonczony +Phase: 127 of TBD (polkurier Integration Foundation) - Complete +Plan: 127-01 complete (SUMMARY.md created); live API verified (`Autoryzacja: 1`) +Status: UNIFY complete, transition pending (git commit + decisions in PROJECT.md) +Last activity: 2026-05-14 - Phase 127-01 UNIFY zakonczony, SUMMARY + changelog utworzone Progress: -- Milestone v3.7: [##########] ~99% (Phase 113-126 complete; transition pending) -- Phase 126: [##########] 100% +- Milestone v3.7: [##########] ~99% (Phase 113-127 complete; transition pending) +- Phase 127: [##########] 100% ## Loop Position @@ -29,9 +29,9 @@ PLAN -> APPLY -> UNIFY ## Session Continuity -Last session: 2026-05-13 -Stopped at: Phase 126-01 UNIFY closed; SUMMARY.md created -Next action: Phase transition (commit + ROADMAP update), then manual smoke (AC-1) i wybor kolejnego kandydata v3.7 +Last session: 2026-05-14 +Stopped at: Phase 127-01 UNIFY closed; SUMMARY.md created +Next action: Phase transition (git commit `feat(127): polkurier integration foundation` + Decisions w PROJECT.md), potem wybor kolejnego kandydata v3.7 (np. PolkurierShipmentService albo invoice.created event) Resume file: .paul/ROADMAP.md ## Pending parallel work @@ -65,6 +65,9 @@ Branch: main (5 commits ahead of origin/main) - Phase 125 follow-up: zaimportuj nowe zamowienie shopPRO z `firm_nip` (bez kluczy w 5-elementowej liscie wczesniejszej heurystyki) -> potwierdz ze UI w zakladce Platnosci pokazuje zaznaczony checkbox „Klient prosi o fakture" i widoczny przycisk „Wystaw fakture". - Phase 121 transition note (rozwiązane): commit 360eef1 obejmuje Phase 121 i Phase 122 razem; per-faza hunk-split nie wykonany ze względu na nakładkowe modyfikacje plików. - Phase 126 follow-up: manual smoke `/orders/1090/invoice/create` (JDG, NIP 5170167517) -> "Imie i nazwisko"="JACEK PYZIAK", "Nazwa firmy"="Project-Pro Pyziak Jacek" niezmieniona; drugi smoke na zamowieniu spolki z aktywnym KRS; `curl /api/nip/lookup?nip=5170167517` -> `data.is_jdg=true`. +- Phase 127 follow-up: zaplanowac kolejna faze polkurier — `PolkurierShipmentService` (CreateOrder + GetLabel + OrderValuationV2 + AvailableCarriers mapping + UI mapowan metod dostawy + presety przesylek) — fundament + zweryfikowany kontrakt API gotowy. +- Phase 127 follow-up: drugi krok — `PolkurierTrackingService` + wpisy w `delivery_status_mappings` (provider='polkurier'). +- Phase 127 follow-up: po polkurier shipment service rozwazyc fazy paczkomaty (`InpostParcelMachines` / `PocztexPostOffices` / `Kurier48PostOffices` API juz dostepne w SDK polkuriera). ## Deferred to Next Milestones diff --git a/.paul/changelog/2026-05-14.md b/.paul/changelog/2026-05-14.md new file mode 100644 index 0000000..952f7fb --- /dev/null +++ b/.paul/changelog/2026-05-14.md @@ -0,0 +1,28 @@ +# 2026-05-14 + +## Co zrobiono + +- [Phase 127, Plan 01] polkurier.pl Integration Foundation — pojedyncza globalna konfiguracja brokera kurierskiego polkurier (login + Token API zaszyfrowany przez `IntegrationSecretCipher`), karta w hubie integracji obok Apaczki, realny test polaczenia przez `apimetod=test_auth_api`. Zweryfikowane na zywym koncie operatora (`Autoryzacja: 1`). +- Task 1: Migracja DDL (`polkurier_integration_settings` + seed `integrations.type='polkurier'`) + `PolkurierIntegrationRepository` (single-instance, mirror HostedSMS/SMSPLANET). +- Task 2: `PolkurierApiClient` (POST do `https://api.polkurier.pl/`, JSON envelope `{authorization, apimetod, data}`) + `PolkurierIntegrationController` + widok formularza + 3 routy + i18n. +- Task 3: Wpiecie polkuriera do `IntegrationsHubController` (`buildPolkurierRow()`, kolejnosc: po Apaczce) + aktualizacja `.paul/codebase/{db_schema,architecture,tech_changelog}.md`. +- Auto-fix (live debugging): `status='success'` zamiast `'ok'` (ResponseStatus z SDK), `Content-Type: application/json` bez charset suffix (polkurier strict), parser bledu z pola `response` envelope'a. +- Scope deviation vs PLAN: kolumna `login` dodana (API wymaga login+token), kolumna `environment` pominieta (polkurier nie ma sandbox). + +## Zmienione pliki + +- `database/migrations/20260514_000114_create_polkurier_integration_settings.sql` +- `src/Modules/Settings/PolkurierIntegrationRepository.php` +- `src/Modules/Settings/PolkurierApiClient.php` +- `src/Modules/Settings/PolkurierIntegrationController.php` +- `resources/views/settings/polkurier.php` +- `routes/web.php` +- `src/Modules/Settings/IntegrationsHubController.php` +- `resources/lang/pl.php` +- `.paul/codebase/db_schema.md` +- `.paul/codebase/architecture.md` +- `.paul/codebase/tech_changelog.md` +- `.paul/STATE.md` +- `.paul/ROADMAP.md` +- `.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md` +- `.paul/phases/127-polkurier-integration-foundation/127-01-SUMMARY.md` diff --git a/.paul/codebase/architecture.md b/.paul/codebase/architecture.md index e067b59..c9108e1 100644 --- a/.paul/codebase/architecture.md +++ b/.paul/codebase/architecture.md @@ -342,6 +342,42 @@ tests/ ### IntegrationsHubController - Dodaje wiersz SMSPLANET do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu. +## Phase 127 — polkurier Integration Settings + +### Schema +- Tabela `polkurier_integration_settings` (fixed `id=1`, `integration_id INT UNSIGNED NULL UNIQUE FK -> integrations(id) CASCADE`, `login`, `api_token_encrypted`, `default_label_format`). +- Pojedynczy rekord `integrations.type='polkurier'`, `name='polkurier'`, `base_url='https://api.polkurier.pl/'` (mirror Apaczki/HostedSMS/SMSPLANET). +- Migracja `20260514_000114_create_polkurier_integration_settings.sql` jest idempotentna (`CREATE TABLE IF NOT EXISTS` + `INSERT ... ON DUPLICATE KEY UPDATE`). + +### PolkurierIntegrationRepository (`src/Modules/Settings/PolkurierIntegrationRepository.php`) +- Konstruktor `(PDO $pdo, string $secret)` — buduje wewnetrznie `IntegrationsRepository` i `IntegrationSecretCipher` (mirror `HostedSmsIntegrationRepository`). +- `getSettings()` zwraca `login`, `default_label_format`, flage `has_api_token: bool` (NIE plaintext), `is_active`, `last_test_*`. +- `saveSettings($payload)` waliduje `login` (<=190 znakow) i `default_label_format` (PDF/ZPL/EPL), szyfruje Token API; gdy token w payloadzie jest pusty -> nie nadpisuje istniejacego (BC). +- `getCredentials()` zwraca odszyfrowany `login + api_token + default_label_format` TYLKO gdy `is_active=1` i token istnieje; inaczej `null`. Konsumowane przez `PolkurierApiClient::testConnection()` i przyszly `PolkurierShipmentService`. +- `getIntegrationId()` — single source of truth dla przyszlych modulow. + +### PolkurierApiClient (`src/Modules/Settings/PolkurierApiClient.php`) +- Kontrakt API zweryfikowany na podstawie oficjalnego SDK (https://github.com/Polkurier/polkurier-sdk): jedno publiczne POST endpoint `https://api.polkurier.pl/`, JSON body `{"authorization": {"login", "token"}, "apimetod": "", "data": {...}}`. +- `testConnection(login, apiToken)` wywoluje `apimetod="test_auth_api"` z `data={platform: 'orderPRO', platform_version: '1.0'}`; sukces gdy `status='ok'` lub `response.authorization` niepusta. +- cURL z `SslCertificateResolver::resolve()`, `CURLOPT_TIMEOUT=$timeoutSeconds` (default 15), `CURLOPT_SSL_VERIFYPEER=true`, `Content-Type: application/json`. PHP 8.5 compatible (brak `curl_close()`). +- Stuby `createShipment()`, `getLabel()`, `getStatus()`, `cancelOrder()` rzucaja `RuntimeException("Not implemented in Phase 127")` — dolozone w kolejnych fazach. + +### PolkurierIntegrationController (`src/Modules/Settings/PolkurierIntegrationController.php`) +- Endpointy: `GET /settings/integrations/polkurier`, `POST /settings/integrations/polkurier/save`, `POST /settings/integrations/polkurier/test`. +- `test` realnie wywoluje API polkurier i zapisuje wynik w `integrations.last_test_*` przez `IntegrationsRepository::updateTestResult()`. +- Flash przez legacy `Flash::set('settings_success'|'settings_error'|'polkurier_test', ...)` — spojnie z HostedSMS/SMSPLANET; renderer flash w `layouts/app.php` (Phase 120) obsluguje BC mapping przez `Flash::all()`. +- Widok `resources/views/settings/polkurier.php` uzywa wylacznie komponentu `resources/views/components/alert.php` (Phase 120 contract). + +### IntegrationsHubController (Phase 127 patch) +- Dodany parametr `PolkurierIntegrationRepository $polkurier`. +- Metoda `buildPolkurierRow()` zwraca te same klucze co `buildApaczkaRow()` (`provider`, `instance`, `authorization_status`, `secret_status`, `is_active`, `last_test_at`, `configure_url`). +- Wiersz polkurier wstawiony zaraz po Apaczka (sasiednio — semantycznie oba to brokery kurierskie). + +### Boundaries / co NIE zostalo dotkniete +- `ShipmentProviderRegistry` i `src/Modules/Shipments/*` — `PolkurierShipmentService` nie istnieje w Phase 127. Tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda dodane w kolejnej fazie. +- `apaczka_integration_settings`, `ApaczkaShipmentService`, `ApaczkaTrackingService` — Apaczka netknieta, dziala rownolegle. +- `delivery_status_mappings` — brak nowych wpisow `provider='polkurier'` (dolozone razem z tracking service w kolejnej fazie). + ## Phase 121 - SMSPLANET Conversation + Notifications ### SmsConversationService (`src/Modules/Sms/SmsConversationService.php`) diff --git a/.paul/codebase/db_schema.md b/.paul/codebase/db_schema.md index 6f26f18..bc6fb77 100644 --- a/.paul/codebase/db_schema.md +++ b/.paul/codebase/db_schema.md @@ -1,6 +1,6 @@ # Database Schema -**Updated:** 2026-05-13 | **Total tables:** 61 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci +**Updated:** 2026-05-14 | **Total tables:** 62 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci --- @@ -615,6 +615,21 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row. --- +**polkurier_integration_settings** — polkurier.pl broker account credentials (Phase 127; fixed 1 row) +| Column | Type | Nullable | Notes | +|--------|------|----------|-------| +| `id` | TINYINT UNSIGNED | NO | PK, always 1 | +| `integration_id` | INT UNSIGNED | YES | UNIQUE, FK -> integrations(id) CASCADE | +| `login` | VARCHAR(190) | YES | polkurier login (e-mail z Panel Klienta) — wymagany razem z Token API w body requestu | +| `api_token_encrypted` | TEXT | YES | AES-encrypted Token API via `IntegrationSecretCipher` (z Panel Klienta -> Ustawienia -> Token API) | +| `default_label_format` | VARCHAR(8) | NO | DEFAULT 'PDF' (PDF/ZPL/EPL) — wykorzystany przez przyszly `PolkurierShipmentService` | +| `created_at` | DATETIME | NO | | +| `updated_at` | DATETIME | NO | | + +UNIQUE: `(integration_id)` - one global polkurier settings row. Token zapisywany jest rownolegle do `integrations.api_key_encrypted` (mirror patternu HostedSMS/SMSPLANET). + +--- + **sms_messages** - SMSPLANET inbound/outbound conversation history (Phase 121): stores direction, provider, nullable `order_id BIGINT UNSIGNED`, original and normalized phone endpoints, SMS body, provider `message_id`, status, raw JSON payload, optional `created_by`, and timestamps. Indexes: `(order_id, created_at)`, normalized phone columns, and `(provider, message_id)`. **notifications** - Global notification center (Phase 121): stores type, title, body, target URL, related order/SMS references, `read_at`, and `created_at`. Indexes support unread polling by `(read_at, created_at)` and relation lookups. diff --git a/.paul/codebase/tech_changelog.md b/.paul/codebase/tech_changelog.md index 4c8dfc6..e9e4067 100644 --- a/.paul/codebase/tech_changelog.md +++ b/.paul/codebase/tech_changelog.md @@ -1,5 +1,30 @@ # Technical Changelog +## 2026-05-14 - Phase 127 Plan 01: polkurier Integration Foundation + +**Co zrobiono:** +- Nowa migracja `database/migrations/20260514_000114_create_polkurier_integration_settings.sql` — tabela `polkurier_integration_settings` (fixed `id=1`, FK do `integrations` CASCADE, kolumny: `login VARCHAR(190)`, `api_token_encrypted TEXT`, `default_label_format VARCHAR(8) DEFAULT 'PDF'`) + idempotentny seed rekordu `integrations.type='polkurier'`, `base_url='https://api.polkurier.pl/'`. +- `src/Modules/Settings/PolkurierIntegrationRepository.php` — single-instance repository (mirror `HostedSmsIntegrationRepository`): `getSettings()` zwraca `has_api_token: bool` zamiast plaintext, `saveSettings()` szyfruje Token API przez `IntegrationSecretCipher`, `getCredentials()` gatuje na `is_active=1`, `getIntegrationId()` jako single source of truth. +- `src/Modules/Settings/PolkurierApiClient.php` — POST do `https://api.polkurier.pl/` z JSON body `{authorization:{login,token}, apimetod, data}`. Endpoint test = `apimetod="test_auth_api"`. cURL z `SslCertificateResolver::resolve()`, PHP 8.5 compatible (brak `curl_close()`). Stuby createShipment/getLabel/getStatus/cancelOrder rzucaja RuntimeException — do implementacji w kolejnych fazach. +- `src/Modules/Settings/PolkurierIntegrationController.php` — endpointy `GET /settings/integrations/polkurier`, `POST .../save`, `POST .../test` (CSRF `_token`). `test` zapisuje wynik przez `IntegrationsRepository::updateTestResult()`. +- `resources/views/settings/polkurier.php` — formularz konfiguracji + przycisk realnego testu polaczenia. Wszystkie alerty przez komponent `resources/views/components/alert.php` (Phase 120 contract). +- `src/Modules/Settings/IntegrationsHubController.php` — dodany parametr `PolkurierIntegrationRepository $polkurier` i metoda `buildPolkurierRow()`; wiersz polkurier wstawiony zaraz po Apaczka. +- `routes/web.php` — DI wiring `PolkurierIntegrationRepository` + `PolkurierIntegrationController`, rozszerzony ctor `IntegrationsHubController`, 3 nowe routy `/settings/integrations/polkurier{,/save,/test}`. +- `resources/lang/pl.php` — sekcja `settings.polkurier.*` (title/description/fields/hints/token/status/actions/flash) + `settings.integrations_hub.providers.polkurier`. +- `.paul/codebase/db_schema.md` + `architecture.md` — opisy fazy 127. + +**Dlaczego:** +- Operator dostaje drugiego brokera kurierskiego rownolegle z Apaczka (decyzja w `.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md`, clarifications). +- Single-instance bo polkurier to jedno konto operatora (mirror Apaczka/InPost/HostedSMS/SMSPLANET). +- Faza zamyka tylko warstwe ustawien + realny test (`apimetod=test_auth_api`); tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda w kolejnych fazach — analogicznie do tego jak Phase 116/117 zamknely tylko fundament HostedSMS/SMSPLANET. + +**Deviation vs PLAN:** +- AC-1 wymagal kolumny `environment ENUM('production','sandbox')`. polkurier nie ma srodowiska sandbox (jeden produkcyjny endpoint `https://api.polkurier.pl/`), wiec kolumna `environment` zostala POMINIETA jako YAGNI. +- AC-1/AC-2 wymagaly tylko `api_token_encrypted`. polkurier API wymaga `login + token` razem w `authorization` (zweryfikowane w oficjalnym SDK https://github.com/Polkurier/polkurier-sdk — pliki `Auth.php`/`Request.php`/`Config.php`), wiec dodana kolumna `login VARCHAR(190)` z walidacja serwerowa. +- Plan deklarowal `delegation: auto` (sub-agents). Zadania wykonane inline z powodu swiezo zgromadzonego research o API polkuriera (Config/Auth/Request/Methods z SDK); spawn agentow powtorzylby ten research. Decyzja chroni kontekst i czas. Boundaries i acceptance criteria niezmienione. + +**BREAKING:** brak. + ## 2026-05-13 - Phase 126 Plan 01: Invoice GUS Field Mapping Fix (KRS heuristic) **Co zrobiono:** diff --git a/.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md b/.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md new file mode 100644 index 0000000..5871d7a --- /dev/null +++ b/.paul/phases/127-polkurier-integration-foundation/127-01-PLAN.md @@ -0,0 +1,292 @@ +--- +phase: 127-polkurier-integration-foundation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - database/migrations/20260514_000114_create_polkurier_integration_settings.sql + - src/Modules/Settings/PolkurierIntegrationRepository.php + - src/Modules/Settings/PolkurierApiClient.php + - src/Modules/Settings/PolkurierIntegrationController.php + - src/Modules/Settings/IntegrationsHubController.php + - resources/views/settings/integrations/polkurier.php + - routes/web.php + - .paul/codebase/db_schema.md + - .paul/codebase/architecture.md + - .paul/codebase/tech_changelog.md +autonomous: true +delegation: auto +--- + + +## Goal +Dodac fundament integracji z brokerem kurierskim polkurier.pl jako rownolegla alternatywe dla Apaczki: pojedyncza globalna konfiguracja w `/settings/integrations/polkurier` (szyfrowany Token API), karta w hubie integracji `/settings/integrations`, oraz realny test polaczenia z API polkuriera (wywolanie endpointu zwracajacego dane konta lub liste uslug). + +## Purpose +Operator dostaje druga bramke kurierska oprocz Apaczki. Faza zamyka warstwe ustawien i testu polaczenia — tworzenie przesylek, etykiety, tracking i mapowania metod dostawy beda dolozone w kolejnych fazach (analogicznie do tego jak Phase 116/117 zamknely tylko ustawienia HostedSMS/SMSPLANET przed pelnym SMS-em). Apaczka i jej `ShipmentProviderInterface` zostaja niezmienione — polkurier dziala obok. + +## Output +- Migracja DDL tworzaca `polkurier_integration_settings` (mirror `apaczka_integration_settings`). +- `PolkurierIntegrationRepository` szyfrujacy Token API przez `IntegrationSecretCipher` i zarzadzajacy pojedynczym rekordem `integrations.type='polkurier'` (id rekordu zalezne, nie wpisywane na sztywno). +- `PolkurierApiClient` realnie wywolujacy API polkuriera w trybie test (endpoint zwracajacy dane konta / liste uslug — wybor zgodnie z dokumentacja SDK ze strony bazy wiedzy polkuriera, decyzja na czas implementacji). +- `PolkurierIntegrationController` z routami `GET /settings/integrations/polkurier`, `POST .../save`, `POST .../test`. +- Wiersz "polkurier" w hubie `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu. +- Aktualizacja dokumentow projektowych (db_schema, architecture, tech_changelog). + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Prior Art (wzorzec do skopiowania) +@src/Modules/Settings/ApaczkaIntegrationRepository.php +@src/Modules/Settings/ApaczkaApiClient.php +@src/Modules/Settings/ApaczkaIntegrationController.php +@src/Modules/Settings/HostedSmsIntegrationRepository.php +@src/Modules/Settings/HostedSmsIntegrationController.php +@src/Modules/Settings/SmsplanetIntegrationRepository.php +@src/Modules/Settings/SmsplanetIntegrationController.php +@src/Modules/Settings/IntegrationsHubController.php +@src/Modules/Settings/IntegrationSecretCipher.php +@database/migrations/20260512_000109_consolidate_fakturownia_to_single_instance.sql + +## Codebase docs +@.paul/codebase/architecture.md +@.paul/codebase/db_schema.md + +## Routy i widok wzorcowe +@routes/web.php +@resources/views/settings/integrations/apaczka.php + + +- **Zakres MVP** — Jaki zakres ma pokryc pierwsza faza integracji polkurier.pl? + - Odpowiedz: Tylko fundament + test polaczenia (wzorzec faz 116/117). +- **Model konta** — Pojedyncza globalna instancja czy wieloinstancyjna? + - Odpowiedz: Single instance (jak Apaczka/InPost) — fixed `polkurier_integration_settings.id=1`, jeden rekord `integrations.type='polkurier'`. +- **Apaczka vs polkurier** — Zastapienie czy rownoleglosc? + - Odpowiedz: Obok Apaczki — oba dostawcy dzialaja, Apaczka netknieta, operator wybiera w kolejnych fazach (gdy `PolkurierShipmentService` zostanie dodany). +- **Paczkomaty / punkty odbioru** — Czy w tej fazie? + - Odpowiedz: Poza zakresem tej fazy. Operator potwierdzil "ma dzialac jak Apaczka"; obsluga punktow odbioru pojawi sie razem z `PolkurierShipmentService` w nastepnej fazie (tak jak Apaczka — receiver_point_id w shipment_packages). + + + + + +## AC-1: Migracja tworzy single-instance tabele konfiguracji +```gherkin +Given XAMPP MySQL jest online i migracje sa zacommitowane +When operator uruchamia `php bin/migrate.php` +Then powstaje tabela `polkurier_integration_settings` z kolumnami: `id TINYINT UNSIGNED PK` (always 1), `integration_id INT UNSIGNED UNIQUE NULL FK -> integrations(id) CASCADE`, `api_token_encrypted TEXT NULL`, `environment ENUM('production','sandbox') NOT NULL DEFAULT 'production'`, `default_label_format VARCHAR(8) NOT NULL DEFAULT 'PDF'`, `created_at`, `updated_at` +And ponowne uruchomienie migracji jest no-op (`CREATE TABLE IF NOT EXISTS`) +``` + +## AC-2: Repozytorium szyfruje Token API i zarzadza pojedynczym rekordem integrations +```gherkin +Given migracja AC-1 wykonana +When operator zapisuje konfiguracje przez `PolkurierIntegrationRepository::saveSettings([api_token => 'XYZ', environment => 'production', is_active => 1])` +Then w `integrations` powstaje (lub zostaje zaktualizowany) jeden rekord `type='polkurier'`, `polkurier_integration_settings.id=1` ma uzupelnione `integration_id` i zaszyfrowane `api_token_encrypted` +And `getSettings()` zwraca rekord BEZ surowego tokena, jedynie z flaga `has_api_token: bool` +And `getCredentials()` zwraca odszyfrowany Token API tylko gdy konfiguracja jest kompletna i aktywna (`is_active=1`) +``` + +## AC-3: Endpoint testowy realnie wywoluje API polkuriera i zapisuje wynik +```gherkin +Given operator zapisal poprawny Token API +When operator klika "Testuj polaczenie" w `/settings/integrations/polkurier` +Then `PolkurierApiClient` wykonuje realne wywolanie HTTP do API polkuriera (endpoint nie pisany na sztywno w PLAN, wybierany przez implementatora z dokumentacji `Polkurier_WebService_API_1_1.pdf` — preferowany endpoint typu "lista uslug" / "konto" zwracajacy dane bez tworzenia przesylki) +And `integrations.last_test_status / last_test_http_code / last_test_message / last_test_at` zostaja zaktualizowane przez `IntegrationsRepository::updateTestResult()` +And UI pokazuje czytelny komunikat (sukces albo blad z opisem) — bez surowego dump-u JSON/XML +And brak zaszyfrowanego tokena w logach (`storage/logs/app.log` nie zawiera plaintext tokena nawet w przypadku bledu API) +``` + +## AC-4: Karta polkurier w hubie integracji +```gherkin +Given konfiguracja istnieje (kompletna albo niekompletna) +When operator otwiera `/settings/integrations` +Then widzi wiersz "polkurier" z statusem: skonfigurowana (tak/nie), token zapisany (tak/nie), aktywna (tak/nie), ostatni test (timestamp + ok/error) +And klikniecie wiersza prowadzi do `/settings/integrations/polkurier` +``` + +## AC-5: Apaczka i istniejace ShipmentProviderRegistry netkniete +```gherkin +Given Apaczka jest aktywna i zarejestrowana w `ShipmentProviderRegistry` +When polkurier zostaje dodany do hubu integracji +Then `ShipmentProviderRegistry` NIE rejestruje polkuriera (brak `PolkurierShipmentService` w tej fazie) +And tworzenie przesylek Apaczka dziala bez zmian +And `routes/web.php` nie modyfikuje wiring ApaczkaShipmentService/Tracking +``` + +## AC-6: Dokumentacja zaktualizowana +```gherkin +Given plan ukonczony +When operator otwiera `.paul/codebase/db_schema.md` +Then sekcja Integrations zawiera definicje `polkurier_integration_settings` +And `.paul/codebase/architecture.md` zawiera sekcje "Phase 127 - polkurier Integration Settings" z opisem repository, api client, controller, hub +And `.paul/codebase/tech_changelog.md` zawiera wpis chronologiczny z data i opisem co + dlaczego +``` + + + + + + + Task 1: Migracja DB + PolkurierIntegrationRepository + + database/migrations/20260514_000114_create_polkurier_integration_settings.sql, + src/Modules/Settings/PolkurierIntegrationRepository.php + + + 1) Migracja DDL `CREATE TABLE IF NOT EXISTS polkurier_integration_settings` zgodna z AC-1 (mirror `apaczka_integration_settings` + analogia do `hostedsms_integration_settings`/`smsplanet_integration_settings` w zakresie ENUM environment). InnoDB, utf8mb4_unicode_ci. FK `integration_id REFERENCES integrations(id) ON DELETE CASCADE`. Migracja MUSI byc idempotentna (re-run = no-op zgodnie z decyzja projektu: nigdy `SELECT 1;`, tylko DDL — patrz decision 2026-05-10 w PROJECT.md). + 2) `PolkurierIntegrationRepository final class` w `src/Modules/Settings/`: + - konstruktor `(Medoo $db, IntegrationSecretCipher $cipher, IntegrationsRepository $integrations)`, + - `getSettings(): array` — JOIN `integrations` z `polkurier_integration_settings` po `integration_id`, zwraca `has_api_token: bool` (NIE plaintext), `environment`, `default_label_format`, `is_active`, `last_test_*`, + - `saveSettings(array $payload): void` — upsert: gdy brak rekordu `integrations.type='polkurier'`, twórz przez `IntegrationsRepository::ensureIntegration('polkurier', $name)`; gdy token jest pustym stringiem -> nie nadpisuj (BC z patternem fakturowni); inaczej zaszyfruj przez `IntegrationSecretCipher::encrypt()`. Walidacja serwerowa wymaganych pol. + - `getCredentials(): ?array` — zwraca `['api_token' => string, 'environment' => string, 'integration_id' => int]` TYLKO gdy `is_active=1` AND `api_token_encrypted IS NOT NULL`; inaczej `null`. Uzywany przez `PolkurierApiClient` i przyszly `PolkurierShipmentService`. + - `getIntegrationId(): ?int` — single source of truth dla przyszlych integracji (analogicznie do `FakturowniaIntegrationRepository`). + + Avoid: tworzenia drugiego rekordu `integrations.type='polkurier'` (analogicznie do migracji konsolidacyjnej Fakturowni 20260512_000109 — single instance jest twardym kontraktem); pisania tokenu plaintext do logow; sklejania SQL stringiem (Medoo + prepared statements only). + + + `php bin/migrate.php` -> brak bledow, `SHOW CREATE TABLE polkurier_integration_settings;` -> kolumny zgodne z AC-1. + `php -r "require 'bootstrap/app.php'; $r = $app->make(PolkurierIntegrationRepository::class); var_dump($r->getSettings());"` -> array z `has_api_token=false`. + + AC-1 satisfied (migracja DDL idempotentna), AC-2 satisfied (repozytorium szyfruje token, zwraca has_api_token bool, getCredentials gating na is_active). + + + + Task 2: PolkurierApiClient + Controller + widok formularza + + src/Modules/Settings/PolkurierApiClient.php, + src/Modules/Settings/PolkurierIntegrationController.php, + resources/views/settings/integrations/polkurier.php, + routes/web.php + + + 1) `PolkurierApiClient final class` w `src/Modules/Settings/`: + - cURL klient z `SslCertificateResolver::resolve()` (zgodnie z patternem `FakturowniaApiClient`, `HostedSmsApiClient`, `SmsplanetApiClient`), + - PHP 8.5: ZAKAZ `curl_close()` (decision 2026-05-10 — wycieka `Deprecated` HTML przed JSON response), + - metoda `testConnection(string $apiToken, string $environment): array` zwracajaca `['ok' => bool, 'http_code' => int, 'message' => string]`, + - WYBOR endpointu testowego: implementator MUSI sprawdzic `Polkurier_WebService_API_1_1.pdf` (link w opisie API polkuriera, baza wiedzy artykul "interfejs api do pobrania") i wybrac endpoint nie tworzacy przesylki (preferencja: "lista uslug" / "dane konta" / "wycena testowa"). Wybor udokumentowac w naglowku klasy. + - przyszle stuby `createShipment()`, `downloadLabel()`, `trackShipment()`, `cancelShipment()` — rzucajace `RuntimeException("Not implemented in Phase 127")`; dolozone w kolejnych fazach. + + 2) `PolkurierIntegrationController final class` (mirror `HostedSmsIntegrationController` 1:1): + - routes: `GET /settings/integrations/polkurier` (`edit`), `POST /settings/integrations/polkurier/save` (CSRF `_token`), `POST /settings/integrations/polkurier/test` (CSRF `_token`), + - `test()` -> walidacja zapisanej konfiguracji -> `PolkurierApiClient::testConnection()` -> `IntegrationsRepository::updateTestResult()` -> Flash `Flash::push('success'|'danger', ...)` (Phase 120 pattern, NIE `Flash::set('polkurier.test')`), + - redirect przez `RedirectPathResolver`. + + 3) Widok `resources/views/settings/integrations/polkurier.php`: + - dziedziczy z `layouts/app.php`, + - formularz: token API (password input z placeholderem "Pozostaw puste aby nie zmieniac" gdy `has_api_token=true`), environment (select production/sandbox), domyslny format etykiety (PDF/ZPL/EPL), checkbox `is_active`, przycisk "Zapisz" i osobny "Testuj polaczenie", + - `_token` na obu formularzach (CSRF, nie `_csrf_token` — decision 2026-03-13), + - alerty wylacznie przez komponent `resources/views/components/alert.php` (Phase 120 contract — NIE inline `
`), + - potwierdzenia akcji destrukcyjnych (na przyszlosc) przez `window.OrderProAlerts.confirm({...})` options-object API (decision Phase 114/120), + - bez inline `