Merge pull request #8468 from turbo124/v5-develop

Fixes for reports
This commit is contained in:
David Bomba 2023-04-24 15:58:31 +10:00 committed by GitHub
commit 5796b9fd31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1036 additions and 62 deletions

View File

@ -15,6 +15,7 @@ use App\Models\Client;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Product; use App\Models\Product;
use App\DataMapper\Tax\ZipTax\Response; use App\DataMapper\Tax\ZipTax\Response;
use App\DataProviders\USStates;
class BaseRule implements RuleInterface class BaseRule implements RuleInterface
{ {
@ -116,7 +117,7 @@ class BaseRule implements RuleInterface
protected ?Client $client; protected ?Client $client;
protected ?Response $tax_data; public ?Response $tax_data;
public mixed $invoice; public mixed $invoice;
@ -129,29 +130,42 @@ class BaseRule implements RuleInterface
return $this; return $this;
} }
public function setInvoice(mixed $invoice): self public function setEntity(mixed $invoice): self
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
$this->configTaxData();
$this->client = $invoice->client; $this->client = $invoice->client;
$this->configTaxData()
->resolveRegions();
$this->tax_data = new Response($this->invoice->tax_data); $this->tax_data = new Response($this->invoice->tax_data);
$this->resolveRegions();
return $this; return $this;
} }
private function configTaxData(): self 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) if($this->invoice->tax_data && $this->invoice->status_id > 1)
return $this; return $this;
//determine if we are taxing locally or if we are taxing globally //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; return $this;
} }
@ -160,20 +174,25 @@ class BaseRule implements RuleInterface
private function resolveRegions(): self 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){ 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, '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, default => $this->client_subregion = $this->client->country->iso_3166_2,
}; };
return $this; 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 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; 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;

View File

@ -143,10 +143,29 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function default(): self 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_rate1 = $this->tax_data->taxSales * 100;
$this->tax_name1 = "{$this->tax_data->geoState} Sales Tax"; $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
return $this; return $this;
} }

View File

@ -12,6 +12,8 @@
namespace App\DataProviders; namespace App\DataProviders;
use Illuminate\Support\Facades\Http;
class USStates class USStates
{ {
protected static array $states = [ protected static array $states = [
@ -33866,11 +33868,146 @@ class USStates
return self::$states; 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])) if(isset(self::$zip_code_map[$zip]))
return 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'); 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];
}
} }

View File

@ -12,8 +12,10 @@
namespace App\Export\CSV; namespace App\Export\CSV;
use App\Models\Client; use App\Models\Client;
use App\Utils\Traits\MakesHash; use App\Models\Invoice;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Builder;
class BaseExport class BaseExport
{ {
@ -46,6 +48,60 @@ class BaseExport
return $query; 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) protected function addDateRange($query)
{ {
$date_range = $this->input['date_range']; $date_range = $this->input['date_range'];

View File

@ -11,13 +11,15 @@
namespace App\Export\CSV; 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\Company;
use App\Models\Invoice; use App\Models\Invoice;
use App\Transformers\InvoiceTransformer; use App\Libraries\MultiDB;
use App\Utils\Ninja; use App\Export\CSV\BaseExport;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use League\Csv\Writer; use App\Transformers\InvoiceTransformer;
class InvoiceExport extends BaseExport class InvoiceExport extends BaseExport
{ {
@ -63,6 +65,10 @@ class InvoiceExport extends BaseExport
'terms' => 'terms', 'terms' => 'terms',
'total_taxes' => 'total_taxes', 'total_taxes' => 'total_taxes',
'currency_id' => 'currency_id', 'currency_id' => 'currency_id',
'payment_number' => 'payment_number',
'payment_date' => 'payment_date',
'payment_amount' => 'payment_amount',
'method' => 'method',
]; ];
private array $decorate_keys = [ private array $decorate_keys = [
@ -107,6 +113,10 @@ class InvoiceExport extends BaseExport
$query = $this->addDateRange($query); $query = $this->addDateRange($query);
if(isset($this->input['status'])){
$query = $this->addInvoiceStatusFilter($query, $this->input['status']);
}
$query->cursor() $query->cursor()
->each(function ($invoice) { ->each(function ($invoice) {
$this->csv->insertOne($this->buildRow($invoice)); $this->csv->insertOne($this->buildRow($invoice));
@ -151,7 +161,17 @@ class InvoiceExport extends BaseExport
if (in_array('status_id', $this->input['report_keys'])) { if (in_array('status_id', $this->input['report_keys'])) {
$entity['status'] = $invoice->stringStatus($invoice->status_id); $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; return $entity;
} }
} }

View File

@ -49,6 +49,7 @@ class PaymentExport extends BaseExport
'transaction_reference' => 'transaction_reference', 'transaction_reference' => 'transaction_reference',
'type' => 'type_id', 'type' => 'type_id',
'vendor' => 'vendor_id', 'vendor' => 'vendor_id',
'invoices' => 'invoices',
]; ];
private array $decorate_keys = [ private array $decorate_keys = [
@ -59,6 +60,7 @@ class PaymentExport extends BaseExport
'currency', 'currency',
'exchange_currency', 'exchange_currency',
'type', 'type',
'invoices',
]; ];
public function __construct(Company $company, array $input) 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['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type';
} }
$entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : '';
return $entity; return $entity;
} }
} }

