diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index e1f32efed6..37213a7adb 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -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; + } } diff --git a/app/DataMapper/Tax/ZipTax/Response.php b/app/DataMapper/Tax/ZipTax/Response.php index 127f15dc48..32e419301a 100644 --- a/app/DataMapper/Tax/ZipTax/Response.php +++ b/app/DataMapper/Tax/ZipTax/Response.php @@ -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) diff --git a/app/Models/Client.php b/app/Models/Client.php index 43dafa6df8..ff4f0f4801 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -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 diff --git a/app/Services/Report/XLS/TaxReport.php b/app/Services/Report/XLS/TaxReport.php index 38de562626..233d4cf8f4 100644 --- a/app/Services/Report/XLS/TaxReport.php +++ b/app/Services/Report/XLS/TaxReport.php @@ -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; } } diff --git a/lang/en/texts.php b/lang/en/texts.php index b4fb7b0701..2ab19130b9 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -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;