Files
InvoiceShelf/app/Domains/Sales/Application/RecurringInvoiceService.php
T
Darko Gjorgjijoski 5ef7804e60 refactor: adopt modular domain architecture (#747)
* refactor: stabilize model identities for domain migration

* refactor: extract module platform context

* refactor: assign models to domain contexts

* refactor: extract ai platform context

* refactor: extract storage platform context

* refactor: extract mail platform context

* refactor: extract pdf platform context

* refactor: extract operations platform context

* refactor: move installation into operations platform

* refactor: extract money domain context

* refactor: extract taxation domain context

* refactor: extract catalog domain context

* refactor: extract metadata domain context

* refactor: extract reporting domain context

* refactor: extract purchases domain context

* refactor: extract receivables domain context

* refactor: extract accounts domain context

* refactor: complete reporting statement boundary

* refactor: extract contacts domain context

* refactor: extract sales domain context

* refactor: remove legacy application layers

* fix: migrate legacy bouncer role identities
2026-08-05 17:40:03 +02:00

254 lines
9.8 KiB
PHP

<?php
namespace App\Domains\Sales\Application;
use App\Domains\Accounts\Models\Company;
use App\Domains\Accounts\Models\CompanySetting;
use App\Domains\Contacts\Models\Customer;
use App\Domains\Metadata\Contracts\CustomFieldValueWriter;
use App\Domains\Sales\Contracts\DocumentExchangeRateRecorder;
use App\Domains\Sales\Models\Invoice;
use App\Domains\Sales\Models\RecurringInvoice;
use App\Facades\Hashids;
use App\Support\Hashids\HashidConnection;
use Carbon\Carbon;
use Illuminate\Support\Collection;
class RecurringInvoiceService
{
public function __construct(
private readonly DocumentItemService $documentItemService,
private readonly InvoiceService $invoiceService,
private readonly CustomFieldValueWriter $customFieldValueWriter,
private readonly DocumentExchangeRateRecorder $exchangeRateRecorder,
) {}
/**
* @param array<string, mixed> $attributes
* @param array<int, array<string, mixed>> $items
* @param array<int, array<string, mixed>>|null $taxes
*/
public function create(
array $attributes,
array $items,
?array $taxes = null,
?iterable $customFields = null,
): RecurringInvoice {
$recurringInvoice = RecurringInvoice::create($attributes);
$companyCurrency = CompanySetting::getSetting('currency', $recurringInvoice->company_id);
if ((string) $recurringInvoice['currency_id'] !== $companyCurrency) {
$this->exchangeRateRecorder->record($recurringInvoice);
}
$this->createItems($recurringInvoice, $items);
if ($taxes) {
$this->createTaxes($recurringInvoice, $taxes);
}
if ($customFields) {
$this->customFieldValueWriter->attach($recurringInvoice, $customFields);
}
return $recurringInvoice;
}
public function update(
RecurringInvoice $recurringInvoice,
array $attributes,
array $items,
?array $taxes = null,
?iterable $customFields = null,
): RecurringInvoice {
$recurringInvoice->update($attributes);
$companyCurrency = CompanySetting::getSetting('currency', $recurringInvoice->company_id);
if ((string) $attributes['currency_id'] !== $companyCurrency) {
$this->exchangeRateRecorder->record($recurringInvoice);
}
$recurringInvoice->items()->delete();
$this->createItems($recurringInvoice, $items);
$recurringInvoice->taxes()->delete();
if ($taxes) {
$this->createTaxes($recurringInvoice, $taxes);
}
if ($customFields) {
$this->customFieldValueWriter->update($recurringInvoice, $customFields);
}
return $recurringInvoice;
}
public function delete(Collection $ids): bool
{
foreach ($ids as $id) {
$recurringInvoice = RecurringInvoice::find($id);
if ($recurringInvoice->invoices()->exists()) {
$recurringInvoice->invoices()->update(['recurring_invoice_id' => null]);
}
if ($recurringInvoice->items()->exists()) {
$recurringInvoice->items()->delete();
}
if ($recurringInvoice->taxes()->exists()) {
$recurringInvoice->taxes()->delete();
}
$recurringInvoice->delete();
}
return true;
}
public function generateInvoice(RecurringInvoice $recurringInvoice): void
{
if (Carbon::now()->lessThan($recurringInvoice->starts_at)) {
return;
}
if ($recurringInvoice->limit_by == 'DATE') {
$startDate = Carbon::today()->format('Y-m-d');
$endDate = $recurringInvoice->limit_date;
if ($endDate >= $startDate) {
$this->createInvoiceFromRecurring($recurringInvoice);
$recurringInvoice->updateNextInvoiceDate();
} else {
$recurringInvoice->markStatusAsCompleted();
}
} elseif ($recurringInvoice->limit_by == 'COUNT') {
$invoiceCount = Invoice::where('recurring_invoice_id', $recurringInvoice->id)->count();
if ($invoiceCount < $recurringInvoice->limit_count) {
$this->createInvoiceFromRecurring($recurringInvoice);
$recurringInvoice->updateNextInvoiceDate();
} else {
$recurringInvoice->markStatusAsCompleted();
}
} else {
$this->createInvoiceFromRecurring($recurringInvoice);
$recurringInvoice->updateNextInvoiceDate();
}
}
private function createInvoiceFromRecurring(RecurringInvoice $recurringInvoice): void
{
$serial = (new SerialNumberService)
->setModel(new Invoice)
->setCompany($recurringInvoice->company_id)
->setCustomer($recurringInvoice->customer_id)
->setSequenceScope(['type' => Invoice::TYPE_INVOICE])
->setNextNumbers();
$days = intval(CompanySetting::getSetting('invoice_due_date_days', $recurringInvoice->company_id));
if (! $days || $days == 'null') {
$days = 7;
}
$newInvoice['creator_id'] = $recurringInvoice->creator_id;
$newInvoice['invoice_date'] = Carbon::today()->format('Y-m-d');
$newInvoice['due_date'] = Carbon::today()->addDays($days)->format('Y-m-d');
$newInvoice['status'] = Invoice::STATUS_DRAFT;
$newInvoice['company_id'] = $recurringInvoice->company_id;
$newInvoice['paid_status'] = Invoice::STATUS_UNPAID;
$newInvoice['sub_total'] = $recurringInvoice->sub_total;
$newInvoice['tax_per_item'] = $recurringInvoice->tax_per_item;
$newInvoice['discount_per_item'] = $recurringInvoice->discount_per_item;
$newInvoice['tax'] = $recurringInvoice->tax;
$newInvoice['total'] = $recurringInvoice->total;
$newInvoice['customer_id'] = $recurringInvoice->customer_id;
$newInvoice['currency_id'] = Customer::find($recurringInvoice->customer_id)->currency_id;
$newInvoice['template_name'] = $recurringInvoice->template_name;
$newInvoice['due_amount'] = $recurringInvoice->total;
$newInvoice['recurring_invoice_id'] = $recurringInvoice->id;
$newInvoice['discount_val'] = $recurringInvoice->discount_val;
$newInvoice['discount'] = $recurringInvoice->discount;
$newInvoice['discount_type'] = $recurringInvoice->discount_type;
$newInvoice['notes'] = $recurringInvoice->notes;
$newInvoice['exchange_rate'] = $recurringInvoice->exchange_rate;
$newInvoice['sales_tax_type'] = $recurringInvoice->sales_tax_type;
$newInvoice['sales_tax_address_type'] = $recurringInvoice->sales_tax_address_type;
$newInvoice['invoice_number'] = $serial->getNextNumber();
$newInvoice['sequence_number'] = $serial->nextSequenceNumber;
$newInvoice['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
$newInvoice['base_due_amount'] = $recurringInvoice->exchange_rate * $recurringInvoice->due_amount;
$newInvoice['base_discount_val'] = $recurringInvoice->exchange_rate * $recurringInvoice->discount_val;
$newInvoice['base_sub_total'] = $recurringInvoice->exchange_rate * $recurringInvoice->sub_total;
$newInvoice['base_tax'] = $recurringInvoice->exchange_rate * $recurringInvoice->tax;
$newInvoice['base_total'] = $recurringInvoice->exchange_rate * $recurringInvoice->total;
$invoice = Invoice::create($newInvoice);
$invoice->unique_hash = Hashids::connection(HashidConnection::Invoice->value)->encode($invoice->id);
$invoice->save();
$recurringInvoice->load('items.taxes');
$this->documentItemService->createItems($invoice, $recurringInvoice->items->toArray());
if ($recurringInvoice->taxes()->exists()) {
$this->documentItemService->createTaxes($invoice, $recurringInvoice->taxes->toArray());
}
if ($recurringInvoice->fields()->exists()) {
$customField = [];
foreach ($recurringInvoice->fields as $data) {
$customField[] = [
'id' => $data->custom_field_id,
'value' => $data->defaultAnswer,
];
}
$this->customFieldValueWriter->attach($invoice, $customField);
}
if ($recurringInvoice->send_automatically == true) {
$data = [
'body' => CompanySetting::getSetting('invoice_mail_body', $recurringInvoice->company_id),
'from' => config('mail.from.address'),
'to' => $recurringInvoice->customer->email,
'subject' => trans('invoices')['new_invoice'],
'invoice' => $invoice->toArray(),
'customer' => $invoice->customer->toArray(),
'company' => Company::find($invoice->company_id),
];
$this->invoiceService->send($invoice, $data);
}
}
private function createItems(RecurringInvoice $recurringInvoice, array $items): void
{
foreach ($items as $item) {
$item['company_id'] = $recurringInvoice->company_id;
$createdItem = $recurringInvoice->items()->create($item);
if (array_key_exists('taxes', $item) && $item['taxes']) {
foreach ($item['taxes'] as $tax) {
$tax['company_id'] = $recurringInvoice->company_id;
if (gettype($tax['amount']) !== 'NULL') {
$createdItem->taxes()->create($tax);
}
}
}
}
}
private function createTaxes(RecurringInvoice $recurringInvoice, array $taxes): void
{
foreach ($taxes as $tax) {
$tax['company_id'] = $recurringInvoice->company_id;
if (gettype($tax['amount']) !== 'NULL') {
$recurringInvoice->taxes()->create($tax);
}
}
}
}