View File

@ -148,7 +148,7 @@ class InvoiceItemSum
$this->rule = new $class(); $this->rule = new $class();
$this->rule $this->rule
->setInvoice($this->invoice) ->setEntity($this->invoice)
->init(); ->init();
$this->calc_tax = true; $this->calc_tax = true;

View File

@ -0,0 +1,84 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Utils\Traits\MakesHash;
use App\Jobs\Report\SendToAdmin;
use App\Services\Report\ARDetailReport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
class ARDetailReportController extends BaseController
{
use MakesHash;
private string $filename = 'ar_detail.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
use App\Jobs\Report\SendToAdmin;
use App\Services\Report\ARSummaryReport;
use App\Utils\Traits\MakesHash;
class ARSummaryReportController extends BaseController
{
use MakesHash;
private string $filename = 'ar_summary.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Utils\Traits\MakesHash;
use App\Jobs\Report\SendToAdmin;
use App\Http\Controllers\BaseController;
use App\Services\Report\ARSummaryReport;
use App\Services\Report\ClientBalanceReport;
use App\Http\Requests\Report\GenericReportRequest;
class ClientBalanceReportController extends BaseController
{
use MakesHash;
private string $filename = 'client_balance.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Utils\Traits\MakesHash;
use App\Jobs\Report\SendToAdmin;
use App\Http\Controllers\BaseController;
use App\Services\Report\ClientSalesReport;
use App\Services\Report\ClientBalanceReport;
use App\Http\Requests\Report\GenericReportRequest;
class ClientSalesReportController extends BaseController
{
use MakesHash;
private string $filename = 'client_sales.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Utils\Traits\MakesHash;
use App\Jobs\Report\SendToAdmin;
use App\Http\Controllers\BaseController;
use App\Services\Report\TaxSummaryReport;
use App\Services\Report\ClientSalesReport;
use App\Http\Requests\Report\GenericReportRequest;
class TaxSummaryReportController extends BaseController
{
use MakesHash;
private string $filename = 'tax_summary.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Utils\Traits\MakesHash;
use App\Jobs\Report\SendToAdmin;
use App\Http\Controllers\BaseController;
use App\Services\Report\UserSalesReport;
use App\Services\Report\TaxSummaryReport;
use App\Http\Requests\Report\GenericReportRequest;
class UserSalesReportController extends BaseController
{
use MakesHash;
private string $filename = 'user_sales.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/tasks",
* operationId="getTaskReport",
* tags={"reports"},
* summary="Task reports",
* description="Export task reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->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);
}
}

View File

@ -33,6 +33,7 @@ class GenericReportRequest extends Request
'start_date' => 'bail|required_if:date_range,custom|nullable|date', 'start_date' => 'bail|required_if:date_range,custom|nullable|date',
'report_keys' => 'present|array', 'report_keys' => 'present|array',
'send_email' => 'required|bool', 'send_email' => 'required|bool',
'status' => 'sometimes|string|nullable|in:all,draft,sent,viewed,paid,unpaid,overdue',
]; ];
} }

View File

