628 lines
25 KiB
PHP
628 lines
25 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\BillingItemType;
|
|
use App\Models\Client;
|
|
use App\Models\ClientInvoice;
|
|
use App\Services\ClientInvoiceApprovalService;
|
|
use App\Services\ClientInvoicePaymentSyncService;
|
|
use App\Services\ClientLookupService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class ClientInvoiceController extends Controller
|
|
{
|
|
public function __construct(
|
|
private ClientInvoiceApprovalService $approvalService,
|
|
private ClientInvoicePaymentSyncService $paymentSyncService,
|
|
private ClientLookupService $clientLookupService,
|
|
) {
|
|
}
|
|
|
|
public function pending(): JsonResponse
|
|
{
|
|
$invoices = ClientInvoice::query()
|
|
->with('client:id,name,customer_id', 'payments.items.billingItemType')
|
|
->whereNull('approved_at')
|
|
->latest('id')
|
|
->get([
|
|
'id',
|
|
'client_id',
|
|
'pending_sql_acc_code',
|
|
'pending_client_name',
|
|
'invoice_no',
|
|
'total_sem_amount',
|
|
'total_net_amount',
|
|
'created_at',
|
|
]);
|
|
|
|
return response()->json([
|
|
'count' => $invoices->count(),
|
|
'invoices' => $invoices->map(function (ClientInvoice $invoice) {
|
|
$previousPayments = $this->previousPaymentsForInvoice($invoice);
|
|
$invoiceBillingTotals = $this->invoiceBillingTotalsFromPayments($previousPayments);
|
|
|
|
return [
|
|
...$invoice->toArray(),
|
|
'requires_client' => $invoice->client_id === null,
|
|
'previous_payments' => $previousPayments,
|
|
'invoice_billing_totals' => $invoiceBillingTotals,
|
|
];
|
|
}),
|
|
]);
|
|
}
|
|
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$request->merge([
|
|
'invoice_no' => $request->input('invoice_no')
|
|
?? $request->input('invoice.invoice_no')
|
|
?? $request->input('invoice.invoice_number'),
|
|
'client_name' => $request->input('client_name')
|
|
?? $request->input('invoice.company_name')
|
|
?? $request->input('invoice.client_name'),
|
|
]);
|
|
|
|
$validated = $request->validate([
|
|
'client_id' => ['nullable', 'exists:clients,id'],
|
|
'sql_acc_code' => ['required_without:client_id', 'nullable', 'string'],
|
|
'client_name' => ['nullable', 'string'],
|
|
'invoice_no' => ['required', 'string'],
|
|
'linked_invoice_id' => ['nullable', 'integer'],
|
|
'is_credit_card' => ['nullable', 'boolean'],
|
|
'payments' => ['nullable', 'array', 'min:1'],
|
|
'payments.*.payment_no' => ['nullable', 'string'],
|
|
'payments.*.payment_total_amount' => ['required_with:payments', 'numeric', 'min:0'],
|
|
'payments.*.payment_nett_amount' => ['required_with:payments', 'numeric', 'min:0'],
|
|
'payments.*.items' => ['required_with:payments', 'array', 'min:1'],
|
|
'payments.*.items.*.billing_item_types_id' => ['nullable', 'integer'],
|
|
'payments.*.items.*.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.billing_item_type' => ['nullable', 'array'],
|
|
'payments.*.items.*.billing_item_type.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.billingItemType' => ['nullable', 'array'],
|
|
'payments.*.items.*.billingItemType.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.billing_item' => ['nullable', 'array'],
|
|
'payments.*.items.*.billing_item.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.item' => ['nullable', 'array'],
|
|
'payments.*.items.*.item.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.item.item' => ['nullable', 'array'],
|
|
'payments.*.items.*.item.item.sql_acc_code' => ['nullable', 'string'],
|
|
'payments.*.items.*.start_date' => ['nullable', 'date'],
|
|
'payments.*.items.*.end_date' => ['nullable', 'date'],
|
|
'payments.*.items.*.payment_item_amount' => ['required_with:payments', 'numeric', 'min:0'],
|
|
'payments.*.items.*.tax_percentage' => ['required_with:payments', 'numeric', 'min:0', 'max:100'],
|
|
'payments.*.items.*.net_amount' => ['required_with:payments', 'numeric', 'min:0'],
|
|
'payments.*.items.*.withholding_tax' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
|
'payments.*.items.*.final_net_amount' => ['required_with:payments', 'numeric', 'min:0'],
|
|
'payments.*.items.*.spending' => ['nullable', 'numeric', 'min:0'],
|
|
'payments.*.items.*.is_creditcard' => ['nullable', 'boolean'],
|
|
'invoice' => ['nullable', 'array'],
|
|
'payment_no' => ['nullable', 'string'],
|
|
'start_date' => ['nullable', 'date'],
|
|
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
|
|
'amount' => ['nullable', 'numeric', 'min:0'],
|
|
'media_fee' => ['nullable', 'numeric', 'min:0'],
|
|
'media_fee_amount' => ['nullable', 'numeric', 'min:0'],
|
|
'management_fee' => ['nullable', 'numeric', 'min:0'],
|
|
'management_fee_amount' => ['nullable', 'numeric', 'min:0'],
|
|
'tax_percent' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
|
'nett_amount' => ['nullable', 'numeric', 'min:0'],
|
|
'total_sem_amount' => ['nullable', 'numeric', 'min:0'],
|
|
'total_net_amount' => ['nullable', 'numeric', 'min:0'],
|
|
'total_spending' => ['nullable', 'numeric', 'min:0'],
|
|
'sem_invoice_items' => ['nullable', 'array'],
|
|
'sem_items' => ['nullable', 'array'],
|
|
]);
|
|
|
|
$sqlAccCode = $this->clientLookupService->normalizeSqlAccCode($validated['sql_acc_code'] ?? null);
|
|
$client = ! empty($validated['client_id'])
|
|
? Client::find($validated['client_id'])
|
|
: $this->clientLookupService->findBySqlAccCode($sqlAccCode);
|
|
|
|
if (! empty($validated['linked_invoice_id'])) {
|
|
$linkedInvoiceExists = $client !== null && ClientInvoice::query()
|
|
->where('id', $validated['linked_invoice_id'])
|
|
->where('client_id', $client->id)
|
|
->exists();
|
|
|
|
if (! $linkedInvoiceExists) {
|
|
throw ValidationException::withMessages([
|
|
'linked_invoice_id' => 'The linked invoice must belong to the resolved client.',
|
|
]);
|
|
}
|
|
}
|
|
|
|
$payments = $this->paymentsPayload($validated);
|
|
if ($payments === []) {
|
|
throw ValidationException::withMessages([
|
|
'payments' => 'At least one payment item is required.',
|
|
]);
|
|
}
|
|
|
|
$invoice = DB::transaction(function () use ($validated, $client, $sqlAccCode, $payments) {
|
|
$invoice = ClientInvoice::create([
|
|
'client_id' => $client?->id,
|
|
'pending_sql_acc_code' => $client === null ? $sqlAccCode : null,
|
|
'pending_client_name' => $client === null ? ($validated['client_name'] ?? null) : null,
|
|
'invoice_no' => $validated['invoice_no'],
|
|
'linked_invoice_id' => $validated['linked_invoice_id'] ?? null,
|
|
'approved_at' => null,
|
|
'total_sem_amount' => $validated['total_sem_amount'] ?? $this->paymentsGrossTotal($payments),
|
|
'total_net_amount' => $validated['total_net_amount'] ?? $this->paymentsNetTotal($payments),
|
|
]);
|
|
|
|
return $this->paymentSyncService->sync($invoice, $payments);
|
|
});
|
|
|
|
return response()->json([
|
|
'message' => 'Invoice created and marked for approval.',
|
|
'invoice' => $invoice->fresh('client', 'payments.items.billingItemType'),
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function paymentsPayload(array $validated): array
|
|
{
|
|
if (! empty($validated['payments'])) {
|
|
return $this->paymentsPayloadFromExplicitPayments($validated);
|
|
}
|
|
|
|
if (! empty($validated['sem_items']) || ! empty($validated['sem_invoice_items'])) {
|
|
return $this->paymentsPayloadFromSemItems($validated);
|
|
}
|
|
|
|
return $this->paymentsPayloadFromLegacyFees($validated);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function paymentsPayloadFromExplicitPayments(array $validated): array
|
|
{
|
|
$payments = $validated['payments'] ?? [];
|
|
$this->paymentSyncService->ensureDefaultItemTypes();
|
|
|
|
$billingItemTypesBySqlCode = BillingItemType::withTrashed()
|
|
->get()
|
|
->keyBy(fn (BillingItemType $itemType) => strtoupper((string) $itemType->sql_acc_code));
|
|
$billingItemTypeIds = BillingItemType::withTrashed()
|
|
->pluck('id')
|
|
->map(fn ($id) => (int) $id)
|
|
->all();
|
|
$externalItemSqlCodes = $this->externalItemSqlCodes($validated);
|
|
|
|
foreach ($payments as $paymentIndex => $payment) {
|
|
foreach (($payment['items'] ?? []) as $itemIndex => $item) {
|
|
$sqlAccCode = $this->paymentItemSqlAccCode($item);
|
|
$billingItemTypeId = (int) ($item['billing_item_types_id'] ?? 0);
|
|
|
|
if ($sqlAccCode === null && $billingItemTypeId > 0) {
|
|
$sqlAccCode = $externalItemSqlCodes[$billingItemTypeId] ?? null;
|
|
}
|
|
|
|
if ($sqlAccCode !== null) {
|
|
$billingItemType = $billingItemTypesBySqlCode->get($sqlAccCode);
|
|
|
|
if ($billingItemType === null) {
|
|
throw ValidationException::withMessages([
|
|
"payments.{$paymentIndex}.items.{$itemIndex}.sql_acc_code" => 'The item SQL account code must match a billing item type.',
|
|
]);
|
|
}
|
|
|
|
$payments[$paymentIndex]['items'][$itemIndex]['billing_item_types_id'] = $billingItemType->id;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (! in_array($billingItemTypeId, $billingItemTypeIds, true)) {
|
|
throw ValidationException::withMessages([
|
|
"payments.{$paymentIndex}.items.{$itemIndex}.billing_item_types_id" => 'The selected billing item type is invalid, and no matching item SQL account code was provided.',
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $payments;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function externalItemSqlCodes(array $validated): array
|
|
{
|
|
$records = collect($validated['sem_invoice_items'] ?? [])
|
|
->merge(data_get($validated, 'invoice.items', []))
|
|
->merge(collect($validated['sem_items'] ?? [])->pluck('item')->filter());
|
|
$sqlCodes = [];
|
|
|
|
foreach ($records as $record) {
|
|
if (! is_array($record)) {
|
|
continue;
|
|
}
|
|
|
|
$sqlAccCode = $this->paymentItemSqlAccCode($record);
|
|
|
|
if ($sqlAccCode === null) {
|
|
continue;
|
|
}
|
|
|
|
foreach (['item_id', 'id', 'item.id'] as $key) {
|
|
$externalId = data_get($record, $key);
|
|
|
|
if (is_numeric($externalId)) {
|
|
$sqlCodes[(int) $externalId] = $sqlAccCode;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $sqlCodes;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function paymentsPayloadFromSemItems(array $validated): array
|
|
{
|
|
$billingItemTypes = $this->paymentSyncService
|
|
->ensureDefaultItemTypes()
|
|
->keyBy(fn (BillingItemType $itemType) => strtoupper((string) $itemType->sql_acc_code));
|
|
$items = [];
|
|
|
|
foreach (($validated['sem_items'] ?? $validated['sem_invoice_items'] ?? []) as $semItem) {
|
|
if (! is_array($semItem)) {
|
|
continue;
|
|
}
|
|
|
|
$sqlAccCode = $this->paymentItemSqlAccCode($semItem);
|
|
$billingItemType = $sqlAccCode === null ? null : $billingItemTypes->get($sqlAccCode);
|
|
|
|
if ($billingItemType === null) {
|
|
Log::warning('Skipping invoice API SEM item with unknown SQL account code.', [
|
|
'invoice_no' => $validated['invoice_no'] ?? null,
|
|
'sql_acc_code' => $sqlAccCode,
|
|
]);
|
|
|
|
continue;
|
|
}
|
|
|
|
$grossAmount = $this->semItemAmount($semItem);
|
|
if ($grossAmount <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$taxPercentage = $this->semItemTaxPercentage($semItem, $validated);
|
|
$netAmount = $this->semItemNetAmount($semItem, $grossAmount, $taxPercentage);
|
|
$isMediaItem = strtolower((string) $billingItemType->fee_type) === 'media';
|
|
|
|
$items[] = [
|
|
'billing_item_types_id' => $billingItemType->id,
|
|
'start_date' => $isMediaItem ? ($semItem['start_date'] ?? $validated['start_date'] ?? null) : null,
|
|
'end_date' => $isMediaItem ? ($semItem['end_date'] ?? $validated['end_date'] ?? null) : null,
|
|
'payment_item_amount' => $grossAmount,
|
|
'tax_percentage' => $taxPercentage,
|
|
'net_amount' => $netAmount,
|
|
'withholding_tax' => (float) ($semItem['withholding_tax'] ?? 0),
|
|
'final_net_amount' => (float) ($semItem['final_net_amount'] ?? $semItem['nett_amount'] ?? $netAmount),
|
|
'spending' => $isMediaItem ? (float) ($semItem['spending'] ?? 0) : 0,
|
|
'is_creditcard' => $isMediaItem && (bool) ($validated['is_credit_card'] ?? false),
|
|
];
|
|
}
|
|
|
|
if ($items === []) {
|
|
return [];
|
|
}
|
|
|
|
return [[
|
|
'payment_no' => $validated['payment_no'] ?? null,
|
|
'payment_total_amount' => array_sum(array_map(fn (array $item) => (float) $item['payment_item_amount'], $items)),
|
|
'payment_nett_amount' => array_sum(array_map(fn (array $item) => (float) $item['final_net_amount'], $items)),
|
|
'items' => $items,
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function paymentsPayloadFromLegacyFees(array $validated): array
|
|
{
|
|
$billingItemTypes = $this->paymentSyncService->ensureDefaultItemTypes()->keyBy('name');
|
|
$taxPercent = (float) ($validated['tax_percent'] ?? 0);
|
|
$mediaFee = (float) ($validated['media_fee'] ?? 0);
|
|
$mediaFeeAmount = (float) ($validated['media_fee_amount'] ?? $this->netFromGross($mediaFee, $taxPercent));
|
|
$managementFee = (float) ($validated['management_fee'] ?? 0);
|
|
$managementFeeAmount = (float) ($validated['management_fee_amount'] ?? $this->netFromGross($managementFee, $taxPercent));
|
|
$items = [];
|
|
|
|
if ($mediaFee > 0) {
|
|
$items[] = [
|
|
'billing_item_types_id' => $billingItemTypes[ClientInvoicePaymentSyncService::MEDIA_SEARCH_NAME]->id,
|
|
'start_date' => $validated['start_date'] ?? null,
|
|
'end_date' => $validated['end_date'] ?? null,
|
|
'payment_item_amount' => $mediaFee,
|
|
'tax_percentage' => $taxPercent,
|
|
'net_amount' => $mediaFeeAmount,
|
|
'withholding_tax' => 0,
|
|
'final_net_amount' => $validated['nett_amount'] ?? $mediaFeeAmount,
|
|
'spending' => $validated['total_spending'] ?? 0,
|
|
'is_creditcard' => (bool) ($validated['is_credit_card'] ?? false),
|
|
];
|
|
}
|
|
|
|
if ($managementFee > 0) {
|
|
$items[] = [
|
|
'billing_item_types_id' => $billingItemTypes[ClientInvoicePaymentSyncService::MANAGEMENT_SEARCH_NAME]->id,
|
|
'start_date' => null,
|
|
'end_date' => null,
|
|
'payment_item_amount' => $managementFee,
|
|
'tax_percentage' => $taxPercent,
|
|
'net_amount' => $managementFeeAmount,
|
|
'withholding_tax' => 0,
|
|
'final_net_amount' => $managementFeeAmount,
|
|
'spending' => 0,
|
|
'is_creditcard' => false,
|
|
];
|
|
}
|
|
|
|
if ($items === []) {
|
|
return [];
|
|
}
|
|
|
|
return [[
|
|
'payment_no' => $validated['payment_no'] ?? null,
|
|
'payment_total_amount' => $mediaFee + $managementFee,
|
|
'payment_nett_amount' => $mediaFeeAmount + $managementFeeAmount,
|
|
'items' => $items,
|
|
]];
|
|
}
|
|
|
|
private function paymentsGrossTotal(array $payments): float
|
|
{
|
|
return array_sum(array_map(fn (array $payment) => (float) ($payment['payment_total_amount'] ?? 0), $payments));
|
|
}
|
|
|
|
private function paymentsNetTotal(array $payments): float
|
|
{
|
|
return array_sum(array_map(fn (array $payment) => (float) ($payment['payment_nett_amount'] ?? 0), $payments));
|
|
}
|
|
|
|
private function semItemAmount(array $semItem): float
|
|
{
|
|
return (float) (
|
|
data_get($semItem, 'exact_price')
|
|
?? data_get($semItem, 'amount')
|
|
?? data_get($semItem, 'item.estimated_total')
|
|
?? data_get($semItem, 'estimated_total')
|
|
?? 0
|
|
);
|
|
}
|
|
|
|
private function semItemTaxPercentage(array $semItem, array $validated): float
|
|
{
|
|
return (float) (
|
|
data_get($semItem, 'item.sql_acc_tax_percent')
|
|
?? data_get($semItem, 'tax_percent')
|
|
?? $validated['tax_percent']
|
|
?? 0
|
|
);
|
|
}
|
|
|
|
private function semItemNetAmount(array $semItem, float $grossAmount, float $taxPercentage): float
|
|
{
|
|
$explicitNetAmount = data_get($semItem, 'net_amount')
|
|
?? data_get($semItem, 'nett_amount');
|
|
|
|
if (is_numeric($explicitNetAmount)) {
|
|
return (float) $explicitNetAmount;
|
|
}
|
|
|
|
$taxAmount = data_get($semItem, 'exact_tax');
|
|
|
|
if (is_numeric($taxAmount)) {
|
|
return max(0, $grossAmount - (float) $taxAmount);
|
|
}
|
|
|
|
return $this->netFromGross($grossAmount, $taxPercentage);
|
|
}
|
|
|
|
public function approve(ClientInvoice $invoice): JsonResponse
|
|
{
|
|
if ($invoice->client_id === null) {
|
|
return response()->json([
|
|
'message' => 'Create and link the client before approving this invoice.',
|
|
], 409);
|
|
}
|
|
|
|
$invoice = $this->approvalService->approve($invoice);
|
|
|
|
return response()->json([
|
|
'message' => 'Invoice approved successfully.',
|
|
'invoice' => $invoice,
|
|
]);
|
|
}
|
|
|
|
private function previousPaymentsForInvoice(ClientInvoice $invoice): array
|
|
{
|
|
if (empty($invoice->invoice_no)) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$response = Http::acceptJson()
|
|
->withHeaders([
|
|
'X-Secret' => config('app.billing_key'),
|
|
'Accept' => 'application/json',
|
|
])
|
|
->get(config('app.billing_url').'/customer/invoices/getInvoicePaymentDetailsByInvoiceGoogle', [
|
|
'invoice_number' => $invoice->invoice_no,
|
|
]);
|
|
|
|
if (! $response->successful()) {
|
|
Log::warning('Unable to fetch invoice payment details.', [
|
|
'invoice_no' => $invoice->invoice_no,
|
|
'status' => $response->status(),
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
|
|
$records = $this->normalizePaymentRecords($response->json('data'));
|
|
|
|
return collect($records)
|
|
->filter(fn (array $payment) => ($payment['payment_number'] ?? null) !== $invoice->payment_no)
|
|
->map(fn (array $payment) => $this->formatPreviousPayment($payment))
|
|
->values()
|
|
->all();
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Unable to fetch invoice payment details.', [
|
|
'invoice_no' => $invoice->invoice_no,
|
|
'message' => $e->getMessage(),
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function normalizePaymentRecords(mixed $data): array
|
|
{
|
|
if (! is_array($data)) {
|
|
return [];
|
|
}
|
|
|
|
if (array_is_list($data)) {
|
|
return $data;
|
|
}
|
|
|
|
return [$data];
|
|
}
|
|
|
|
private function formatPreviousPayment(array $payment): array
|
|
{
|
|
$items = collect($payment['items'] ?? [])
|
|
->filter(fn (mixed $paymentItem) => is_array($paymentItem))
|
|
->values();
|
|
|
|
$paymentAmount = (float) ($payment['amount'] ?? 0);
|
|
$estimatedItemsTotal = $items->sum(
|
|
fn (array $paymentItem) => $this->paymentItemEstimatedTotal($paymentItem)
|
|
);
|
|
$itemAmounts = $items
|
|
->map(fn (array $paymentItem) => (float) ($paymentItem['amount'] ?? 0))
|
|
->filter(fn (float $amount) => $amount > 0)
|
|
->unique()
|
|
->values();
|
|
$usesRepeatedPaymentAmount = $items->count() > 1
|
|
&& $paymentAmount > 0
|
|
&& $estimatedItemsTotal > 0
|
|
&& $itemAmounts->count() === 1
|
|
&& abs($itemAmounts->first() - $paymentAmount) < 0.01;
|
|
|
|
$totals = $items->reduce(function (array $totals, array $paymentItem) use ($usesRepeatedPaymentAmount, $paymentAmount, $estimatedItemsTotal) {
|
|
$sqlAccCode = $this->paymentItemSqlAccCode($paymentItem);
|
|
$estimatedTotal = $this->paymentItemEstimatedTotal($paymentItem);
|
|
$exact_tax = $this->paymentItemTax($paymentItem);
|
|
$taxPercent = $this->paymentTaxPercent($paymentItem) / 100 + 1;
|
|
$amount = $usesRepeatedPaymentAmount
|
|
? $paymentAmount * ($estimatedTotal / $estimatedItemsTotal)
|
|
: (float) ($paymentItem['amount'] ?? 0);
|
|
|
|
if ($sqlAccCode === 'G03') {
|
|
$totals['media_fee'] += $amount;
|
|
$totals['invoice_media_fee'] += $estimatedTotal;
|
|
}
|
|
|
|
if ($sqlAccCode === 'GOOGLE') {
|
|
$totals['management_fee'] += $amount;
|
|
$totals['invoice_management_fee'] += $estimatedTotal;
|
|
}
|
|
|
|
return $totals;
|
|
}, [
|
|
'media_fee' => 0.0,
|
|
'management_fee' => 0.0,
|
|
'invoice_media_fee' => 0.0,
|
|
'invoice_management_fee' => 0.0,
|
|
]);
|
|
|
|
return [
|
|
'payment_number' => $payment['payment_number'] ?? null,
|
|
'pending_client_name' => $payment['company_name'] ?? null,
|
|
'status' => $payment['status'] ?? null,
|
|
'sql_created_at' => $payment['sql_created_at'] ?? null,
|
|
'amount' => $payment['amount'] ?? null,
|
|
'media_fee' => $totals['media_fee'] / 1.08,
|
|
'management_fee' => $totals['management_fee'] / 1.08,
|
|
'invoice_media_fee' => $totals['invoice_media_fee'] / 1.08,
|
|
'invoice_management_fee' => $totals['invoice_management_fee'] / 1.08,
|
|
'invoice_number' => data_get($payment, 'invoice.invoice_number'),
|
|
];
|
|
}
|
|
|
|
private function paymentItemSqlAccCode(array $paymentItem): ?string
|
|
{
|
|
$sqlAccCode = data_get($paymentItem, 'item.item.sql_acc_code')
|
|
?? data_get($paymentItem, 'item.sql_acc_code')
|
|
?? data_get($paymentItem, 'billing_item_type.sql_acc_code')
|
|
?? data_get($paymentItem, 'billingItemType.sql_acc_code')
|
|
?? data_get($paymentItem, 'billing_item.sql_acc_code')
|
|
?? data_get($paymentItem, 'sql_acc_code');
|
|
|
|
if (! is_string($sqlAccCode)) {
|
|
return null;
|
|
}
|
|
|
|
$sqlAccCode = strtoupper(trim($sqlAccCode));
|
|
|
|
return $sqlAccCode === '' ? null : $sqlAccCode;
|
|
}
|
|
|
|
private function paymentItemEstimatedTotal(array $paymentItem): float
|
|
{
|
|
return (float) (
|
|
data_get($paymentItem, 'item.estimated_total')
|
|
?? data_get($paymentItem, 'estimated_total')
|
|
?? 0
|
|
);
|
|
}
|
|
|
|
private function paymentItemTax(array $paymentItem): float
|
|
{
|
|
return (float) (
|
|
data_get($paymentItem, 'exact_tax')
|
|
?? 0
|
|
);
|
|
}
|
|
|
|
private function paymentTaxPercent(array $paymentItem): float
|
|
{
|
|
return (float) (
|
|
data_get($paymentItem, 'item.sql_acc_tax_percent')
|
|
?? 0
|
|
);
|
|
}
|
|
|
|
private function invoiceBillingTotalsFromPayments(array $payments): array
|
|
{
|
|
$payment = $payments[0] ?? null;
|
|
|
|
return [
|
|
'media_fee' => $payment['invoice_media_fee'] ?? 0,
|
|
'management_fee' => $payment['invoice_management_fee'] ?? 0,
|
|
];
|
|
}
|
|
|
|
private function netFromGross(float $grossAmount, float $taxPercent): float
|
|
{
|
|
return $taxPercent > 0
|
|
? $grossAmount / (1 + ($taxPercent / 100))
|
|
: $grossAmount;
|
|
}
|
|
}
|