diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index 1c154e6035d1..bf8481b1faaf 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -15,6 +15,7 @@ use App\Models\Client; use App\Models\Invoice; use App\Models\Product; use App\DataMapper\Tax\ZipTax\Response; +use App\DataProviders\USStates; class BaseRule implements RuleInterface { @@ -116,7 +117,7 @@ class BaseRule implements RuleInterface protected ?Client $client; - protected ?Response $tax_data; + public ?Response $tax_data; public mixed $invoice; @@ -129,29 +130,42 @@ class BaseRule implements RuleInterface return $this; } - public function setInvoice(mixed $invoice): self + public function setEntity(mixed $invoice): self { $this->invoice = $invoice; - - $this->configTaxData(); $this->client = $invoice->client; + $this->configTaxData() + ->resolveRegions(); + $this->tax_data = new Response($this->invoice->tax_data); - $this->resolveRegions(); - - return $this; } private function configTaxData(): self { + + if(!array_key_exists($this->client->country->iso_3166_2, $this->region_codes)) { + throw new \Exception('Automatic tax calculations not supported for this country'); + } + + $this->client_region = $this->region_codes[$this->client->country->iso_3166_2]; + if($this->invoice->tax_data && $this->invoice->status_id > 1) return $this; //determine if we are taxing locally or if we are taxing globally - // $this->invoice->tax_data = $this->invoice->client->tax_data; + $this->invoice->tax_data = $this->invoice->client->tax_data ?: new Response([]); + + if(strlen($this->invoice->tax_data?->originDestination) == 0 && $this->client->company->tax_data->seller_subregion != $this->client_subregion) { + $tax_data = $this->invoice->tax_data; + $tax_data->originDestination = "D"; + $tax_data->geoState = $this->client_subregion; + $this->invoice->tax_data = $tax_data; + $this->invoice->saveQuietly(); + } return $this; } @@ -160,20 +174,25 @@ class BaseRule implements RuleInterface private function resolveRegions(): self { - if(!array_key_exists($this->client->country->iso_3166_2, $this->region_codes)) - throw new \Exception('Automatic tax calculations not supported for this country'); - - $this->client_region = $this->region_codes[$this->client->country->iso_3166_2]; - match($this->client_region){ - 'US' => $this->client_subregion = $this->tax_data->geoState, + 'US' => $this->client_subregion = strlen($this->invoice?->tax_data?->geoState) > 1 ? $this->invoice?->tax_data?->geoState : $this->getUSState(), 'EU' => $this->client_subregion = $this->client->country->iso_3166_2, + 'AU' => $this->client_subregion = 'AU', default => $this->client_subregion = $this->client->country->iso_3166_2, }; return $this; } + private function getUSState(): string + { + try { + return USStates::getState(strlen($this->client->postal_code) > 1 ? $this->client->postal_code : $this->client->shipping_postal_code); + } catch (\Exception $e) { + return $this->client->company->country()->iso_3166_2 == 'US' ? $this->client->company->tax_data->seller_subregion : 'CA'; + } + } + public function isTaxableRegion(): bool { return $this->client->company->tax_data->regions->{$this->client_region}->tax_all_subregions || $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->apply_tax; diff --git a/app/DataMapper/Tax/US/Rule.php b/app/DataMapper/Tax/US/Rule.php index 4b2789becba7..11b08ce41f81 100644 --- a/app/DataMapper/Tax/US/Rule.php +++ b/app/DataMapper/Tax/US/Rule.php @@ -143,10 +143,29 @@ class Rule extends BaseRule implements RuleInterface */ public function default(): self { - + + if($this->tax_data?->stateSalesTax == 0) { + + if($this->tax_data->originDestination == "O"){ + $tax_region = $this->client->company->tax_data->seller_subregion; + $this->tax_rate1 = $this->invoice->client->company->tax_data->regions->US->subregions->{$tax_region}->tax_rate; + $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax"; + } else { + $this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate; + $this->tax_name1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name; + + if($this->client_region == 'US') + $this->tax_name1 = "{$this->client_subregion} ".$this->tax_name1; + + } + + return $this; + } + $this->tax_rate1 = $this->tax_data->taxSales * 100; $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax"; + return $this; } diff --git a/app/DataProviders/USStates.php b/app/DataProviders/USStates.php index 265574aab2a9..d973bfe5f4e7 100644 --- a/app/DataProviders/USStates.php +++ b/app/DataProviders/USStates.php @@ -12,6 +12,8 @@ namespace App\DataProviders; +use Illuminate\Support\Facades\Http; + class USStates { protected static array $states = [ @@ -33866,11 +33868,146 @@ class USStates return self::$states; } - public static function getState(string $zip): string + public static function getState(?string $zip = '90210'): string { if(isset(self::$zip_code_map[$zip])) return self::$zip_code_map[$zip]; + $prefix_state = self::getStateFromThreeDigitPrefix($zip); + + if($prefix_state) + return $prefix_state; + + $zippo_response = self::getStateFromZippo($zip); + + if($zippo_response) + return $zippo_response; + throw new \Exception('Zip code not found'); } + + /* + { + "post code": "90210", + "country": "United States", + "country abbreviation": "US", + "places": [ + { + "place name": "Beverly Hills", + "longitude": "-118.4065", + "state": "California", + "state abbreviation": "CA", + "latitude": "34.0901" + } + ] + } + */ + public static function getStateFromZippo($zip): mixed + { + + $response = Http::get("https://api.zippopotam.us/us/{$zip}"); + + if($response->failed()) + return false; + + $data = $response->object(); + + if(isset($data->places[0])) { + return $data->places[0]->{'state abbreviation'}; + } + + return false; + + } + + public static function getStateFromThreeDigitPrefix($zip): mixed + { + + /* 000 to 999 */ + $zip_by_state = [ + '--', '--', '--', '--', '--', 'NY', 'PR', 'PR', 'VI', 'PR', 'MA', 'MA', 'MA', + 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', 'MA', + 'MA', 'MA', 'RI', 'RI', 'NH', 'NH', 'NH', 'NH', 'NH', 'NH', 'NH', 'NH', 'NH', + 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'ME', 'VT', 'VT', + 'VT', 'VT', 'VT', 'MA', 'VT', 'VT', 'VT', 'VT', 'CT', 'CT', 'CT', 'CT', 'CT', + 'CT', 'CT', 'CT', 'CT', 'CT', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', + 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'AE', + 'AE', 'AE', 'AE', 'AE', 'AE', 'AE', 'AE', 'AE', '--', 'NY', 'NY', 'NY', 'NY', + 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', + 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', + 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', + 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'NY', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', + 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', + 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', + 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', 'PA', '--', 'PA', 'PA', + 'PA', 'PA', 'DE', 'DE', 'DE', 'DC', 'VA', 'DC', 'DC', 'DC', 'DC', 'MD', 'MD', + 'MD', 'MD', 'MD', 'MD', 'MD', '--', 'MD', 'MD', 'MD', 'MD', 'MD', 'MD', 'VA', + 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', + 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', 'VA', + 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', + 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', 'WV', '--', 'NC', 'NC', 'NC', + 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', 'NC', + 'NC', 'NC', 'NC', 'NC', 'SC', 'SC', 'SC', 'SC', 'SC', 'SC', 'SC', 'SC', 'SC', + 'SC', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', + 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'GA', 'FL', 'FL', 'FL', 'FL', 'FL', + 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', 'FL', + 'FL', 'FL', 'AA', 'FL', 'FL', '--', 'FL', '--', 'FL', 'FL', '--', 'FL', 'AL', + 'AL', 'AL', '--', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', + 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', + 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'MS', 'MS', 'MS', 'MS', + 'MS', 'MS', 'MS', 'MS', 'MS', 'MS', 'MS', 'MS', 'GA', '--', 'KY', 'KY', 'KY', + 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', + 'KY', 'KY', 'KY', '--', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', 'KY', '--', + '--', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', + 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', 'OH', + 'OH', 'OH', 'OH', 'OH', '--', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', + 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'IN', 'MI', + 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', + 'MI', 'MI', 'MI', 'MI', 'MI', 'MI', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', + 'IA', 'IA', '--', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', '--', '--', '--', + 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', 'IA', '--', 'WI', 'WI', 'WI', + '--', 'WI', 'WI', '--', 'WI', 'WI', 'WI', 'WI', 'WI', 'WI', 'WI', 'WI', 'WI', + 'WI', 'WI', 'WI', 'WI', 'MN', 'MN', '--', 'MN', 'MN', 'MN', 'MN', 'MN', 'MN', + 'MN', 'MN', 'MN', 'MN', 'MN', 'MN', 'MN', 'MN', 'MN', '--', 'DC', 'SD', 'SD', + 'SD', 'SD', 'SD', 'SD', 'SD', 'SD', '--', '--', 'ND', 'ND', 'ND', 'ND', 'ND', + 'ND', 'ND', 'ND', 'ND', '--', 'MT', 'MT', 'MT', 'MT', 'MT', 'MT', 'MT', 'MT', + 'MT', 'MT', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', + 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', '--', 'IL', 'IL', + 'IL', 'IL', 'IL', 'IL', 'IL', 'IL', 'MO', 'MO', '--', 'MO', 'MO', 'MO', 'MO', + 'MO', 'MO', 'MO', 'MO', 'MO', '--', '--', 'MO', 'MO', 'MO', 'MO', 'MO', '--', + 'MO', 'MO', 'MO', 'MO', 'MO', 'MO', 'MO', 'MO', 'MO', '--', 'KS', 'KS', 'KS', + '--', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', 'KS', + 'KS', 'KS', 'KS', 'KS', 'NE', 'NE', '--', 'NE', 'NE', 'NE', 'NE', 'NE', 'NE', + 'NE', 'NE', 'NE', 'NE', 'NE', '--', '--', '--', '--', '--', '--', 'LA', 'LA', + '--', 'LA', 'LA', 'LA', 'LA', 'LA', 'LA', '--', 'LA', 'LA', 'LA', 'LA', 'LA', + '--', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', 'AR', + 'AR', 'AR', 'OK', 'OK', '--', 'TX', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', + 'OK', '--', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'TX', 'TX', 'TX', 'TX', + 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', + 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', + 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', + 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'TX', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', + 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', 'CO', '--', '--', + '--', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', 'WY', + 'ID', 'ID', 'ID', 'ID', 'ID', 'ID', 'ID', '--', 'UT', 'UT', '--', 'UT', 'UT', + 'UT', 'UT', 'UT', '--', '--', 'AZ', 'AZ', 'AZ', 'AZ', '--', 'AZ', 'AZ', 'AZ', + '--', 'AZ', 'AZ', '--', '--', 'AZ', 'AZ', 'AZ', '--', '--', '--', '--', 'NM', + 'NM', '--', 'NM', 'NM', 'NM', '--', 'NM', 'NM', 'NM', 'NM', 'NM', 'NM', 'NM', + 'NM', 'NM', '--', '--', '--', '--', 'NV', 'NV', '--', 'NV', 'NV', 'NV', '--', + 'NV', 'NV', '--', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', '--', + 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', + 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', '--', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', + 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', + 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', 'CA', + 'AP', 'AP', 'AP', 'AP', 'AP', 'HI', 'HI', 'GU', 'OR', 'OR', 'OR', 'OR', 'OR', + 'OR', 'OR', 'OR', 'OR', 'OR', 'WA', 'WA', 'WA', 'WA', 'WA', 'WA', 'WA', '--', + 'WA', 'WA', 'WA', 'WA', 'WA', 'WA', 'WA', 'AK', 'AK', 'AK', 'AK', 'AK' + ]; + + $prefix = substr($zip, 0, 3); + $index = intval($prefix); + /* converts prefix to integer */ + return $zip_by_state[$index] == "--" ? false : $zip_by_state[$index]; + + } } diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 1c0a0294e42e..65b3bd50b363 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -12,8 +12,10 @@ namespace App\Export\CSV; use App\Models\Client; -use App\Utils\Traits\MakesHash; +use App\Models\Invoice; use Illuminate\Support\Carbon; +use App\Utils\Traits\MakesHash; +use Illuminate\Database\Eloquent\Builder; class BaseExport { @@ -46,6 +48,60 @@ class BaseExport return $query; } + protected function addInvoiceStatusFilter($query, $status): Builder + { + + $status_parameters = explode(',', $status); + + + if(in_array('all', $status_parameters)) + return $query; + + $query->where(function ($nested) use ($status_parameters) { + + $invoice_filters = []; + + if (in_array('draft', $status_parameters)) { + $invoice_filters[] = Invoice::STATUS_DRAFT; + } + + if (in_array('sent', $status_parameters)) { + $invoice_filters[] = Invoice::STATUS_SENT; + } + + if (in_array('paid', $status_parameters)) { + $invoice_filters[] = Invoice::STATUS_PAID; + } + + if (in_array('unpaid', $status_parameters)) { + $invoice_filters[] = Invoice::STATUS_SENT; + $invoice_filters[] = Invoice::STATUS_PARTIAL; + } + + if (count($invoice_filters) > 0) { + $nested->whereIn('status_id', $invoice_filters); + } + + if (in_array('overdue', $status_parameters)) { + $nested->orWhereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('due_date', '<', Carbon::now()) + ->orWhere('partial_due_date', '<', Carbon::now()); + } + + if(in_array('viewed', $status_parameters)){ + + $nested->whereHas('invitations', function ($q){ + $q->whereNotNull('viewed_date')->whereNotNull('deleted_at'); + }); + + } + + + }); + + return $query; + } + protected function addDateRange($query) { $date_range = $this->input['date_range']; diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 750cbd5f60e8..2141358fa134 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -11,13 +11,15 @@ namespace App\Export\CSV; -use App\Libraries\MultiDB; +use App\Utils\Ninja; +use App\Utils\Number; +use League\Csv\Writer; use App\Models\Company; use App\Models\Invoice; -use App\Transformers\InvoiceTransformer; -use App\Utils\Ninja; +use App\Libraries\MultiDB; +use App\Export\CSV\BaseExport; use Illuminate\Support\Facades\App; -use League\Csv\Writer; +use App\Transformers\InvoiceTransformer; class InvoiceExport extends BaseExport { @@ -63,6 +65,10 @@ class InvoiceExport extends BaseExport 'terms' => 'terms', 'total_taxes' => 'total_taxes', 'currency_id' => 'currency_id', + 'payment_number' => 'payment_number', + 'payment_date' => 'payment_date', + 'payment_amount' => 'payment_amount', + 'method' => 'method', ]; private array $decorate_keys = [ @@ -107,6 +113,10 @@ class InvoiceExport extends BaseExport $query = $this->addDateRange($query); + if(isset($this->input['status'])){ + $query = $this->addInvoiceStatusFilter($query, $this->input['status']); + } + $query->cursor() ->each(function ($invoice) { $this->csv->insertOne($this->buildRow($invoice)); @@ -151,7 +161,17 @@ class InvoiceExport extends BaseExport if (in_array('status_id', $this->input['report_keys'])) { $entity['status'] = $invoice->stringStatus($invoice->status_id); } + + $payment_exists = $invoice->payments()->exists(); + $entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : ''; + + $entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : ''; + + $entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid'); + + $entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : ""; + return $entity; } } diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 41e090f520f0..78592b763c86 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -49,6 +49,7 @@ class PaymentExport extends BaseExport 'transaction_reference' => 'transaction_reference', 'type' => 'type_id', 'vendor' => 'vendor_id', + 'invoices' => 'invoices', ]; private array $decorate_keys = [ @@ -59,6 +60,7 @@ class PaymentExport extends BaseExport 'currency', 'exchange_currency', 'type', + 'invoices', ]; public function __construct(Company $company, array $input) @@ -154,6 +156,8 @@ class PaymentExport extends BaseExport $entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type'; } + $entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : ''; + return $entity; } } diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index afef994e2ba0..6c6846470378 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -148,7 +148,7 @@ class InvoiceItemSum $this->rule = new $class(); $this->rule - ->setInvoice($this->invoice) + ->setEntity($this->invoice) ->init(); $this->calc_tax = true; diff --git a/app/Http/Controllers/Reports/ARDetailReportController.php b/app/Http/Controllers/Reports/ARDetailReportController.php new file mode 100644 index 000000000000..4f6c460e7a31 --- /dev/null +++ b/app/Http/Controllers/Reports/ARDetailReportController.php @@ -0,0 +1,84 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ARDetailReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new ARDetailReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Controllers/Reports/ARSummaryReportController.php b/app/Http/Controllers/Reports/ARSummaryReportController.php new file mode 100644 index 000000000000..a9c9f6e68946 --- /dev/null +++ b/app/Http/Controllers/Reports/ARSummaryReportController.php @@ -0,0 +1,84 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ARSummaryReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new ARSummaryReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Controllers/Reports/ClientBalanceReportController.php b/app/Http/Controllers/Reports/ClientBalanceReportController.php new file mode 100644 index 000000000000..0202af3d2f68 --- /dev/null +++ b/app/Http/Controllers/Reports/ClientBalanceReportController.php @@ -0,0 +1,85 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ClientBalanceReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new ClientBalanceReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Controllers/Reports/ClientSalesReportController.php b/app/Http/Controllers/Reports/ClientSalesReportController.php new file mode 100644 index 000000000000..056b157775b3 --- /dev/null +++ b/app/Http/Controllers/Reports/ClientSalesReportController.php @@ -0,0 +1,85 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ClientSalesReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new ClientSalesReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Controllers/Reports/TaxSummaryReportController.php b/app/Http/Controllers/Reports/TaxSummaryReportController.php new file mode 100644 index 000000000000..776fa988d694 --- /dev/null +++ b/app/Http/Controllers/Reports/TaxSummaryReportController.php @@ -0,0 +1,85 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), TaxSummaryReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new TaxSummaryReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Controllers/Reports/UserSalesReportController.php b/app/Http/Controllers/Reports/UserSalesReportController.php new file mode 100644 index 000000000000..582eb4e71727 --- /dev/null +++ b/app/Http/Controllers/Reports/UserSalesReportController.php @@ -0,0 +1,85 @@ +has('send_email') && $request->get('send_email')) { + SendToAdmin::dispatch(auth()->user()->company(), $request->all(), UserSalesReport::class, $this->filename); + + return response()->json(['message' => 'working...'], 200); + } + // expect a list of visible fields, or use the default + + $export = new UserSalesReport(auth()->user()->company(), $request->all()); + + $csv = $export->run(); + + $headers = [ + 'Content-Disposition' => 'attachment', + 'Content-Type' => 'text/csv', + ]; + + return response()->streamDownload(function () use ($csv) { + echo $csv; + }, $this->filename, $headers); + } +} diff --git a/app/Http/Requests/Report/GenericReportRequest.php b/app/Http/Requests/Report/GenericReportRequest.php index 63837aa41341..3e95479d23b5 100644 --- a/app/Http/Requests/Report/GenericReportRequest.php +++ b/app/Http/Requests/Report/GenericReportRequest.php @@ -33,6 +33,7 @@ class GenericReportRequest extends Request 'start_date' => 'bail|required_if:date_range,custom|nullable|date', 'report_keys' => 'present|array', 'send_email' => 'required|bool', + 'status' => 'sometimes|string|nullable|in:all,draft,sent,viewed,paid,unpaid,overdue', ]; } diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php index b1b7c82eef12..8b1a64d2977e 100644 --- a/app/Listeners/Payment/PaymentNotification.php +++ b/app/Listeners/Payment/PaymentNotification.php @@ -56,8 +56,39 @@ class PaymentNotification implements ShouldQueue $this->trackRevenue($event); } - if($payment->is_manual) + /* Manual Payment Notifications */ + if($payment->is_manual){ + + foreach ($payment->company->company_users as $company_user) { + $user = $company_user->user; + + $methods = $this->findUserEntityNotificationType( + $payment, + $company_user, + [ + 'payment_manual', + 'payment_manual_all', + 'payment_manual_user', + 'all_notifications', ] + ); + + if (($key = array_search('mail', $methods)) !== false) { + unset($methods[$key]); + + $nmo = new NinjaMailerObject; + $nmo->mailable = new NinjaMailer((new EntityPaidObject($payment))->build()); + $nmo->company = $event->company; + $nmo->settings = $event->company->settings; + $nmo->to_user = $user; + + (new NinjaMailerJob($nmo))->handle(); + + $nmo = null; + } + } + return; + } /*User notifications*/ foreach ($payment->company->company_users as $company_user) { diff --git a/app/Services/Report/ARDetailReport.php b/app/Services/Report/ARDetailReport.php index 8c324cafa42b..c283a0a84ab0 100644 --- a/app/Services/Report/ARDetailReport.php +++ b/app/Services/Report/ARDetailReport.php @@ -40,6 +40,7 @@ class ARDetailReport extends BaseExport public array $report_keys = [ 'date', + 'due_date', 'invoice_number', 'status', 'client_name', @@ -114,6 +115,7 @@ class ARDetailReport extends BaseExport $client = $invoice->client; return [ + $this->translateDate($invoice->date, $this->company->date_format(), $this->company->locale()), $this->translateDate($invoice->due_date, $this->company->date_format(), $this->company->locale()), $invoice->number, $invoice->stringStatus($invoice->status_id), diff --git a/app/Services/Report/ClientBalanceReport.php b/app/Services/Report/ClientBalanceReport.php index 498a018a2aea..d94679776106 100644 --- a/app/Services/Report/ClientBalanceReport.php +++ b/app/Services/Report/ClientBalanceReport.php @@ -36,6 +36,7 @@ class ClientBalanceReport extends BaseExport 'client_name', 'client_number', 'id_number', + 'invoices', 'invoice_balance', 'credit_balance', ]; @@ -68,7 +69,7 @@ class ClientBalanceReport extends BaseExport $this->csv->insertOne([]); $this->csv->insertOne([]); $this->csv->insertOne([]); - $this->csv->insertOne([ctrans('texts.customer_balance_report')]); + $this->csv->insertOne([ctrans('texts.client_balance_report')]); $this->csv->insertOne([ctrans('texts.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]); if (count($this->input['report_keys']) == 0) { diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php index af7f3338a536..512fd9a15a4e 100644 --- a/app/Transformers/ClientTransformer.php +++ b/app/Transformers/ClientTransformer.php @@ -149,7 +149,7 @@ class ClientTransformer extends EntityTransformer 'number' => (string) $client->number ?: '', 'has_valid_vat_number' => (bool) $client->has_valid_vat_number, 'is_tax_exempt' => (bool) $client->is_tax_exempt, - 'tax_data' => $client->tax_data ?: '', + // 'tax_data' => $client->tax_data ?: '', ]; } } diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index 5e3b0a93955d..b76d3d6cfddb 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -149,7 +149,7 @@ class InvoiceTransformer extends EntityTransformer 'paid_to_date' => (float) $invoice->paid_to_date, 'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id), 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, - 'tax_data' => $invoice->tax_data ?: '', + // 'tax_data' => $invoice->tax_data ?: '', ]; } } diff --git a/lang/en/texts.php b/lang/en/texts.php index f8519d1424f3..565fe7684e00 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5062,6 +5062,14 @@ $LANG = array( 'here' => 'here', 'industry_Restaurant & Catering' => 'Restaurant & Catering', 'show_credits_table' => 'Show Credits Table', + 'manual_payment' => 'Payment Manual', + 'tax_summary_report' => 'Tax Summary Report', + 'tax_category' => 'Tax Category', + 'physical_goods' => 'Physical Goods', + 'digital_products' => 'Digital Products', + 'services' => 'Services', + 'shipping' => 'Shipping', + 'tax_exempt' => 'Tax Exempt', ); diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index b747f2ea6650..c15a08f77ca9 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -1333,7 +1333,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'finish_setup' => 'Terminer la configuration', 'created_wepay_confirmation_required' => 'Veuillez vérifier vos courriel et confirmer votre adresse courriel avec WePay.', 'switch_to_wepay' => 'Changer pour WePay', - 'switch' => 'Changer', + 'switch' => 'Commutateur', 'restore_account_gateway' => 'Restaurer la passerelle de paiement', 'restored_account_gateway' => 'La passerelle de paiement a été restaurée', 'united_states' => 'États-Unis', @@ -3333,7 +3333,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'accent_color' => 'Couleur de mise en évidence', 'comma_sparated_list' => 'Liste séparée par virgule', 'single_line_text' => 'Ligne de texte simple', - 'multi_line_text' => 'Multiligne de texte', + 'multi_line_text' => 'Zone de texte multiligne', 'dropdown' => 'Liste déroulante', 'field_type' => 'Type de champ', 'recover_password_email_sent' => 'Un courriel a été envoyé pour la récupération du mot de passe', @@ -4609,7 +4609,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'add' => 'Ajouter', 'last_sent_template' => 'Modèle pour dernier envoi', 'enable_flexible_search' => 'Activer la recherche flexible', - 'enable_flexible_search_help' => 'Match non-contiguous characters, ie. "ct" matches "cat"', + 'enable_flexible_search_help' => 'Correspondance de caractères non contigus, ex. "ct" pour "cat"', 'vendor_details' => 'Informations du fournisseur', 'purchase_order_details' => 'Détails du bon de commande', 'qr_iban' => 'QR IBAN', @@ -4805,7 +4805,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'task_update_authorization_error' => 'Permission d\'accès insuffisante ou tâche verrouillée', 'cash_vs_accrual' => 'Comptabilité d\'exercice', 'cash_vs_accrual_help' => 'Activer pour comptabilité d\'exercice. Désactiver pour comptabilité d\'encaisse', - 'expense_paid_report' => 'Expensed reporting', + 'expense_paid_report' => 'Rapport des déboursés', 'expense_paid_report_help' => 'Activer pour un rapport de toutes les dépenses. Désactiver pour un rapport des dépenses payées seulement', 'online_payment_email_help' => 'Envoyer un courriel lorsque un paiement en ligne à été fait', 'manual_payment_email_help' => 'Envoyer un courriel lorsque un paiement a été saisi manuellement', diff --git a/routes/api.php b/routes/api.php index 4eed06948c5f..350e547333e5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -90,12 +90,18 @@ use App\Http\Controllers\Reports\PaymentReportController; use App\Http\Controllers\Reports\ProductReportController; use App\Http\Controllers\Reports\ProfitAndLossController; use App\Http\Controllers\Reports\ActivityReportController; +use App\Http\Controllers\Reports\ARDetailReportController; use App\Http\Controllers\Reports\DocumentReportController; +use App\Http\Controllers\Reports\ARSummaryReportController; use App\Http\Controllers\Reports\QuoteItemReportController; +use App\Http\Controllers\Reports\UserSalesReportController; +use App\Http\Controllers\Reports\TaxSummaryReportController; use App\Http\Controllers\Support\Messages\SendingController; +use App\Http\Controllers\Reports\ClientSalesReportController; use App\Http\Controllers\Reports\InvoiceItemReportController; use App\Http\Controllers\PaymentNotificationWebhookController; use App\Http\Controllers\Reports\ProductSalesReportController; +use App\Http\Controllers\Reports\ClientBalanceReportController; use App\Http\Controllers\Reports\ClientContactReportController; use App\Http\Controllers\Reports\RecurringInvoiceReportController; @@ -285,7 +291,14 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('reports/product_sales', ProductSalesReportController::class); Route::post('reports/tasks', TaskReportController::class); Route::post('reports/profitloss', ProfitAndLossController::class); - + + Route::post('reports/ar_detail_report', ARDetailReportController::class); + Route::post('reports/ar_summary_report', ARSummaryReportController::class); + Route::post('reports/client_balance_report', ClientBalanceReportController::class); + Route::post('reports/client_sales_report', ClientSalesReportController::class); + Route::post('reports/tax_summary_report', TaxSummaryReportController::class); + Route::post('reports/user_sales_report', UserSalesReportController::class); + Route::resource('task_schedulers', TaskSchedulerController::class); Route::post('task_schedulers/bulk', [TaskSchedulerController::class, 'bulk'])->name('task_schedulers.bulk'); diff --git a/tests/Feature/Export/ArDetailReportTest.php b/tests/Feature/Export/ArDetailReportTest.php index 0d053f34c710..8f523a52c551 100644 --- a/tests/Feature/Export/ArDetailReportTest.php +++ b/tests/Feature/Export/ArDetailReportTest.php @@ -22,6 +22,7 @@ use App\Services\Report\ARDetailReport; use App\Services\Report\UserSalesReport; use App\Utils\Traits\MakesHash; use Illuminate\Routing\Middleware\ThrottleRequests; +use Tests\MockAccountData; use Tests\TestCase; /** @@ -44,6 +45,7 @@ class ARDetailReportTest extends TestCase ); $this->withoutExceptionHandling(); + } public $company; diff --git a/tests/Feature/Export/ReportApiTest.php b/tests/Feature/Export/ReportApiTest.php new file mode 100644 index 000000000000..14fd1f57297b --- /dev/null +++ b/tests/Feature/Export/ReportApiTest.php @@ -0,0 +1,145 @@ +faker = \Faker\Factory::create(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + + $this->makeTestData(); + + } + + public function testUserSalesReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/user_sales_report', $data) + ->assertStatus(200); + + } + + + public function testTaxSummaryReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/tax_summary_report', $data) + ->assertStatus(200); + + } + + + public function testClientSalesReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/client_sales_report', $data) + ->assertStatus(200); + + } + + + public function testArDetailReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/ar_detail_report', $data) + ->assertStatus(200); + + } + + public function testArSummaryReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/ar_summary_report', $data) + ->assertStatus(200); + + } + + public function testClientBalanceReportApiRoute() + { + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/client_balance_report', $data) + ->assertStatus(200); + + } + + +} \ No newline at end of file diff --git a/tests/Feature/Scheduler/SchedulerTest.php b/tests/Feature/Scheduler/SchedulerTest.php index 370483683738..579186e7c597 100644 --- a/tests/Feature/Scheduler/SchedulerTest.php +++ b/tests/Feature/Scheduler/SchedulerTest.php @@ -480,14 +480,14 @@ class SchedulerTest extends TestCase $c = Client::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->user->id, - 'number' => rand(1000, 100000), + 'number' => rand(1000, 10000000), 'name' => 'A fancy client' ]); $c2 = Client::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->user->id, - 'number' => rand(1000, 100000), + 'number' => rand(1000, 10000000), 'name' => 'A fancy client' ]); diff --git a/tests/Unit/Tax/EuTaxTest.php b/tests/Unit/Tax/EuTaxTest.php index 6f41d282bbc3..537cfaa5fd19 100644 --- a/tests/Unit/Tax/EuTaxTest.php +++ b/tests/Unit/Tax/EuTaxTest.php @@ -349,7 +349,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); $this->assertEquals('EU', $process->seller_region); @@ -399,11 +399,11 @@ class EuTaxTest extends TestCase 'status_id' => Invoice::STATUS_SENT, 'tax_data' => new Response([ 'geoState' => 'CA', - ]), + ]), ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); $this->assertEquals('EU', $process->seller_region); @@ -458,7 +458,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); $this->assertEquals('EU', $process->seller_region); @@ -513,7 +513,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); $this->assertInstanceOf(Rule::class, $process); @@ -564,7 +564,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); @@ -615,7 +615,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); @@ -666,7 +666,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); @@ -717,7 +717,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); $this->assertInstanceOf(Rule::class, $process); @@ -766,7 +766,7 @@ class EuTaxTest extends TestCase ]); $process = new Rule(); - $process->setInvoice($invoice); + $process->setEntity($invoice); $process->init(); diff --git a/tests/Unit/Tax/SumTaxTest.php b/tests/Unit/Tax/SumTaxTest.php index f1a4ad5b08fe..ec3b3047237f 100644 --- a/tests/Unit/Tax/SumTaxTest.php +++ b/tests/Unit/Tax/SumTaxTest.php @@ -102,10 +102,13 @@ class SumTaxTest extends TestCase $this->company->tax_data = $tax_data; $this->company->save(); + $tax_data = new TaxData($this->response); + $client = Client::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, 'country_id' => 840, + 'tax_data' => $tax_data, ]); $invoice = InvoiceFactory::create($this->company->id, $this->user->id); @@ -114,7 +117,7 @@ class SumTaxTest extends TestCase $line_items = []; - $invoice->tax_data = new TaxData($this->response); + $invoice->tax_data = $tax_data; $line_item = new InvoiceItem(); $line_item->quantity = 1; @@ -131,7 +134,6 @@ class SumTaxTest extends TestCase $line_items = $invoice->line_items; - $this->assertEquals(10, $invoice->amount); $this->assertEquals("", $line_items[0]->tax_name1); $this->assertEquals(0, $line_items[0]->tax_rate1); @@ -152,19 +154,23 @@ class SumTaxTest extends TestCase $this->company->tax_data = $tax_data; $this->company->save(); - $client = Client::factory()->create([ - 'user_id' => $this->user->id, - 'company_id' => $this->company->id, - 'country_id' => 840, - ]); +$tax_data = new TaxData($this->response); - $invoice = InvoiceFactory::create($this->company->id, $this->user->id); - $invoice->client_id = $client->id; - $invoice->uses_inclusive_taxes = false; +$client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'country_id' => 840, + 'tax_data' => $tax_data, +]); - $line_items = []; +$invoice = InvoiceFactory::create($this->company->id, $this->user->id); +$invoice->client_id = $client->id; +$invoice->uses_inclusive_taxes = false; + +$line_items = []; + +$invoice->tax_data = $tax_data; - $invoice->tax_data = new TaxData($this->response); $line_item = new InvoiceItem; $line_item->quantity = 1; diff --git a/tests/Unit/Tax/UsTaxTest.php b/tests/Unit/Tax/UsTaxTest.php index f08b295b3829..5ed356723cd2 100644 --- a/tests/Unit/Tax/UsTaxTest.php +++ b/tests/Unit/Tax/UsTaxTest.php @@ -107,6 +107,7 @@ class UsTaxTest extends TestCase 'shipping_country_id' => 840, 'has_valid_vat_number' => false, 'postal_code' => $postal_code, + 'tax_data' => new Response($this->mock_response), ]); $invoice = Invoice::factory()->create([ @@ -309,6 +310,7 @@ class UsTaxTest extends TestCase 'shipping_country_id' => 276, 'has_valid_vat_number' => false, 'postal_code' => 'xx', + 'tax_data' => new Response($this->mock_response), ]); $invoice = Invoice::factory()->create([ @@ -353,18 +355,18 @@ class UsTaxTest extends TestCase { $invoice = $this->invoiceStub('92582'); - $client = $invoice->client; - $client->is_tax_exempt = false; - $client->save(); + $invoice->client->is_tax_exempt = false; + $invoice->client->tax_data = new Response($this->mock_response); - $company = $invoice->company; - $tax_data = $company->tax_data; + $invoice->client->push(); + + $tax_data = $invoice->company->tax_data; $tax_data->regions->US->has_sales_above_threshold = true; $tax_data->regions->US->tax_all_subregions = true; - $company->tax_data = $tax_data; - $company->save(); + $invoice->company->tax_data = $tax_data; + $invoice->company->push(); $invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();