@ -56,8 +56,39 @@ class PaymentNotification implements ShouldQueue
$this->trackRevenue($event); $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; return;
}
/*User notifications*/ /*User notifications*/
foreach ($payment->company->company_users as $company_user) { foreach ($payment->company->company_users as $company_user) {

View File

@ -40,6 +40,7 @@ class ARDetailReport extends BaseExport
public array $report_keys = [ public array $report_keys = [
'date', 'date',
'due_date',
'invoice_number', 'invoice_number',
'status', 'status',
'client_name', 'client_name',
@ -114,6 +115,7 @@ class ARDetailReport extends BaseExport
$client = $invoice->client; $client = $invoice->client;
return [ return [
$this->translateDate($invoice->date, $this->company->date_format(), $this->company->locale()),
$this->translateDate($invoice->due_date, $this->company->date_format(), $this->company->locale()), $this->translateDate($invoice->due_date, $this->company->date_format(), $this->company->locale()),
$invoice->number, $invoice->number,
$invoice->stringStatus($invoice->status_id), $invoice->stringStatus($invoice->status_id),

View File

@ -36,6 +36,7 @@ class ClientBalanceReport extends BaseExport
'client_name', 'client_name',
'client_number', 'client_number',
'id_number', 'id_number',
'invoices',
'invoice_balance', 'invoice_balance',
'credit_balance', 'credit_balance',
]; ];
@ -68,7 +69,7 @@ class ClientBalanceReport extends BaseExport
$this->csv->insertOne([]); $this->csv->insertOne([]);
$this->csv->insertOne([]); $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())]); $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) { if (count($this->input['report_keys']) == 0) {

View File

@ -149,7 +149,7 @@ class ClientTransformer extends EntityTransformer
'number' => (string) $client->number ?: '', 'number' => (string) $client->number ?: '',
'has_valid_vat_number' => (bool) $client->has_valid_vat_number, 'has_valid_vat_number' => (bool) $client->has_valid_vat_number,
'is_tax_exempt' => (bool) $client->is_tax_exempt, 'is_tax_exempt' => (bool) $client->is_tax_exempt,
'tax_data' => $client->tax_data ?: '', // 'tax_data' => $client->tax_data ?: '',
]; ];
} }
} }

View File

@ -149,7 +149,7 @@ class InvoiceTransformer extends EntityTransformer
'paid_to_date' => (float) $invoice->paid_to_date, 'paid_to_date' => (float) $invoice->paid_to_date,
'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id), 'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id),
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
'tax_data' => $invoice->tax_data ?: '', // 'tax_data' => $invoice->tax_data ?: '',
]; ];
} }
} }

View File

@ -5062,6 +5062,14 @@ $LANG = array(
'here' => 'here', 'here' => 'here',
'industry_Restaurant & Catering' => 'Restaurant & Catering', 'industry_Restaurant & Catering' => 'Restaurant & Catering',
'show_credits_table' => 'Show Credits Table', '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',
); );

View File

@ -1333,7 +1333,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'finish_setup' => 'Terminer la configuration', 'finish_setup' => 'Terminer la configuration',
'created_wepay_confirmation_required' => 'Veuillez vérifier vos courriel et confirmer votre adresse courriel avec WePay.', 'created_wepay_confirmation_required' => 'Veuillez vérifier vos courriel et confirmer votre adresse courriel avec WePay.',
'switch_to_wepay' => 'Changer pour WePay', 'switch_to_wepay' => 'Changer pour WePay',
'switch' => 'Changer', 'switch' => 'Commutateur',
'restore_account_gateway' => 'Restaurer la passerelle de paiement', 'restore_account_gateway' => 'Restaurer la passerelle de paiement',
'restored_account_gateway' => 'La passerelle de paiement a été restaurée', 'restored_account_gateway' => 'La passerelle de paiement a été restaurée',
'united_states' => 'États-Unis', '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', 'accent_color' => 'Couleur de mise en évidence',
'comma_sparated_list' => 'Liste séparée par virgule', 'comma_sparated_list' => 'Liste séparée par virgule',
'single_line_text' => 'Ligne de texte simple', 'single_line_text' => 'Ligne de texte simple',
'multi_line_text' => 'Multiligne de texte', 'multi_line_text' => 'Zone de texte multiligne',
'dropdown' => 'Liste déroulante', 'dropdown' => 'Liste déroulante',
'field_type' => 'Type de champ', 'field_type' => 'Type de champ',
'recover_password_email_sent' => 'Un courriel a été envoyé pour la récupération du mot de passe', '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', 'add' => 'Ajouter',
'last_sent_template' => 'Modèle pour dernier envoi', 'last_sent_template' => 'Modèle pour dernier envoi',
'enable_flexible_search' => 'Activer la recherche flexible', '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', 'vendor_details' => 'Informations du fournisseur',
'purchase_order_details' => 'Détails du bon de commande', 'purchase_order_details' => 'Détails du bon de commande',
'qr_iban' => 'QR IBAN', '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', 'task_update_authorization_error' => 'Permission d\'accès insuffisante ou tâche verrouillée',
'cash_vs_accrual' => 'Comptabilité d\'exercice', 'cash_vs_accrual' => 'Comptabilité d\'exercice',
'cash_vs_accrual_help' => 'Activer pour comptabilité d\'exercice. Désactiver pour comptabilité d\'encaisse', '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', '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', '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', 'manual_payment_email_help' => 'Envoyer un courriel lorsque un paiement a été saisi manuellement',

