mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 21:22:58 +01:00
commit
12e9a78881
@ -1 +1 @@
|
||||
5.6.23
|
||||
5.6.24
|
@ -40,16 +40,13 @@ class RecurringExpenseToExpenseFactory
|
||||
$expense->tax_name3 = $recurring_expense->tax_name3;
|
||||
$expense->tax_rate3 = $recurring_expense->tax_rate3;
|
||||
$expense->date = now()->format('Y-m-d');
|
||||
$expense->payment_date = $recurring_expense->payment_date ?: now()->format('Y-m-d');
|
||||
// $expense->payment_date = $recurring_expense->payment_date ?: now()->format('Y-m-d');
|
||||
$expense->amount = $recurring_expense->amount;
|
||||
$expense->foreign_amount = $recurring_expense->foreign_amount ?: 0;
|
||||
|
||||
//11-09-2022 - we should be tracking the recurring expense!!
|
||||
$expense->recurring_expense_id = $recurring_expense->id;
|
||||
|
||||
// $expense->private_notes = $recurring_expense->private_notes;
|
||||
// $expense->public_notes = $recurring_expense->public_notes;
|
||||
|
||||
$expense->public_notes = self::transformObject($recurring_expense->public_notes, $recurring_expense);
|
||||
$expense->private_notes = self::transformObject($recurring_expense->private_notes, $recurring_expense);
|
||||
|
||||
|
@ -183,6 +183,48 @@ class InvoiceFilters extends QueryFilters
|
||||
->where('client_id', $this->decodePrimaryKey($client_id));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @return Builder
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function date(string $date = ''): Builder
|
||||
{
|
||||
if (strlen($date) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (is_numeric($date)) {
|
||||
$date = Carbon::createFromTimestamp((int)$date);
|
||||
} else {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
|
||||
return $this->builder->where('date', '>=', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @return Builder
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function due_date(string $date = ''): Builder
|
||||
{
|
||||
if (strlen($date) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
if (is_numeric($date)) {
|
||||
$date = Carbon::createFromTimestamp((int)$date);
|
||||
} else {
|
||||
$date = Carbon::parse($date);
|
||||
}
|
||||
|
||||
return $this->builder->where('due_date', '>=', $date);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
|
@ -12,11 +12,13 @@
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
|
||||
class InvoiceItemSumInclusive
|
||||
@ -25,6 +27,71 @@ class InvoiceItemSumInclusive
|
||||
use Discounter;
|
||||
use Taxer;
|
||||
|
||||
|
||||
private array $eu_tax_jurisdictions = [
|
||||
'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
|
||||
];
|
||||
|
||||
private array $tax_jurisdictions = [
|
||||
'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
|
||||
|
||||
'US', // USA
|
||||
|
||||
'AU', // Australia
|
||||
];
|
||||
|
||||
protected RecurringInvoice | Invoice | Quote | Credit | PurchaseOrder | RecurringQuote $invoice;
|
||||
|
||||
private $currency;
|
||||
@ -39,6 +106,12 @@ class InvoiceItemSumInclusive
|
||||
|
||||
private $tax_collection;
|
||||
|
||||
private bool $calc_tax = false;
|
||||
|
||||
private ?Client $client;
|
||||
|
||||
private RuleInterface $rule;
|
||||
|
||||
public function __construct(RecurringInvoice | Invoice | Quote | Credit | PurchaseOrder | RecurringQuote $invoice)
|
||||
{
|
||||
$this->tax_collection = collect([]);
|
||||
@ -47,6 +120,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
}
|
||||
@ -107,12 +181,46 @@ class InvoiceItemSumInclusive
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to calculate taxes based on the clients location
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function calcTaxesAutomatically(): self
|
||||
{
|
||||
$this->rule->tax($this->item);
|
||||
|
||||
$precision = strlen(substr(strrchr($this->rule->tax_rate1, "."), 1));
|
||||
|
||||
$this->item->tax_name1 = $this->rule->tax_name1;
|
||||
$this->item->tax_rate1 = round($this->rule->tax_rate1, $precision);
|
||||
|
||||
$precision = strlen(substr(strrchr($this->rule->tax_rate2, "."), 1));
|
||||
|
||||
$this->item->tax_name2 = $this->rule->tax_name2;
|
||||
$this->item->tax_rate2 = round($this->rule->tax_rate2, $precision);
|
||||
|
||||
$precision = strlen(substr(strrchr($this->rule->tax_rate3, "."), 1));
|
||||
|
||||
$this->item->tax_name3 = $this->rule->tax_name3;
|
||||
$this->item->tax_rate3 = round($this->rule->tax_rate3, $precision);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Taxes effect the line totals and item costs. we decrement both on
|
||||
* application of inclusive tax rates.
|
||||
*/
|
||||
private function calcTaxes()
|
||||
{
|
||||
|
||||
if ($this->calc_tax) {
|
||||
$this->calcTaxesAutomatically();
|
||||
}
|
||||
|
||||
$item_tax = 0;
|
||||
|
||||
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / 100));
|
||||
@ -275,4 +383,36 @@ class InvoiceItemSumInclusive
|
||||
|
||||
$this->setTotalTaxes($item_tax);
|
||||
}
|
||||
|
||||
|
||||
private function shouldCalculateTax(): self
|
||||
{
|
||||
|
||||
if (!$this->invoice->company?->calculate_taxes || $this->invoice->company->account->isFreeHostedClient()) {
|
||||
$this->calc_tax = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (in_array($this->client->company->country()->iso_3166_2, $this->tax_jurisdictions) ) { //only calculate for supported tax jurisdictions
|
||||
|
||||
$class = "App\DataMapper\Tax\\".$this->client->company->country()->iso_3166_2."\\Rule";
|
||||
|
||||
$this->rule = new $class();
|
||||
|
||||
if($this->rule->regionWithNoTaxCoverage($this->client->country->iso_3166_2))
|
||||
return $this;
|
||||
|
||||
$this->rule
|
||||
->setEntity($this->invoice)
|
||||
->init();
|
||||
|
||||
$this->calc_tax = $this->rule->shouldCalcTax();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ use App\Models\Subscription;
|
||||
use App\Repositories\ClientContactRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
@ -34,6 +35,7 @@ use Livewire\Component;
|
||||
|
||||
class BillingPortalPurchasev2 extends Component
|
||||
{
|
||||
use MakesHash;
|
||||
/**
|
||||
* Random hash generated by backend to handle the tracking of state.
|
||||
*
|
||||
@ -422,6 +424,7 @@ class BillingPortalPurchasev2 extends Component
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
$data = [
|
||||
'name' => '',
|
||||
'group_id' => $this->encodePrimaryKey($this->subscription->group_id),
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
],
|
||||
|
@ -71,7 +71,7 @@ class UpdateInvoiceRequest extends Request
|
||||
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
|
||||
$rules['status_id'] = 'bail|sometimes|not_in:5'; //do not all cancelled invoices to be modfified.
|
||||
$rules['status_id'] = 'bail|sometimes|not_in:5'; //do not allow cancelled invoices to be modfified.
|
||||
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
|
||||
|
||||
// not needed.
|
||||
|
@ -106,7 +106,7 @@ class ValidRefundableRequest implements Rule
|
||||
if ($payment->credits()->exists()) {
|
||||
$paymentable_credit = $payment->credits->where('id', $credit->id)->first();
|
||||
|
||||
if (! $paymentable_invoice) {
|
||||
if (! $paymentable_credit) {
|
||||
$this->error_msg = ctrans('texts.credit_not_related_to_payment', ['credit' => $credit->hashed_id]);
|
||||
|
||||
return false;
|
||||
|
@ -103,8 +103,11 @@ class RecurringExpensesCron
|
||||
$expense = RecurringExpenseToExpenseFactory::create($recurring_expense);
|
||||
$expense->saveQuietly();
|
||||
|
||||
if($expense->company->mark_expenses_paid)
|
||||
$expense->payment_date = now()->format('Y-m-d');
|
||||
|
||||
$expense->number = $this->getNextExpenseNumber($expense);
|
||||
$expense->save();
|
||||
$expense->saveQuietly();
|
||||
|
||||
$recurring_expense->next_send_date = $recurring_expense->nextSendDate();
|
||||
$recurring_expense->next_send_date_client = $recurring_expense->next_send_date;
|
||||
|
@ -15,12 +15,13 @@ use App\Events\Payment\PaymentWasRefunded;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Services\Ledger\LedgerService;
|
||||
use App\Services\Payment\PaymentService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\Payment\Refundable;
|
||||
use Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@ -251,6 +252,11 @@ class Payment extends BaseModel
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function transaction(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BankTransaction::class);
|
||||
}
|
||||
|
||||
public function exchange_currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class, 'exchange_currency_id', 'id');
|
||||
|
@ -198,7 +198,7 @@ class Product extends BaseModel
|
||||
],
|
||||
]);
|
||||
|
||||
return $converter->convert($this->notes);
|
||||
return $converter->convert($this->notes ?? '');
|
||||
}
|
||||
|
||||
public function portalUrl($use_react_url): string
|
||||
|
@ -54,13 +54,14 @@ class DeletePayment
|
||||
/** @return $this */
|
||||
private function cleanupPayment()
|
||||
{
|
||||
|
||||
$this->payment->is_deleted = true;
|
||||
$this->payment->delete();
|
||||
|
||||
// BankTransaction::where('payment_id', $this->payment->id)->cursor()->each(function ($bt){
|
||||
// $bt->payment_id = null;
|
||||
// $bt->save();
|
||||
// });
|
||||
BankTransaction::where('payment_id', $this->payment->id)->cursor()->each(function ($bt){
|
||||
$bt->payment_id = null;
|
||||
$bt->save();
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION','5.6.23'),
|
||||
'app_tag' => env('APP_TAG','5.6.23'),
|
||||
'app_version' => env('APP_VERSION','5.6.24'),
|
||||
'app_tag' => env('APP_TAG','5.6.24'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -33,9 +33,14 @@ class PermissionsTest extends TestCase
|
||||
|
||||
public Company $company;
|
||||
|
||||
public $faker;
|
||||
|
||||
public $token;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
$account = Account::factory()->create([
|
||||
@ -75,6 +80,56 @@ class PermissionsTest extends TestCase
|
||||
$company_token->save();
|
||||
}
|
||||
|
||||
public function testClientOverviewPermissions()
|
||||
{
|
||||
$u = User::factory()->create([
|
||||
'account_id' => $this->company->account_id,
|
||||
'confirmation_code' => '123',
|
||||
'email' => $this->faker->safeEmail(),
|
||||
]);
|
||||
|
||||
$c = Client::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $u->id,
|
||||
]);
|
||||
|
||||
Invoice::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $c->id,
|
||||
'user_id' => $u->id,
|
||||
'status_id' => 2
|
||||
]);
|
||||
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
$low_cu->permissions = '["edit_client","create_client","create_invoice","edit_invoice","create_quote","edit_quote"]';
|
||||
$low_cu->save();
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/invoices');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
$this->assertEquals(2, count($data));
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get("/api/v1/invoices?include=client&client_id={$c->hashed_id}&sort=id|desc&per_page=10&page=1&filter=&status=active");
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
$this->assertEquals(2, count($data));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testHasExcludedPermissions()
|
||||
{
|
||||
$low_cu = CompanyUser::where(['company_id' => $this->company->id, 'user_id' => $this->user->id])->first();
|
||||
|
Loading…
Reference in New Issue
Block a user