feat(115): wystawianie faktury z zamowienia (lokalne + delegowane Fakturownia)
Phase 115 complete (vertical slice "zamowienie z NIP -> faktura PDF"):
- Task 1: InvoiceRepository + InvoiceService (dual-flow orchestrator) +
InvoiceIssueException + FakturowniaApiClient::createInvoice + buildPdfUrl
- Task 2: InvoiceController + OrdersController::toggleInvoiceRequested +
OrdersRepository::setInvoiceRequested + auto-import invoice_requested z
Allegro (invoice.required) i shopPRO (5-key flexible parser) + show.php
(toggle w zakladce Platnosci + warunkowy przycisk Wystaw fakture)
- Task 3: Lista wystawionych /settings/accounting/invoices/issued z filtrami
+ invoice_preview + invoice_pdf Dompdf template + hub link
- Task 3b (dodany): NIP lookup przez MF Biala Lista (publiczne API, bez
rejestracji) — MfWhitelistApiClient w src/Core/Http/ + /api/nip/lookup +
przycisk "Pobierz z GUS" w formularzu
Auto-fixes podczas smoke testu (5):
- GUS endpoint Fakturowni nie istnial (HTML 404 -> "json is not valid");
switch na MF Biala Liste
- PHP 8.5 curl_close() deprecation wycieka HTML przed JSON; usuniete z
MfWhitelistApiClient i FakturowniaApiClient (3 miejsca)
- Fakturownia 422 payment_to_kind_days (nieistniejace pole) -> usuniete
- Generic "error" w 422 -> parser plaskuje errors: {pole: [...]} +
error_log z 1000 znakow raw body
- Fakturownia security odrzuca seller_*/department_id jako "create new
department"; usuniete z payloadu (Fakturownia uzywa danych konta)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,9 @@ use App\Core\Security\Csrf;
|
||||
use App\Core\View\Template;
|
||||
use App\Core\Support\Flash;
|
||||
use App\Core\Support\StringHelper;
|
||||
use App\Modules\Accounting\InvoiceRepository;
|
||||
use App\Modules\Accounting\ReceiptRepository;
|
||||
use App\Modules\Settings\InvoiceConfigRepository;
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Modules\Email\EmailSendingService;
|
||||
use App\Modules\Settings\EmailMailboxRepository;
|
||||
@@ -37,7 +39,9 @@ final class OrdersController
|
||||
private readonly string $storagePath = '',
|
||||
private readonly ?\App\Modules\Printing\PrintJobRepository $printJobRepo = null,
|
||||
private readonly ?ShopproIntegrationsRepository $shopproIntegrations = null,
|
||||
private readonly ?AutomationService $automation = null
|
||||
private readonly ?AutomationService $automation = null,
|
||||
private readonly ?InvoiceRepository $invoiceRepo = null,
|
||||
private readonly ?InvoiceConfigRepository $invoiceConfigRepo = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -228,6 +232,17 @@ final class OrdersController
|
||||
$emailTemplates = $this->emailTemplateRepo !== null ? $this->emailTemplateRepo->listActive() : [];
|
||||
$emailMailboxes = $this->emailMailboxRepo !== null ? $this->emailMailboxRepo->listActive() : [];
|
||||
|
||||
$invoices = $this->invoiceRepo !== null
|
||||
? $this->invoiceRepo->findByOrderId($orderId)
|
||||
: [];
|
||||
$activeInvoiceConfigs = [];
|
||||
if ($this->invoiceConfigRepo !== null) {
|
||||
$activeInvoiceConfigs = array_values(array_filter(
|
||||
$this->invoiceConfigRepo->listAll(),
|
||||
static fn (array $c): bool => (int) ($c['is_active'] ?? 0) === 1
|
||||
));
|
||||
}
|
||||
|
||||
$flashSuccess = (string) Flash::get('order.success', '');
|
||||
$flashError = (string) Flash::get('order.error', '');
|
||||
|
||||
@@ -259,6 +274,8 @@ final class OrdersController
|
||||
'flashError' => $flashError,
|
||||
'receipts' => $receipts,
|
||||
'receiptConfigs' => $activeReceiptConfigs,
|
||||
'invoices' => $invoices,
|
||||
'invoiceConfigs' => $activeInvoiceConfigs,
|
||||
'emailTemplates' => $emailTemplates,
|
||||
'emailMailboxes' => $emailMailboxes,
|
||||
'customerRiskInfo' => $customerRiskInfo,
|
||||
@@ -460,6 +477,34 @@ final class OrdersController
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
public function toggleInvoiceRequested(Request $request): Response
|
||||
{
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
if ($orderId <= 0) {
|
||||
return Response::json(['success' => false, 'error' => 'Not found'], 404);
|
||||
}
|
||||
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
return Response::json(['success' => false, 'error' => $this->translator->get('auth.errors.csrf_expired')], 403);
|
||||
}
|
||||
|
||||
$value = (int) $request->input('invoice_requested', 0) === 1;
|
||||
$this->orders->setInvoiceRequested($orderId, $value);
|
||||
|
||||
$user = $this->auth->user();
|
||||
$actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
|
||||
$this->orders->recordActivity(
|
||||
$orderId,
|
||||
'invoice_requested_changed',
|
||||
'Klient prosi o fakture: ' . ($value ? 'tak' : 'nie'),
|
||||
['invoice_requested' => $value ? 1 : 0],
|
||||
'user',
|
||||
$actorName !== '' ? $actorName : null
|
||||
);
|
||||
|
||||
return Response::json(['success' => true, 'invoice_requested' => $value ? 1 : 0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
|
||||
Reference in New Issue
Block a user