View File

@ -90,12 +90,18 @@ use App\Http\Controllers\Reports\PaymentReportController;
use App\Http\Controllers\Reports\ProductReportController; use App\Http\Controllers\Reports\ProductReportController;
use App\Http\Controllers\Reports\ProfitAndLossController; use App\Http\Controllers\Reports\ProfitAndLossController;
use App\Http\Controllers\Reports\ActivityReportController; use App\Http\Controllers\Reports\ActivityReportController;
use App\Http\Controllers\Reports\ARDetailReportController;
use App\Http\Controllers\Reports\DocumentReportController; use App\Http\Controllers\Reports\DocumentReportController;
use App\Http\Controllers\Reports\ARSummaryReportController;
use App\Http\Controllers\Reports\QuoteItemReportController; 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\Support\Messages\SendingController;
use App\Http\Controllers\Reports\ClientSalesReportController;
use App\Http\Controllers\Reports\InvoiceItemReportController; use App\Http\Controllers\Reports\InvoiceItemReportController;
use App\Http\Controllers\PaymentNotificationWebhookController; use App\Http\Controllers\PaymentNotificationWebhookController;
use App\Http\Controllers\Reports\ProductSalesReportController; use App\Http\Controllers\Reports\ProductSalesReportController;
use App\Http\Controllers\Reports\ClientBalanceReportController;
use App\Http\Controllers\Reports\ClientContactReportController; use App\Http\Controllers\Reports\ClientContactReportController;
use App\Http\Controllers\Reports\RecurringInvoiceReportController; 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/product_sales', ProductSalesReportController::class);
Route::post('reports/tasks', TaskReportController::class); Route::post('reports/tasks', TaskReportController::class);
Route::post('reports/profitloss', ProfitAndLossController::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::resource('task_schedulers', TaskSchedulerController::class);
Route::post('task_schedulers/bulk', [TaskSchedulerController::class, 'bulk'])->name('task_schedulers.bulk'); Route::post('task_schedulers/bulk', [TaskSchedulerController::class, 'bulk'])->name('task_schedulers.bulk');

View File

@ -22,6 +22,7 @@ use App\Services\Report\ARDetailReport;
use App\Services\Report\UserSalesReport; use App\Services\Report\UserSalesReport;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
/** /**
@ -44,6 +45,7 @@ class ARDetailReportTest extends TestCase
); );
$this->withoutExceptionHandling(); $this->withoutExceptionHandling();
} }
public $company; public $company;

View File

@ -0,0 +1,145 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature\Export;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
*/
class ReportApiTest extends TestCase
{
use MakesHash;
use MockAccountData;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->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);
}
}

View File

@ -480,14 +480,14 @@ class SchedulerTest extends TestCase
$c = Client::factory()->create([ $c = Client::factory()->create([
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'number' => rand(1000, 100000), 'number' => rand(1000, 10000000),
'name' => 'A fancy client' 'name' => 'A fancy client'
]); ]);
$c2 = Client::factory()->create([ $c2 = Client::factory()->create([
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'number' => rand(1000, 100000), 'number' => rand(1000, 10000000),
'name' => 'A fancy client' 'name' => 'A fancy client'
]); ]);

View File

