Files
2026-09-09 16:56:22 +07:00

73 lines
2.2 KiB
PHP

<?php
namespace App\Integrations\Whmcs\Transformers;
use App\Integrations\Whmcs\DTOs\InvoiceItemData;
use App\Integrations\Whmcs\Support\ProductIdResolver;
use Illuminate\Support\Facades\DB;
class InvoiceItemTransformer
{
public function __construct(private ProductIdResolver $products)
{
}
/**
* Returns null for WHMCS tax/manual lines (blank `type`) — those are already
* reflected in invoices.tax on the header, keeping them would double-count.
* Also returns null for discount lines (negative amount) — invoice_items.unit_price
* is unsigned; those are rolled into invoices.discount_amount instead, see InvoiceTransformer.
*/
public function transform(array|object $row): ?InvoiceItemData
{
$row = (array) $row;
$whmcsType = trim((string) ($row['type'] ?? ''));
if ($whmcsType === '') {
return null;
}
$amount = (int) round((float) $row['amount']);
if ($amount < 0) {
return null;
}
return new InvoiceItemData(
legacyId: (int) $row['id'],
type: $this->mapType($whmcsType),
description: $row['description'] ? substr($row['description'], 0, 500) : null,
unitPrice: $amount,
totalPrice: $amount,
productId: $this->resolveProductId($whmcsType, (int) $row['relid']),
);
}
/**
* Only 'Hosting' line items carry a resolvable product: relid -> tblhosting.id -> packageid -> products.legacy_id.
* Domain/Addon relids point at tbldomains/tblhostingaddons, which don't map onto the products table.
*/
private function resolveProductId(string $whmcsType, int $relid): ?int
{
if ($whmcsType !== 'Hosting' || $relid <= 0) {
return null;
}
$packageId = DB::connection('whmcs')->table('tblhosting')->where('id', $relid)->value('packageid');
return $packageId ? $this->products->resolve((int) $packageId) : null;
}
private function mapType(string $whmcsType): string
{
if ($whmcsType === 'Addon') {
return 'addon';
}
if (str_contains($whmcsType, 'Upgrade')) {
return 'upgrade';
}
return 'service';
}
}