1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 13:12:50 +01:00

Merge pull request #8435 from turbo124/v5-develop

v5.5.104
This commit is contained in:
David Bomba 2023-04-15 08:11:57 +10:00 committed by GitHub
commit 8a9a09463b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 37975 additions and 370 deletions

View File

@ -1 +1 @@
5.5.103
5.5.104

View File

@ -0,0 +1,73 @@
<?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\DataMapper\Schedule;
class EmailReport
{
/**
* Defines the template name
*
* @var string
*/
public string $template = 'email_report';
/**
* An array of clients hashed_ids
*
* Leave blank if this action should apply to all clients
*
* @var array
*/
public array $clients = [];
/**
* The consts to be used to define the date_range variable of the statement
*/
public const LAST7 = "last7_days";
public const LAST30 = "last30_days";
public const LAST365 = "last365_days";
public const THIS_MONTH = "this_month";
public const LAST_MONTH = "last_month";
public const THIS_QUARTER = "this_quarter";
public const LAST_QUARTER = "last_quarter";
public const THIS_YEAR = "this_year";
public const LAST_YEAR = "last_year";
public const CUSTOM_RANGE = "custom";
/**
* The date range the statement should include
*
* @var string
*/
public string $date_range = 'this_month';
/**
* If a custom range is select for the date range then
* the start_date should be supplied in Y-m-d format
*
* @var string
*/
public string $start_date = '';
/**
* If a custom range is select for the date range then
* the end_date should be supplied in Y-m-d format
*
* @var string
*/
public string $end_date = '';
/** @var string $report_name */
public string $report_name = '';
}

View File

@ -11,8 +11,9 @@
namespace App\DataMapper\Tax;
use App\DataMapper\Tax\ZipTax\Response;
use App\Models\Client;
use App\Models\Product;
use App\DataMapper\Tax\ZipTax\Response;
class BaseRule implements RuleInterface
{
@ -27,34 +28,74 @@ class BaseRule implements RuleInterface
public bool $foreign_consumer_tax_exempt = true;
public string $seller_region = '';
public string $client_region = '';
public string $client_subregion = '';
public array $eu_country_codes = [
'AT', // Austria
'BE', // Belgium
'BG', // Bulgaria
'CY', // Cyprus
'CZ', // Czech Republic
'DE', // Germany
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'FI', // Finland
'FR', // France
'GR', // Greece
'HR', // Croatia
'HU', // Hungary
'IE', // Ireland
'IT', // Italy
'LT', // Lithuania
'LU', // Luxembourg
'LV', // Latvia
'MT', // Malta
'NL', // Netherlands
'PL', // Poland
'PT', // Portugal
'RO', // Romania
'SE', // Sweden
'SI', // Slovenia
'SK', // Slovakia
'AT', // Austria
'BE', // Belgium
'BG', // Bulgaria
'CY', // Cyprus
'CZ', // Czech Republic
'DE', // Germany
'DK', // Denmark
'EE', // Estonia
'ES', // Spain
'FI', // Finland
'FR', // France
'GR', // Greece
'HR', // Croatia
'HU', // Hungary
'IE', // Ireland
'IT', // Italy
'LT', // Lithuania
'LU', // Luxembourg
'LV', // Latvia
'MT', // Malta
'NL', // Netherlands
'PL', // Poland
'PT', // Portugal
'RO', // Romania
'SE', // Sweden
'SI', // Slovenia
'SK', // Slovakia
];
public array $region_codes = [
'AT' => 'EU', // Austria
'BE' => 'EU', // Belgium
'BG' => 'EU', // Bulgaria
'CY' => 'EU', // Cyprus
'CZ' => 'EU', // Czech Republic
'DE' => 'EU', // Germany
'DK' => 'EU', // Denmark
'EE' => 'EU', // Estonia
'ES' => 'EU', // Spain
'FI' => 'EU', // Finland
'FR' => 'EU', // France
'GR' => 'EU', // Greece
'HR' => 'EU', // Croatia
'HU' => 'EU', // Hungary
'IE' => 'EU', // Ireland
'IT' => 'EU', // Italy
'LT' => 'EU', // Lithuania
'LU' => 'EU', // Luxembourg
'LV' => 'EU', // Latvia
'MT' => 'EU', // Malta
'NL' => 'EU', // Netherlands
'PL' => 'EU', // Poland
'PT' => 'EU', // Portugal
'RO' => 'EU', // Romania
'SE' => 'EU', // Sweden
'SI' => 'EU', // Slovenia
'SK' => 'EU', // Slovakia
'US' => 'US', // United States
'AU' => 'AU', // Australia
];
/** EU TAXES */
@ -89,6 +130,8 @@ class BaseRule implements RuleInterface
{
$this->client = $client;
$this->resolveRegions();
return $this;
}
@ -99,12 +142,75 @@ class BaseRule implements RuleInterface
return $this;
}
public function tax($product_tax_type): self
// Refactor to support switching between shipping / billing country / region / subregion
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,
'EU' => $this->client_subregion = $this->client->country->iso_3166_2,
default => $this->client->country->iso_3166_2,
};
return $this;
}
public function taxByType($product_tax_type): self
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;
}
public function defaultForeign(): self
{
if($this->client_region == 'US') {
$this->tax_rate1 = $this->tax_data->taxSales * 100;
$this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
return $this;
}
$this->tax_rate1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = $this->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_name;
return $this;
}
public function tax($item = null): self
{
nlog($this->client_region);
nlog($this->seller_region);
if ($this->client->is_tax_exempt) {
return $this->taxExempt();
} elseif($this->client_region == $this->seller_region && $this->isTaxableRegion()) {
$this->taxByType($item->tax_id);
return $this;
} elseif($this->isTaxableRegion()) { //other regions outside of US
match($item->tax_id) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(),
Product::PRODUCT_TYPE_REDUCED_TAX => $this->taxReduced(),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $this->override(),
default => $this->defaultForeign(),
};
}
return $this;
}
public function taxByType(mixed $type): self
{
return $this;
}

View File

