Add invoice tax summary data
This commit is contained in:
parent
0d1625644f
commit
4ceb15773e
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Casts;
|
||||||
|
|
||||||
|
use App\DataMapper\TransactionEventMetadata;
|
||||||
|
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||||
|
|
||||||
|
class TransactionEventMetadataCast implements CastsAttributes
|
||||||
|
{
|
||||||
|
public function get($model, string $key, $value, array $attributes)
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($value, true);
|
||||||
|
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionEventMetadata($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set($model, string $key, $value, array $attributes)
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return [$key => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
$key => json_encode($value->toArray())
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ use App\Jobs\Ninja\BankTransactionSync;
|
||||||
use App\Jobs\Cron\RecurringExpensesCron;
|
use App\Jobs\Cron\RecurringExpensesCron;
|
||||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||||
use App\Jobs\EDocument\EInvoicePullDocs;
|
use App\Jobs\EDocument\EInvoicePullDocs;
|
||||||
|
use App\Jobs\Cron\InvoiceTaxSummary;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
||||||
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
|
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
|
||||||
|
|
@ -72,6 +73,32 @@ class Kernel extends ConsoleKernel
|
||||||
/* Checks for scheduled tasks */
|
/* Checks for scheduled tasks */
|
||||||
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
$schedule->job(new TaskScheduler())->hourlyAt(10)->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
||||||
|
|
||||||
|
/* Generates the tax summary for invoices */
|
||||||
|
$schedule->job(new InvoiceTaxSummary())->monthly('23:30')->withoutOverlapping()->name('invoice-tax-summary-job')->onOneServer();
|
||||||
|
|
||||||
|
// Run hourly over 26-hour period for complete timezone coverage
|
||||||
|
$schedule->job(new InvoiceTaxSummary())
|
||||||
|
->hourly()
|
||||||
|
->when(function () {
|
||||||
|
$now = now();
|
||||||
|
$hour = $now->hour;
|
||||||
|
|
||||||
|
// Run for 26 hours starting from UTC 10:00 on last day of month
|
||||||
|
// This covers the transition period when timezones move to next month
|
||||||
|
if ($now->isLastOfMonth()) {
|
||||||
|
// Start at UTC 10:00 (when UTC+14 moves to next day)
|
||||||
|
return $hour >= 10;
|
||||||
|
} elseif ($now->isFirstOfMonth()) {
|
||||||
|
// Continue until UTC 12:00 (when UTC-12 moves to next day)
|
||||||
|
return $hour <= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
->withoutOverlapping()
|
||||||
|
->name('invoice-tax-summary-26hour-coverage')
|
||||||
|
->onOneServer();
|
||||||
|
|
||||||
/* Checks Rotessa Transactions */
|
/* Checks Rotessa Transactions */
|
||||||
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
|
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\TaxReport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment history for tracking partial payments across periods
|
||||||
|
*/
|
||||||
|
class PaymentHistory
|
||||||
|
{
|
||||||
|
public string $number;
|
||||||
|
public string $date;
|
||||||
|
public float $amount;
|
||||||
|
public float $refunded;
|
||||||
|
|
||||||
|
public function __construct(array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->number = $attributes['number'] ?? '';
|
||||||
|
$this->date = $attributes['date'] ?? '';
|
||||||
|
$this->amount = $attributes['amount'] ?? 0.0;
|
||||||
|
$this->refunded = $attributes['refunded'] ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'number' => $this->number,
|
||||||
|
'date' => $this->date,
|
||||||
|
'amount' => $this->amount,
|
||||||
|
'refunded' => $this->refunded,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\TaxReport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Individual tax detail object with status tracking
|
||||||
|
*/
|
||||||
|
class TaxDetail
|
||||||
|
{
|
||||||
|
public string $tax_name; // e.g., Sales Tax
|
||||||
|
public float $tax_rate = 0; //21%
|
||||||
|
public string $nexus; // Tax jurisdiction nexus (e.g. "CA", "NY", "FL")
|
||||||
|
public string $country_nexus; // Country nexus (e.g. "US", "UK", "CA")
|
||||||
|
public float $taxable_amount; // net amount exclusive of taxes
|
||||||
|
public float $tax_amount; // total tax amount
|
||||||
|
public float $tax_amount_paid; // Amount actually paid (Based on the payment history)
|
||||||
|
public float $tax_amount_remaining; // Amount still pending
|
||||||
|
public string $tax_status; // "collected", "pending", "refundable", "partially_paid", "adjustment"
|
||||||
|
|
||||||
|
// Adjustment-specific fields (used when tax_status is "adjustment")
|
||||||
|
public ?string $adjustment_reason; // "invoice_cancelled", "tax_rate_change", "exemption_applied", "correction"
|
||||||
|
|
||||||
|
public function __construct(array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->tax_name = $attributes['tax_name'] ?? '';
|
||||||
|
$this->tax_rate = $attributes['tax_rate'] ?? 0;
|
||||||
|
$this->nexus = $attributes['nexus'] ?? '';
|
||||||
|
$this->country_nexus = $attributes['country_nexus'] ?? '';
|
||||||
|
$this->taxable_amount = $attributes['taxable_amount'] ?? 0.0;
|
||||||
|
$this->tax_amount = $attributes['tax_amount'] ?? 0.0;
|
||||||
|
$this->tax_amount_paid = $attributes['tax_amount_paid'] ?? 0.0;
|
||||||
|
$this->tax_amount_remaining = $attributes['tax_amount_remaining'] ?? 0.0;
|
||||||
|
$this->tax_status = $attributes['tax_status'] ?? 'pending';
|
||||||
|
|
||||||
|
// Adjustment fields
|
||||||
|
$this->adjustment_reason = $attributes['adjustment_reason'] ?? null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'tax_name' => $this->tax_name,
|
||||||
|
'tax_rate' => $this->tax_rate,
|
||||||
|
'nexus' => $this->nexus,
|
||||||
|
'country_nexus' => $this->country_nexus,
|
||||||
|
'taxable_amount' => $this->taxable_amount,
|
||||||
|
'tax_amount' => $this->tax_amount,
|
||||||
|
'tax_amount_paid' => $this->tax_amount_paid,
|
||||||
|
'tax_amount_remaining' => $this->tax_amount_remaining,
|
||||||
|
'tax_status' => $this->tax_status,
|
||||||
|
'adjustment_reason' => $this->adjustment_reason,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\TaxReport;
|
||||||
|
|
||||||
|
use App\DataMapper\TaxReport\TaxDetail;
|
||||||
|
use App\DataMapper\TaxReport\TaxSummary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tax report object for InvoiceSync - tracks incremental tax history
|
||||||
|
*/
|
||||||
|
class TaxReport
|
||||||
|
{
|
||||||
|
public ?TaxSummary $tax_summary; // Summary totals
|
||||||
|
public ?array $tax_details; // Array of TaxDetail objects (includes adjustments)
|
||||||
|
public float $amount; // The total amount of the invoice
|
||||||
|
public ?array $payment_history; // Array of PaymentHistory objects
|
||||||
|
|
||||||
|
public function __construct(array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->tax_summary = isset($attributes['tax_summary'])
|
||||||
|
? new TaxSummary($attributes['tax_summary'])
|
||||||
|
: null;
|
||||||
|
$this->tax_details = isset($attributes['tax_details'])
|
||||||
|
? array_map(fn ($detail) => new TaxDetail($detail), $attributes['tax_details'])
|
||||||
|
: null;
|
||||||
|
$this->payment_history = isset($attributes['payment_history'])
|
||||||
|
? array_map(fn ($payment) => new PaymentHistory($payment), $attributes['payment_history'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'tax_summary' => $this->tax_summary?->toArray(),
|
||||||
|
'tax_details' => $this->tax_details ? array_map(fn ($detail) => $detail->toArray(), $this->tax_details) : null,
|
||||||
|
'payment_history' => $this->payment_history ? array_map(fn ($payment) => $payment->toArray(), $this->payment_history) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\TaxReport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tax summary with totals for different tax states
|
||||||
|
*/
|
||||||
|
class TaxSummary
|
||||||
|
{
|
||||||
|
public float $total_taxes; // Tax collected and confirmed (ie. Invoice Paid)
|
||||||
|
public float $total_paid; // Tax pending collection (Outstanding tax of balance owing)
|
||||||
|
public string $status;
|
||||||
|
|
||||||
|
public function __construct(array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->total_taxes = $attributes['total_taxes'] ?? 0.0;
|
||||||
|
$this->total_paid = $attributes['total_paid'] ?? 0.0;
|
||||||
|
$this->status = $attributes['status'] ?? 'updated';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'total_taxes' => $this->total_taxes,
|
||||||
|
'total_paid' => $this->total_paid,
|
||||||
|
'status' => $this->status,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper;
|
||||||
|
|
||||||
|
use App\Casts\TransactionEventMetadataCast;
|
||||||
|
use App\DataMapper\TaxReport\TaxReport;
|
||||||
|
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TransactionEventMetadata.
|
||||||
|
*/
|
||||||
|
class TransactionEventMetadata implements Castable
|
||||||
|
{
|
||||||
|
public TaxReport $tax_report;
|
||||||
|
|
||||||
|
public function __construct(array $attributes = [])
|
||||||
|
{
|
||||||
|
$this->tax_report = isset($attributes['tax_report'])
|
||||||
|
? new TaxReport($attributes['tax_report'])
|
||||||
|
: new TaxReport([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the caster class to use when casting from / to this cast target.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $arguments
|
||||||
|
*/
|
||||||
|
public static function castUsing(array $arguments): string
|
||||||
|
{
|
||||||
|
return TransactionEventMetadataCast::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $data): self
|
||||||
|
{
|
||||||
|
return new self($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'tax_report' => $this->tax_report->toArray(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -241,14 +241,6 @@ class InvoiceController extends BaseController
|
||||||
|
|
||||||
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars($user ? $user->id : null)));
|
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars($user ? $user->id : null)));
|
||||||
|
|
||||||
$transaction = [
|
|
||||||
'invoice' => $invoice->transaction_event(),
|
|
||||||
'payment' => [],
|
|
||||||
'client' => $invoice->client->transaction_event(),
|
|
||||||
'credit' => [],
|
|
||||||
'metadata' => [],
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->itemResponse($invoice);
|
return $this->itemResponse($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Jobs\Cron;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Webhook;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Timezone;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use App\Jobs\Entity\EmailEntity;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Listeners\Invoice\InvoiceTransactionEventEntry;
|
||||||
|
|
||||||
|
class InvoiceTaxSummary implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$currentUtcHour = now()->hour;
|
||||||
|
$transitioningTimezones = $this->getTransitioningTimezones($currentUtcHour);
|
||||||
|
|
||||||
|
foreach(MultiDB::$dbs as $db) {
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
// Only process companies in timezones that just transitioned
|
||||||
|
$companies = $this->getCompaniesInTimezones($transitioningTimezones);
|
||||||
|
|
||||||
|
foreach ($companies as $company) {
|
||||||
|
|
||||||
|
$this->processCompanyTaxSummary($company);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTransitioningTimezones($utcHour)
|
||||||
|
{
|
||||||
|
$transitioningTimezones = [];
|
||||||
|
|
||||||
|
// Get all timezones from the database
|
||||||
|
$timezones = app('timezones');
|
||||||
|
|
||||||
|
foreach ($timezones as $timezone) {
|
||||||
|
// Calculate the current UTC offset for this timezone (accounting for DST)
|
||||||
|
$currentOffset = $this->getCurrentUtcOffset($timezone->name);
|
||||||
|
|
||||||
|
// Calculate when this timezone transitions to the next day
|
||||||
|
$transitionHour = $this->getTimezoneTransitionHour($currentOffset);
|
||||||
|
|
||||||
|
// If this timezone transitions at the current UTC hour, include it
|
||||||
|
if ($transitionHour === $utcHour) {
|
||||||
|
$transitioningTimezones[] = $timezone->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transitioningTimezones;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCurrentUtcOffset($timezoneName)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$dateTime = new \DateTime('now', new \DateTimeZone($timezoneName));
|
||||||
|
return $dateTime->getOffset();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Fallback to UTC if timezone is invalid
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTimezoneTransitionHour($utcOffset)
|
||||||
|
{
|
||||||
|
// Calculate which UTC hour this timezone transitions to the next day
|
||||||
|
// A timezone with UTC offset +X transitions at UTC hour (24 - X)
|
||||||
|
// For example: UTC+14 transitions at UTC 10:00 (24 - 14 = 10)
|
||||||
|
// UTC-12 transitions at UTC 12:00 (24 - (-12) = 36, but we use modulo 24)
|
||||||
|
|
||||||
|
$transitionHour = (24 - ($utcOffset / 3600)) % 24;
|
||||||
|
|
||||||
|
// Handle negative offsets properly
|
||||||
|
if ($transitionHour < 0) {
|
||||||
|
$transitionHour += 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $transitionHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCompaniesInTimezones($timezoneIds)
|
||||||
|
{
|
||||||
|
if (empty($timezoneIds)) {
|
||||||
|
return collect(); // No companies to process
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get companies that have timezone_id in their JSON settings matching the transitioning timezones
|
||||||
|
return Company::whereRaw("JSON_EXTRACT(settings, '$.timezone_id') IN (" . implode(',', $timezoneIds) . ")")->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processCompanyTaxSummary($company)
|
||||||
|
{
|
||||||
|
// Your existing tax summary logic here
|
||||||
|
// This will only run for companies in timezones that just transitioned
|
||||||
|
|
||||||
|
$startDate = now()->subMonth()->startOfMonth()->format('Y-m-d');
|
||||||
|
$endDate = now()->subMonth()->endOfMonth()->format('Y-m-d');
|
||||||
|
|
||||||
|
// Process tax summary for the company
|
||||||
|
$this->generateTaxSummary($company, $startDate, $endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateTaxSummary($company, $startDate, $endDate)
|
||||||
|
{
|
||||||
|
$todayStart = now()->subHours(15)->timestamp;
|
||||||
|
$todayEnd = now()->endOfDay()->timestamp;
|
||||||
|
|
||||||
|
Invoice::withTrashed()
|
||||||
|
->with('payments')
|
||||||
|
->where('company_id', $company->id)
|
||||||
|
->whereIn('status_id', [2,3,4,5])
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->whereHas('client', function ($query) {
|
||||||
|
$query->where('is_deleted', false);
|
||||||
|
})
|
||||||
|
->whereHas('company', function ($query) {
|
||||||
|
$query->where('is_disabled', 0)
|
||||||
|
->whereHas('account', function ($q) {
|
||||||
|
$q->where('is_flagged', false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->whereBetween('date', [$startDate, $endDate])
|
||||||
|
->whereDoesntHave('transaction_events', function ($query) use ($todayStart, $todayEnd) {
|
||||||
|
$query->where('timestamp', '>=', $todayStart)
|
||||||
|
->where('timestamp', '<=', $todayEnd);
|
||||||
|
})
|
||||||
|
->cursor()
|
||||||
|
->each(function (Invoice $invoice) {
|
||||||
|
(new InvoiceTransactionEventEntry())->run($invoice);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2025. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Listeners\Invoice;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Activity;
|
||||||
|
use App\Models\TransactionEvent;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use App\DataMapper\TaxReport\TaxDetail;
|
||||||
|
use App\DataMapper\TaxReport\TaxReport;
|
||||||
|
use App\DataMapper\TaxReport\TaxSummary;
|
||||||
|
use App\Repositories\ActivityRepository;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use App\DataMapper\TransactionEventMetadata;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class InvoiceTransactionEventEntry
|
||||||
|
{
|
||||||
|
|
||||||
|
private Collection $payments;
|
||||||
|
|
||||||
|
private float $paid_ratio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the event.
|
||||||
|
*
|
||||||
|
* @param Invoice $invoice
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function run($invoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->setPaidRatio($invoice);
|
||||||
|
|
||||||
|
$this->payments = $invoice->payments->flatMap(function ($payment) {
|
||||||
|
return $payment->invoices()->get()->map(function ($invoice) use ($payment) {
|
||||||
|
return [
|
||||||
|
'number' => $payment->number,
|
||||||
|
'amount' => $invoice->pivot->amount,
|
||||||
|
'refunded' => $invoice->pivot->refunded,
|
||||||
|
'date' => $invoice->pivot->created_at->format('Y-m-d'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TransactionEvent::create([
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'client_id' => $invoice->client_id,
|
||||||
|
'client_balance' => $invoice->client->balance,
|
||||||
|
'client_paid_to_date' => $invoice->client->paid_to_date,
|
||||||
|
'client_credit_balance' => $invoice->client->credit_balance,
|
||||||
|
'invoice_balance' => $invoice->balance ?? 0,
|
||||||
|
'invoice_amount' => $invoice->amount ?? 0 ,
|
||||||
|
'invoice_partial' => $invoice->partial ?? 0,
|
||||||
|
'invoice_paid_to_date' => $invoice->paid_to_date ?? 0,
|
||||||
|
'invoice_status' => $invoice->is_deleted ? 7 : $invoice->status_id,
|
||||||
|
'event_id' => $invoice->is_deleted ? TransactionEvent::INVOICE_DELETED : TransactionEvent::INVOICE_UPDATED,
|
||||||
|
'timestamp' => now()->timestamp,
|
||||||
|
'metadata' => $this->getMetadata($invoice),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setPaidRatio(Invoice $invoice): self
|
||||||
|
{
|
||||||
|
if($invoice->amount == 0){
|
||||||
|
$this->paid_ratio = 0;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->paid_ratio = $invoice->paid_to_date / $invoice->amount;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateRatio(float $amount): float
|
||||||
|
{
|
||||||
|
return round($amount * $this->paid_ratio, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing tax details are not deleted, but pending taxes are set to 0
|
||||||
|
*
|
||||||
|
* @param mixed $invoice
|
||||||
|
*/
|
||||||
|
private function getCancelledMetaData($invoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
$calc = $invoice->calc();
|
||||||
|
|
||||||
|
$details = [];
|
||||||
|
|
||||||
|
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
|
||||||
|
|
||||||
|
foreach ($taxes as $tax) {
|
||||||
|
$tax_detail = [
|
||||||
|
'tax_name' => $tax['name'],
|
||||||
|
'tax_rate' => $tax['tax_rate'],
|
||||||
|
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
|
||||||
|
'tax_amount' => $this->calculateRatio($tax['total']),
|
||||||
|
'tax_amount_paid' => $this->calculateRatio($tax['total']),
|
||||||
|
'tax_amount_remaining' => 0,
|
||||||
|
];
|
||||||
|
$details[] = $tax_detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionEventMetadata([
|
||||||
|
'tax_report' => [
|
||||||
|
'tax_details' => $details,
|
||||||
|
'payment_history' => $this->payments->toArray(),
|
||||||
|
'tax_summary' => [
|
||||||
|
'total_taxes' => $invoice->total_taxes,
|
||||||
|
'total_paid' => $this->getTotalTaxPaid($invoice),
|
||||||
|
'status' => 'cancelled',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all tax details to 0
|
||||||
|
*
|
||||||
|
* @param mixed $invoice
|
||||||
|
*/
|
||||||
|
private function getDeletedMetaData($invoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
$calc = $invoice->calc();
|
||||||
|
|
||||||
|
$details = [];
|
||||||
|
|
||||||
|
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
|
||||||
|
|
||||||
|
foreach ($taxes as $tax) {
|
||||||
|
$tax_detail = [
|
||||||
|
'tax_name' => $tax['name'],
|
||||||
|
'tax_rate' => $tax['tax_rate'],
|
||||||
|
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
|
||||||
|
'tax_amount' => $tax['total'],
|
||||||
|
'tax_amount_paid' => $this->calculateRatio($tax['total']),
|
||||||
|
'tax_amount_remaining' => 0,
|
||||||
|
];
|
||||||
|
$details[] = $tax_detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionEventMetadata([
|
||||||
|
'tax_report' => [
|
||||||
|
'tax_details' => $details,
|
||||||
|
'payment_history' => $this->payments->toArray(),
|
||||||
|
'tax_summary' => [
|
||||||
|
'total_taxes' => $invoice->total_taxes,
|
||||||
|
'total_paid' => $this->getTotalTaxPaid($invoice),0,
|
||||||
|
'status' => 'deleted',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMetadata($invoice)
|
||||||
|
{
|
||||||
|
|
||||||
|
if ($invoice->status_id == Invoice::STATUS_CANCELLED) {
|
||||||
|
return $this->getCancelledMetaData($invoice);
|
||||||
|
} elseif ($invoice->is_deleted) {
|
||||||
|
return $this->getDeletedMetaData($invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
$calc = $invoice->calc();
|
||||||
|
|
||||||
|
$details = [];
|
||||||
|
|
||||||
|
$taxes = array_merge($calc->getTaxMap()->merge($calc->getTotalTaxMap())->toArray());
|
||||||
|
|
||||||
|
foreach ($taxes as $tax) {
|
||||||
|
$tax_detail = [
|
||||||
|
'tax_name' => $tax['name'],
|
||||||
|
'tax_rate' => $tax['tax_rate'],
|
||||||
|
'taxable_amount' => $tax['base_amount'] ?? $calc->getNetSubtotal(),
|
||||||
|
'tax_amount' => $tax['total'],
|
||||||
|
'tax_amount_paid' => $this->calculateRatio($tax['total']),
|
||||||
|
'tax_amount_remaining' => $tax['total'] - $this->calculateRatio($tax['total']),
|
||||||
|
];
|
||||||
|
$details[] = $tax_detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionEventMetadata([
|
||||||
|
'tax_report' => [
|
||||||
|
'tax_details' => $details,
|
||||||
|
'payment_history' => $this->payments->toArray(),
|
||||||
|
'tax_summary' => [
|
||||||
|
'total_taxes' => $invoice->total_taxes,
|
||||||
|
'total_paid' => $this->getTotalTaxPaid($invoice),
|
||||||
|
'status' => 'updated',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTotalTaxPaid($invoice)
|
||||||
|
{
|
||||||
|
if($invoice->amount == 0){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_paid = $this->payments->sum('amount') - $this->payments->sum('refunded');
|
||||||
|
|
||||||
|
return round($invoice->total_taxes * ($total_paid / $invoice->amount), 2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -982,19 +982,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||||
|
|
||||||
return $offset;
|
return $offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transaction_event()
|
|
||||||
{
|
|
||||||
$client = $this->fresh();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'client_id' => $client->id,
|
|
||||||
'client_balance' => $client->balance ?: 0,
|
|
||||||
'client_paid_to_date' => $client->paid_to_date ?: 0,
|
|
||||||
'client_credit_balance' => $client->credit_balance ?: 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function translate_entity(): string
|
public function translate_entity(): string
|
||||||
{
|
{
|
||||||
return ctrans('texts.client');
|
return ctrans('texts.client');
|
||||||
|
|
|
||||||
|
|
@ -407,18 +407,6 @@ class Credit extends BaseModel
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transaction_event()
|
|
||||||
{
|
|
||||||
$credit = $this->fresh();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'credit_id' => $credit->id,
|
|
||||||
'credit_amount' => $credit->amount ?: 0,
|
|
||||||
'credit_balance' => $credit->balance ?: 0,
|
|
||||||
'credit_status' => $credit->status_id ?: 1,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function translate_entity(): string
|
public function translate_entity(): string
|
||||||
{
|
{
|
||||||
return ctrans('texts.credit');
|
return ctrans('texts.credit');
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ use App\Utils\Number;
|
||||||
* @property-read int|null $tasks_count
|
* @property-read int|null $tasks_count
|
||||||
* @property-read \App\Models\User $user
|
* @property-read \App\Models\User $user
|
||||||
* @property-read \App\Models\Vendor|null $vendor
|
* @property-read \App\Models\Vendor|null $vendor
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\TransactionEvent> $transaction_events
|
||||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
|
||||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
|
||||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
|
||||||
|
|
@ -349,6 +350,11 @@ class Invoice extends BaseModel
|
||||||
return $this->hasMany(InvoiceInvitation::class);
|
return $this->hasMany(InvoiceInvitation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function transaction_events(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(TransactionEvent::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function client(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
public function client(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Client::class)->withTrashed();
|
return $this->belongsTo(Client::class)->withTrashed();
|
||||||
|
|
@ -684,21 +690,7 @@ class Invoice extends BaseModel
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transaction_event()
|
|
||||||
{
|
|
||||||
$invoice = $this->fresh();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'invoice_id' => $invoice->id,
|
|
||||||
'invoice_amount' => $invoice->amount ?: 0,
|
|
||||||
'invoice_partial' => $invoice->partial ?: 0,
|
|
||||||
'invoice_balance' => $invoice->balance ?: 0,
|
|
||||||
'invoice_paid_to_date' => $invoice->paid_to_date ?: 0,
|
|
||||||
'invoice_status' => $invoice->status_id ?: 1,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function expense_documents()
|
public function expense_documents()
|
||||||
{
|
{
|
||||||
$line_items = $this->line_items;
|
$line_items = $this->line_items;
|
||||||
|
|
|
||||||
|
|
@ -481,21 +481,6 @@ class Payment extends BaseModel
|
||||||
return $domain.'/client/payment/'.$this->client->contacts()->first()->contact_key.'/'.$this->hashed_id.'?next=/client/payments/'.$this->hashed_id;
|
return $domain.'/client/payment/'.$this->client->contacts()->first()->contact_key.'/'.$this->hashed_id.'?next=/client/payments/'.$this->hashed_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transaction_event()
|
|
||||||
{
|
|
||||||
$payment = $this->fresh();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'payment_id' => $payment->id,
|
|
||||||
'payment_amount' => $payment->amount ?: 0,
|
|
||||||
'payment_applied' => $payment->applied ?: 0,
|
|
||||||
'payment_refunded' => $payment->refunded ?: 0,
|
|
||||||
'payment_status' => $payment->status_id ?: 1,
|
|
||||||
'paymentables' => $payment->paymentables->toArray(),
|
|
||||||
'payment_request' => [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function translate_entity(): string
|
public function translate_entity(): string
|
||||||
{
|
{
|
||||||
return ctrans('texts.payment');
|
return ctrans('texts.payment');
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\DataMapper\TransactionEventMetadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Bank.
|
* Class Bank.
|
||||||
*
|
*
|
||||||
|
|
@ -42,34 +44,6 @@ namespace App\Models;
|
||||||
* @property int|null $credit_status
|
* @property int|null $credit_status
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
|
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns)
|
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns)
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent newModelQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent newQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent query()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereClientBalance($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereClientCreditBalance($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereClientId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereClientPaidToDate($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereCreditAmount($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereCreditBalance($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereCreditId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereCreditStatus($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereEventId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoiceAmount($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoiceBalance($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoiceId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoicePaidToDate($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoicePartial($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereInvoiceStatus($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereMetadata($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentAmount($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentApplied($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentRefunded($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentRequest($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentStatus($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent wherePaymentables($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder|TransactionEvent whereTimestamp($value)
|
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
class TransactionEvent extends StaticModel
|
class TransactionEvent extends StaticModel
|
||||||
|
|
@ -79,36 +53,14 @@ class TransactionEvent extends StaticModel
|
||||||
public $guarded = ['id'];
|
public $guarded = ['id'];
|
||||||
|
|
||||||
public $casts = [
|
public $casts = [
|
||||||
'metadata' => 'array',
|
'metadata' => TransactionEventMetadata::class,
|
||||||
'payment_request' => 'array',
|
'payment_request' => 'array',
|
||||||
'paymentables' => 'array',
|
'paymentables' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const INVOICE_MARK_PAID = 1;
|
public const INVOICE_UPDATED = 1;
|
||||||
|
|
||||||
public const INVOICE_UPDATED = 2;
|
public const INVOICE_DELETED = 2;
|
||||||
|
|
||||||
public const INVOICE_DELETED = 3;
|
public const PAYMENT_DELETED = 3;
|
||||||
|
|
||||||
public const INVOICE_PAYMENT_APPLIED = 4;
|
|
||||||
|
|
||||||
public const INVOICE_CANCELLED = 5;
|
|
||||||
|
|
||||||
public const INVOICE_FEE_APPLIED = 6;
|
|
||||||
|
|
||||||
public const INVOICE_REVERSED = 7;
|
|
||||||
|
|
||||||
public const PAYMENT_MADE = 100;
|
|
||||||
|
|
||||||
public const PAYMENT_APPLIED = 101;
|
|
||||||
|
|
||||||
public const PAYMENT_REFUND = 102;
|
|
||||||
|
|
||||||
public const PAYMENT_FAILED = 103;
|
|
||||||
|
|
||||||
public const GATEWAY_PAYMENT_MADE = 104;
|
|
||||||
|
|
||||||
public const PAYMENT_DELETED = 105;
|
|
||||||
|
|
||||||
public const CLIENT_STATUS = 200;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ class ApplyPayment extends AbstractService
|
||||||
->workFlow()
|
->workFlow()
|
||||||
->unlockDocuments()
|
->unlockDocuments()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
return $this->invoice;
|
return $this->invoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,8 @@ class DeletePayment
|
||||||
$paymentable_invoice->delete();
|
$paymentable_invoice->delete();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ class PaymentService
|
||||||
->updatePaidToDate($invoice->pivot->amount * -1)
|
->updatePaidToDate($invoice->pivot->amount * -1)
|
||||||
->setStatus(Invoice::STATUS_SENT)
|
->setStatus(Invoice::STATUS_SENT)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -309,6 +309,7 @@ class RefundPayment
|
||||||
if ($invoice->is_deleted) {
|
if ($invoice->is_deleted) {
|
||||||
$invoice->delete();
|
$invoice->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,8 @@ class UpdateInvoicePayment
|
||||||
$invoice->service()
|
$invoice->service()
|
||||||
->applyNumber()
|
->applyNumber()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Updates the company ledger */
|
/* Updates the company ledger */
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ use App\Export\CSV\BaseExport;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use App\Services\Template\TemplateService;
|
use App\Services\Template\TemplateService;
|
||||||
use App\Jobs\Invoice\InvoiceTaxReportUpdate;
|
|
||||||
|
|
||||||
class TaxSummaryReport extends BaseExport
|
class TaxSummaryReport extends BaseExport
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,49 +36,20 @@ class TaxReport
|
||||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||||
|
|
||||||
$this->spreadsheet = new Spreadsheet();
|
$this->spreadsheet = new Spreadsheet();
|
||||||
|
|
||||||
$this->updateTaxData();
|
|
||||||
|
|
||||||
|
|
||||||
// ->createGroupedTaxSummarySheetAccrual()
|
$this->buildData()
|
||||||
// ->createGroupedTaxSummarySheetCash();
|
->setCurrencyFormat()
|
||||||
|
->createSummarySheet()
|
||||||
|
->createInvoiceSummarySheetAccrual()
|
||||||
|
->createInvoiceSummarySheetCash()
|
||||||
|
->createInvoiceItemSummarySheetAccrual()
|
||||||
|
->createInvoiceItemSummarySheetCash();
|
||||||
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function postUpdateContinuation()
|
|
||||||
{
|
|
||||||
$this->buildData()
|
|
||||||
->setCurrencyFormat()
|
|
||||||
->createSummarySheet()
|
|
||||||
->createInvoiceSummarySheetAccrual()
|
|
||||||
->createInvoiceSummarySheetCash()
|
|
||||||
->createInvoiceItemSummarySheetAccrual()
|
|
||||||
->createInvoiceItemSummarySheetCash();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateTaxData()
|
|
||||||
{
|
|
||||||
|
|
||||||
$batch_key = Str::uuid();
|
|
||||||
|
|
||||||
$updates = Invoice::withTrashed()
|
|
||||||
->whereIn('id', $this->ids)
|
|
||||||
->get()
|
|
||||||
->map(function ($invoice) use ($batch_key) {
|
|
||||||
return new InvoiceTaxReportUpdate($invoice, $this->company->db);
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
|
|
||||||
$batch = Bus::batch($updates)
|
|
||||||
->then(function (Batch $batch) {
|
|
||||||
$this->postUpdateContinuation();
|
|
||||||
})
|
|
||||||
->name($batch_key)->dispatch();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCurrencyFormat()
|
public function setCurrencyFormat()
|
||||||
{
|
{
|
||||||
$currency = $this->company->currency();
|
$currency = $this->company->currency();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue