Files
portal-api/app/Integrations/Whmcs/Transformers/InvoiceTransformer.php

92 lines
3.4 KiB
PHP

<?php
namespace App\Integrations\Whmcs\Transformers;
use App\Integrations\Whmcs\DTOs\InvoiceData;
use App\Integrations\Whmcs\Extractors\InvoiceExtractor;
use App\Integrations\Whmcs\Support\CustomerIdResolver;
use App\Integrations\Whmcs\Support\OrderIdResolver;
use Illuminate\Support\Carbon;
class InvoiceTransformer
{
public function __construct(
private CustomerIdResolver $customers,
private OrderIdResolver $orders,
private InvoiceExtractor $extractor,
private InvoiceItemTransformer $itemTransformer,
) {
}
public function transform(array|object $row): InvoiceData
{
$row = (array) $row;
$legacyId = (int) $row['id'];
$dueDate = $this->nullableDate($row['duedate'] ?? null);
$status = $this->mapStatus($row['status'], $dueDate);
$total = (int) round((float) $row['total']);
$whmcsOrderId = $this->extractor->orderIdFor($legacyId);
$rawItems = $this->extractor->items($legacyId);
// WHMCS records discounts as negative-amount line items; invoice_items.unit_price
// is unsigned, so fold them into the header's discount_amount instead.
$discountAmount = (int) round(-$rawItems->sum(fn ($item) => min(0, (float) $item->amount)));
$items = $rawItems
->map(fn ($item) => $this->itemTransformer->transform($item))
->filter()
->all();
return new InvoiceData(
legacyId: $legacyId,
invoiceNumber: $row['invoicenum'] ?: "WHMCS-{$legacyId}",
customerId: $this->customers->resolveFor('invoice', $row['invoicenum'] ?: $row['id'], (int) $row['userid']),
orderId: $whmcsOrderId ? $this->orders->resolve($whmcsOrderId) : null,
date: $this->nullableDate($row['date'] ?? null),
subTotal: (int) round((float) $row['subtotal']),
discountAmount: $discountAmount,
tax: (int) round((float) $row['tax'] + (float) $row['tax2']),
total: $total,
// Không có amount_paid thì mọi hoá đơn WHMCS đã thanh toán sẽ hiện "chưa thu"
// và outstanding = total trên toàn bộ dữ liệu import.
amountPaid: $status === 2 ? $total : (int) round((float) ($row['credit'] ?? 0)),
status: $status,
paymentMethod: $row['paymentmethod'] ?: null,
dueDate: $dueDate,
paidAt: $this->nullableDate($row['datepaid'] ?? null),
note: $row['notes'] ?: null,
createdAt: $this->nullableDate($row['created_at'] ?? null),
items: $items,
);
}
/**
* invoices.status: 1=unpaid, 2=paid, 3=cancelled, 4=overdue, 5=refunded.
*/
private function mapStatus(string $status, ?string $dueDate): int
{
return match ($status) {
'Paid' => 2,
'Cancelled' => 3,
'Refunded' => 5,
'Collections' => 4,
default => ($dueDate && Carbon::parse($dueDate)->isPast()) ? 4 : 1,
};
}
/**
* WHMCS has zero-dates ('0000-00-00...') and, in this data, at least one bogus
* year (e.g. '0004-04-22') — both invalid for MySQL strict mode, so nullify them.
*/
private function nullableDate(?string $value): ?string
{
if (! $value || (int) substr($value, 0, 4) < 1970) {
return null;
}
return $value;
}
}