@ -11,97 +11,54 @@
namespace App\DataMapper\Tax\DE;
use App\Models\Client;
use App\Models\Product;
use Illuminate\Support\Str;
use App\DataMapper\Tax\BaseRule;
use App\DataMapper\Tax\RuleInterface;
use App\DataMapper\Tax\ZipTax\Response;
class Rule extends BaseRule implements RuleInterface
{
public string $vendor_country_code = 'DE';
public string $client_country_code = 'DE';
{
/** @var string $seller_region */
public string $seller_region = 'EU';
/** @var bool $consumer_tax_exempt */
public bool $consumer_tax_exempt = false;
/** @var bool $business_tax_exempt */
public bool $business_tax_exempt = false;
/** @var bool $eu_business_tax_exempt */
public bool $eu_business_tax_exempt = true;
/** @var bool $foreign_business_tax_exempt */
public bool $foreign_business_tax_exempt = true;
/** @var bool $foreign_consumer_tax_exempt */
public bool $foreign_consumer_tax_exempt = true;
public string $tax_name1 = '';
public float $tax_rate1 = 0;
public string $tax_name2 = '';
public float $tax_rate2 = 0;
/** @var float $tax_rate */
public float $tax_rate = 0;
public string $tax_name3 = '';
/** @var float $reduced_tax_rate */
public float $reduced_tax_rate = 0;
public float $tax_rate3 = 0;
public float $vat_rate = 0;
public float $reduced_vat_rate = 0;
protected ?Client $client;
protected ?Response $tax_data;
public function __construct()
{
}
/**
* Initializes the rules and builds any required data.
*
* @return self
*/
public function init(): self
{
$this->client_country_code = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : $this->client->country->iso_3166_2;
$this->calculateRates();
return $this;
}
public function setClient(Client $client): self
{
$this->client = $client;
return $this;
}
public function setTaxData(Response $tax_data): self
{
$this->tax_data = $tax_data;
return $this;
}
//need to add logic here to capture if
public function tax($type): self
{
if ($this->client->is_tax_exempt) {
return $this->taxExempt();
} elseif ($this->client->company->tax_data->regions->EU->tax_all_subregions || $this->client->company->tax_data->regions->EU->subregions->{$this->client_country_code}->apply_tax) {
$this->taxByType($type);
return $this;
}
return $this;
}
/**
* Sets the correct tax rate based on the product type.
*
* @param mixed $product_tax_type
* @return self
*/
public function taxByType($product_tax_type): self
{
@ -122,15 +79,25 @@ class Rule extends BaseRule implements RuleInterface
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function taxReduced(): self
{
$this->tax_rate1 = $this->reduced_vat_rate;
$this->tax_rate1 = $this->reduced_tax_rate;
$this->tax_name1 = 'ermäßigte MwSt.';
return $this;
}
/**
* Calculates the tax rate for a tax exempt product
*
* @return self
*/
public function taxExempt(): self
{
$this->tax_name1 = '';
@ -138,37 +105,56 @@ class Rule extends BaseRule implements RuleInterface
return $this;
}
/**
* Calculates the tax rate for a digital product
*
* @return self
*/
public function taxDigital(): self
{
// $this->tax();
return $this;
}
/**
* Calculates the tax rate for a service product
*
* @return self
*/
public function taxService(): self
{
// $this->tax();
return $this;
}
/**
* Calculates the tax rate for a shipping product
*
* @return self
*/
public function taxShipping(): self
{
// $this->tax();
return $this;
}
/**
* Calculates the tax rate for a physical product
*
* @return self
*/
public function taxPhysical(): self
{
// $this->tax();
$this->tax_rate1 = $this->vat_rate;
$this->tax_rate1 = $this->tax_rate;
$this->tax_name1 = 'MwSt.';
return $this;
}
/**
* Calculates the tax rate for a default product
*
* @return self
*/
public function default(): self
{
@ -177,54 +163,63 @@ class Rule extends BaseRule implements RuleInterface
return $this;
}
/**
* Calculates the tax rate for an override product
*
* @return self
*/
public function override(): self
{
return $this;
}
/**
* Calculates the tax rates based on the client's location.
*
* @return self
*/
public function calculateRates(): self
{
if ($this->client->is_tax_exempt) {
$this->vat_rate = 0;
$this->reduced_vat_rate = 0;
// nlog("tax exempt");
$this->tax_rate = 0;
$this->reduced_tax_rate = 0;
}
elseif($this->client_country_code != $this->vendor_country_code && in_array($this->client_country_code, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt)
elseif($this->client_subregion != $this->client->company->tax_data->seller_subregion && in_array($this->client_subregion, $this->eu_country_codes) && $this->client->has_valid_vat_number && $this->eu_business_tax_exempt)
{
$this->vat_rate = 0;
$this->reduced_vat_rate = 0;
// nlog("euro zone and tax exempt");
$this->tax_rate = 0;
$this->reduced_tax_rate = 0;
}
elseif(!in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) //foreign + tax exempt
elseif(!in_array($this->client_subregion, $this->eu_country_codes) && ($this->foreign_consumer_tax_exempt || $this->foreign_business_tax_exempt)) //foreign + tax exempt
{
$this->vat_rate = 0;
$this->reduced_vat_rate = 0;
// nlog("foreign and tax exempt");
$this->tax_rate = 0;
$this->reduced_tax_rate = 0;
}
elseif(in_array(strtoupper($this->client_country_code), $this->eu_country_codes) && !$this->client->has_valid_vat_number) //eu country / no valid vat
elseif(in_array($this->client_subregion, $this->eu_country_codes) && !$this->client->has_valid_vat_number) //eu country / no valid vat
{
if(($this->vendor_country_code != $this->client_country_code) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold)
if(($this->client->company->tax_data->seller_subregion != $this->client_subregion) && $this->client->company->tax_data->regions->EU->has_sales_above_threshold)
{
$this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->vat_rate;
$this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_vat_rate;
// nlog("eu zone with sales above threshold");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->tax_rate;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->country->iso_3166_2}->reduced_tax_rate;
}
else {
$this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->vat_rate;
$this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_vat_rate;
// nlog("same eu country with");
// nlog("EU with intra-community supply ie DE to DE");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate;
}
}
else {
$this->vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->vat_rate;
$this->reduced_vat_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_vat_rate;
// nlog("default tax");
$this->tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->tax_rate;
$this->reduced_tax_rate = $this->client->company->tax_data->regions->EU->subregions->{$this->client->company->country()->iso_3166_2}->reduced_tax_rate;
}
return $this;
}
}
}

View File

@ -11,14 +11,11 @@
namespace App\DataMapper\Tax;
use App\Models\Client;
use App\DataMapper\Tax\ZipTax\Response;
interface RuleInterface
{
public function init();
public function tax(mixed $type);
public function tax($item = null);
public function taxByType($type);
@ -38,9 +35,5 @@ interface RuleInterface
public function override();
public function setClient(Client $client);
public function setTaxData(Response $tax_data);
public function calculateRates();
}

View File

@ -13,12 +13,22 @@ namespace App\DataMapper\Tax;
class TaxModel
{
// public string $seller_region = 'US';
/** @var mixed $seller_subregion */
public string $seller_subregion = 'CA';
/** @var mixed $version */
public string $version = 'alpha';
/** @var mixed $regions */
public object $regions;
/**
* __construct
*
* @param mixed $model
* @return void
*/
public function __construct(public ?TaxModel $model = null)
{
@ -28,7 +38,12 @@ class TaxModel
$this->regions = $model;
}
/**
* Initializes the rules and builds any required data.
*
* @return void
*/
public function init()
{
$this->regions = new \stdClass();
@ -36,12 +51,51 @@ class TaxModel
$this->regions->EU = new \stdClass();
$this->usRegion()
->euRegion();
->euRegion()
->auRegion();
return $this->regions;
}
/**
* Builds the model for Australian Taxes
*
* @return self
*/
private function auRegion(): self
{
$this->regions->AU = new \stdClass();
$this->regions->AU->has_sales_above_threshold = false;
$this->regions->AU->tax_all_subregions = false;
$this->regions->AU->vat_threshold = 75000;
$this->auSubRegions();
return $this;
}
/**
* Builds the model for Australian Subregions
*
* @return self
*/
private function auSubRegions(): self
{
$this->regions->AU->subregions = new \stdClass();
$this->regions->AU->subregions->AU = new \stdClass();
$this->regions->AU->subregions->AU->apply_tax = false;
$this->regions->AU->subregions->AU->tax_rate = 10;
$this->regions->AU->subregions->AU->tax_name = 'GST';
return $this;
}
/**
* Builds the model for US Taxes
*
* @return self
*/
private function usRegion(): self
{
$this->regions->US->has_sales_above_threshold = false;
@ -50,7 +104,12 @@ class TaxModel
return $this;
}
/**
* Builds the model for EU Taxes
*
* @return self
*/
private function euRegion(): self
{
@ -61,252 +120,387 @@ class TaxModel
return $this;
}
/**
* Builds the model for US States
*
* @return self
*/
private function usSubRegions(): self
{
$this->regions->US->subregions = new \stdClass();
$this->regions->US->subregions->AL = new \stdClass();
$this->regions->US->subregions->AL->apply_tax = false;
$this->regions->US->subregions->AL->tax_rate = 4;
$this->regions->US->subregions->AL->tax_name = 'Sales Tax';
$this->regions->US->subregions->AK = new \stdClass();
$this->regions->US->subregions->AK->apply_tax = false;
$this->regions->US->subregions->AK->tax_rate = 0;
$this->regions->US->subregions->AK->tax_name = 'Sales Tax';
$this->regions->US->subregions->AZ = new \stdClass();
$this->regions->US->subregions->AZ->apply_tax = false;
$this->regions->US->subregions->AZ->tax_rate = 5.6;
$this->regions->US->subregions->AZ->tax_name = 'Sales Tax';
$this->regions->US->subregions->AR = new \stdClass();
$this->regions->US->subregions->AR->apply_tax = false;
$this->regions->US->subregions->AR->tax_rate = 6.5;
$this->regions->US->subregions->AR->tax_name = 'Sales Tax';
$this->regions->US->subregions->CA = new \stdClass();
$this->regions->US->subregions->CA->apply_tax = false;
$this->regions->US->subregions->CA->tax_rate = 7.25;
$this->regions->US->subregions->CA->tax_name = 'Sales Tax';
$this->regions->US->subregions->CO = new \stdClass();
$this->regions->US->subregions->CO->apply_tax = false;
$this->regions->US->subregions->CO->tax_rate = 2.9;
$this->regions->US->subregions->CO->tax_name = 'Sales Tax';
$this->regions->US->subregions->CT = new \stdClass();
$this->regions->US->subregions->CT->apply_tax = false;
$this->regions->US->subregions->CT->tax_rate = 6.35;
$this->regions->US->subregions->CT->tax_name = 'Sales Tax';
$this->regions->US->subregions->DE = new \stdClass();
$this->regions->US->subregions->DE->apply_tax = false;
$this->regions->US->subregions->DE->tax_rate = 0;
$this->regions->US->subregions->DE->tax_name = 'Sales Tax';
$this->regions->US->subregions->FL = new \stdClass();
$this->regions->US->subregions->FL->apply_tax = false;
$this->regions->US->subregions->FL->tax_rate = 6;
$this->regions->US->subregions->FL->tax_name = 'Sales Tax';
$this->regions->US->subregions->GA = new \stdClass();
$this->regions->US->subregions->GA->apply_tax = false;
$this->regions->US->subregions->GA->tax_rate = 4;
$this->regions->US->subregions->GA->tax_name = 'Sales Tax';
$this->regions->US->subregions->HI = new \stdClass();
$this->regions->US->subregions->HI->apply_tax = false;
$this->regions->US->subregions->HI->tax_rate = 4;
$this->regions->US->subregions->HI->tax_name = 'Sales Tax';
$this->regions->US->subregions->ID = new \stdClass();
$this->regions->US->subregions->ID->apply_tax = false;
$this->regions->US->subregions->ID->tax_rate = 6;
$this->regions->US->subregions->ID->tax_name = 'Sales Tax';
$this->regions->US->subregions->IL = new \stdClass();
$this->regions->US->subregions->IL->apply_tax = false;
$this->regions->US->subregions->IL->tax_rate = 6.25;
$this->regions->US->subregions->IL->tax_name = 'Sales Tax';
$this->regions->US->subregions->IN = new \stdClass();
$this->regions->US->subregions->IN->apply_tax = false;
$this->regions->US->subregions->IN->tax_rate = 7;
$this->regions->US->subregions->IN->tax_name = 'Sales Tax';
$this->regions->US->subregions->IA = new \stdClass();
$this->regions->US->subregions->IA->apply_tax = false;
$this->regions->US->subregions->IA->tax_rate = 6;
$this->regions->US->subregions->IA->tax_name = 'Sales Tax';
$this->regions->US->subregions->KS = new \stdClass();
$this->regions->US->subregions->KS->apply_tax = false;
$this->regions->US->subregions->KS->tax_rate = 6.5;
$this->regions->US->subregions->KS->tax_name = 'Sales Tax';
$this->regions->US->subregions->KY = new \stdClass();
$this->regions->US->subregions->KY->apply_tax = false;
$this->regions->US->subregions->KY->tax_rate = 6;
$this->regions->US->subregions->KY->tax_name = 'Sales Tax';
$this->regions->US->subregions->LA = new \stdClass();
$this->regions->US->subregions->LA->apply_tax = false;
$this->regions->US->subregions->LA->tax_rate = 4.45;
$this->regions->US->subregions->LA->tax_name = 'Sales Tax';
$this->regions->US->subregions->ME = new \stdClass();
$this->regions->US->subregions->ME->apply_tax = false;
$this->regions->US->subregions->ME->tax_rate = 5.5;
$this->regions->US->subregions->ME->tax_name = 'Sales Tax';
$this->regions->US->subregions->MD = new \stdClass();
$this->regions->US->subregions->MD->apply_tax = false;
$this->regions->US->subregions->MD->tax_rate = 6;
$this->regions->US->subregions->MD->tax_name = 'Sales Tax';
$this->regions->US->subregions->MA = new \stdClass();
$this->regions->US->subregions->MA->apply_tax = false;
$this->regions->US->subregions->MA->tax_rate = 6.25;
$this->regions->US->subregions->MA->tax_name = 'Sales Tax';
$this->regions->US->subregions->MI = new \stdClass();
$this->regions->US->subregions->MI->apply_tax = false;
$this->regions->US->subregions->MI->tax_rate = 6;
$this->regions->US->subregions->MI->tax_name = 'Sales Tax';
$this->regions->US->subregions->MN = new \stdClass();
$this->regions->US->subregions->MN->apply_tax = false;
$this->regions->US->subregions->MN->tax_rate = 6.875;
$this->regions->US->subregions->MN->tax_name = 'Sales Tax';
$this->regions->US->subregions->MS = new \stdClass();
$this->regions->US->subregions->MS->apply_tax = false;
$this->regions->US->subregions->MS->tax_rate = 7;
$this->regions->US->subregions->MS->tax_name = 'Sales Tax';
$this->regions->US->subregions->MO = new \stdClass();
$this->regions->US->subregions->MO->apply_tax = false;
$this->regions->US->subregions->MO->tax_rate = 4.225;
$this->regions->US->subregions->MO->tax_name = 'Sales Tax';
$this->regions->US->subregions->MT = new \stdClass();
$this->regions->US->subregions->MT->apply_tax = false;
$this->regions->US->subregions->MT->tax_rate = 0;
$this->regions->US->subregions->MT->tax_name = 'Sales Tax';
$this->regions->US->subregions->NE = new \stdClass();
$this->regions->US->subregions->NE->apply_tax = false;
$this->regions->US->subregions->NE->tax_rate = 5.5;
$this->regions->US->subregions->NE->tax_name = 'Sales Tax';
$this->regions->US->subregions->NV = new \stdClass();
$this->regions->US->subregions->NV->apply_tax = false;
$this->regions->US->subregions->NV->tax_rate = 6.85;
$this->regions->US->subregions->NV->tax_name = 'Sales Tax';
$this->regions->US->subregions->NH = new \stdClass();
$this->regions->US->subregions->NH->apply_tax = false;
$this->regions->US->subregions->NH->tax_rate = 0;
$this->regions->US->subregions->NH->tax_name = 'Sales Tax';
$this->regions->US->subregions->NJ = new \stdClass();
$this->regions->US->subregions->NJ->apply_tax = false;
$this->regions->US->subregions->NJ->tax_rate = 6.625;
$this->regions->US->subregions->NJ->tax_name = 'Sales Tax';
$this->regions->US->subregions->NM = new \stdClass();
$this->regions->US->subregions->NM->apply_tax = false;
$this->regions->US->subregions->NM->tax_rate = 5.125;
$this->regions->US->subregions->NM->tax_name = 'Sales Tax';
$this->regions->US->subregions->NY = new \stdClass();
$this->regions->US->subregions->NY->apply_tax = false;
$this->regions->US->subregions->NY->tax_rate = 4;
$this->regions->US->subregions->NY->tax_name = 'Sales Tax';
$this->regions->US->subregions->NC = new \stdClass();
$this->regions->US->subregions->NC->apply_tax = false;
$this->regions->US->subregions->NC->tax_rate = 4.75;
$this->regions->US->subregions->NC->tax_name = 'Sales Tax';
$this->regions->US->subregions->ND = new \stdClass();
$this->regions->US->subregions->ND->apply_tax = false;
$this->regions->US->subregions->ND->tax_rate = 5;
$this->regions->US->subregions->ND->tax_name = 'Sales Tax';
$this->regions->US->subregions->OH = new \stdClass();
$this->regions->US->subregions->OH->apply_tax = false;
$this->regions->US->subregions->OH->tax_rate = 5.75;
$this->regions->US->subregions->OH->tax_name = 'Sales Tax';
$this->regions->US->subregions->OK = new \stdClass();
$this->regions->US->subregions->OK->apply_tax = false;
$this->regions->US->subregions->OK->tax_rate = 4.5;
$this->regions->US->subregions->OK->tax_name = 'Sales Tax';
$this->regions->US->subregions->OR = new \stdClass();
$this->regions->US->subregions->OR->apply_tax = false;
$this->regions->US->subregions->OR->tax_rate = 0;
$this->regions->US->subregions->OR->tax_name = 'Sales Tax';
$this->regions->US->subregions->PA = new \stdClass();
$this->regions->US->subregions->PA->apply_tax = false;
$this->regions->US->subregions->PA->tax_rate = 6;
$this->regions->US->subregions->PA->tax_name = 'Sales Tax';
$this->regions->US->subregions->RI = new \stdClass();
$this->regions->US->subregions->RI->apply_tax = false;
$this->regions->US->subregions->RI->tax_rate = 7;
$this->regions->US->subregions->RI->tax_name = 'Sales Tax';
$this->regions->US->subregions->SC = new \stdClass();
$this->regions->US->subregions->SC->apply_tax = false;
$this->regions->US->subregions->SC->tax_rate = 6;
$this->regions->US->subregions->SC->tax_name = 'Sales Tax';
$this->regions->US->subregions->SD = new \stdClass();
$this->regions->US->subregions->SD->apply_tax = false;
$this->regions->US->subregions->SD->tax_rate = 4.5;
$this->regions->US->subregions->SD->tax_name = 'Sales Tax';
$this->regions->US->subregions->TN = new \stdClass();
$this->regions->US->subregions->TN->apply_tax = false;
$this->regions->US->subregions->TN->tax_rate = 7;
$this->regions->US->subregions->TN->tax_name = 'Sales Tax';
$this->regions->US->subregions->TX = new \stdClass();
$this->regions->US->subregions->TX->apply_tax = false;
$this->regions->US->subregions->TX->tax_rate = 6.25;
$this->regions->US->subregions->TX->tax_name = 'Sales Tax';
$this->regions->US->subregions->UT = new \stdClass();
$this->regions->US->subregions->UT->apply_tax = false;
$this->regions->US->subregions->UT->tax_rate = 5.95;
$this->regions->US->subregions->UT->tax_name = 'Sales Tax';
$this->regions->US->subregions->VT = new \stdClass();
$this->regions->US->subregions->VT->apply_tax = false;
$this->regions->US->subregions->VT->tax_rate = 6;
$this->regions->US->subregions->VT->tax_name = 'Sales Tax';
$this->regions->US->subregions->VA = new \stdClass();
$this->regions->US->subregions->VA->apply_tax = false;
$this->regions->US->subregions->VA->tax_rate = 5.3;
$this->regions->US->subregions->VA->tax_name = 'Sales Tax';
$this->regions->US->subregions->WA = new \stdClass();
$this->regions->US->subregions->WA->apply_tax = false;
$this->regions->US->subregions->WA->tax_rate = 6.5;
$this->regions->US->subregions->WA->tax_name = 'Sales Tax';
$this->regions->US->subregions->WV = new \stdClass();
$this->regions->US->subregions->WV->apply_tax = false;
$this->regions->US->subregions->WV->tax_rate = 6;
$this->regions->US->subregions->WV->tax_name = 'Sales Tax';
$this->regions->US->subregions->WI = new \stdClass();
$this->regions->US->subregions->WI->apply_tax = false;
$this->regions->US->subregions->WI->tax_rate = 5;
$this->regions->US->subregions->WI->tax_name = 'Sales Tax';
$this->regions->US->subregions->WY = new \stdClass();
$this->regions->US->subregions->WY->apply_tax = false;
$this->regions->US->subregions->WY->tax_rate = 4;
$this->regions->US->subregions->WY->tax_name = 'Sales Tax';
return $this;
}
/**
* Create the EU member countries
*
* @return self
*/
private function euSubRegions(): self
{
$this->regions->EU->subregions = new \stdClass();
$this->regions->EU->subregions->AT = new \stdClass();
$this->regions->EU->subregions->AT->vat_rate = 21;
$this->regions->EU->subregions->AT->reduced_vat_rate = 11;
$this->regions->EU->subregions->AT->tax_rate = 21;
$this->regions->EU->subregions->AT->tax_name = 'USt';
$this->regions->EU->subregions->AT->reduced_tax_rate = 11;
$this->regions->EU->subregions->AT->apply_tax = false;
$this->regions->EU->subregions->BE = new \stdClass();
$this->regions->EU->subregions->BE->vat_rate = 21;
$this->regions->EU->subregions->BE->reduced_vat_rate = 6;
$this->regions->EU->subregions->BE->tax_rate = 21;
$this->regions->EU->subregions->BE->tax_name = 'BTW';
$this->regions->EU->subregions->BE->reduced_tax_rate = 6;
$this->regions->EU->subregions->BE->apply_tax = false;
$this->regions->EU->subregions->BG = new \stdClass();
$this->regions->EU->subregions->BG->vat_rate = 20;
$this->regions->EU->subregions->BG->reduced_vat_rate = 9;
$this->regions->EU->subregions->BG->tax_rate = 20;
$this->regions->EU->subregions->BG->tax_name = 'НДС';
$this->regions->EU->subregions->BG->reduced_tax_rate = 9;
$this->regions->EU->subregions->BG->apply_tax = false;
$this->regions->EU->subregions->CY = new \stdClass();
$this->regions->EU->subregions->CY->vat_rate = 19;
$this->regions->EU->subregions->CY->reduced_vat_rate = 9;
$this->regions->EU->subregions->CY->tax_rate = 19;
$this->regions->EU->subregions->CY->tax_name = 'ΦΠΑ';
$this->regions->EU->subregions->CY->reduced_tax_rate = 9;
$this->regions->EU->subregions->CY->apply_tax = false;
$this->regions->EU->subregions->CZ = new \stdClass();
$this->regions->EU->subregions->CZ->vat_rate = 21;
$this->regions->EU->subregions->CZ->reduced_vat_rate = 15;
$this->regions->EU->subregions->CZ->tax_rate = 21;
$this->regions->EU->subregions->CZ->tax_name = 'DPH';
$this->regions->EU->subregions->CZ->reduced_tax_rate = 15;
$this->regions->EU->subregions->CZ->apply_tax = false;
$this->regions->EU->subregions->DE = new \stdClass();
$this->regions->EU->subregions->DE->vat_rate = 19;
$this->regions->EU->subregions->DE->reduced_vat_rate = 7;
$this->regions->EU->subregions->DE->tax_rate = 19;
$this->regions->EU->subregions->DE->tax_name = 'MwSt';
$this->regions->EU->subregions->DE->reduced_tax_rate = 7;
$this->regions->EU->subregions->DE->apply_tax = false;
$this->regions->EU->subregions->DK = new \stdClass();
$this->regions->EU->subregions->DK->vat_rate = 25;
$this->regions->EU->subregions->DK->reduced_vat_rate = 0;
$this->regions->EU->subregions->DK->tax_rate = 25;
$this->regions->EU->subregions->DK->reduced_tax_rate = 0;
$this->regions->EU->subregions->DK->apply_tax = false;
$this->regions->EU->subregions->EE = new \stdClass();
$this->regions->EU->subregions->EE->vat_rate = 20;
$this->regions->EU->subregions->EE->reduced_vat_rate = 9;
$this->regions->EU->subregions->EE->tax_rate = 20;
$this->regions->EU->subregions->EE->tax_name = 'KM';
$this->regions->EU->subregions->EE->reduced_tax_rate = 9;
$this->regions->EU->subregions->EE->apply_tax = false;
$this->regions->EU->subregions->ES = new \stdClass();
$this->regions->EU->subregions->ES->vat_rate = 21;
$this->regions->EU->subregions->ES->reduced_vat_rate = 10;
$this->regions->EU->subregions->ES->tax_rate = 21;
$this->regions->EU->subregions->ES->tax_name = 'IVA';
$this->regions->EU->subregions->ES->reduced_tax_rate = 10;
$this->regions->EU->subregions->ES->apply_tax = false;
$this->regions->EU->subregions->FI = new \stdClass();
$this->regions->EU->subregions->FI->vat_rate = 24;
$this->regions->EU->subregions->FI->reduced_vat_rate = 14;
$this->regions->EU->subregions->FI->tax_rate = 24;
$this->regions->EU->subregions->FI->tax_name = 'ALV';
$this->regions->EU->subregions->FI->reduced_tax_rate = 14;
$this->regions->EU->subregions->FI->apply_tax = false;
$this->regions->EU->subregions->FR = new \stdClass();
$this->regions->EU->subregions->FR->vat_rate = 20;
$this->regions->EU->subregions->FR->reduced_vat_rate = 5.5;
$this->regions->EU->subregions->FR->tax_rate = 20;
$this->regions->EU->subregions->FR->tax_name = 'TVA';
$this->regions->EU->subregions->FR->reduced_tax_rate = 5.5;
$this->regions->EU->subregions->FR->apply_tax = false;
// $this->regions->EU->subregions->GB = new \stdClass();
// $this->regions->EU->subregions->GB->vat_rate = 20;
// $this->regions->EU->subregions->GB->reduced_vat_rate = 0;
// $this->regions->EU->subregions->GB->tax_rate = 20;
// $this->regions->EU->subregions->GB->reduced_tax_rate = 0;
// $this->regions->EU->subregions->GB->apply_tax = false;
$this->regions->EU->subregions->GR = new \stdClass();
$this->regions->EU->subregions->GR->vat_rate = 24;
$this->regions->EU->subregions->GR->reduced_vat_rate = 13;
$this->regions->EU->subregions->GR->tax_rate = 24;
$this->regions->EU->subregions->GR->tax_name = 'ΦΠΑ';
$this->regions->EU->subregions->GR->reduced_tax_rate = 13;
$this->regions->EU->subregions->GR->apply_tax = false;
$this->regions->EU->subregions->HR = new \stdClass();
$this->regions->EU->subregions->HR->vat_rate = 25;
$this->regions->EU->subregions->HR->reduced_vat_rate = 5;
$this->regions->EU->subregions->HR->tax_rate = 25;
$this->regions->EU->subregions->HR->tax_name = 'PDV';
$this->regions->EU->subregions->HR->reduced_tax_rate = 5;
$this->regions->EU->subregions->HR->apply_tax = false;
$this->regions->EU->subregions->HU = new \stdClass();
$this->regions->EU->subregions->HU->vat_rate = 27;
$this->regions->EU->subregions->HU->reduced_vat_rate = 5;
$this->regions->EU->subregions->HU->tax_rate = 27;
$this->regions->EU->subregions->HU->tax_name = 'ÁFA';
$this->regions->EU->subregions->HU->reduced_tax_rate = 5;
$this->regions->EU->subregions->HU->apply_tax = false;
$this->regions->EU->subregions->IE = new \stdClass();
$this->regions->EU->subregions->IE->vat_rate = 23;
$this->regions->EU->subregions->IE->reduced_vat_rate = 0;
$this->regions->EU->subregions->IE->tax_rate = 23;
$this->regions->EU->subregions->IE->tax_name = 'VAT';
$this->regions->EU->subregions->IE->reduced_tax_rate = 0;
$this->regions->EU->subregions->IE->apply_tax = false;
$this->regions->EU->subregions->IT = new \stdClass();
$this->regions->EU->subregions->IT->vat_rate = 22;
$this->regions->EU->subregions->IT->reduced_vat_rate = 10;
$this->regions->EU->subregions->IT->tax_rate = 22;
$this->regions->EU->subregions->IT->tax_name = 'IVA';
$this->regions->EU->subregions->IT->reduced_tax_rate = 10;
$this->regions->EU->subregions->IT->apply_tax = false;
$this->regions->EU->subregions->LT = new \stdClass();
$this->regions->EU->subregions->LT->vat_rate = 21;
$this->regions->EU->subregions->LT->reduced_vat_rate = 9;
$this->regions->EU->subregions->LT->tax_rate = 21;
$this->regions->EU->subregions->LT->tax_name = 'PVM';
$this->regions->EU->subregions->LT->reduced_tax_rate = 9;
$this->regions->EU->subregions->LT->apply_tax = false;
$this->regions->EU->subregions->LU = new \stdClass();
$this->regions->EU->subregions->LU->vat_rate = 17;
$this->regions->EU->subregions->LU->reduced_vat_rate = 3;
$this->regions->EU->subregions->LU->tax_rate = 17;
$this->regions->EU->subregions->LU->tax_name = 'TVA';
$this->regions->EU->subregions->LU->reduced_tax_rate = 3;
$this->regions->EU->subregions->LU->apply_tax = false;
$this->regions->EU->subregions->LV = new \stdClass();
$this->regions->EU->subregions->LV->vat_rate = 21;
$this->regions->EU->subregions->LV->reduced_vat_rate = 12;
$this->regions->EU->subregions->LV->tax_rate = 21;
$this->regions->EU->subregions->LV->tax_name = 'PVN';
$this->regions->EU->subregions->LV->reduced_tax_rate = 12;
$this->regions->EU->subregions->LV->apply_tax = false;
$this->regions->EU->subregions->MT = new \stdClass();
$this->regions->EU->subregions->MT->vat_rate = 18;
$this->regions->EU->subregions->MT->reduced_vat_rate = 5;
$this->regions->EU->subregions->MT->tax_rate = 18;
$this->regions->EU->subregions->MT->tax_name = 'VAT';
$this->regions->EU->subregions->MT->reduced_tax_rate = 5;
$this->regions->EU->subregions->MT->apply_tax = false;
$this->regions->EU->subregions->NL = new \stdClass();
$this->regions->EU->subregions->NL->vat_rate = 21;
$this->regions->EU->subregions->NL->reduced_vat_rate = 9;
$this->regions->EU->subregions->NL->tax_rate = 21;
$this->regions->EU->subregions->NL->tax_name = 'BTW';
$this->regions->EU->subregions->NL->reduced_tax_rate = 9;
$this->regions->EU->subregions->NL->apply_tax = false;
$this->regions->EU->subregions->PT = new \stdClass();
$this->regions->EU->subregions->PT->vat_rate = 23;
$this->regions->EU->subregions->PT->reduced_vat_rate = 6;
$this->regions->EU->subregions->PT->tax_rate = 23;
$this->regions->EU->subregions->PT->tax_name = 'IVA';
$this->regions->EU->subregions->PT->reduced_tax_rate = 6;
$this->regions->EU->subregions->PT->apply_tax = false;
$this->regions->EU->subregions->RO = new \stdClass();
$this->regions->EU->subregions->RO->vat_rate = 19;
$this->regions->EU->subregions->RO->reduced_vat_rate = 5;
$this->regions->EU->subregions->RO->tax_rate = 19;
$this->regions->EU->subregions->RO->tax_name = 'TVA';
$this->regions->EU->subregions->RO->reduced_tax_rate = 5;
$this->regions->EU->subregions->RO->apply_tax = false;
$this->regions->EU->subregions->SE = new \stdClass();
$this->regions->EU->subregions->SE->vat_rate = 25;
$this->regions->EU->subregions->SE->reduced_vat_rate = 12;
$this->regions->EU->subregions->SE->tax_rate = 25;
$this->regions->EU->subregions->SE->tax_name = 'Moms';
$this->regions->EU->subregions->SE->reduced_tax_rate = 12;
$this->regions->EU->subregions->SE->apply_tax = false;
$this->regions->EU->subregions->SI = new \stdClass();
$this->regions->EU->subregions->SI->vat_rate = 22;
$this->regions->EU->subregions->SI->reduced_vat_rate = 9.5;
$this->regions->EU->subregions->SI->tax_rate = 22;
$this->regions->EU->subregions->SI->tax_name = 'DDV';
$this->regions->EU->subregions->SI->reduced_tax_rate = 9.5;
$this->regions->EU->subregions->SI->apply_tax = false;
$this->regions->EU->subregions->SK = new \stdClass();
$this->regions->EU->subregions->SK->vat_rate = 20;
$this->regions->EU->subregions->SK->reduced_vat_rate = 10;
$this->regions->EU->subregions->SK->tax_rate = 20;
$this->regions->EU->subregions->SK->tax_name = 'DPH';
$this->regions->EU->subregions->SK->reduced_tax_rate = 10;
$this->regions->EU->subregions->SK->apply_tax = false;
return $this;

View File

@ -11,71 +11,53 @@
namespace App\DataMapper\Tax\US;
use App\Models\Client;
use App\Models\Product;
use App\DataMapper\Tax\BaseRule;
use App\DataMapper\Tax\RuleInterface;
use App\DataMapper\Tax\ZipTax\Response;
use App\Models\Product;
class Rule implements RuleInterface
/**
* The rules apply US => US taxes using the tax calculator.
*
* US => Foreign taxes we check the product types still for exemptions, and we all back to the client country tax rate.
*/
class Rule extends BaseRule implements RuleInterface
{
public string $tax_name1 = '';
public float $tax_rate1 = 0;
public string $tax_name2 = '';
public float $tax_rate2 = 0;
/** @var string $seller_region */
public string $seller_region = 'US';
public string $tax_name3 = '';
public float $tax_rate3 = 0;
/**
* Initializes the rules and builds any required data.
*
* @return self
*/
public function init(): self
{
$this->calculateRates();
return $this;
}
public ?Client $client;
public ?Response $tax_data;
public function __construct()
/**
* Override tax class, we use this when we do not modify the input taxes
*
* @return self
*/
public function override(): self
{
}
public function override()
{
return $this;
}
public function setTaxData(Response $tax_data): self
{
$this->tax_data = $tax_data;
return $this;
}
public function setClient(Client $client):self
{
$this->client = $client;
return $this;
}
public function tax($type): self
{
if ($this->client->is_tax_exempt) {
return $this->taxExempt();
}
else if($this->client->company->tax_data->regions->US->tax_all_subregions || $this->client->company->tax_data->regions->US->subregions->{$this->tax_data->geoState}->apply_tax){
$this->taxByType($type);
return $this;
}
return $this;
}
/**
* Sets the correct tax rate based on the product type.
*
* @param mixed $product_tax_type
* @return self
*/
public function taxByType($product_tax_type): self
{
match($product_tax_type){
match($product_tax_type) {
Product::PRODUCT_TYPE_EXEMPT => $this->taxExempt(),
Product::PRODUCT_TYPE_DIGITAL => $this->taxDigital(),
Product::PRODUCT_TYPE_SERVICE => $this->taxService(),
@ -88,7 +70,12 @@ class Rule implements RuleInterface
return $this;
}
/**
* Sets the tax as exempt (0)
*
* @return self
*/
public function taxExempt(): self
{
$this->tax_name1 = '';
@ -96,37 +83,64 @@ class Rule implements RuleInterface
return $this;
}
/**
* Calculates the tax rate for a digital product
*
* @return self
*/
public function taxDigital(): self
{
$this->default();
return $this;
}
/**
* Calculates the tax rate for a service product
*
* @return self
*/
public function taxService(): self
{
if($this->tax_data->txbService == 'Y')
if($this->tax_data->txbService == 'Y') {
$this->default();
}
return $this;
}
/**
* Calculates the tax rate for a shipping product
*
* @return self
*/
public function taxShipping(): self
{
if($this->tax_data->txbFreight == 'Y')
if($this->tax_data->txbFreight == 'Y') {
$this->default();
}
return $this;
}
/**
* Calculates the tax rate for a physical product
*
* @return self
*/
public function taxPhysical(): self
{
$this->default();
return $this;
}
/**
* Calculates the tax rate for an undefined product uses the default tax rate for the client county
*
* @return self
*/
public function default(): self
{
@ -135,19 +149,24 @@ class Rule implements RuleInterface
return $this;
}
/**
* Calculates the tax rate for a reduced tax product
*
* @return self
*/
public function taxReduced(): self
{
$this->default();
return $this;
}
public function init(): self
{
return $this;
}
/**
* Calculates the tax rates to be applied
*
* @return self
*/
public function calculateRates(): self
{
return $this;

View File

@ -57,7 +57,7 @@ class Response
* ];
*
*/
public string $seller_region = "";
public string $seller_subregion = "";
//US
public string $geoPostalCode = "";

View File

@ -108,7 +108,6 @@ region:
tax_all: false
vat_threshold: 10000
has_sales_above_threshold: false
seller_region: DE
subregions:
AT:
vat: 21

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,11 @@ class BaseExport
$this->client_description = $client->present()->name;
return $query->where('client_id', $this->input['client_id']);
}
elseif(isset($this->input['clients']) && count($this->input['clients']) > 0) {
$this->client_description = 'Multiple Clients';
return $query->whereIn('client_id', $this->input['clients']);
}
return $query;
}
@ -87,6 +91,10 @@ class BaseExport
$this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter()->format('Y-m-d');
$this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter()->format('Y-m-d');
return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-6 months'))->firstOfQuarter(), (new \Carbon\Carbon('-6 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC');
case 'last365_days':
$this->start_date = now()->startOfDay()->subDays(365)->format('Y-m-d');
$this->end_date = now()->startOfDay()->format('Y-m-d');
return $query->whereBetween($this->date_key, [now()->subDays(365), now()])->orderBy($this->date_key, 'ASC');
case 'this_year':
$this->start_date = now()->startOfYear()->format('Y-m-d');
$this->end_date = now()->format('Y-m-d');
@ -102,7 +110,7 @@ class BaseExport
}
}
protected function buildHeader() :array
public function buildHeader() :array
{
$header = [];

View File

@ -97,8 +97,6 @@ class ProductSalesExport extends BaseExport
}
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
@ -109,6 +107,8 @@ class ProductSalesExport extends BaseExport
$query = $this->filterByClients($query);
$this->csv->insertOne($this->buildHeader());
$query->cursor()
->each(function ($invoice) {
foreach ($invoice->line_items as $item) {

View File

@ -22,7 +22,7 @@ class PaymentFilters extends QueryFilters
/**
* Filter based on search text.
*
* @param string query filter
* @param string $filter
* @return Builder
* @deprecated
*/
@ -35,6 +35,8 @@ class PaymentFilters extends QueryFilters
return $this->builder->where(function ($query) use ($filter) {
$query->where('amount', 'like', '%'.$filter.'%')
->orWhere('date', 'like', '%'.$filter.'%')
->orWhere('number','like', '%'.$filter.'%')
->owWhere('transaction_reference', 'like', '%'.$filter.'%')
->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%')
@ -55,7 +57,7 @@ class PaymentFilters extends QueryFilters
* - partially refunded
* - refunded
*
* @param string client_status The payment status as seen by the client
* @param string $value The payment status as seen by the client
* @return Builder
*/
public function client_status(string $value = ''): Builder

View File

@ -54,7 +54,9 @@ class InvoiceItemSum
'SI', // Slovenia
'SK', // Slovakia
'US', //USA
'US', // USA
'AU', // Australia
];
protected $invoice;
@ -200,7 +202,7 @@ class InvoiceItemSum
*/
private function calcTaxesAutomatically(): self
{
$this->rule->tax($this->item->tax_id ?? null);
$this->rule->tax($this->item);
$this->item->tax_name1 = $this->rule->tax_name1;
$this->item->tax_rate1 = $this->rule->tax_rate1;

View File

@ -274,6 +274,7 @@ class TaskController extends BaseController
$old_task = json_decode(json_encode($task));
$task = $this->task_repo->save($request->all(), $task);
$task = $this->task_repo->triggeredActions($request, $task);
if ($task->status_order != $old_task->status_order) {

View File

@ -736,4 +736,18 @@ class UserController extends BaseController
return $this->itemResponse($user->fresh());
}
public function disconnectOauth(DisconnectUserMailerRequest $request, User $user)
{
$user->oauth_user_id = null;
$user->oauth_provider_id = null;
$user->oauth_user_token_expiry = null;
$user->oauth_user_token = null;
$user->oauth_user_refresh_token = null;
$user->save();
return $this->itemResponse($user->fresh());
}
}

View File

@ -84,7 +84,7 @@ class UpdateTaskRequest extends Request
public function prepareForValidation()
{
$input = $this->decodePrimaryKeys($this->all());
if (array_key_exists('status_id', $input) && is_string($input['status_id'])) {
$input['status_id'] = $this->decodePrimaryKey($input['status_id']);
}

View File

@ -45,6 +45,8 @@ class StoreSchedulerRequest extends Request
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
'parameters.entity_id' => ['bail', 'sometimes', 'string'],
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report', 'in:ar_summary_report,ar_detail_report,tax_summary_report,user_sales_report,client_sales_report,client_balance_report,product_sales_report'],
'parameters.date_key' => ['bail','sometimes', 'string'],
];
return $rules;

View File

@ -40,6 +40,10 @@ class UpdateSchedulerRequest extends Request
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
'parameters.entity_id' => ['bail', 'sometimes', 'string'],
'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report', 'in:ar_summary_report,ar_detail_report,tax_summary_report,user_sales_report,client_sales_report,client_balance_report,product_sales_report'],
'parameters.date_key' => ['bail','sometimes', 'string'],
];
return $rules;

View File

@ -28,7 +28,8 @@ class BlackListRule implements Rule
'dataservices.space',
'karenkey.com',
'sharklasers.com',
'100072641.help'
'100072641.help',
'yandex.com',
];
/**

View File

@ -426,7 +426,9 @@ class BaseImport
$message = $ex->getMessage();
} else {
report($ex);
$message = 'Unknown error';
$message = 'Unknown error ';
nlog($ex->getMessage());
nlog($raw_invoice);
}
$this->error_array['invoice'][] = [

View File

@ -11,24 +11,26 @@
namespace App\Import\Transformer;
use App\Factory\ExpenseCategoryFactory;
use App\Factory\ProjectFactory;
use App\Factory\VendorFactory;
use App\Models\Quote;
use App\Utils\Number;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Vendor;
use App\Models\Country;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Invoice;
use App\Models\PaymentType;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\TaxRate;
use App\Models\Vendor;
use App\Utils\Number;
use App\Models\PaymentType;
use App\Models\ClientContact;
use App\Factory\ClientFactory;
use App\Factory\VendorFactory;
use Illuminate\Support\Carbon;
use App\Factory\ProjectFactory;
use App\Models\ExpenseCategory;
use Illuminate\Support\Facades\Cache;
use App\Repositories\ClientRepository;
use App\Factory\ExpenseCategoryFactory;
/**
* Class BaseTransformer.
@ -129,7 +131,28 @@ class BaseTransformer
}
}
return null;
$client_repository = app()->make(ClientRepository::class);
$client_repository->import_mode = true;
$client = $client_repository->save(
[
'name' => $client_name,
'contacts' => [
[
'first_name' => $client_name,
'email' => $client_email,
],
],
],
ClientFactory::create(
$this->company->id,
$this->company->owner()->id
)
);
$client_repository = null;
return $client->id;
}
///////////////////////////////////////////////////////////////////////////////////
@ -249,9 +272,9 @@ class BaseTransformer
/**
* @param $email
*
* @return ?Contact
* @return ?ClientContact
*/
public function getContact($email)
public function getContact($email): ?ClientContact
{
$contact = ClientContact::where('company_id', $this->company->id)
->whereRaw("LOWER(REPLACE(`email`, ' ' ,'')) = ?", [

View File

@ -61,8 +61,14 @@ class InvoiceTransformer extends BaseTransformer
],
];
$client_id =
$this->getClient($this->getString($invoice_data, 'Name'), $this->getString($invoice_data, 'EmailRecipient'));
$client_id = null;
if($this->hasClient($this->getString($invoice_data, 'Name') || $this->getContact($this->getString($invoice_data, 'EmailRecipient'))))
{
$client_id = $this->getClient($this->getString($invoice_data, 'Name'), $this->getString($invoice_data, 'EmailRecipient'));
}
if ($client_id) {
$transformed['client_id'] = $client_id;

View File

@ -161,7 +161,7 @@ class MatchBankTransactions implements ShouldQueue
foreach($_expenses as $_expense) {
$expense = Expense::withTrashed()
->where('id', $this->decodePrimaryKey($_expense))
->where('id', $this->decodePrimaryKey($_expense))
->where('company_id', $this->bt->company_id)
->first();

View File

@ -148,8 +148,8 @@ class CompanyExport implements ShouldQueue
$this->export_data['clients'] = $this->company->clients()->orderBy('number', 'DESC')->cursor()->map(function ($client) {
$client = $this->transformArrayOfKeys($client, ['company_id', 'user_id', 'assigned_user_id', 'group_settings_id']);
return $client->makeVisible(['id','private_notes','user_id','company_id','last_login','hashed_id']);
$client->tax_data = '';
return $client->makeVisible(['id','private_notes','user_id','company_id','last_login','hashed_id'])->makeHidden(['is_tax_exempt','has_valid_vat_number']);
})->all();
@ -229,6 +229,7 @@ class CompanyExport implements ShouldQueue
$this->export_data['invoices'] = $this->company->invoices()->orderBy('number', 'DESC')->cursor()->map(function ($invoice) {
$invoice = $this->transformBasicEntities($invoice);
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','project_id']);
$invoice->tax_data = '';
return $invoice->makeVisible(['id',
'private_notes',

View File

@ -1338,7 +1338,8 @@ class CompanyImport implements ShouldQueue
unset($obj_array[$un]);
}
if ($class instanceof CompanyGateway) {
if ($class == 'App\Models\CompanyGateway') {
if (Ninja::isHosted() && $obj_array['gateway_key'] == 'd14dd26a37cecc30fdd65700bfb55b23') {
$obj_array['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34';
}

View File

@ -162,6 +162,14 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Company> $companies
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankIntegration> $bank_integrations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Company> $companies
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankIntegration> $bank_integrations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Company> $companies
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @mixin \Eloquent
*/
class Account extends BaseModel

View File

@ -40,6 +40,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankSubaccount> $bank_subaccounts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankSubaccount> $bank_subaccounts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankSubaccount> $bank_subaccounts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankSubaccount> $bank_subaccounts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankSubaccount> $bank_subaccounts
* @mixin \Eloquent
*/
class BankAccount extends BaseModel

View File

@ -83,6 +83,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $transactions
* @mixin \Eloquent
*/
class BankIntegration extends BaseModel

View File

@ -308,6 +308,44 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property int $has_valid_vat_number
* @property string $leitweg_id
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Project> $projects
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringExpense> $recurring_expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @method static \Illuminate\Database\Eloquent\Builder|Client whereHasValidVatNumber($value)
* @method static \Illuminate\Database\Eloquent\Builder|Client whereLeitwegId($value)
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Project> $projects
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringExpense> $recurring_expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @mixin \Eloquent
*/
class Client extends BaseModel implements HasLocalePreference

View File

@ -163,6 +163,16 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $quote_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $recurring_invoice_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CreditInvitation> $credit_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation> $invoice_invitations
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $quote_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $recurring_invoice_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CreditInvitation> $credit_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation> $invoice_invitations
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $quote_invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $recurring_invoice_invitations
* @mixin \Eloquent
*/
class ClientContact extends Authenticatable implements HasLocalePreference

View File

@ -639,6 +639,99 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Vendor> $vendors
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
* @property int $tax_all_products
* @property int $use_xinvoice
* @property string $xinvoice_type
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $all_activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $all_documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankIntegration> $bank_integrations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransactionRule> $bank_transaction_rules
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $bank_transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $client_contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyGateway> $company_gateways
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Design> $designs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ExpenseCategory> $expense_categories
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GroupSetting> $group_settings
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GroupSetting> $groups
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentTerm> $payment_terms
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Product> $products
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Project> $projects
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrder> $purchase_orders
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringExpense> $recurring_expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Scheduler> $schedulers
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Subscription> $subscriptions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_log_relation
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Scheduler> $task_schedulers
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaskStatus> $task_statuses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaxRate> $tax_rates
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens_hashed
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Design> $user_designs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentTerm> $user_payment_terms
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Vendor> $vendors
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
* @method static \Illuminate\Database\Eloquent\Builder|Company whereUseXinvoice($value)
* @method static \Illuminate\Database\Eloquent\Builder|Company whereXinvoiceType($value)
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $all_activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $all_documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankIntegration> $bank_integrations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransactionRule> $bank_transaction_rules
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\BankTransaction> $bank_transactions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $client_contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyGateway> $company_gateways
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Design> $designs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ExpenseCategory> $expense_categories
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GroupSetting> $group_settings
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GroupSetting> $groups
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentTerm> $payment_terms
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Product> $products
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Project> $projects
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrder> $purchase_orders
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringExpense> $recurring_expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Scheduler> $schedulers
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Subscription> $subscriptions
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_log_relation
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Scheduler> $task_schedulers
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaskStatus> $task_statuses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\TaxRate> $tax_rates
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens_hashed
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Design> $user_designs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentTerm> $user_payment_terms
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Vendor> $vendors
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Webhook> $webhooks
* @mixin \Eloquent
*/
class Company extends BaseModel

View File

@ -100,6 +100,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $client_gateway_tokens
* @mixin \Eloquent
*/
class CompanyGateway extends BaseModel

View File

@ -91,6 +91,12 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $token
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $token
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $token
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @mixin \Eloquent
*/
class CompanyUser extends Pivot

View File

@ -241,6 +241,20 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CreditInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CreditInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CreditInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @mixin \Eloquent
*/
class Credit extends BaseModel

View File

@ -138,6 +138,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \App\Models\BankTransaction|null $transaction
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class Expense extends BaseModel

View File

@ -36,6 +36,8 @@ namespace App\Models;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentType> $payment_methods
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentType> $payment_methods
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentType> $payment_methods
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentType> $payment_methods
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PaymentType> $payment_methods
* @mixin \Eloquent
*/
class GatewayType extends StaticModel

View File

@ -70,6 +70,10 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class GroupSetting extends StaticModel

View File

@ -277,6 +277,24 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @mixin \Eloquent
*/
class Invoice extends BaseModel

View File

@ -172,6 +172,16 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Paymentable> $paymentables
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Paymentable> $paymentables
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Paymentable> $paymentables
* @mixin \Eloquent
*/
class Payment extends BaseModel

View File

@ -106,6 +106,8 @@ use League\CommonMark\CommonMarkConverter;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class Product extends BaseModel

View File

@ -88,6 +88,12 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property int|null $current_hours
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @method static \Illuminate\Database\Eloquent\Builder|Project whereCurrentHours($value)
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @mixin \Eloquent
*/
class Project extends BaseModel

View File

@ -36,6 +36,8 @@ use App\Utils\Traits\MakesHash;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class Proposal extends BaseModel

View File

@ -228,6 +228,18 @@ use Illuminate\Support\Facades\Storage;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @mixin \Eloquent
*/
class PurchaseOrder extends BaseModel

View File

@ -213,6 +213,14 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @mixin \Eloquent
*/
class Quote extends BaseModel

View File

@ -147,6 +147,8 @@ use Illuminate\Support\Carbon;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class RecurringExpense extends BaseModel

View File

@ -219,6 +219,16 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoiceInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @mixin \Eloquent
*/
class RecurringInvoice extends BaseModel

View File

@ -211,6 +211,16 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringQuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringQuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringQuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @mixin \Eloquent
*/
class RecurringQuote extends BaseModel

View File

@ -76,6 +76,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|Scheduler whereUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder|Scheduler withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Scheduler withoutTrashed()
* @property-read \App\Models\User $user
* @mixin \Eloquent
*/
class Scheduler extends BaseModel
@ -122,6 +123,11 @@ class Scheduler extends BaseModel
return $this->belongsTo(Company::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
/**
* remainingCycles
*

View File

@ -99,6 +99,8 @@ use Illuminate\Support\Carbon;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @mixin \Eloquent
*/
class Task extends BaseModel

View File

@ -195,6 +195,20 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Company> $companies
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Client> $clients
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Company> $companies
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyUser> $company_users
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyToken> $tokens
* @mixin \Eloquent
*/
class User extends Authenticatable implements MustVerifyEmail
@ -243,6 +257,7 @@ class User extends Authenticatable implements MustVerifyEmail
'custom_value3',
'custom_value4',
'is_deleted',
'oauth_user_token',
];
/**

View File

@ -136,6 +136,14 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @mixin \Eloquent
*/
class Vendor extends BaseModel

View File

@ -126,6 +126,10 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $purchase_order_invitations
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $purchase_order_invitations
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $purchase_order_invitations
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\PurchaseOrderInvitation> $purchase_order_invitations
* @mixin \Eloquent
*/
class VendorContact extends Authenticatable implements HasLocalePreference

View File

@ -35,7 +35,7 @@ class TaskRepository extends BaseRepository
*
* @return task|null task Object
*/
public function save(array $data, Task $task) : ?Task
public function save(array $data, Task $task): ?Task
{
if ($task->id) {
$this->new_task = false;
@ -97,8 +97,12 @@ class TaskRepository extends BaseRepository
} else {
$time_log = [];
}
$key_values = array_column($time_log, 0);
array_multisort($key_values, SORT_ASC, $time_log);
array_multisort($time_log);
// array_multisort($time_log);
// ksort($time_log);
if (isset($data['action'])) {
if ($data['action'] == 'start') {
@ -118,8 +122,6 @@ class TaskRepository extends BaseRepository
}
$task->time_log = json_encode($time_log);
// $task->start_time = $task->start_time ?: $task->calcStartTime();
// $task->duration = $task->calcDuration();
$task->saveQuietly();
@ -206,10 +208,12 @@ class TaskRepository extends BaseRepository
$last = end($log);
if (is_array($last) && $last[1] !== 0) {
if (is_array($last) && $last[1] !== 0) { // this line is a disaster
$new = [time(), 0];
$log = array_merge($log, [$new]);
$task->time_log = json_encode($log);
$task->saveQuietly();
}
@ -226,7 +230,7 @@ class TaskRepository extends BaseRepository
$last[1] = time();
array_pop($log);
$log = array_merge($log, [$last]);
$log = array_merge($log, [$last]);//check at this point, it may be prepending here.
$task->time_log = json_encode($log);
$task->saveQuietly();

View File

@ -0,0 +1,141 @@
<?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\Services\Report;
use Carbon\Carbon;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Models\Client;
use League\Csv\Writer;
use App\Models\Company;
use App\Models\Invoice;
use App\Libraries\MultiDB;
use App\Export\CSV\BaseExport;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
class ARDetailReport extends BaseExport
{
use MakesDates;
//Date
//Invoice #
//Status
//Customer
//Age - Days
//Amount
//Balance
public Writer $csv;
public string $date_key = 'created_at';
public array $report_keys = [
'date',
'invoice_number',
'status',
'client_name',
'client_number',
'id_number',
'age',
'amount',
'balance',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([ctrans('texts.aged_receivable_detailed_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) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
$query = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->where('balance', '>', 0)
->orderBy('due_date', 'ASC')
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]);
$query = $this->addDateRange($query);
$query = $this->filterByClients($query);
$this->csv->insertOne($this->buildHeader());
$query->cursor()
->each(function ($invoice) {
$this->csv->insertOne($this->buildRow($invoice));
});
return $this->csv->toString();
}
private function buildRow(Invoice $invoice): array
{
$client = $invoice->client;
return [
$this->translateDate($invoice->due_date, $this->company->date_format(), $this->company->locale()),
$invoice->number,
$invoice->stringStatus($invoice->status_id),
$client->present()->name(),
$client->number,
$client->id_number,
Carbon::parse($invoice->due_date)->diffInDays(now()),
Number::formatMoney($invoice->amount, $client),
Number::formatMoney($invoice->balance, $client),
];
}
public function buildHeader() :array
{
$header = [];
foreach ($this->input['report_keys'] as $value) {
$header[] = ctrans("texts.{$value}");
}
return $header;
}
}

View File

@ -0,0 +1,212 @@
<?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\Services\Report;
use App\Export\CSV\BaseExport;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use Carbon\Carbon;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
class ARSummaryReport extends BaseExport
{
use MakesDates;
public Writer $csv;
public string $date_key = 'created_at';
public Client $client;
public array $report_keys = [
'client_name',
'client_number',
'id_number',
'current',
'age_group_0',
'age_group_30',
'age_group_60',
'age_group_90',
'age_group_120',
'total',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([ctrans('texts.aged_receivable_summary_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) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->cursor()
->each(function ($client) {
$this->csv->insertOne($this->buildRow($client));
});
return $this->csv->toString();
}
private function buildRow(Client $client): array
{
$this->client = $client;
return [
$this->client->present()->name(),
$this->client->number,
$this->client->id_number,
$this->getCurrent(),
$this->getAgingAmount('30'),
$this->getAgingAmount('60'),
$this->getAgingAmount('90'),
$this->getAgingAmount('120'),
$this->getAgingAmount('120+'),
];
}
private function getCurrent(): string
{
$amount = Invoice::withTrashed()
->where('client_id', $this->client->id)
->where('company_id', $this->client->company_id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->where('is_deleted', 0)
->where('due_date', '<', now()->startOfDay())
->sum('balance');
return Number::formatMoney($amount, $this->client);
}
/**
* Generate aging amount.
*
* @param mixed $range
* @return string
*/
private function getAgingAmount($range)
{
$ranges = $this->calculateDateRanges($range);
$from = $ranges[0];
$to = $ranges[1];
$amount = Invoice::withTrashed()
->where('client_id', $this->client->id)
->where('company_id', $this->client->company_id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->where('is_deleted', 0)
->whereBetween('due_date', [$to, $from])
->sum('balance');
return Number::formatMoney($amount, $this->client);
}
/**
* Calculate date ranges for aging.
*
* @param mixed $range
* @return array
*/
private function calculateDateRanges($range)
{
$ranges = [];
switch ($range) {
case '30':
$ranges[0] = now()->startOfDay();
$ranges[1] = now()->startOfDay()->subDays(30);
return $ranges;
case '60':
$ranges[0] = now()->startOfDay()->subDays(30);
$ranges[1] = now()->startOfDay()->subDays(60);
return $ranges;
case '90':
$ranges[0] = now()->startOfDay()->subDays(60);
$ranges[1] = now()->startOfDay()->subDays(90);
return $ranges;
case '120':
$ranges[0] = now()->startOfDay()->subDays(90);
$ranges[1] = now()->startOfDay()->subDays(120);
return $ranges;
case '120+':
$ranges[0] = now()->startOfDay()->subDays(120);
$ranges[1] = now()->startOfDay()->subYears(20);
return $ranges;
default:
$ranges[0] = now()->startOfDay()->subDays(0);
$ranges[1] = now()->subDays(30);
return $ranges;
}
}
public function buildHeader() :array
{
$header = [];
foreach ($this->input['report_keys'] as $value) {
$header[] = ctrans("texts.{$value}");
}
return $header;
}
}

View File

@ -0,0 +1,121 @@
<?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\Services\Report;
use App\Utils\Ninja;
use App\Models\Client;
use League\Csv\Writer;
use App\Models\Company;
use App\Models\Invoice;
use App\Libraries\MultiDB;
use App\Export\CSV\BaseExport;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
class ClientBalanceReport extends BaseExport
{
use MakesDates;
//Name
//Invoice count
//Amount
//Amount with Tax
public Writer $csv;
public string $date_key = 'created_at';
public array $report_keys = [
'client_name',
'client_number',
'id_number',
'invoice_balance',
'credit_balance',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$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.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]);
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->cursor()
->each(function ($client){
$this->csv->insertOne($this->buildRow($client));
});
return $this->csv->toString();
}
public function buildHeader(): array
{
$headers = [];
foreach($this->report_keys as $key)
$headers[] = ctrans("texts.{$key}");
return $headers;
}
private function buildRow(Client $client): array
{
$query = Invoice::where('client_id', $client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]);
$query = $this->addDateRange($query);
return [
$client->present()->name(),
$client->number,
$client->id_number,
$query->count(),
$query->sum('balance'),
$client->credit_balance,
];
}
}

View File

@ -0,0 +1,136 @@
<?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\Services\Report;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Models\Client;
use League\Csv\Writer;
use App\Models\Company;
use App\Models\Invoice;
use App\Libraries\MultiDB;
use App\Export\CSV\BaseExport;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
class ClientSalesReport extends BaseExport
{
use MakesDates;
//Name
//Invoice count
//Amount
//Amount with Tax
public Writer $csv;
public string $date_key = 'created_at';
public array $report_keys = [
'client_name',
'client_number',
'id_number',
'invoices',
'amount',
'balance',
'total_taxes',
'amount_paid',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([ctrans('texts.client_sales_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) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->cursor()
->each(function ($client) {
$this->csv->insertOne($this->buildRow($client));
});
return $this->csv->toString();
}
private function buildRow(Client $client): array
{
$query = Invoice::where('client_id', $client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]);
$query = $this->addDateRange($query);
$amount = $query->sum('amount');
$balance = $query->sum('balance');
$paid = $amount-$balance;
return [
$client->present()->name(),
$client->number,
$client->id_number,
$query->count(),
Number::formatMoney($amount, $client),
Number::formatMoney($balance, $client),
Number::formatMoney($query->sum('total_taxes'), $client),
Number::formatMoney($amount-$balance, $client),
];
}
public function buildHeader() :array
{
$header = [];
foreach ($this->input['report_keys'] as $value) {
$header[] = ctrans("texts.{$value}");
}
return $header;
}
}

View File

@ -0,0 +1,135 @@
<?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\Services\Report;
use App\Export\CSV\BaseExport;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
class TaxSummaryReport extends BaseExport
{
use MakesDates;
//Name
//Invoice count
//Amount
//Amount with Tax
public Writer $csv;
public string $date_key = 'created_at';
public array $report_keys = [
'tax_name',
// 'taxable_amount',
'tax_amount',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([]);
$this->csv->insertOne([ctrans('texts.tax_summary')]);
$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) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
$query = Invoice::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc');
$query = $this->addDateRange($query);
$query = $this->filterByClients($query);
$map = [];
foreach($query->cursor() as $invoice)
{
$taxes = $invoice->calc()->getTaxMap();
foreach($taxes as $tax)
{
$key = $tax['name'];
if(!isset($map[$key])) {
$map[$key]['tax_amount'] = 0;
// $map[$key]['taxable_amount'] = 0;
}
$map[$key]['tax_amount'] += $tax['total'];
// $map[$key]['taxable_amount'] += $invoice->amount;
}
}
foreach($map as $key => $value)
{
$this->csv->insertOne([$key, Number::formatMoney($value['tax_amount'], $this->company)]);
// $this->csv->insertOne([$key, Number::formatMoney($value['taxable_amount'], $this->company), Number::formatMoney($value['tax_amount'], $this->company)]);
}
return $this->csv->toString();
}
public function buildHeader() :array
{
$header = [];
foreach ($this->input['report_keys'] as $value) {
$header[] = ctrans("texts.{$value}");
}
return $header;
}
}

View File

@ -0,0 +1,120 @@
<?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\Services\Report;
use App\Models\User;
use App\Utils\Ninja;
use App\Utils\Number;
use League\Csv\Writer;
use App\Models\Company;
use App\Models\Invoice;
use App\Libraries\MultiDB;
use App\Export\CSV\BaseExport;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
class UserSalesReport extends BaseExport
{
use MakesDates;
//Name
//Invoice count
//Amount
//Amount with Tax
public Writer $csv;
public string $date_key = 'created_at';
public array $report_keys = [
'name',
'invoices',
'invoice_amount',
'total_taxes',
];
/**
@param array $input
[
'date_range',
'start_date',
'end_date',
'clients',
'client_id',
]
*/
public function __construct(public Company $company, public array $input)
{
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->csv = Writer::createFromString();
$query = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]);
$query = $this->addDateRange($query);
$query = $this->filterByClients($query);
$this->csv->insertOne([ctrans('texts.user_sales_report_header', ['client' => $this->client_description, 'start_date' => $this->start_date, 'end_date' => $this->end_date])]);
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = $this->report_keys;
}
$this->csv->insertOne($this->buildHeader());
$users = $this->company->users;
$report = $users->map(function ($user) use($query){
$new_query = $query;
$new_query->where('user_id', $user->id);
return [
$user->present()->name(),
$new_query->count(),
Number::formatMoney($new_query->sum('amount'), $this->company),
Number::formatMoney($new_query->sum('total_taxes'), $this->company),
];
})->toArray();
$key_values = array_column($report, 1);
array_multisort($key_values, SORT_DESC, $report);
$this->csv->insertAll($report);
return $this->csv->toString();
}
public function buildHeader() :array
{
$header = [];
foreach ($this->input['report_keys'] as $value) {
$header[] = ctrans("texts.{$value}");
}
return $header;
}
}

View File

@ -27,7 +27,10 @@ class EmailRecord
{
$class = 'App\\Models\\' . Str::camel($this->scheduler->parameters['entity']);
$class::find($this->decodePrimaryKey($this->scheduler->parameters['entity_id']))->service()->sendEmail();
$entity = $class::find($this->decodePrimaryKey($this->scheduler->parameters['entity_id']));
if($entity)
$entity->service()->sendEmail();
$this->scheduler->forceDelete();
}

View File

@ -0,0 +1,133 @@
<?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\Services\Scheduler;
use App\Models\Client;
use App\Models\Scheduler;
use App\Mail\DownloadReport;
use App\Export\CSV\TaskExport;
use App\Export\CSV\QuoteExport;
use App\Utils\Traits\MakesHash;
use App\Export\CSV\ClientExport;
use App\Export\CSV\CreditExport;
use App\Utils\Traits\MakesDates;
use App\Export\CSV\ContactExport;
use App\Export\CSV\ExpenseExport;
use App\Export\CSV\InvoiceExport;
use App\Export\CSV\PaymentExport;
use App\Export\CSV\ProductExport;
use App\Jobs\Mail\NinjaMailerJob;
use App\Export\CSV\DocumentExport;
use App\Export\CSV\QuoteItemExport;
use App\Services\Report\ProfitLoss;
use App\Jobs\Mail\NinjaMailerObject;
use App\Export\CSV\InvoiceItemExport;
use App\Export\CSV\ProductSalesExport;
use App\Services\Report\ARDetailReport;
use App\Services\Report\ARSummaryReport;
use App\Services\Report\UserSalesReport;
use App\Services\Report\TaxSummaryReport;
use App\Export\CSV\RecurringInvoiceExport;
use App\Services\Report\ClientSalesReport;
use App\Services\Report\ClientBalanceReport;
class EmailReport
{
use MakesHash;
use MakesDates;
private Client $client;
private bool $multiple_clients = false;
private string $file_name = 'file.csv';
public function __construct(public Scheduler $scheduler)
{
}
public function run()
{
$start_end_dates = $this->calculateStartAndEndDates();
$data = [];
$data = [
'start_date' => $start_end_dates[0],
'end_date' => $start_end_dates[1],
'date_range' => 'custom',
'client_id' => null,
'report_keys' => []
];
if (count($this->scheduler->parameters['clients']) >= 1) {
$data['clients'] = $this->transformKeys($this->scheduler->parameters['clients']);
}
$export = false;
match($this->scheduler->parameters['report_name'])
{
'product_sales_report' => $export = (new ProductSalesExport($this->scheduler->company, $data)),
'email_ar_detailed_report' => $export = (new ARDetailReport($this->scheduler->company, $data)),
'email_ar_summary_report' => $export = (new ARSummaryReport($this->scheduler->company, $data)),
'email_tax_summary_report' => $export = (new TaxSummaryReport($this->scheduler->company, $data)),
'email_client_balance_report' => $export = (new ClientBalanceReport($this->scheduler->company, $data)),
'email_client_sales_report' => $export = (new ClientSalesReport($this->scheduler->company, $data)),
'email_user_sales_report' => $export = (new UserSalesReport($this->scheduler->company, $data)),
'clients' => $export = (new ClientExport($this->scheduler->company, $data)),
'client_contacts' => $export = (new ContactExport($this->scheduler->company, $data)),
'credits' => $export = (new CreditExport($this->scheduler->company, $data)),
'documents' => $export = (new DocumentExport($this->scheduler->company, $data)),
'expenses' => $export = (new ExpenseExport($this->scheduler->company, $data)),
'invoices' => $export = (new InvoiceExport($this->scheduler->company, $data)),
'invoice_items' => $export = (new InvoiceItemExport($this->scheduler->company, $data)),
'quotes' => $export = (new QuoteExport($this->scheduler->company, $data)),
'quote_items' => $export = (new QuoteItemExport($this->scheduler->company, $data)),
'recurring_invoices' => $export = (new RecurringInvoiceExport($this->scheduler->company, $data)),
'payments' => $export = (new PaymentExport($this->scheduler->company, $data)),
'products' => $export = (new ProductExport($this->scheduler->company, $data)),
'tasks' => $export = (new TaskExport($this->scheduler->company, $data)),
'profitloss' => $export = (new ProfitLoss($this->scheduler->company, $data)),
default => $export = false,
};
if(!$export) {
$this->cancelSchedule();
return;
}
$csv = $export->run();
//todo - potentially we send this to more than one user.
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadReport($this->scheduler->company, $csv, $this->file_name);
$nmo->company = $this->scheduler->company;
$nmo->settings = $this->scheduler->company->settings;
$nmo->to_user = $this->scheduler->user;
NinjaMailerJob::dispatch($nmo);
//calculate next run dates;
$this->scheduler->calculateNextRun();
}
private function cancelSchedule()
{
$this->scheduler->forceDelete();
}
}

View File

@ -15,10 +15,12 @@ use App\Models\Client;
use App\Models\Scheduler;
use App\Utils\Traits\MakesHash;
use App\DataMapper\Schedule\EmailStatement;
use App\Utils\Traits\MakesDates;
class EmailStatementService
{
use MakesHash;
use MakesDates;
private Client $client;

View File

@ -12,8 +12,9 @@
namespace App\Services\Scheduler;
use App\Models\Scheduler;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use App\Services\Scheduler\EmailReport;
class SchedulerService
{
@ -43,12 +44,16 @@ class SchedulerService
(new EmailRecord($this->scheduler))->run();
}
private function email_statement()
{
(new EmailStatementService($this->scheduler))->run();
}
private function email_report()
{
(new EmailReport($this->scheduler))->run();
}
/**
* Sets the next run date of the scheduled task

View File

@ -11,9 +11,10 @@
namespace App\Utils\Traits;
use Carbon\Carbon;
use DateTime;
use DateTimeZone;
use Carbon\Carbon;
use App\DataMapper\Schedule\EmailStatement;
/**
* Class MakesDates.
@ -112,4 +113,27 @@ trait MakesDates
return 'Invalid date!';
}
}
/**
* Start and end date of the statement
*
* @return array [$start_date, $end_date];
*/
public function calculateStartAndEndDates(): array
{
return match ($this->scheduler->parameters['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST365 => [now()->startOfDay()->subDays(365)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::THIS_MONTH => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::THIS_QUARTER => [now()->startOfDay()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
};
}
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.103',
'app_tag' => '5.5.103',
'app_version' => '5.5.104',
'app_tag' => '5.5.104',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -1,9 +1,12 @@
<?php
use App\Models\BankTransaction;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Company;
use App\Models\Product;
use App\Models\GatewayType;
use App\Models\PaymentType;
use App\Models\BankTransaction;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
@ -67,6 +70,172 @@ return new class extends Migration
$product->save();
});
//payment types from 34
if(Ninja::isSelfHost()) {
$pt = PaymentType::find(34);
if(!$pt) {
$type = new PaymentType();
$type->id = 34;
$type->name = 'Mollie Bank Transfer';
$type->gateway_type_id = GatewayType::BANK_TRANSFER;
$type->save();
}
$pt = PaymentType::find(35);
if(!$pt) {
$type = new PaymentType();
$type->id = 35;
$type->name = 'KBC/CBC';
$type->gateway_type_id = GatewayType::KBC;
$type->save();
}
$pt = PaymentType::find(36);
if(!$pt) {
$type = new PaymentType();
$type->id = 36;
$type->name = 'Bancontact';
$type->gateway_type_id = GatewayType::BANCONTACT;
$type->save();
}
$pt = PaymentType::find(37);
if(!$pt) {
$type = new PaymentType();
$type->id = 37;
$type->name = 'iDEAL';
$type->gateway_type_id = GatewayType::IDEAL;
$type->save();
}
$pt = PaymentType::find(38);
if(!$pt) {
$type = new PaymentType();
$type->id = 38;
$type->name = 'Hosted Page';
$type->gateway_type_id = GatewayType::HOSTED_PAGE;
$type->save();
}
$pt = PaymentType::find(39);
if(!$pt) {
$type = new PaymentType();
$type->id = 39;
$type->name = 'GiroPay';
$type->gateway_type_id = GatewayType::GIROPAY;
$type->save();
}
$pt = PaymentType::find(40);
if(!$pt) {
$type = new PaymentType();
$type->id = 40;
$type->name = 'Przelewy24';
$type->gateway_type_id = GatewayType::PRZELEWY24;
$type->save();
}
$pt = PaymentType::find(41);
if(!$pt) {
$type = new PaymentType();
$type->id = 41;
$type->name = 'EPS';
$type->gateway_type_id = GatewayType::EPS;
$type->save();
}
$pt = PaymentType::find(42);
if(!$pt) {
$type = new PaymentType();
$type->id = 42;
$type->name = 'Direct Debit';
$type->gateway_type_id = GatewayType::DIRECT_DEBIT;
$type->save();
}
$pt = PaymentType::find(43);
if(!$pt) {
$type = new PaymentType();
$type->id = 43;
$type->name = 'BECS';
$type->gateway_type_id = GatewayType::BECS;
$type->save();
}
$pt = PaymentType::find(44);
if(!$pt) {
$type = new PaymentType();
$type->id = 44;
$type->name = 'ACSS';
$type->gateway_type_id = GatewayType::ACSS;
$type->save();
}
$pt = PaymentType::find(45);
if(!$pt) {
$type = new PaymentType();
$type->id = PaymentType::INSTANT_BANK_PAY;
$type->name = 'Instant Bank Pay';
$type->gateway_type_id = GatewayType::INSTANT_BANK_PAY;
$type->save();
}
$pt = PaymentType::find(47);
if (!$pt) {
$type = new PaymentType();
$type->id = 47;
$type->name = 'Klarna';
$type->gateway_type_id = GatewayType::KLARNA;
$type->save();
}
$pt = PaymentType::find(48);
if (!$pt) {
$type = new PaymentType();
$type->id = 48;
$type->name = 'Interac E-Transfer';
$type->save();
}
$gt = GatewayType::find(23);
if (!$gt) {
$type = new GatewayType();
$type->id = 23;
$type->alias = 'klarna';
$type->name = 'Klarna';
$type->save();
}
}
}
/**

View File

@ -5032,6 +5032,14 @@ $LANG = array(
'quote_product_columns' => 'Quote Product Columns',
'vendors' => 'Vendors',
'product_sales' => 'Product Sales',
'user_sales_report_header' => 'User sales report for client/s :client from :start_date to :end_date',
'client_balance_report' => 'Customer balance report',
'client_sales_report' => 'Customer sales report',
'user_sales_report' => 'User sales report',
'aged_receivable_detailed_report' => 'Aged Receivable Detailed Report',
'aged_receivable_summary_report' => 'Aged Receivable Summary Report',
'taxable_amount' => 'Taxable Amount',
'tax_summary' => 'Tax Summary',
);

View File

@ -332,6 +332,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('users/bulk', [UserController::class, 'bulk'])->name('users.bulk')->middleware('password_protected');
Route::post('/users/{user}/invite', [UserController::class, 'invite'])->middleware('password_protected');
Route::post('/users/{user}/disconnect_mailer', [UserController::class, 'disconnectOauthMailer']);
Route::post('/users/{user}/disconnect_oauth', [UserController::class, 'disconnectOauth']);
Route::post('/user/{user}/reconfirm', [UserController::class, 'reconfirm']);
Route::resource('webhooks', WebhookController::class);

View File

@ -77,6 +77,7 @@ class BankTransactionTest extends TestCase
$this->assertEquals($this->expense->refresh()->transaction_id, $bt->id);
$this->assertEquals($this->expense->hashed_id, $bt->refresh()->expense_id);
$this->assertEquals($bt->id, $this->expense->transaction_id);
$this->assertEquals($this->vendor->id, $bt->vendor_id);
$this->assertEquals(BankTransaction::STATUS_CONVERTED, $bt->status_id);

View File

@ -0,0 +1,203 @@
<?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\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\ARDetailReport;
use App\Services\Report\UserSalesReport;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\TestCase;
/**
* @test
*/
class ARDetailReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new ARDetailReport($this->company, $this->payload);
$this->assertInstanceOf(ARDetailReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new ARDetailReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -0,0 +1,203 @@
<?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\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\ARDetailReport;
use App\Services\Report\ARSummaryReport;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\TestCase;
/**
* @test
*/
class ARSummaryReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new ARSummaryReport($this->company, $this->payload);
$this->assertInstanceOf(ARSummaryReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new ARSummaryReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -0,0 +1,203 @@
<?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\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\ARSummaryReport;
use App\Services\Report\ClientBalanceReport;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\TestCase;
/**
* @test
*/
class ClientBalanceReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new ClientBalanceReport($this->company, $this->payload);
$this->assertInstanceOf(ClientBalanceReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new ClientBalanceReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -0,0 +1,203 @@
<?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 Tests\TestCase;
use App\Models\User;
use App\Models\Client;
use App\Models\Account;
use App\Models\Company;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use App\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Services\Report\ClientSalesReport;
use App\Services\Report\ClientBalanceReport;
use Illuminate\Routing\Middleware\ThrottleRequests;
/**
* @test
*/
class ClientSalesReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new ClientSalesReport($this->company, $this->payload);
$this->assertInstanceOf(ClientSalesReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new ClientSalesReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -0,0 +1,203 @@
<?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\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\ARDetailReport;
use App\Services\Report\TaxSummaryReport;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\TestCase;
/**
* @test
*/
class TaxSummaryReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new TaxSummaryReport($this->company, $this->payload);
$this->assertInstanceOf(TaxSummaryReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
'tax_rate2' => 17.5,
'tax_rate3' => 5,
'tax_name1' => 'GST',
'tax_name2' => 'VAT',
'tax_name3' => 'CA Sales Tax',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new TaxSummaryReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -0,0 +1,203 @@
<?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\DataMapper\CompanySettings;
use App\Export\CSV\ProductSalesExport;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\UserSalesReport;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\TestCase;
/**
* @test
*/
class UserSalesReportTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
}
public function testUserSalesInstance()
{
$this->buildData();
$pl = new UserSalesReport($this->company, $this->payload);
$this->assertInstanceOf(UserSalesReport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $this->client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new UserSalesReport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -96,7 +96,7 @@ class BaseTransformerTest extends TestCase
$this->assertEquals($client->id, $base_transformer->getClient('magic', 'null'));
$this->assertEquals($client->id, $base_transformer->getClient('nomagic', 'test@gmail.com'));
$this->assertEquals($client->id, $base_transformer->getClient(null, 'test@gmail.com'));
$this->assertNull($base_transformer->getClient('null', 'notest@gmail.com'));
$this->assertNotNull($base_transformer->getClient('null', 'notest@gmail.com'));
$this->assertEquals($client->id, $base_transformer->getClientId(' magic'));
$this->assertEquals($client->id, $base_transformer->getClientId('Magic '));

View File

@ -122,6 +122,7 @@ class Invoice2GoTest extends TestCase
$this->assertInstanceOf(Client::class, $client);
$this->assertEquals('840', $client->country_id);
$this->assertEquals('wade@projectx.net', $client->contacts->first()->email);
$this->assertEquals('2584 Sesame Street', $client->address1);
$this->assertTrue($base_transformer->hasInvoice('1'));

View File

@ -22,10 +22,11 @@ use App\Factory\SchedulerFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Session;
use App\DataMapper\Schedule\EmailStatement;
use App\Services\Scheduler\SchedulerService;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Testing\WithoutEvents;
use App\Services\Scheduler\EmailStatementService;
use App\Services\Scheduler\EmailReport;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
/**
@ -37,6 +38,7 @@ class SchedulerTest extends TestCase
use MakesHash;
use MockAccountData;
use WithoutEvents;
use DatabaseTransactions;
protected function setUp(): void
{
@ -53,8 +55,198 @@ class SchedulerTest extends TestCase
$this->withoutMiddleware(
ThrottleRequests::class
);
// $this->withoutExceptionHandling();
}
public function testReportValidationRules()
{
$data = [
'name' => 'A test product sales scheduler',
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
'next_run' => now()->format('Y-m-d'),
'template' => 'email_report',
'parameters' => [
'date_range' => EmailStatement::LAST_MONTH,
'clients' => [],
'report_keys' => [],
'client_id' => $this->client->hashed_id,
'report_name' => '',
],
];
$response = false;
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(422);
}
public function testProductSalesReportGenerationOneClientSeparateParam()
{
$data = [
'name' => 'A test product sales scheduler',
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
'next_run' => now()->format('Y-m-d'),
'template' => 'email_report',
'parameters' => [
'date_range' => EmailStatement::LAST_MONTH,
'clients' => [],
'report_keys' => [],
'client_id' => $this->client->hashed_id,
'report_name' => 'product_sales_report',
],
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(200);
}
catch(\Exception $e){
nlog($e->getMessage());
}
$arr = $response->json();
$id = $this->decodePrimaryKey($arr['data']['id']);
$scheduler = Scheduler::find($id);
$user = $scheduler->user;
$user->email = "{rand(5,555555}@gmail.com";
$user->save();
$this->assertNotNull($scheduler);
$export = (new EmailReport($scheduler))->run();
$this->assertEquals(now()->addMonth()->format('Y-m-d'), $scheduler->next_run->format('Y-m-d'));
}
public function testProductSalesReportGenerationOneClient()
{
$data = [
'name' => 'A test product sales scheduler',
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
'next_run' => now()->format('Y-m-d'),
'template' => 'email_report',
'parameters' => [
'date_range' => EmailStatement::LAST_MONTH,
'clients' => [$this->client->hashed_id],
'report_keys' => [],
'client_id' => null,
'report_name' => 'product_sales_report',
],
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(200);
}
catch(\Exception $e){
nlog($e->getMessage());
}
$arr = $response->json();
$id = $this->decodePrimaryKey($arr['data']['id']);
$scheduler = Scheduler::find($id);
$user = $scheduler->user;
$user->email = "{rand(5,555555}@gmail.com";
$user->save();
$this->assertNotNull($scheduler);
$export = (new EmailReport($scheduler))->run();
$this->assertEquals(now()->addMonth()->format('Y-m-d'), $scheduler->next_run->format('Y-m-d'));
}
public function testProductSalesReportGeneration()
{
$data = [
'name' => 'A test product sales scheduler',
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
'next_run' => now()->format('Y-m-d'),
'template' => 'email_report',
'parameters' => [
'date_range' => EmailStatement::LAST_MONTH,
'clients' => [],
'report_keys' => [],
'client_id' => null,
'report_name' => 'product_sales_report',
],
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(200);
}
catch(\Exception $e){
nlog($e->getMessage());
}
$arr = $response->json();
$id = $this->decodePrimaryKey($arr['data']['id']);
$scheduler = Scheduler::find($id);
$this->assertNotNull($scheduler);
$export = (new EmailReport($scheduler))->run();
$this->assertEquals(now()->addMonth()->format('Y-m-d'), $scheduler->next_run->format('Y-m-d'));
}
public function testProductSalesReportStore()
{
$data = [
'name' => 'A test product sales scheduler',
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
'next_run' => now()->format('Y-m-d'),
'template' => 'email_report',
'parameters' => [
'date_range' => EmailStatement::LAST_MONTH,
'clients' => [],
'report_name' => 'product_sales_report',
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/task_schedulers', $data);
$response->assertStatus(200);
}
public function testSchedulerGet3()
{
@ -539,6 +731,7 @@ class SchedulerTest extends TestCase
$response->assertStatus(200);
}
public function testDeleteSchedule()
{
$data = [

View File

@ -99,6 +99,134 @@ class TaskApiTest extends TestCase
}
}
public function testMultiSortArray()
{
$logs = [
[1680035007,1680036807,"",true],
];
$key_values = array_column($logs, 0);
array_multisort($key_values, SORT_ASC, $logs);
$start = $logs[0];
$this->assertEquals(1680035007, $start[0]);
$logs = [
];
$key_values = array_column($logs, 0);
array_multisort($key_values, SORT_ASC, $logs);
$this->assertIsArray($logs);
}
public function testKsortPerformance()
{
$logs = [
[1680035007,1680036807,"",true],
[1681156840,1681158000,"",true],
[1680302433,1680387960,"",true],
[1680715620,1680722820,"",true],
[1,1680737460,"",true]
];
$key_values = array_column($logs, 0);
array_multisort($key_values, SORT_ASC, $logs);
$start = $logs[0];
$this->assertEquals(1, $start[0]);
}
public function testStartStopSanity()
{
$task = Task::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'description' => 'Test Task',
'time_log' => '[[1681165417,1681165432,"sumtin",true],[1681165446,0]]',
]);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true");
$response->assertStatus(200);
}
public function testStoppingTaskWithDescription()
{
$task = Task::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'description' => 'Test Task',
'time_log' => '[[1681165417,1681165432,"sumtin",true],[1681165446,0]]',
]);
$task_repo = new \App\Repositories\TaskRepository();
$task = $task_repo->stop($task);
$log = json_decode($task->time_log);
$last = end($log);
$this->assertNotEquals(0, $last[1]);
$this->assertCount(2, $last);
}
public function testMultiDimensionArrayOfTimes()
{
$logs = [
'[[1680302433,1680387960,"",true]]',
'[[1680715620,1680722820,"",true],[1680729660,1680737460,"",true]]',
'[[1681156840,1681158000,"",true]]',
'[[1680035007,1680036807,"",true]]',
];
foreach($logs as $log)
{
$this->assertTrue($this->checkTimeLog(json_decode($log)));
}
}
public function testArrayOfTimes()
{
$logs = [
"[[1675275148,1675277829]]",
"[[1675375200,1675384200],[1676074247,1676074266]]",
"[[1675443600,1675461600],[1676053305,1676055950],[1676063112,1676067834]]",
"[[1676068200,1676070900]]",
"[[1678134638,1678156238]]",
"[[1678132800,1678134582],[1678134727,1678136801]]",
"[[1678343569,1678344469]]",
"[[1678744339,1678755139]]",
"[[1678894860,1678906620]]",
"[[1679339870,1679341672]]",
"[[1680547478,1680547482]]",
"[[1681156881,0]]",
];
foreach($logs as $log)
{
$this->assertTrue($this->checkTimeLog(json_decode($log)));
}
}
public function testTimeLogChecker1()
{
$log = [

View File

@ -11,13 +11,15 @@
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\Invoice;
use Tests\MockAccountData;
use App\DataMapper\InvoiceItem;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Models\Invoice;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
@ -47,6 +49,32 @@ class InvoiceTest extends TestCase
$this->invoice_calc = new InvoiceSum($this->invoice);
}
public function testRoundingWithLargeUnitCostPrecision()
{
$invoice = InvoiceFactory::create($this->company->id, $this->user->id);
$invoice->client_id = $this->client->id;
$invoice->uses_inclusive_taxes = false;
$line_items = [];
$line_item = new InvoiceItem;
$line_item->quantity = 1;
$line_item->cost = 82.6446;
$line_item->tax_rate1 = 21;
$line_item->tax_name1 = 'Test';
$line_item->product_key = 'Test';
$line_item->notes = 'Test';
$line_items[] = $line_item;
$invoice->line_items = $line_items;
$invoice->save();
$invoice = $invoice->calc()->getInvoice();
$this->assertEquals(100, $invoice->amount);
}
public function testInclusiveRounding()
{
$this->invoice->line_items = [];

View File

@ -15,6 +15,7 @@ use Tests\TestCase;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Product;
use Tests\MockAccountData;
use App\DataMapper\Tax\DE\Rule;
use App\DataMapper\Tax\TaxModel;
@ -44,13 +45,88 @@ class EuTaxTest extends TestCase
$this->makeTestData();
}
public function testEuToUsTaxCalculation()
{
$settings = CompanySettings::defaults();
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
$tax_data->regions->US->tax_all_subregions = true;
$tax_data->regions->US->has_sales_above_threshold = true;
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
'tax_data' => $tax_data,
'calculate_taxes' => true,
]);
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'country_id' => 840,
'shipping_country_id' => 840,
'has_valid_vat_number' => false,
'is_tax_exempt' => false,
'tax_data' => new Response([
'geoState' => 'CA',
'taxSales' => 0.07,
]),
]);
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'client_id' => $client->id,
'status_id' => 1,
'user_id' => $this->user->id,
'uses_inclusive_taxes' => false,
'discount' => 0,
'line_items' => [
[
'product_key' => 'Test',
'notes' => 'Test',
'cost' => 100,
'quantity' => 1,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'type_id' => '1',
'tax_id' => Product::PRODUCT_TYPE_PHYSICAL,
],
],
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_data' => new Response([
'geoState' => 'CA',
'taxSales' => 0.07,
]),
]);
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();
$this->assertEquals(107, $invoice->amount);
}
public function testInvoiceTaxCalcDetoBeNoVat()
{
$settings = CompanySettings::defaults();
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
@ -114,7 +190,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
@ -179,7 +254,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
@ -246,7 +320,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
@ -269,17 +342,16 @@ class EuTaxTest extends TestCase
$process->setClient($client);
$process->init();
$this->assertEquals('DE', $process->vendor_country_code);
$this->assertEquals('DE', $process->client_country_code);
$this->assertEquals('EU', $process->seller_region);
$this->assertEquals('DE', $process->client_subregion);
$this->assertFalse($client->has_valid_vat_number);
$this->assertInstanceOf(Rule::class, $process);
$this->assertEquals(19, $process->vat_rate);
$this->assertEquals(19, $process->tax_rate);
$this->assertEquals(7, $process->reduced_vat_rate);
$this->assertEquals(7, $process->reduced_tax_rate);
}
@ -291,7 +363,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
@ -316,17 +387,17 @@ class EuTaxTest extends TestCase
$process->init();
$this->assertEquals('DE', $process->vendor_country_code);
$this->assertEquals('EU', $process->seller_region);
$this->assertEquals('BE', $process->client_country_code);
$this->assertEquals('BE', $process->client_subregion);
$this->assertFalse($client->has_valid_vat_number);
$this->assertInstanceOf(Rule::class, $process);
$this->assertEquals(21, $process->vat_rate);
$this->assertEquals(21, $process->tax_rate);
$this->assertEquals(6, $process->reduced_vat_rate);
$this->assertEquals(6, $process->reduced_tax_rate);
}
@ -337,11 +408,18 @@ class EuTaxTest extends TestCase
$settings = CompanySettings::defaults();
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings
'settings' => $settings,
'tax_data' => $tax_data,
]);
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
@ -351,24 +429,27 @@ class EuTaxTest extends TestCase
]);
$process = new Rule();
$process->setTaxData(new Response([
'geoState' => 'CA',
]));
$process->setClient($client);
$process->init();
$this->assertEquals('DE', $process->vendor_country_code);
$this->assertEquals('EU', $process->seller_region);
$this->assertEquals('US', $process->client_country_code);
$this->assertEquals('CA', $process->client_subregion);
$this->assertFalse($client->has_valid_vat_number);
$this->assertInstanceOf(Rule::class, $process);
$this->assertEquals(0, $process->vat_rate);
$this->assertEquals(0, $process->reduced_vat_rate);
$this->assertEquals(0, $process->tax_rate);
$this->assertEquals(0, $process->reduced_tax_rate);
}
public function testSubThresholdCorrectRate()
{
@ -376,7 +457,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -404,9 +484,9 @@ class EuTaxTest extends TestCase
$this->assertFalse($client->has_valid_vat_number);
$this->assertEquals(19, $process->vat_rate);
$this->assertEquals(19, $process->tax_rate);
$this->assertEquals(7, $process->reduced_vat_rate);
$this->assertEquals(7, $process->reduced_tax_rate);
}
@ -418,7 +498,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -446,9 +525,9 @@ class EuTaxTest extends TestCase
$this->assertTrue($client->has_valid_vat_number);
$this->assertEquals(19, $process->vat_rate);
$this->assertEquals(19, $process->tax_rate);
$this->assertEquals(7, $process->reduced_vat_rate);
$this->assertEquals(7, $process->reduced_tax_rate);
}
@ -459,7 +538,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -487,9 +565,9 @@ class EuTaxTest extends TestCase
$this->assertTrue($client->has_valid_vat_number);
$this->assertEquals(0, $process->vat_rate);
$this->assertEquals(0, $process->tax_rate);
$this->assertEquals(0, $process->reduced_vat_rate);
$this->assertEquals(0, $process->reduced_tax_rate);
}
@ -499,7 +577,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -528,9 +605,9 @@ class EuTaxTest extends TestCase
$this->assertTrue($client->is_tax_exempt);
$this->assertEquals(0, $process->vat_rate);
$this->assertEquals(0, $process->tax_rate);
$this->assertEquals(0, $process->reduced_vat_rate);
$this->assertEquals(0, $process->reduced_tax_rate);
}
@ -540,7 +617,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -569,9 +645,9 @@ class EuTaxTest extends TestCase
$this->assertTrue($client->is_tax_exempt);
$this->assertEquals(0, $process->vat_rate);
$this->assertEquals(0, $process->tax_rate);
$this->assertEquals(0, $process->reduced_vat_rate);
$this->assertEquals(0, $process->reduced_tax_rate);
}
@ -581,8 +657,6 @@ class EuTaxTest extends TestCase
$settings->country_id = '276'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'DE';
$tax_data->seller_subregion = 'DE';
$tax_data->regions->EU->has_sales_above_threshold = false;
$tax_data->regions->EU->tax_all_subregions = true;
@ -603,6 +677,7 @@ class EuTaxTest extends TestCase
]);
$process = new Rule();
$process->setTaxData(new Response([]));
$process->setClient($client);
$process->init();
@ -610,9 +685,9 @@ class EuTaxTest extends TestCase
$this->assertTrue($client->is_tax_exempt);
$this->assertEquals(0, $process->vat_rate);
$this->assertEquals(0, $process->tax_rate);
$this->assertEquals(0, $process->reduced_vat_rate);
$this->assertEquals(0, $process->reduced_tax_rate);
}

View File

@ -94,7 +94,6 @@ class SumTaxTest extends TestCase
$tax_data = new TaxModel();
$tax_data->seller_region = 'US';
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;
@ -145,7 +144,6 @@ class SumTaxTest extends TestCase
$tax_data = new TaxModel();
$tax_data->seller_region = 'US';
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;

View File

@ -15,6 +15,7 @@ use Tests\TestCase;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Product;
use Tests\MockAccountData;
use App\DataMapper\Tax\DE\Rule;
use App\DataMapper\Tax\TaxModel;
@ -88,7 +89,6 @@ class UsTaxTest extends TestCase
$settings->country_id = '840'; // germany
$tax_data = new TaxModel();
$tax_data->seller_region = 'US';
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;
@ -146,6 +146,209 @@ class UsTaxTest extends TestCase
return $invoice;
}
public function testForeignTaxesEnabledWithExemptProduct()
{
$settings = CompanySettings::defaults();
$settings->country_id = '840'; // germany
$tax_data = new TaxModel();
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
$tax_data->regions->EU->subregions->DE->tax_rate = 21;
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
'tax_data' => $tax_data,
'calculate_taxes' => true,
]);
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'country_id' => 276,
'shipping_country_id' => 276,
'has_valid_vat_number' => false,
'postal_code' => 'xx',
]);
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'client_id' => $client->id,
'status_id' => 1,
'user_id' => $this->user->id,
'uses_inclusive_taxes' => false,
'discount' => 0,
'line_items' => [
[
'product_key' => 'Test',
'notes' => 'Test',
'cost' => 100,
'quantity' => 1,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'type_id' => '1',
'tax_id' => Product::PRODUCT_TYPE_EXEMPT,
],
],
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_data' => new Response($this->mock_response),
]);
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();
$this->assertEquals(100, $invoice->amount);
}
public function testForeignTaxesDisabled()
{
$settings = CompanySettings::defaults();
$settings->country_id = '840'; // germany
$tax_data = new TaxModel();
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = false;
$tax_data->regions->EU->subregions->DE->tax_rate = 21;
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
'tax_data' => $tax_data,
'calculate_taxes' => true,
]);
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'country_id' => 276,
'shipping_country_id' => 276,
'has_valid_vat_number' => false,
'postal_code' => 'xx',
]);
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'client_id' => $client->id,
'status_id' => 1,
'user_id' => $this->user->id,
'uses_inclusive_taxes' => false,
'discount' => 0,
'line_items' => [
[
'product_key' => 'Test',
'notes' => 'Test',
'cost' => 100,
'quantity' => 1,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'type_id' => '1',
'tax_id' => Product::PRODUCT_TYPE_PHYSICAL,
],
],
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_data' => new Response($this->mock_response),
]);
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();
$this->assertEquals(100, $invoice->amount);
}
public function testForeignTaxesEnabled()
{
$settings = CompanySettings::defaults();
$settings->country_id = '840'; // germany
$tax_data = new TaxModel();
$tax_data->seller_subregion = 'CA';
$tax_data->regions->US->has_sales_above_threshold = true;
$tax_data->regions->US->tax_all_subregions = true;
$tax_data->regions->EU->has_sales_above_threshold = true;
$tax_data->regions->EU->tax_all_subregions = true;
$tax_data->regions->EU->subregions->DE->tax_rate = 21;
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
'tax_data' => $tax_data,
'calculate_taxes' => true,
]);
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $company->id,
'country_id' => 276,
'shipping_country_id' => 276,
'has_valid_vat_number' => false,
'postal_code' => 'xx',
]);
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'client_id' => $client->id,
'status_id' => 1,
'user_id' => $this->user->id,
'uses_inclusive_taxes' => false,
'discount' => 0,
'line_items' => [
[
'product_key' => 'Test',
'notes' => 'Test',
'cost' => 100,
'quantity' => 1,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'type_id' => '1',
'tax_id' => Product::PRODUCT_TYPE_PHYSICAL,
],
],
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'tax_data' => new Response($this->mock_response),
]);
$invoice = $invoice->calc()->getInvoice()->service()->markSent()->save();
$this->assertEquals(121, $invoice->amount);
}
public function testCompanyTaxAllOffButTaxUSRegion()
{