211 lines
6.7 KiB
PHP
211 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Services\ClientInvoicePaymentSyncService;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class Client extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
private ?array $latestPaymentDateRange = null;
|
|
|
|
protected $fillable = ['name', 'customer_id', 'status', 'time_zone', 'industry', 'sql_acc_code'];
|
|
|
|
protected $appends = [
|
|
'latest_remaining_amount',
|
|
'latest_start_date',
|
|
'latest_end_date',
|
|
'latest_remaining_days',
|
|
];
|
|
|
|
public function campaigns()
|
|
{
|
|
return $this->hasMany(GoogleCampaign::class);
|
|
}
|
|
|
|
public function assignations()
|
|
{
|
|
return $this->hasMany(ClientUserAssignation::class);
|
|
}
|
|
|
|
public function invoices(): HasMany
|
|
{
|
|
return $this->hasMany(ClientInvoice::class);
|
|
}
|
|
|
|
public function invoiceAdjustments(): HasMany
|
|
{
|
|
return $this->hasMany(ClientInvoiceAdjustment::class);
|
|
}
|
|
|
|
public function getLatestRemainingAmountAttribute(): string
|
|
{
|
|
return $this->latestRemainingAmount();
|
|
}
|
|
|
|
public function getLatestStartDateAttribute(): ?string
|
|
{
|
|
return $this->latestPaymentDateRange()['start_date'];
|
|
}
|
|
|
|
public function getLatestEndDateAttribute(): ?string
|
|
{
|
|
return $this->latestPaymentDateRange()['end_date'];
|
|
}
|
|
|
|
public function getLatestRemainingDaysAttribute(): ?int
|
|
{
|
|
return $this->latestRemainingDays();
|
|
}
|
|
|
|
public function latestRemainingAmount(): string
|
|
{
|
|
$invoices = $this->relationLoaded('invoices')
|
|
? $this->invoices
|
|
: $this->invoices()->with('payments.items.billingItemType')->get();
|
|
|
|
$invoices->loadMissing('payments.items.billingItemType', 'linkedInvoice.payments.items.billingItemType');
|
|
|
|
$adjustments = $this->relationLoaded('invoiceAdjustments')
|
|
? $this->invoiceAdjustments
|
|
: $this->invoiceAdjustments()->get(['client_id', 'entry_type', 'amount']);
|
|
|
|
$invoiceItems = $invoices->flatMap(fn (ClientInvoice $invoice) => $invoice->payments
|
|
->flatMap(fn ($payment) => $payment->items
|
|
->map(fn (ClientInvoicePaymentItem $item) => [
|
|
'invoice' => $invoice,
|
|
'item' => $item,
|
|
])));
|
|
|
|
$nettAmount = $invoiceItems
|
|
->filter(fn (array $row) => ! $row['item']->is_creditcard && ($row['item']->billingItemType?->nett_contribution ?? false))
|
|
->sum(fn (array $row) => (float) ($row['item']->final_net_amount ?? $row['item']->net_amount ?? 0));
|
|
|
|
$billableSpending = $invoiceItems
|
|
->reject(fn (array $row) => $this->isCreditCardSpend($row['invoice'], $row['item']))
|
|
->sum(fn (array $row) => (float) ($row['item']->spending ?? 0));
|
|
|
|
$adjustmentNet = $adjustments->sum(function (ClientInvoiceAdjustment $adjustment) {
|
|
$amount = (float) ($adjustment->amount ?? 0);
|
|
|
|
return $adjustment->entry_type === ClientInvoiceAdjustment::TYPE_CREDIT
|
|
? $amount
|
|
: -$amount;
|
|
});
|
|
|
|
return number_format($nettAmount + $adjustmentNet - $billableSpending, 2, '.', '');
|
|
}
|
|
|
|
public function latestPaymentDateRange(): array
|
|
{
|
|
if ($this->latestPaymentDateRange !== null) {
|
|
return $this->latestPaymentDateRange;
|
|
}
|
|
|
|
$row = $this->effectiveInvoiceItems()
|
|
->filter(fn (array $row) => $row['item']->start_date !== null || $row['item']->end_date !== null)
|
|
->sortByDesc(function (array $row) {
|
|
$item = $row['item'];
|
|
|
|
return sprintf(
|
|
'%s|%s|%010d',
|
|
$this->dateSortValue($item->start_date),
|
|
$this->dateSortValue($item->end_date),
|
|
$item->id ?? 0,
|
|
);
|
|
})
|
|
->first();
|
|
|
|
if ($row === null) {
|
|
return $this->latestPaymentDateRange = [
|
|
'start_date' => null,
|
|
'end_date' => null,
|
|
];
|
|
}
|
|
|
|
return $this->latestPaymentDateRange = [
|
|
'start_date' => $row['item']->start_date?->toDateString(),
|
|
'end_date' => $row['item']->end_date?->toDateString(),
|
|
];
|
|
}
|
|
|
|
public function latestRemainingDays(): ?int
|
|
{
|
|
$range = $this->latestPaymentDateRange();
|
|
|
|
if ($range['start_date'] === null || $range['end_date'] === null) {
|
|
return null;
|
|
}
|
|
|
|
$startDate = Carbon::parse($range['start_date'])->startOfDay();
|
|
$endDate = Carbon::parse($range['end_date'])->startOfDay();
|
|
|
|
if ($endDate->lessThan($startDate)) {
|
|
return null;
|
|
}
|
|
|
|
$today = today()->startOfDay();
|
|
|
|
if ($today->greaterThan($endDate)) {
|
|
return 0;
|
|
}
|
|
|
|
$fromDate = $today->lessThan($startDate) ? $startDate : $today;
|
|
|
|
return (int) $fromDate->diffInDays($endDate);
|
|
}
|
|
|
|
public function activitiesList(): HasMany
|
|
{
|
|
return $this->hasMany(ClientProjectActivities::class);
|
|
}
|
|
|
|
private function isCreditCardSpend(ClientInvoice $invoice, ClientInvoicePaymentItem $item): bool
|
|
{
|
|
if ($item->is_creditcard) {
|
|
return true;
|
|
}
|
|
|
|
return $invoice->is_credit_card
|
|
&& $item->billingItemType?->name === ClientInvoicePaymentSyncService::MANAGEMENT_SEARCH_NAME
|
|
&& (float) ($item->spending ?? 0) > 0;
|
|
}
|
|
|
|
private function effectiveInvoiceItems(): Collection
|
|
{
|
|
$invoices = $this->relationLoaded('invoices')
|
|
? $this->invoices
|
|
: $this->invoices()->with('payments.items.billingItemType', 'linkedInvoice.payments.items.billingItemType')->get();
|
|
|
|
$invoices->loadMissing('payments.items.billingItemType', 'linkedInvoice.payments.items.billingItemType');
|
|
|
|
return $invoices->flatMap(function (ClientInvoice $invoice) {
|
|
return collect([$invoice, $invoice->linkedInvoice])
|
|
->filter()
|
|
->flatMap(fn (ClientInvoice $effectiveInvoice) => $effectiveInvoice->payments
|
|
->flatMap(fn ($payment) => $payment->items
|
|
->map(fn (ClientInvoicePaymentItem $item) => [
|
|
'invoice' => $effectiveInvoice,
|
|
'item' => $item,
|
|
])));
|
|
});
|
|
}
|
|
|
|
private function dateSortValue(?CarbonInterface $date): string
|
|
{
|
|
return $date?->toDateString() ?? '0000-00-00';
|
|
}
|
|
|
|
public function customers()
|
|
{
|
|
return $this->hasMany(ClientCustomer::class);
|
|
}
|
|
}
|