feat(admin): IP pool allocation, billing exports, service attachments

- IP pool: sheet import (xlsx/csv) matched by address, bulk allocate/revoke
  with notification email (ip_allocated/ip_revoked), ip_pool_logs history.
- Orders/invoices: Excel export on branded template (BrandedReportWriter),
  shared billing filters, order item service info in list responses.
- Web design quote Excel export; activity log export.
- Service attachments with copy-to-invoice; deployment invoice attachments.
- Customer email attachments stored on disk and attached by path.
- Admin password change endpoint; user.fullname backfill migration;
  orders.user_id backfill from logs.
- Disable deploy workflow until CI/CD is set up.
- Ignore stored customer email and service attachment uploads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-15 19:58:36 +07:00
co-authored by Claude Opus 5.5
parent ea5697cb81
commit 46aa138eec
95 changed files with 4247 additions and 454 deletions
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace App\Exports;
use App\Enums\Invoice\InvoiceStatus;
use App\Exports\Concerns\FormatsBillingCells;
use App\Models\Invoice;
use App\Services\InvoicePaymentService;
use Illuminate\Database\Eloquent\Builder;
final class InvoicesExport implements BrandedReport
{
use FormatsBillingCells;
private const TYPE_LABELS = [1 => 'Khởi tạo', 2 => 'Gia hạn', 3 => 'Nâng cấp'];
public function __construct(private readonly Builder $query) {}
public function title(): string
{
return 'Danh sách hoá đơn';
}
public function query(): Builder
{
return $this->query->latest('id');
}
public function moneyColumns(): array
{
return ['G', 'H', 'I', 'J', 'K', 'L'];
}
public function headings(): array
{
return [
'Mã hoá đơn', 'Mã đơn hàng', 'Loại', 'Khách hàng', 'Email', 'Dịch vụ', 'Tạm tính',
'Giảm giá', 'Thuế', 'Tổng tiền', 'Đã thu', 'Còn nợ', 'Trạng thái', 'Phương thức thanh toán',
'Hạn thanh toán', 'Ngày thanh toán', 'Ngày tạo',
];
}
/**
* @param Invoice $invoice
*/
public function map($invoice): array
{
return [
$invoice->invoice_number,
$invoice->order?->order_num,
self::TYPE_LABELS[(int) $invoice->invoice_type] ?? $invoice->invoice_type,
$invoice->customer?->fullname,
$invoice->customer?->email,
$this->describeServices($invoice->order),
(int) $invoice->sub_total,
(int) $invoice->discount_amount,
(int) $invoice->tax,
(int) $invoice->total,
(int) $invoice->amount_paid,
InvoicePaymentService::outstanding($invoice),
InvoiceStatus::tryFrom((int) $invoice->status)?->label() ?? $invoice->status,
$invoice->payment?->name ?? $this->paymentMethodLabel($invoice->payment_method),
$this->formatDate($invoice->due_date),
$this->formatDate($invoice->paid_at, 'd/m/Y H:i'),
$this->formatDate($invoice->created_at, 'd/m/Y H:i'),
];
}
}