@ -349,7 +349,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
$this->assertEquals('EU', $process->seller_region); $this->assertEquals('EU', $process->seller_region);
@ -399,11 +399,11 @@ class EuTaxTest extends TestCase
'status_id' => Invoice::STATUS_SENT, 'status_id' => Invoice::STATUS_SENT,
'tax_data' => new Response([ 'tax_data' => new Response([
'geoState' => 'CA', 'geoState' => 'CA',
]), ]),
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
$this->assertEquals('EU', $process->seller_region); $this->assertEquals('EU', $process->seller_region);
@ -458,7 +458,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
$this->assertEquals('EU', $process->seller_region); $this->assertEquals('EU', $process->seller_region);
@ -513,7 +513,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
$this->assertInstanceOf(Rule::class, $process); $this->assertInstanceOf(Rule::class, $process);
@ -564,7 +564,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
@ -615,7 +615,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
@ -666,7 +666,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
@ -717,7 +717,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();
$this->assertInstanceOf(Rule::class, $process); $this->assertInstanceOf(Rule::class, $process);
@ -766,7 +766,7 @@ class EuTaxTest extends TestCase
]); ]);
$process = new Rule(); $process = new Rule();
$process->setInvoice($invoice); $process->setEntity($invoice);
$process->init(); $process->init();

View File

@ -102,10 +102,13 @@ class SumTaxTest extends TestCase
$this->company->tax_data = $tax_data; $this->company->tax_data = $tax_data;
$this->company->save(); $this->company->save();
$tax_data = new TaxData($this->response);
$client = Client::factory()->create([ $client = Client::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'country_id' => 840, 'country_id' => 840,
'tax_data' => $tax_data,
]); ]);
$invoice = InvoiceFactory::create($this->company->id, $this->user->id); $invoice = InvoiceFactory::create($this->company->id, $this->user->id);
@ -114,7 +117,7 @@ class SumTaxTest extends TestCase
$line_items = []; $line_items = [];
$invoice->tax_data = new TaxData($this->response); $invoice->tax_data = $tax_data;
$line_item = new InvoiceItem(); $line_item = new InvoiceItem();
$line_item->quantity = 1; $line_item->quantity = 1;
@ -131,7 +134,6 @@ class SumTaxTest extends TestCase
$line_items = $invoice->line_items; $line_items = $invoice->line_items;
$this->assertEquals(10, $invoice->amount); $this->assertEquals(10, $invoice->amount);
$this->assertEquals("", $line_items[0]->tax_name1); $this->assertEquals("", $line_items[0]->tax_name1);
$this->assertEquals(0, $line_items[0]->tax_rate1); $this->assertEquals(0, $line_items[0]->tax_rate1);
@ -152,19 +154,23 @@ class SumTaxTest extends TestCase
$this->company->tax_data = $tax_data; $this->company->tax_data = $tax_data;
$this->company->save(); $this->company->save();
$client = Client::factory()->create([ $tax_data = new TaxData($this->response);
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'country_id' => 840,
]);
$invoice = InvoiceFactory::create($this->company->id, $this->user->id); $client = Client::factory()->create([
$invoice->client_id = $client->id; 'user_id' => $this->user->id,
$invoice->uses_inclusive_taxes = false; '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 = new InvoiceItem;
$line_item->quantity = 1; $line_item->quantity = 1;

View File

@ -107,6 +107,7 @@ class UsTaxTest extends TestCase
'shipping_country_id' => 840, 'shipping_country_id' => 840,
'has_valid_vat_number' => false, 'has_valid_vat_number' => false,
'postal_code' => $postal_code, 'postal_code' => $postal_code,
'tax_data' => new Response($this->mock_response),
]); ]);
$invoice = Invoice::factory()->create([ $invoice = Invoice::factory()->create([
@ -309,6 +310,7 @@ class UsTaxTest extends TestCase
'shipping_country_id' => 276, 'shipping_country_id' => 276,
'has_valid_vat_number' => false, 'has_valid_vat_number' => false,
'postal_code' => 'xx', 'postal_code' => 'xx',
'tax_data' => new Response($this->mock_response),
]); ]);
$invoice = Invoice::factory()->create([ $invoice = Invoice::factory()->create([
@ -353,18 +355,18 @@ class UsTaxTest extends TestCase
{ {
$invoice = $this->invoiceStub('92582'); $invoice = $this->invoiceStub('92582');
$client = $invoice->client; $invoice->client->is_tax_exempt = false;
$client->is_tax_exempt = false; $invoice->client->tax_data = new Response($this->mock_response);
$client->save();
$company = $invoice->company; $invoice->client->push();
$tax_data = $company->tax_data;
$tax_data = $invoice->company->tax_data;
$tax_data->regions->US->has_sales_above_threshold = true; $tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true; $tax_data->regions->US->tax_all_subregions = true;
$company->tax_data = $tax_data; $invoice->company->tax_data = $tax_data;
$company->save(); $invoice->company->push();
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save(); $invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();