Expand taxdata object to accept nexus information

This commit is contained in:
David Bomba 2025-08-04 13:32:29 +10:00
parent 75ba84082e
commit 39bdbb9c21
5 changed files with 125 additions and 28 deletions

View File

@ -39,6 +39,10 @@ class BaseRule implements RuleInterface
public string $client_subregion = '';
private string $nexus = '';
private string $country_nexus = '';
public array $eu_country_codes = [
'AT', // Austria
'BE', // Belgium
@ -187,7 +191,6 @@ class BaseRule implements RuleInterface
$this->resolveRegions();
if (!$this->isTaxableRegion()) {
$this->tax_data = null;
$this->tax_rate1 = 0;
@ -234,7 +237,6 @@ class BaseRule implements RuleInterface
* Destination - Client Tax Data
*
*/
$tax_data = false;
if ($this->seller_region == 'US' && $this->client_region == 'US') {
@ -252,20 +254,36 @@ class BaseRule implements RuleInterface
if ($company->origin_tax_data->originDestination == 'O' && ($company->tax_data?->seller_subregion == $this->client_subregion)) {
$tax_data = $company->origin_tax_data;
$tax_data->nexus = $tax_data->geoState;
$tax_data->country_nexus = 'US';
} elseif ($this->invoice->location && $this->invoice->location->is_shipping_location && $this->invoice->location->tax_data) {
$tax_data = $this->invoice->location->tax_data;
$tax_data->nexus = $tax_data->geoState;
$tax_data->country_nexus = 'US';
} elseif ($this->client->tax_data) {
$tax_data = $this->client->tax_data;
$tax_data->nexus = $tax_data->geoState;
$tax_data->country_nexus = 'US';
}
}
$this->saveTaxData($tax_data);
return $this;
}
private function saveTaxData(mixed $tax_data): self
{
/** Applies the tax data to the invoice */
if (($this->invoice instanceof Invoice || $this->invoice instanceof Quote) && $tax_data) {
@ -283,10 +301,8 @@ class BaseRule implements RuleInterface
}
return $this;
}
/**
* Resolve Regions & Subregions
*
@ -361,6 +377,9 @@ class BaseRule implements RuleInterface
$this->tax_rate1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->AU->subregions->AU->tax_name;
$this->nexus = 'AU';
$this->country_nexus = 'AU';
return $this;
}
@ -380,18 +399,33 @@ class BaseRule implements RuleInterface
if ($is_b2c && $is_over_threshold) {
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
$this->nexus = $this->client_subregion;
$this->country_nexus = $this->client_region;
}
// Otherwise, use origin country tax rates
elseif (in_array($company_country_code, $this->eu_country_codes)) {
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$company_country_code}->tax_name;
$this->nexus = $company_country_code;
$this->country_nexus = $company_country_code;
} elseif ($is_over_threshold) {
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
$this->nexus = $this->client_subregion;
$this->country_nexus = $this->client_region;
}
} else {
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
$this->nexus = $this->client_subregion;
$this->country_nexus = $this->client_region;
}
}

View File

@ -99,6 +99,9 @@ class Response
public float $district5UseTax = 0;
/* US SPECIFIC TAX CODES */
public string $nexus = "";
public string $country_nexus = "";
public string $originDestination = "D"; // defines if the client origin is the locale where the tax is remitted to
public function __construct($data = null)

View File

@ -44,6 +44,7 @@ use Illuminate\Contracts\Translation\HasLocalePreference;
* @property string|null $private_notes
* @property string|null $public_notes
* @property string|null $client_hash
* @property string|null $classification
* @property string|null $logo
* @property string|null $phone
* @property string|null $routing_id

View File

@ -17,7 +17,11 @@ class TaxReport
private Spreadsheet $spreadsheet;
private array $data = [];
private string $currency_format;
private string $number_format;
public function __construct(public Company $company, public TaxSummaryReport $tsr, public Builder $query)
{
}
@ -34,6 +38,7 @@ class TaxReport
$this->spreadsheet = new Spreadsheet();
$this->buildData()
->setCurrencyFormat()
->createSummarySheet()
->createInvoiceSummarySheetAccrual()
->createInvoiceSummarySheetCash()
@ -46,12 +51,29 @@ class TaxReport
}
public function setCurrencyFormat()
{
$currency = $this->company->currency();
$formatted = number_format(9990.00, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
$formatted = str_replace('9', '#', $formatted);
$this->number_format = $formatted;
$formatted = "{$currency->symbol}{$formatted}";
$this->currency_format = $formatted;
return $this;
}
public function createSummarySheet()
{
$worksheet = $this->spreadsheet->getActiveSheet();
$worksheet->setTitle(ctrans('texts.tax_summary'));
// Add summary data and formatting here if needed
// For now, this sheet is empty but could be populated with summary statistics
return $this;
}
@ -59,9 +81,15 @@ class TaxReport
public function createInvoiceSummarySheetAccrual()
{
$newWorksheet = $this->spreadsheet->createSheet();
$newWorksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_vs_accrual'));
$newWorksheet->fromArray($this->data['invoices'], null, 'A1');
$worksheet = $this->spreadsheet->createSheet();
$worksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_vs_accrual'));
$worksheet->fromArray($this->data['invoices'], null, 'A1');
$worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column
$worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column
$worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column
$worksheet->getStyle('E:E')->getNumberFormat()->setFormatCode($this->currency_format); // Total taxes column
$worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column
return $this;
}
@ -73,18 +101,33 @@ class TaxReport
return $invoice[3] != 0;
})->toArray();
$newWorksheet = $this->spreadsheet->createSheet();
$newWorksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_accounting'));
$newWorksheet->fromArray($cash_invoices, null, 'A1');
$worksheet = $this->spreadsheet->createSheet();
$worksheet->setTitle(ctrans('texts.invoice')." ".ctrans('texts.cash_accounting'));
$worksheet->fromArray($cash_invoices, null, 'A1');
$worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column
$worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column
$worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column
$worksheet->getStyle('E:E')->getNumberFormat()->setFormatCode($this->currency_format); // Total taxes column
$worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column
return $this;
}
public function createInvoiceItemSummarySheetAccrual()
{
$newWorksheet = $this->spreadsheet->createSheet();
$newWorksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_vs_accrual'));
$newWorksheet->fromArray($this->data['invoice_items'], null, 'A1');
$worksheet = $this->spreadsheet->createSheet();
$worksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_vs_accrual'));
$worksheet->fromArray($this->data['invoice_items'], null, 'A1');
$worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column
$worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column
$worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column
$worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->number_format."%"); // Tax rate column
$worksheet->getStyle('G:G')->getNumberFormat()->setFormatCode($this->currency_format); // Tax amount column
$worksheet->getStyle('H:H')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column
$worksheet->getStyle('I:I')->getNumberFormat()->setFormatCode($this->currency_format); // Taxable amount column
// Column J (tax_nexus) is text, so no special formatting needed
return $this;
}
@ -96,9 +139,18 @@ class TaxReport
return $invoice_item[3] != 0;
})->toArray();
$newWorksheet = $this->spreadsheet->createSheet();
$newWorksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_accounting'));
$newWorksheet->fromArray($cash_invoice_items, null, 'A1');
$worksheet = $this->spreadsheet->createSheet();
$worksheet->setTitle(ctrans('texts.invoice_item')." ".ctrans('texts.cash_accounting'));
$worksheet->fromArray($cash_invoice_items, null, 'A1');
$worksheet->getStyle('B:B')->getNumberFormat()->setFormatCode($this->company->date_format()); // Invoice date column
$worksheet->getStyle('C:C')->getNumberFormat()->setFormatCode($this->currency_format); // Invoice total column
$worksheet->getStyle('D:D')->getNumberFormat()->setFormatCode($this->currency_format); // Paid amount column
$worksheet->getStyle('F:F')->getNumberFormat()->setFormatCode($this->number_format."%"); // Tax rate column
$worksheet->getStyle('G:G')->getNumberFormat()->setFormatCode($this->currency_format); // Tax amount column
$worksheet->getStyle('H:H')->getNumberFormat()->setFormatCode($this->currency_format); // Tax paid column
$worksheet->getStyle('I:I')->getNumberFormat()->setFormatCode($this->currency_format); // Taxable amount column
// Column J (tax_nexus) is text, so no special formatting needed
return $this;
}
@ -130,6 +182,7 @@ class TaxReport
ctrans('texts.tax_amount'),
ctrans('texts.tax_paid'),
ctrans('texts.taxable_amount'),
ctrans('texts.tax_nexus'),
];
$offset = $this->company->timezone_offset();
@ -181,6 +234,7 @@ class TaxReport
$tax['total'],
$tax['total'] * $pro_rata_payment_ratio,
$tax['base_amount'] ?? $calc->getNetSubtotal(),
$tax['nexus'] ?? '',
];
}
@ -192,16 +246,20 @@ class TaxReport
public function getXlsFile()
{
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($this->spreadsheet);
$writer->save('/tmp/tax_report.xlsx');
$tempFile = tempnam(sys_get_temp_dir(), 'tax_report_');
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($this->spreadsheet);
$writer->save($tempFile);
$writer->save('/home/david/ttx.xslx');
// Read file content
$fileContent = file_get_contents($tempFile);
nlog($tempFile);
// Clean up temp file
// unlink($tempFile);
return $fileContent;
// return $this->spreadsheet;
// Use output buffering to capture the file content
// ob_start();
// $this->spreadsheet->save('php://output');
// $fileContent = ob_get_clean();
// return $fileContent;
}
}

View File

@ -5611,6 +5611,7 @@ $lang = array(
'auto_send_help' => 'Automatically emails the invoice to the client',
'include_project_tasks' => 'Include Project Tasks',
'include_project_tasks_help' => 'Also invoice tasks that are part of a project',
'tax_nexus' => 'Tax Nexus',
);
return $lang;