Add invoice tax summary data

This commit is contained in:
David Bomba 2025-08-05 11:59:46 +10:00
parent 0d1625644f
commit 4ceb15773e
22 changed files with 738 additions and 156 deletions

View File

@ -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())
];
}
}

View File

@ -34,6 +34,7 @@ use App\Jobs\Ninja\BankTransactionSync;
use App\Jobs\Cron\RecurringExpensesCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\EDocument\EInvoicePullDocs;
use App\Jobs\Cron\InvoiceTaxSummary;
use Illuminate\Console\Scheduling\Schedule;
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
@ -72,6 +73,32 @@ class Kernel extends ConsoleKernel
/* Checks for scheduled tasks */
$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 */
$schedule->job(new TransactionReport())->dailyAt('01:48')->withoutOverlapping()->name('rotessa-transaction-report')->onOneServer();

View File

@ -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,
];
}
}

View File

@ -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;
}
}

View File

@ -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,
];
}
}

View File

@ -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,
];
}
}

View File

@ -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(),
];
}
}

View File

@ -241,14 +241,6 @@ class InvoiceController extends BaseController
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);
}

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -982,19 +982,7 @@ class Client extends BaseModel implements HasLocalePreference
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
{
return ctrans('texts.client');

View File

@ -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
{
return ctrans('texts.credit');

View File

@ -127,6 +127,7 @@ use App\Utils\Number;
* @property-read int|null $tasks_count
* @property-read \App\Models\User $user
* @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\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
@ -349,6 +350,11 @@ class Invoice extends BaseModel
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
{
return $this->belongsTo(Client::class)->withTrashed();
@ -684,21 +690,7 @@ class Invoice extends BaseModel
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()
{
$line_items = $this->line_items;

View File

@ -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;
}
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
{
return ctrans('texts.payment');

View File

@ -12,6 +12,8 @@
namespace App\Models;
use App\DataMapper\TransactionEventMetadata;
/**
* Class Bank.
*
@ -42,34 +44,6 @@ namespace App\Models;
* @property int|null $credit_status
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
* @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
*/
class TransactionEvent extends StaticModel
@ -79,36 +53,14 @@ class TransactionEvent extends StaticModel
public $guarded = ['id'];
public $casts = [
'metadata' => 'array',
'metadata' => TransactionEventMetadata::class,
'payment_request' => '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 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;
public const PAYMENT_DELETED = 3;
}

View File

@ -88,7 +88,7 @@ class ApplyPayment extends AbstractService
->workFlow()
->unlockDocuments()
->save();
return $this->invoice;
}
}

View File

@ -156,6 +156,8 @@ class DeletePayment
$paymentable_invoice->delete();
}
});
}

View File

@ -65,6 +65,7 @@ class PaymentService
->updatePaidToDate($invoice->pivot->amount * -1)
->setStatus(Invoice::STATUS_SENT)
->save();
}
});

View File

@ -309,6 +309,7 @@ class RefundPayment
if ($invoice->is_deleted) {
$invoice->delete();
}
}
} else {

View File

@ -163,6 +163,8 @@ class UpdateInvoicePayment
$invoice->service()
->applyNumber()
->save();
}
/* Updates the company ledger */

View File

@ -24,7 +24,6 @@ use App\Export\CSV\BaseExport;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
use App\Services\Template\TemplateService;
use App\Jobs\Invoice\InvoiceTaxReportUpdate;
class TaxSummaryReport extends BaseExport
{

View File

@ -36,49 +36,20 @@ class TaxReport
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->spreadsheet = new Spreadsheet();
$this->updateTaxData();
// ->createGroupedTaxSummarySheetAccrual()
// ->createGroupedTaxSummarySheetCash();
$this->buildData()
->setCurrencyFormat()
->createSummarySheet()
->createInvoiceSummarySheetAccrual()
->createInvoiceSummarySheetCash()
->createInvoiceItemSummarySheetAccrual()
->createInvoiceItemSummarySheetCash();
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()
{
$currency = $this->company->currency();