Merge pull request #6665 from turbo124/v5-stable

v5.3.15
This commit is contained in:
David Bomba 2021-09-17 19:41:13 +10:00 committed by GitHub
commit c889ed644a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 606345 additions and 600386 deletions

View File

@ -1 +1 @@
5.3.10
5.3.15

View File

@ -12,6 +12,7 @@
namespace App\Console;
use App\Jobs\Cron\AutoBillCron;
use App\Jobs\Cron\RecurringExpensesCron;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Cron\SubscriptionCron;
use App\Jobs\Ninja\AdjustEmailQuota;
@ -62,6 +63,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
$schedule->job(new RecurringExpensesCron)->dailyAt('23:45')->withoutOverlapping();
$schedule->job(new AutoBillCron)->dailyAt('00:30')->withoutOverlapping();
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();

View File

@ -98,6 +98,12 @@ class CompanySettings extends BaseSettings
public $expense_number_pattern = ''; //@implemented
public $expense_number_counter = 1; //@implemented
public $recurring_expense_number_pattern = '';
public $recurring_expense_number_counter = 1;
public $recurring_quote_number_pattern = '';
public $recurring_quote_number_counter = 1;
public $vendor_number_pattern = ''; //@implemented
public $vendor_number_counter = 1; //@implemented
@ -349,6 +355,10 @@ class CompanySettings extends BaseSettings
'task_number_counter' => 'int',
'expense_number_pattern' => 'string',
'expense_number_counter' => 'int',
'recurring_expense_number_pattern' => 'string',
'recurring_expense_number_counter' => 'int',
'recurring_quote_number_pattern' => 'string',
'recurring_quote_number_counter' => 'int',
'vendor_number_pattern' => 'string',
'vendor_number_counter' => 'int',
'ticket_number_pattern' => 'string',

View File

@ -43,6 +43,8 @@ class InvoiceItem
public $line_total = 0;
public $gross_line_total = 0;
public $date = '';
public $custom_value1 = '';
@ -72,6 +74,7 @@ class InvoiceItem
'tax_rate3' => 'float',
'sort_id' => 'string',
'line_total' => 'float',
'gross_line_total' => 'float',
'date' => 'string',
'custom_value1' => 'string',
'custom_value2' => 'string',

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringExpense;
use App\Models\Company;
use App\Models\RecurringExpense;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringExpenseWasArchived.
*/
class RecurringExpenseWasArchived
{
use SerializesModels;
/**
* @var RecurringExpense
*/
public $recurring_expense;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringExpense $recurring_expense
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringExpense $recurring_expense, Company $company, array $event_vars)
{
$this->recurring_expense = $recurring_expense;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringExpense;
use App\Models\Company;
use App\Models\RecurringExpense;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringExpenseWasCreated.
*/
class RecurringExpenseWasCreated
{
use SerializesModels;
/**
* @var RecurringExpense
*/
public $recurring_expense;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringExpense $recurring_expense
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringExpense $recurring_expense, Company $company, array $event_vars)
{
$this->recurring_expense = $recurring_expense;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringExpense;
use App\Models\Company;
use App\Models\RecurringExpense;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringExpenseWasDeleted.
*/
class RecurringExpenseWasDeleted
{
use SerializesModels;
/**
* @var RecurringExpense
*/
public $recurring_expense;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringExpense $recurring_expense
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringExpense $recurring_expense, Company $company, array $event_vars)
{
$this->recurring_expense = $recurring_expense;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,49 @@
<?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 App\Events\RecurringExpense;
use App\Models\Company;
use App\Models\RecurringExpense;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringExpenseWasRestored.
*/
class RecurringExpenseWasRestored
{
use SerializesModels;
/**
* @var RecurringExpense
*/
public $recurring_expense;
public $company;
public $event_vars;
public $fromDeleted;
/**
* Create a new event instance.
*
* @param RecurringExpense $recurring_expense
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringExpense $recurring_expense, $fromDeleted, Company $company, array $event_vars)
{
$this->recurring_expense = $recurring_expense;
$this->fromDeleted = $fromDeleted;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringExpense;
use App\Models\Company;
use App\Models\RecurringExpense;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringExpenseWasUpdated.
*/
class RecurringExpenseWasUpdated
{
use SerializesModels;
/**
* @var RecurringExpense
*/
public $recurring_expense;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringExpense $recurring_expense
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringExpense $recurring_expense, Company $company, array $event_vars)
{
$this->recurring_expense = $recurring_expense;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringQuote;
use App\Models\Company;
use App\Models\RecurringQuote;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringQuoteWasArchived.
*/
class RecurringQuoteWasArchived
{
use SerializesModels;
/**
* @var Invoice
*/
public $recurring_quote;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $recurring_quote
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringQuote $recurring_quote, Company $company, array $event_vars)
{
$this->recurring_quote = $recurring_quote;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringQuote;
use App\Models\Company;
use App\Models\RecurringQuote;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringQuoteWasCreated.
*/
class RecurringQuoteWasCreated
{
use SerializesModels;
/**
* @var RecurringQuote
*/
public $recurring_quote;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringQuote $recurring_quote
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringQuote $recurring_quote, Company $company, array $event_vars)
{
$this->recurring_quote = $recurring_quote;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,47 @@
<?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 App\Events\RecurringQuote;
use App\Models\Company;
use App\Models\RecurringQuote;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringQuoteWasDeleted.
*/
class RecurringQuoteWasDeleted
{
use SerializesModels;
/**
* @var RecurringQuote
*/
public $recurring_quote;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringQuote $recurring_quote, Company $company, array $event_vars)
{
$this->recurring_quote = $recurring_quote;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,51 @@
<?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 App\Events\RecurringQuote;
use App\Models\Company;
use App\Models\RecurringQuote;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringQuoteWasRestored.
*/
class RecurringQuoteWasRestored
{
use SerializesModels;
/**
* @var RecurringQuote
*/
public $recurring_quote;
public $fromDeleted;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Invoice $invoice
* @param $fromDeleted
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringQuote $recurring_quote, $fromDeleted, Company $company, array $event_vars)
{
$this->recurring_quote = $recurring_quote;
$this->fromDeleted = $fromDeleted;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -0,0 +1,49 @@
<?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 App\Events\RecurringQuote;
use App\Models\Company;
use App\Models\RecurringQuote;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
/**
* Class RecurringQuoteWasUpdated.
*/
class RecurringQuoteWasUpdated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @var Invoice
*/
public $recurring_quote;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param RecurringQuote $recurring_quote
* @param Company $company
* @param array $event_vars
*/
public function __construct(RecurringQuote $recurring_quote, Company $company, array $event_vars)
{
$this->recurring_quote = $recurring_quote;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -35,6 +35,7 @@ class InvoiceItemFactory
$item->tax_rate3 = 0;
$item->sort_id = 0;
$item->line_total = 0;
$item->gross_line_total = 0;
$item->custom_value1 = '';
$item->custom_value2 = '';
$item->custom_value3 = '';

View File

@ -0,0 +1,57 @@
<?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 App\Factory;
use App\Models\Quote;
use App\Models\RecurringQuote;
class QuoteToRecurringQuoteFactory
{
public static function create(Quote $quote) :RecurringQuote
{
$recurring_quote = new RecurringQuote;
$recurring_quote->status_id = RecurringQuote::STATUS_DRAFT;
$recurring_quote->discount = $quote->discount;
$recurring_quote->number = '';
$recurring_quote->is_amount_discount = $quote->is_amount_discount;
$recurring_quote->po_number = $quote->po_number;
$recurring_quote->footer = $quote->footer;
$recurring_quote->terms = $quote->terms;
$recurring_quote->public_notes = $quote->public_notes;
$recurring_quote->private_notes = $quote->private_notes;
$recurring_quote->date = date_create()->format($quote->client->date_format());
$recurring_quote->due_date = $quote->due_date; //todo calculate based on terms
$recurring_quote->is_deleted = $quote->is_deleted;
$recurring_quote->line_items = $quote->line_items;
$recurring_quote->tax_name1 = $quote->tax_name1;
$recurring_quote->tax_rate1 = $quote->tax_rate1;
$recurring_quote->tax_name2 = $quote->tax_name2;
$recurring_quote->tax_rate2 = $quote->tax_rate2;
$recurring_quote->custom_value1 = $quote->custom_value1;
$recurring_quote->custom_value2 = $quote->custom_value2;
$recurring_quote->custom_value3 = $quote->custom_value3;
$recurring_quote->custom_value4 = $quote->custom_value4;
$recurring_quote->amount = $quote->amount;
// $recurring_quote->balance = $quote->balance;
$recurring_quote->user_id = $quote->user_id;
$recurring_quote->client_id = $quote->client_id;
$recurring_quote->company_id = $quote->company_id;
$recurring_quote->frequency_id = RecurringQuote::FREQUENCY_MONTHLY;
$recurring_quote->last_sent_date = null;
$recurring_quote->next_send_date = null;
$recurring_quote->remaining_cycles = 0;
$recurring_quote->paid_to_date = 0;
return $recurring_quote;
}
}

View File

@ -0,0 +1,53 @@
<?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 App\Factory;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
class RecurringExpenseFactory
{
public static function create(int $company_id, int $user_id) :RecurringExpense
{
$recurring_expense = new RecurringExpense();
$recurring_expense->status_id = RecurringInvoice::STATUS_DRAFT;
$recurring_expense->user_id = $user_id;
$recurring_expense->company_id = $company_id;
$recurring_expense->is_deleted = false;
$recurring_expense->invoice_documents = false;
$recurring_expense->should_be_invoiced = false;
$recurring_expense->tax_name1 = '';
$recurring_expense->tax_rate1 = 0;
$recurring_expense->tax_name2 = '';
$recurring_expense->tax_rate2 = 0;
$recurring_expense->tax_name3 = '';
$recurring_expense->tax_rate3 = 0;
$recurring_expense->tax_amount1 = 0;
$recurring_expense->tax_amount2 = 0;
$recurring_expense->tax_amount3 = 0;
$recurring_expense->date = null;
$recurring_expense->payment_date = null;
$recurring_expense->amount = 0;
$recurring_expense->foreign_amount = 0;
$recurring_expense->private_notes = '';
$recurring_expense->public_notes = '';
$recurring_expense->transaction_reference = '';
$recurring_expense->custom_value1 = '';
$recurring_expense->custom_value2 = '';
$recurring_expense->custom_value3 = '';
$recurring_expense->custom_value4 = '';
$recurring_expense->uses_inclusive_taxes = true;
$recurring_expense->calculate_tax_by_amount = true;
return $recurring_expense;
}
}

View File

@ -0,0 +1,62 @@
<?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 App\Factory;
use App\Models\Expense;
use App\Models\RecurringExpense;
class RecurringExpenseToExpenseFactory
{
public static function create(RecurringExpense $recurring_expense) :Expense
{
$expense = new Expense();
$expense->user_id = $recurring_expense->user_id;
$expense->assigned_user_id = $recurring_expense->assigned_user_id;
$expense->vendor_id = $recurring_expense->vendor_id;
$expense->invoice_id = $recurring_expense->invoice_id;
$expense->currency_id = $recurring_expense->currency_id;
$expense->company_id = $recurring_expense->company_id;
$expense->bank_id = $recurring_expense->bank_id;
$expense->exchange_rate = $recurring_expense->exchange_rate;
$expense->is_deleted = false;
$expense->should_be_invoiced = $recurring_expense->should_be_invoiced;
$expense->tax_name1 = $recurring_expense->tax_name1;
$expense->tax_rate1 = $recurring_expense->tax_rate1;
$expense->tax_name2 = $recurring_expense->tax_name2;
$expense->tax_rate2 = $recurring_expense->tax_rate2;
$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;
$expense->amount = $recurring_expense->amount;
$expense->foreign_amount = $recurring_expense->foreign_amount;
$expense->private_notes = $recurring_expense->private_notes;
$expense->public_notes = $recurring_expense->public_notes;
$expense->transaction_reference = $recurring_expense->transaction_reference;
$expense->custom_value1 = $recurring_expense->custom_value1;
$expense->custom_value2 = $recurring_expense->custom_value2;
$expense->custom_value3 = $recurring_expense->custom_value3;
$expense->custom_value4 = $recurring_expense->custom_value4;
$expense->transaction_id = $recurring_expense->transaction_id;
$expense->category_id = $recurring_expense->category_id;
$expense->payment_type_id = $recurring_expense->payment_type_id;
$expense->project_id = $recurring_expense->project_id;
$expense->invoice_documents = $recurring_expense->invoice_documents;
$expense->tax_amount1 = $recurring_expense->tax_amount1;
$expense->tax_amount2 = $recurring_expense->tax_amount2;
$expense->tax_amount3 = $recurring_expense->tax_amount3;
$expense->uses_inclusive_taxes = $recurring_expense->uses_inclusive_taxes;
$expense->calculate_tax_by_amount = $recurring_expense->calculate_tax_by_amount;
return $expense;
}
}

View File

@ -22,6 +22,7 @@ class RecurringQuoteFactory
$quote->discount = 0;
$quote->is_amount_discount = true;
$quote->po_number = '';
$quote->number = '';
$quote->footer = '';
$quote->terms = '';
$quote->public_notes = '';
@ -48,6 +49,7 @@ class RecurringQuoteFactory
$quote->last_sent_date = null;
$quote->next_send_date = null;
$quote->remaining_cycles = 0;
$quote->paid_to_date = 0;
return $quote;
}

View File

@ -0,0 +1,60 @@
<?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 App\Factory;
use App\Models\Client;
use App\Models\Quote;
use App\Models\RecurringQuote;
class RecurringQuoteToQuoteFactory
{
public static function create(RecurringQuote $recurring_quote, Client $client) :Quote
{
$quote = new Quote();
$quote->status_id = Quote::STATUS_DRAFT;
$quote->discount = $recurring_quote->discount;
$quote->is_amount_discount = $recurring_quote->is_amount_discount;
$quote->po_number = $recurring_quote->po_number;
$quote->footer = $recurring_quote->footer;
$quote->terms = $recurring_quote->terms;
$quote->public_notes = $recurring_quote->public_notes;
$quote->private_notes = $recurring_quote->private_notes;
//$quote->date = now()->format($client->date_format());
//$quote->due_date = $recurring_quote->calculateDueDate(now());
$quote->is_deleted = $recurring_quote->is_deleted;
$quote->line_items = $recurring_quote->line_items;
$quote->tax_name1 = $recurring_quote->tax_name1;
$quote->tax_rate1 = $recurring_quote->tax_rate1;
$quote->tax_name2 = $recurring_quote->tax_name2;
$quote->tax_rate2 = $recurring_quote->tax_rate2;
$quote->tax_name3 = $recurring_quote->tax_name3;
$quote->tax_rate3 = $recurring_quote->tax_rate3;
$quote->total_taxes = $recurring_quote->total_taxes;
$quote->subscription_id = $recurring_quote->subscription_id;
$quote->custom_value1 = $recurring_quote->custom_value1;
$quote->custom_value2 = $recurring_quote->custom_value2;
$quote->custom_value3 = $recurring_quote->custom_value3;
$quote->custom_value4 = $recurring_quote->custom_value4;
$quote->amount = $recurring_quote->amount;
// $quote->balance = $recurring_quote->balance;
$quote->user_id = $recurring_quote->user_id;
$quote->assigned_user_id = $recurring_quote->assigned_user_id;
$quote->company_id = $recurring_quote->company_id;
$quote->recurring_id = $recurring_quote->id;
$quote->client_id = $client->id;
$quote->auto_bill_enabled = $recurring_quote->auto_bill_enabled;
$quote->paid_to_date = 0;
$quote->design_id = $recurring_quote->design_id;
return $quote;
}
}

View File

@ -68,7 +68,6 @@ class ClientFilters extends QueryFilters
$query->where('email', $email);
});
//return $this->builder->where('email', $email);
}
public function id_number(string $id_number):Builder

View File

@ -38,11 +38,6 @@ class ExpenseFilters extends QueryFilters
return $this->builder->where(function ($query) use ($filter) {
$query->where('expenses.name', 'like', '%'.$filter.'%')
->orWhere('expenses.id_number', 'like', '%'.$filter.'%')
->orWhereHas('contacts', function ($query) use($filter){
$query->where('expense_contacts.first_name', 'like', '%'.$filter.'%');
$query->orWhere('expense_contacts.last_name', 'like', '%'.$filter.'%');
$query->orWhere('expense_contacts.email', 'like', '%'.$filter.'%');
})
->orWhere('expenses.custom_value1', 'like', '%'.$filter.'%')
->orWhere('expenses.custom_value2', 'like', '%'.$filter.'%')
->orWhere('expenses.custom_value3', 'like', '%'.$filter.'%')

View File

@ -0,0 +1,148 @@
<?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 App\Filters;
use App\Models\RecurringExpense;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
/**
* RecurringExpenseFilters.
*/
class RecurringExpenseFilters extends QueryFilters
{
/**
* Filter based on search text.
*
* @param string query filter
* @return Builder
* @deprecated
*/
public function filter(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('recurring_expenses.name', 'like', '%'.$filter.'%')
->orWhere('recurring_expenses.id_number', 'like', '%'.$filter.'%')
->orWhere('recurring_expenses.custom_value1', 'like', '%'.$filter.'%')
->orWhere('recurring_expenses.custom_value2', 'like', '%'.$filter.'%')
->orWhere('recurring_expenses.custom_value3', 'like', '%'.$filter.'%')
->orWhere('recurring_expenses.custom_value4', 'like', '%'.$filter.'%');
});
}
/**
* Filters the list based on the status
* archived, active, deleted.
*
* @param string filter
* @return Builder
*/
public function status(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
$table = 'expenses';
$filters = explode(',', $filter);
return $this->builder->where(function ($query) use ($filters, $table) {
$query->whereNull($table.'.id');
if (in_array(parent::STATUS_ACTIVE, $filters)) {
$query->orWhereNull($table.'.deleted_at');
}
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
$query->orWhere(function ($query) use ($table) {
$query->whereNotNull($table.'.deleted_at');
if (! in_array($table, ['users'])) {
$query->where($table.'.is_deleted', '=', 0);
}
});
}
if (in_array(parent::STATUS_DELETED, $filters)) {
$query->orWhere($table.'.is_deleted', '=', 1);
}
});
}
/**
* Sorts the list based on $sort.
*
* @param string sort formatted as column|asc
* @return Builder
*/
public function sort(string $sort) : Builder
{
$sort_col = explode('|', $sort);
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
/**
* Returns the base query.
*
* @param int company_id
* @param User $user
* @return Builder
* @deprecated
*/
public function baseQuery(int $company_id, User $user) : Builder
{
$query = DB::table('recurring_expenses')
->join('companies', 'companies.id', '=', 'recurring_expenses.company_id')
->where('recurring_expenses.company_id', '=', $company_id)
->select(
DB::raw('COALESCE(recurring_expenses.country_id, companies.country_id) country_id'),
'recurring_expenses.id',
'recurring_expenses.private_notes',
'recurring_expenses.custom_value1',
'recurring_expenses.custom_value2',
'recurring_expenses.custom_value3',
'recurring_expenses.custom_value4',
'recurring_expenses.created_at',
'recurring_expenses.created_at as expense_created_at',
'recurring_expenses.deleted_at',
'recurring_expenses.is_deleted',
'recurring_expenses.user_id',
);
/*
* If the user does not have permissions to view all invoices
* limit the user to only the invoices they have created
*/
if (Gate::denies('view-list', RecurringExpense::class)) {
$query->where('recurring_expenses.user_id', '=', $user->id);
}
return $query;
}
/**
* Filters the query by the users company ID.
*
* @return Illuminate\Database\Query\Builder
*/
public function entityFilter()
{
return $this->builder->company();
}
}

View File

@ -28,6 +28,8 @@ class InvoiceItemSum
private $line_total;
private $gross_line_total;
private $currency;
private $total_taxes;
@ -38,6 +40,8 @@ class InvoiceItemSum
private $sub_total;
private $gross_sub_total;
private $total_discount;
private $tax_collection;
@ -83,6 +87,8 @@ class InvoiceItemSum
{
$this->sub_total += $this->getLineTotal();
$this->gross_sub_total += $this->getGrossLineTotal();
$this->line_items[] = $this->item;
return $this;
@ -145,9 +151,10 @@ class InvoiceItemSum
if($item_tax_rate3_total != 0)
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
$this->setTotalTaxes($this->formatValue($item_tax, $this->currency->precision));
$this->item->gross_line_total = $this->getLineTotal() + $item_tax;
return $this;
}
@ -186,6 +193,11 @@ class InvoiceItemSum
return $this->item->line_total;
}
public function getGrossLineTotal()
{
return $this->item->gross_line_total;
}
public function getLineItems()
{
return $this->line_items;
@ -208,6 +220,11 @@ class InvoiceItemSum
return $this->sub_total;
}
public function getGrossSubTotal()
{
return $this->gross_sub_total;
}
public function setSubTotal($value)
{
$this->sub_total = $value;
@ -263,6 +280,7 @@ class InvoiceItemSum
if ($item_tax_rate3_total != 0) {
$this->groupTax($this->item->tax_name3, $this->item->tax_rate3, $item_tax_rate3_total);
}
}
$this->setTotalTaxes($item_tax);

View File

@ -177,6 +177,11 @@ class InvoiceItemSumInclusive
return $this->item->line_total;
}
public function getGrossLineTotal()
{
return $this->item->line_total;
}
public function getLineItems()
{
return $this->line_items;
@ -199,6 +204,11 @@ class InvoiceItemSumInclusive
return $this->sub_total;
}
public function getGrossSubTotal()
{
return $this->sub_total;
}
public function setSubTotal($value)
{
$this->sub_total = $value;

View File

@ -42,6 +42,8 @@ class InvoiceSum
private $sub_total;
private $gross_sub_total;
/**
* Constructs the object with Invoice and Settings object.
*
@ -75,6 +77,7 @@ class InvoiceSum
$this->invoice->line_items = $this->invoice_items->getLineItems();
$this->total = $this->invoice_items->getSubTotal();
$this->setSubTotal($this->invoice_items->getSubTotal());
$this->setGrossSubTotal($this->invoice_items->getGrossSubTotal());
return $this;
}
@ -266,6 +269,18 @@ class InvoiceSum
return $this;
}
public function getGrossSubTotal()
{
return $this->gross_sub_total;
}
public function setGrossSubTotal($value)
{
$this->gross_sub_total = $value;
return $this;
}
public function getTotalDiscount()
{
return $this->total_discount;

View File

@ -259,6 +259,11 @@ class InvoiceSumInclusive
return $this->sub_total;
}
public function getGrossSubTotal()
{
return $this->sub_total;
}
public function setSubTotal($value)
{
$this->sub_total = $value;

View File

@ -84,6 +84,7 @@ class BaseController extends Controller
'company.payments.documents',
'company.payment_terms.company',
'company.projects.documents',
'company.recurring_expenses',
'company.recurring_invoices',
'company.recurring_invoices.invitations.contact',
'company.recurring_invoices.invitations.company',
@ -307,6 +308,13 @@ class BaseController extends Controller
if(!$user->hasPermission('view_recurring_invoice'))
$query->where('recurring_invoices.user_id', $user->id)->orWhere('recurring_invoices.assigned_user_id', $user->id);
},
'company.recurring_expenses'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
if(!$user->hasPermission('view_recurring_expense'))
$query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
},
'company.tasks'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
@ -738,6 +746,7 @@ class BaseController extends Controller
//pass referral code to front end
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
$data['build'] = request()->has('build') ? request()->input('build') : '';
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
$data['path'] = $this->setBuild();

View File

@ -512,6 +512,9 @@ class ClientController extends BaseController
$ids = request()->input('ids');
$clients = Client::withTrashed()->whereIn('id', $this->transformKeys($ids))->cursor();
if(!in_array($action, ['restore','archive','delete']))
return response()->json(['message' => 'That action is not available.'], 400);
$clients->each(function ($client, $key) use ($action) {
if (auth()->user()->can('edit', $client)) {
$this->client_repo->{$action}($client);

View File

@ -210,7 +210,10 @@ class QuoteController extends BaseController
$quote = $this->quote_repo->save($request->all(), QuoteFactory::create(auth()->user()->company()->id, auth()->user()->id));
$quote = $quote->service()->fillDefaults()->save();
$quote = $quote->service()
->fillDefaults()
->triggeredActions($request)
->save();
event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -0,0 +1,616 @@
<?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 App\Http\Controllers;
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
use App\Factory\RecurringExpenseFactory;
use App\Filters\RecurringExpenseFilters;
use App\Http\Requests\RecurringExpense\CreateRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\DestroyRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\EditRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\ShowRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\StoreRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\UpdateRecurringExpenseRequest;
use App\Http\Requests\RecurringExpense\UploadRecurringExpenseRequest;
use App\Models\Account;
use App\Models\RecurringExpense;
use App\Repositories\RecurringExpenseRepository;
use App\Transformers\RecurringExpenseTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
/**
* Class RecurringExpenseController.
* @covers App\Http\Controllers\RecurringExpenseController
*/
class RecurringExpenseController extends BaseController
{
use MakesHash;
use Uploadable;
use BulkOptions;
use SavesDocuments;
protected $entity_type = RecurringExpense::class;
protected $entity_transformer = RecurringExpenseTransformer::class;
/**
* @var RecurringExpenseepository
*/
protected $recurring_expense_repo;
/**
* RecurringExpenseController constructor.
* @param RecurringExpenseRepository $recurring_expense_repo
*/
public function __construct(RecurringExpenseRepository $recurring_expense_repo)
{
parent::__construct();
$this->recurring_expense_repo = $recurring_expense_repo;
}
/**
* @OA\Get(
* path="/api/v1/recurring_expenses",
* operationId="getRecurringExpenses",
* tags={"recurring_expenses"},
* summary="Gets a list of recurring_expenses",
* description="Lists recurring_expenses, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the recurring_expenses, these are handled by the RecurringExpenseFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of recurring_expenses",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param RecurringExpenseFilters $filters
* @return Response|mixed
*/
public function index(RecurringExpenseFilters $filters)
{
$recurring_expenses = RecurringExpense::filter($filters);
return $this->listResponse($recurring_expenses);
}
/**
* Display the specified resource.
*
* @param ShowRecurringExpenseRequest $request
* @param RecurringExpense $recurring_expense
* @return Response
*
*
* @OA\Get(
* path="/api/v1/recurring_expenses/{id}",
* operationId="showRecurringExpense",
* tags={"recurring_expenses"},
* summary="Shows a client",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringExpense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the recurring_expense object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function show(ShowRecurringExpenseRequest $request, RecurringExpense $recurring_expense)
{
return $this->itemResponse($recurring_expense);
}
/**
* Show the form for editing the specified resource.
*
* @param EditRecurringExpenseRequest $request
* @param RecurringExpense $recurring_expense
* @return Response
*
*
* @OA\Get(
* path="/api/v1/recurring_expenses/{id}/edit",
* operationId="editRecurringExpense",
* tags={"recurring_expenses"},
* summary="Shows a client for editting",
* description="Displays a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringExpense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function edit(EditRecurringExpenseRequest $request, RecurringExpense $recurring_expense)
{
return $this->itemResponse($recurring_expense);
}
/**
* Update the specified resource in storage.
*
* @param UpdateRecurringExpenseRequest $request
* @param RecurringExpense $recurring_expense
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/recurring_expenses/{id}",
* operationId="updateRecurringExpense",
* tags={"recurring_expenses"},
* summary="Updates a client",
* description="Handles the updating of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringExpense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function update(UpdateRecurringExpenseRequest $request, RecurringExpense $recurring_expense)
{
if ($request->entityIsDeleted($recurring_expense)) {
return $request->disallowUpdate();
}
$recurring_expense = $this->recurring_expense_repo->save($request->all(), $recurring_expense);
$this->uploadLogo($request->file('company_logo'), $recurring_expense->company, $recurring_expense);
event(new RecurringExpenseWasUpdated($recurring_expense, $recurring_expense->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($recurring_expense->fresh());
}
/**
* Show the form for creating a new resource.
*
* @param CreateRecurringExpenseRequest $request
* @return Response
*
*
*
* @OA\Get(
* path="/api/v1/recurring_expenses/create",
* operationId="getRecurringExpensesCreate",
* tags={"recurring_expenses"},
* summary="Gets a new blank client object",
* description="Returns a blank object with default values",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="A blank client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function create(CreateRecurringExpenseRequest $request)
{
$recurring_expense = RecurringExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($recurring_expense);
}
/**
* Store a newly created resource in storage.
*
* @param StoreRecurringExpenseRequest $request
* @return Response
*
*
*
* @OA\Post(
* path="/api/v1/recurring_expenses",
* operationId="storeRecurringExpense",
* tags={"recurring_expenses"},
* summary="Adds a client",
* description="Adds an client to a company",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="Returns the saved client object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function store(StoreRecurringExpenseRequest $request)
{
$recurring_expense = $this->recurring_expense_repo->save($request->all(), RecurringExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id));
event(new RecurringExpenseWasCreated($recurring_expense, $recurring_expense->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($recurring_expense);
}
/**
* Remove the specified resource from storage.
*
* @param DestroyRecurringExpenseRequest $request
* @param RecurringExpense $recurring_expense
* @return Response
*
*
* @throws \Exception
* @OA\Delete(
* path="/api/v1/recurring_expenses/{id}",
* operationId="deleteRecurringExpense",
* tags={"recurring_expenses"},
* summary="Deletes a client",
* description="Handles the deletion of a client by id",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringExpense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns a HTTP status",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function destroy(DestroyRecurringExpenseRequest $request, RecurringExpense $recurring_expense)
{
$this->recurring_expense_repo->delete($recurring_expense);
return $this->itemResponse($recurring_expense->fresh());
}
/**
* Perform bulk actions on the list view.
*
* @return Response
*
*
* @OA\Post(
* path="/api/v1/recurring_expenses/bulk",
* operationId="bulkRecurringExpenses",
* tags={"recurring_expenses"},
* summary="Performs bulk actions on an array of recurring_expenses",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="User credentials",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The RecurringExpense User response",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function bulk()
{
$action = request()->input('action');
$ids = request()->input('ids');
$recurring_expenses = RecurringExpense::withTrashed()->find($this->transformKeys($ids));
$recurring_expenses->each(function ($recurring_expense, $key) use ($action) {
if (auth()->user()->can('edit', $recurring_expense)) {
$this->performAction($recurring_expense, $action, true);
}
});
return $this->listResponse(RecurringExpense::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
private function performAction(RecurringExpense $recurring_expense, string $action, $bulk = false)
{
switch ($action) {
case 'archive':
$this->recurring_expense_repo->archive($recurring_expense);
if (! $bulk) {
return $this->listResponse($recurring_expense);
}
break;
case 'restore':
$this->recurring_expense_repo->restore($recurring_expense);
if (! $bulk) {
return $this->listResponse($recurring_expense);
}
break;
case 'delete':
$this->recurring_expense_repo->delete($recurring_expense);
if (! $bulk) {
return $this->listResponse($recurring_expense);
}
break;
case 'email':
//dispatch email to queue
break;
case 'start':
$recurring_expense = $recurring_expense->service()->start()->save();
if (! $bulk) {
$this->itemResponse($recurring_expense);
}
break;
case 'stop':
$recurring_expense = $recurring_expense->service()->stop()->save();
if (! $bulk) {
$this->itemResponse($recurring_expense);
}
break;
default:
// code...
break;
}
}
/**
* Update the specified resource in storage.
*
* @param UploadRecurringExpenseRequest $request
* @param RecurringExpense $recurring_expense
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/recurring_expenses/{id}/upload",
* operationId="uploadRecurringExpense",
* tags={"recurring_expense"},
* summary="Uploads a document to a recurring_expense",
* description="Handles the uploading of a document to a recurring_expense",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The RecurringExpense Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the RecurringExpense object",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/RecurringExpense"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function upload(UploadRecurringExpenseRequest $request, RecurringExpense $recurring_expense)
{
if(!$this->checkFeature(Account::FEATURE_DOCUMENTS))
return $this->featureFailure();
if ($request->has('documents'))
$this->saveDocuments($request->file('documents'), $recurring_expense);
return $this->itemResponse($recurring_expense->fresh());
}
}

View File

@ -31,7 +31,6 @@ use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;

View File

@ -577,7 +577,7 @@ class RecurringQuoteController extends BaseController
public function action(ActionRecurringQuoteRequest $request, RecurringQuote $recurring_quote, $action)
{
switch ($action) {
case 'clone_to_RecurringQuote':
case 'clone_to_recurring_quote':
// $recurring_invoice = CloneRecurringQuoteFactory::create($recurring_invoice, auth()->user()->id);
// return $this->itemResponse($recurring_invoice);
break;

View File

@ -0,0 +1,36 @@
<?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 App\Http\Controllers;
use App\Http\Requests\Account\CreateAccountRequest;
use App\Jobs\Account\CreateAccount;
use App\Models\Account;
use App\Models\CompanyUser;
use App\Transformers\CompanyUserTransformer;
use App\Utils\Statics;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response;
class StaticController extends BaseController
{
public function __invoke()
{
$response = Statics::company(auth()->user()->getCompany()->getLocale());
return response()->json($response, 200, ['Content-type'=> 'application/json; charset=utf-8'], JSON_PRETTY_PRINT);
}
}

View File

@ -41,6 +41,10 @@ class ContactKeyLogin
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
$payload = Cache::get($request->segment(3));
if(!$payload)
abort(403, 'Link expired.');
$contact_email = $payload['email'];
if($client_contact = ClientContact::where('email', $contact_email)->where('company_id', $payload['company_id'])->first()){

View File

@ -74,7 +74,6 @@ class StoreClientRequest extends Request
$rules['number'] = ['nullable',Rule::unique('clients')->where('company_id', auth()->user()->company()->id)];
$rules['id_number'] = ['nullable',Rule::unique('clients')->where('company_id', auth()->user()->company()->id)];
return $rules;
}

View File

@ -0,0 +1,57 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
use App\Models\RecurringExpense;
use App\Utils\Traits\BulkOptions;
class BulkRecurringExpenseRequest extends Request
{
use BulkOptions;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (! $this->has('action')) {
return false;
}
if (! in_array($this->action, $this->getBulkOptions(), true)) {
return false;
}
return auth()->user()->can(auth()->user()->isAdmin(), RecurringExpense::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = $this->getGlobalRules();
/* We don't require IDs on bulk storing. */
if ($this->action !== self::$STORE_METHOD) {
$rules['ids'] = ['required'];
}
return $rules;
}
}

View File

@ -0,0 +1,28 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
use App\Models\RecurringExpense;
class CreateRecurringExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', RecurringExpense::class);
}
}

View File

@ -0,0 +1,27 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
class DestroyRecurringExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_expense);
}
}

View File

@ -0,0 +1,28 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
class EditRecurringExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_expense);
}
}

View File

@ -0,0 +1,27 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
class ShowRecurringExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('view', $this->recurring_expense);
}
}

View File

@ -0,0 +1,75 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
use App\Http\ValidationRules\RecurringExpense\UniqueRecurringExpenseNumberRule;
use App\Models\RecurringExpense;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class StoreRecurringExpenseRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('create', RecurringExpense::class);
}
public function rules()
{
$rules = [];
if ($this->number)
$rules['number'] = Rule::unique('recurring_expenses')->where('company_id', auth()->user()->company()->id);
if(!empty($this->client_id))
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
return $this->globalRules($rules);
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
}
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
}
if(array_key_exists('color', $input) && is_null($input['color']))
$input['color'] = '';
$this->replace($input);
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
];
}
}

View File

@ -0,0 +1,79 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class UpdateRecurringExpenseRequest extends Request
{
use MakesHash;
use ChecksEntityStatus;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_expense);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules['country_id'] = 'integer|nullable';
$rules['contacts.*.email'] = 'nullable|distinct';
if (isset($this->number)) {
$rules['number'] = Rule::unique('recurring_expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_expense->id);
}
return $this->globalRules($rules);
}
public function messages()
{
return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']),
'email' => ctrans('validation.email', ['attribute' => 'email']),
'name.required' => ctrans('validation.required', ['attribute' => 'name']),
'required' => ctrans('validation.required', ['attribute' => 'email']),
];
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
}
if (array_key_exists('documents', $input)) {
unset($input['documents']);
}
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
}
$this->replace($input);
}
}

View File

@ -0,0 +1,39 @@
<?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 App\Http\Requests\RecurringExpense;
use App\Http\Requests\Request;
class UploadRecurringExpenseRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_expense);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -12,9 +12,12 @@
namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Recurring\UniqueRecurringQuoteNumberRule;
use App\Models\Client;
use App\Models\RecurringQuote;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\UploadedFile;
class StoreRecurringQuoteRequest extends Request
{
@ -33,26 +36,61 @@ class StoreRecurringQuoteRequest extends Request
public function rules()
{
return [
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
'client_id' => 'required|exists:clients,id,company_id,'.auth()->user()->company()->id,
];
$rules = [];
if ($this->input('documents') && is_array($this->input('documents'))) {
$documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
} elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['invitations.*.client_contact_id'] = 'distinct';
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
$rules['number'] = new UniqueRecurringQuoteNumberRule($this->all());
return $rules;
}
protected function prepareForValidation()
{
$input = $this->all();
if ($input['client_id']) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
//$input['line_items'] = json_encode($input['line_items']);
if (isset($input['auto_bill'])) {
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);
} else {
if ($client = Client::find($input['client_id'])) {
$input['auto_bill'] = $client->getSetting('auto_bill');
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);
}
}
$this->replace($input);
}
private function setAutoBillFlag($auto_bill)
{
if ($auto_bill == 'always' || $auto_bill == 'optout') {
return true;
}
return false;
}
public function messages()
{
return [];
}
}

View File

@ -14,12 +14,15 @@ namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request;
use App\Utils\Traits\ChecksEntityStatus;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\UploadedFile;
use Illuminate\Validation\Rule;
class UpdateRecurringQuoteRequest extends Request
{
use ChecksEntityStatus;
use CleanLineItems;
use MakesHash;
/**
* Determine if the user is authorized to make this request.
@ -33,24 +36,64 @@ class UpdateRecurringQuoteRequest extends Request
public function rules()
{
return [
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
];
$rules = [];
if ($this->input('documents') && is_array($this->input('documents'))) {
$documents = count($this->input('documents'));
foreach (range(0, $documents) as $index) {
$rules['documents.'.$index] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
} elseif ($this->input('documents')) {
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
}
if($this->number)
$rules['number'] = Rule::unique('recurring_quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_quote->id);
return $rules;
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
}
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
if (isset($input['auto_bill'])) {
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);
}
if($this->number)
$rules['number'] = Rule::unique('recurring_quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_quote->id);
if (array_key_exists('documents', $input)) {
unset($input['documents']);
}
$this->replace($input);
}
/**
* if($auto_bill == '')
* off / optin / optout will reset the status of this field to off to allow
* the client to choose whether to auto_bill or not.
*
* @param enum $auto_bill off/always/optin/optout
*
* @return bool
*/
private function setAutoBillFlag($auto_bill) :bool
{
if ($auto_bill == 'always') {
return true;
}
// if($auto_bill == '')
// off / optin / optout will reset the status of this field to off to allow
// the client to choose whether to auto_bill or not.
return false;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Quote Ninja (https://paymentninja.com).
*
* @link https://github.com/paymentninja/paymentninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://paymentninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\RecurringQuote;
use App\Http\Requests\Request;
class UploadRecurringQuoteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->recurring_quote);
}
public function rules()
{
$rules = [];
if($this->input('documents'))
$rules['documents'] = 'file|mimes:html,csv,png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000';
return $rules;
}
}

View File

@ -136,6 +136,10 @@ class Request extends FormRequest
if (isset($input['contacts']) && is_array($input['contacts'])) {
foreach ($input['contacts'] as $key => $contact) {
if(!is_array($contact))
continue;
if (array_key_exists('id', $contact) && is_numeric($contact['id'])) {
unset($input['contacts'][$key]['id']);
} elseif (array_key_exists('id', $contact) && is_string($contact['id'])) {
@ -154,6 +158,7 @@ class Request extends FormRequest
}
}
}
}
}

View File

@ -59,7 +59,7 @@ class InvoiceBalanceSanity implements Rule
DB::connection(config('database.default'))->beginTransaction();
$this->invoice = Invoice::on(config('database.default'))->find($this->invoice->id);
$this->invoice = Invoice::on(config('database.default'))->withTrashed()->find($this->invoice->id);
$this->invoice->line_items = $this->input['line_items'];
$temp_invoice = $this->invoice->calc()->getTempEntity();

View File

@ -0,0 +1,67 @@
<?php
/**
* Quote Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Recurring;
use App\Models\RecurringQuote;
use Illuminate\Contracts\Validation\Rule;
/**
* Class UniqueRecurringQuoteNumberRule.
*/
class UniqueRecurringQuoteNumberRule implements Rule
{
public $input;
public function __construct($input)
{
$this->input = $input;
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkIfQuoteNumberUnique(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.recurring_quote_number_taken', ['number' => $this->input['number']]);
}
/**
* @return bool
*/
private function checkIfQuoteNumberUnique() : bool
{
if (empty($this->input['number'])) {
return true;
}
$invoice = RecurringQuote::where('client_id', $this->input['client_id'])
->where('number', $this->input['number'])
->withTrashed()
->exists();
if ($invoice) {
return false;
}
return true;
}
}

View File

@ -36,6 +36,7 @@ use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
use Turbo124\Beacon\Facades\LightLogs;
use Illuminate\Support\Facades\App;
class CreateAccount
{
@ -114,8 +115,22 @@ class CreateAccount
$spaa9f78->fresh();
if(Ninja::isHosted())
if(Ninja::isHosted()){
nlog("welcome");
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($sp035a66->settings));
$nmo = new NinjaMailerObject;
$nmo->mailable = new \Modules\Admin\Mail\Welcome($sp035a66->owner());
$nmo->company = $sp035a66;
$nmo->settings = $sp035a66->settings;
$nmo->to_user = $sp035a66->owner();
NinjaMailerJob::dispatch($nmo);
\Modules\Admin\Jobs\Account\NinjaUser::dispatch([], $sp035a66);
}
VersionCheck::dispatch();
@ -123,6 +138,9 @@ class CreateAccount
->increment()
->batch();
return $sp794f3f;
}

View File

@ -483,7 +483,7 @@ class CompanyImport implements ShouldQueue
{
$this->genericImport(Client::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'gateway_tokens', 'contacts', 'documents'],
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'gateway_tokens', 'contacts', 'documents','country'],
[['users' => 'user_id'], ['users' => 'assigned_user_id']],
'clients',
'number');
@ -496,7 +496,7 @@ class CompanyImport implements ShouldQueue
{
$this->genericImport(ClientContact::class,
['user_id', 'company_id', 'id', 'hashed_id'],
['user_id', 'company_id', 'id', 'hashed_id','company'],
[['users' => 'user_id'], ['clients' => 'client_id']],
'client_contacts',
'email');

View File

@ -0,0 +1,98 @@
<?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 App\Jobs\Cron;
use App\Factory\RecurringExpenseToExpenseFactory;
use App\Libraries\MultiDB;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Carbon;
class RecurringExpensesCron
{
use Dispatchable;
use GeneratesCounter;
public $tries = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle() : void
{
/* Get all expenses where the send date is less than NOW + 30 minutes() */
nlog("Sending recurring expenses ".Carbon::now()->format('Y-m-d h:i:s'));
if (! config('ninja.db.multi_db_enabled')) {
$this->getRecurringExpenses();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db) {
MultiDB::setDB($db);
$this->getRecurringExpenses();
}
}
}
private function getRecurringExpenses()
{
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->with('company')
->cursor();
nlog(now()->format('Y-m-d') . ' Generating Recurring Expenses. Count = '.$recurring_expenses->count());
$recurring_expenses->each(function ($recurring_expense, $key) {
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_expense->next_send_date);
if (!$recurring_expense->company->is_disabled) {
$this->generateExpense($recurring_expense);
}
});
}
private function generateExpense(RecurringExpense $recurring_expense)
{
$expense = RecurringExpenseToExpenseFactory::create($recurring_expense);
$expense->save();
$expense->number = $this->getNextExpenseNumber($expense);
$expense->save();
$recurring_expense->next_send_date = $recurring_expense->nextSendDate();
$recurring_expense->remaining_cycles = $recurring_expense->remainingCycles();
$recurring_expense->save();
}
}

View File

@ -83,7 +83,6 @@ class CreateUser
]);
if(!Ninja::isSelfHost()) {
nlog("in the create user class");
event(new UserWasCreated($user, $user, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}

View File

@ -59,6 +59,7 @@ use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
@ -132,13 +133,14 @@ class Import implements ShouldQueue
'projects',
'products',
'credits',
'invoices',
'recurring_invoices',
'invoices',
'quotes',
'payments',
'expense_categories',
'task_statuses',
'expenses',
'recurring_expenses',
'tasks',
'documents',
];
@ -812,6 +814,68 @@ class Import implements ShouldQueue
$product_repository = null;
}
private function processRecurringExpenses(array $data) :void
{
RecurringExpense::unguard();
$rules = [
'*.amount' => ['numeric'],
];
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new MigrationValidatorFailed(json_encode($validator->errors()));
}
foreach ($data as $resource) {
$modified = $resource;
unset($modified['id']);
$modified['company_id'] = $this->company->id;
$modified['user_id'] = $this->processUserId($resource);
if (isset($resource['client_id'])) {
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
}
if (isset($resource['category_id'])) {
$modified['category_id'] = $this->transformId('expense_categories', $resource['category_id']);
}
if (isset($resource['vendor_id'])) {
$modified['vendor_id'] = $this->transformId('vendors', $resource['vendor_id']);
}
$expense = RecurringExpense::create($modified);
if(array_key_exists('created_at', $modified))
$expense->created_at = Carbon::parse($modified['created_at']);
if(array_key_exists('updated_at', $modified))
$expense->updated_at = Carbon::parse($modified['updated_at']);
$expense->save(['timestamps' => false]);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
$key = "recurring_expenses_{$resource['id']}";
$this->ids['recurring_expenses'][$key] = [
'old' => $resource['id'],
'new' => $expense->id,
];
}
RecurringExpense::reguard();
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
}
private function processRecurringInvoices(array $data) :void
{
RecurringInvoice::unguard();
@ -908,6 +972,10 @@ class Import implements ShouldQueue
}
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
if(array_key_exists('recurring_id', $resource) && !is_null($resource['recurring_id']))
$modified['recurring_id'] = $this->transformId('recurring_invoices', (string)$resource['recurring_id']);
$modified['user_id'] = $this->processUserId($resource);
$modified['company_id'] = $this->company->id;
$modified['line_items'] = $this->cleanItems($modified['line_items']);

View File

@ -116,9 +116,9 @@ class WebhookHandler implements ShouldQueue
]);
SystemLogger::dispatch(
$response,
array_merge((array)$response,$data),
SystemLog::CATEGORY_WEBHOOK,
SystemLog::EVENT_WEBHOOK_RESPONSE,
SystemLog::EVENT_WEBHOOK_SUCCESS,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->company->clients->first(),
$this->company

View File

@ -0,0 +1,57 @@
<?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 App\Listeners\RecurringExpense;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class CreatedRecurringExpenseActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$recurring_expense = $event->recurring_expense;
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_expense->user_id;
$fields->recurring_expense_id = $recurring_expense->id;
$fields->user_id = $user_id;
$fields->company_id = $recurring_expense->company_id;
$fields->activity_type_id = Activity::CREATE_RECURRING_EXPENSE;
$this->activity_repo->save($fields, $recurring_expense, $event->event_vars);
}
}

View File

@ -0,0 +1,57 @@
<?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 App\Listeners\RecurringExpense;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringExpenseArchivedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$recurring_expense = $event->recurring_expense;
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_expense->user_id;
$fields->recurring_expense_id = $recurring_expense->id;
$fields->user_id = $user_id;
$fields->company_id = $recurring_expense->company_id;
$fields->activity_type_id = Activity::ARCHIVE_RECURRING_EXPENSE;
$this->activity_repo->save($fields, $recurring_expense, $event->event_vars);
}
}

View File

@ -0,0 +1,55 @@
<?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 App\Listeners\RecurringExpense;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringExpenseDeletedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_expense->user_id;
$fields->recurring_expense_id = $event->recurring_expense->id;
$fields->user_id = $user_id;
$fields->company_id = $event->recurring_expense->company_id;
$fields->activity_type_id = Activity::DELETE_RECURRING_EXPENSE;
$this->activity_repo->save($fields, $event->recurring_expense, $event->event_vars);
}
}

View File

@ -0,0 +1,55 @@
<?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 App\Listeners\RecurringExpense;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringExpenseRestoredActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_expense->user_id;
$fields->recurring_expense_id = $event->recurring_expense->id;
$fields->user_id = $user_id;
$fields->company_id = $event->recurring_expense->company_id;
$fields->activity_type_id = Activity::RESTORE_RECURRING_EXPENSE;
$this->activity_repo->save($fields, $event->recurring_expense, $event->event_vars);
}
}

View File

@ -0,0 +1,57 @@
<?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 App\Listeners\RecurringExpense;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringExpenseUpdatedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$recurring_expense = $event->recurring_expense;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_expense->user_id;
$fields = new stdClass;
$fields->recurring_expense_id = $recurring_expense->id;
$fields->user_id = $user_id;
$fields->company_id = $recurring_expense->company_id;
$fields->activity_type_id = Activity::UPDATE_RECURRING_EXPENSE;
$this->activity_repo->save($fields, $recurring_expense, $event->event_vars);
}
}

View File

@ -0,0 +1,56 @@
<?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 App\Listeners\RecurringQuote;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class CreateRecurringQuoteActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_quote->user_id;
$fields->user_id = $user_id;
$fields->recurring_quote_id = $event->recurring_quote->id;
$fields->client_id = $event->recurring_quote->client_id;
$fields->company_id = $event->recurring_quote->company_id;
$fields->activity_type_id = Activity::CREATE_RECURRING_QUOTE;
$this->activity_repo->save($fields, $event->recurring_quote, $event->event_vars);
}
}

View File

@ -0,0 +1,57 @@
<?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 App\Listeners\RecurringQuote;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringQuoteArchivedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$event->recurring_quote->service()->deletePdf();
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_quote->user_id;
$fields->user_id = $user_id;
$fields->recurring_quote_id = $event->recurring_quote->id;
$fields->client_id = $event->recurring_quote->client_id;
$fields->company_id = $event->recurring_quote->company_id;
$fields->activity_type_id = Activity::ARCHIVE_RECURRING_QUOTE;
$this->activity_repo->save($fields, $event->recurring_quote, $event->event_vars);
}
}

View File

@ -0,0 +1,55 @@
<?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 App\Listeners\RecurringQuote;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringQuoteDeletedActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_quote->user_id;
$fields->user_id = $user_id;
$fields->recurring_quote_id = $event->recurring_quote->id;
$fields->client_id = $event->recurring_quote->client_id;
$fields->company_id = $event->recurring_quote->company_id;
$fields->activity_type_id = Activity::DELETE_RECURRING_QUOTE;
$this->activity_repo->save($fields, $event->recurring_quote, $event->event_vars);
}
}

View File

@ -0,0 +1,55 @@
<?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 App\Listeners\RecurringQuote;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class RecurringQuoteRestoredActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_quote->user_id;
$fields->user_id = $user_id;
$fields->recurring_quote_id = $event->recurring_quote->id;
$fields->client_id = $event->recurring_quote->client_id;
$fields->company_id = $event->recurring_quote->company_id;
$fields->activity_type_id = Activity::RESTORE_RECURRING_QUOTE;
$this->activity_repo->save($fields, $event->recurring_quote, $event->event_vars);
}
}

View File

@ -0,0 +1,55 @@
<?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 App\Listeners\RecurringQuote;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class UpdateRecurringQuoteActivity implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDB($event->company->db);
$fields = new stdClass;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->recurring_quote->user_id;
$fields->user_id = $user_id;
$fields->client_id = $event->recurring_quote->client_id;
$fields->company_id = $event->recurring_quote->company_id;
$fields->activity_type_id = Activity::UPDATE_RECURRING_QUOTE;
$fields->recurring_quote_id = $event->recurring_quote->id;
$this->activity_repo->save($fields, $event->recurring_quote, $event->event_vars);
}
}

View File

@ -24,6 +24,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Carbon;
class SendVerificationNotification implements ShouldQueue
{
@ -53,6 +54,9 @@ class SendVerificationNotification implements ShouldQueue
$event->user->service()->invite($event->company);
if(Carbon::parse($event->company->created_at)->lt(now()->subDay()))
{
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($event->company->settings));
@ -63,7 +67,7 @@ class SendVerificationNotification implements ShouldQueue
$nmo->settings = $event->company->settings;
$nmo->to_user = $event->creating_user;
NinjaMailerJob::dispatch($nmo);
}
}
}

View File

@ -62,10 +62,10 @@ class SupportMessageSent extends Mailable
$company = auth()->user()->company();
$user = auth()->user();
$db = str_replace("db-ninja-", "", $company->db);
$is_large = $company->is_large ? "L" : "";
$is_large = $company->is_large ? "L" : "S";
if(Ninja::isHosted())
$subject = "{$priority}Hosted-{$db}{$is_large} :: {$plan} :: ".date('M jS, g:ia');
$subject = "{$priority}Hosted-{$db}-{$is_large} :: {$plan} :: ".date('M jS, g:ia');
else
$subject = "{$priority}Self Hosted :: {$plan} :: ".date('M jS, g:ia');

View File

@ -96,6 +96,18 @@ class Activity extends StaticModel
const DELETE_RECURRING_INVOICE = 103;
const RESTORE_RECURRING_INVOICE = 104;
const CREATE_RECURRING_QUOTE = 110;
const UPDATE_RECURRING_QUOTE = 111;
const ARCHIVE_RECURRING_QUOTE = 112;
const DELETE_RECURRING_QUOTE = 113;
const RESTORE_RECURRING_QUOTE = 114;
const CREATE_RECURRING_EXPENSE = 120;
const UPDATE_RECURRING_EXPENSE = 121;
const ARCHIVE_RECURRING_EXPENSE = 122;
const DELETE_RECURRING_EXPENSE = 123;
const RESTORE_RECURRING_EXPENSE = 124;
protected $casts = [
'is_system' => 'boolean',
'updated_at' => 'timestamp',

View File

@ -75,7 +75,6 @@ class Client extends BaseModel implements HasLocalePreference
'shipping_postal_code',
'shipping_country_id',
'settings',
'payment_terms',
'vat_number',
'id_number',
'group_settings_id',

View File

@ -260,6 +260,14 @@ class Company extends BaseModel
return $this->hasMany(RecurringInvoice::class)->withTrashed();
}
/**
* @return HasMany
*/
public function recurring_expenses()
{
return $this->hasMany(RecurringExpense::class)->withTrashed();
}
/**
* @return HasMany
*/

View File

@ -12,6 +12,7 @@
namespace App\Models\Presenters;
use App\Models\Country;
use Illuminate\Support\Str;
/**
* Class CompanyPresenter.
@ -139,4 +140,24 @@ class CompanyPresenter extends EntityPresenter
{
return $this->entity->size ? $this->entity->size->name : '';
}
/**
* Return company website URL.
*
* @return string
*/
public function website(): string
{
$website = $this->entity->getSetting('website');
if (empty($website)) {
return $website;
}
if (Str::contains($website, ['http', 'https'])) {
return $website;
}
return \sprintf('http://%s', $website);
}
}

View File

@ -0,0 +1,31 @@
<?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 App\Models\Presenters;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use Laracasts\Presenter\PresentableTrait;
/**
* Class QuotePresenter.
*
* For convenience and to allow users to easiliy
* customise their invoices, we provide all possible
* invoice variables to be available from this presenter.
*
* Shortcuts to other presenters are here to facilitate
* a clean UI / UX
*/
class RecurringQuotePresenter extends InvoicePresenter
{
}

View File

@ -0,0 +1,167 @@
<?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 App\Models;
use App\Models\RecurringInvoice;
use App\Services\Recurring\RecurringService;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
class RecurringExpense extends BaseModel
{
use SoftDeletes;
use Filterable;
protected $fillable = [
'client_id',
'assigned_user_id',
'vendor_id',
'invoice_id',
'currency_id',
'date',
'invoice_currency_id',
'amount',
'foreign_amount',
'exchange_rate',
'private_notes',
'public_notes',
'bank_id',
'transaction_id',
'category_id',
'tax_rate1',
'tax_name1',
'tax_rate2',
'tax_name2',
'tax_rate3',
'tax_name3',
'payment_date',
'payment_type_id',
'project_id',
'transaction_reference',
'invoice_documents',
'should_be_invoiced',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'number',
'tax_amount1',
'tax_amount2',
'tax_amount3',
'uses_inclusive_taxes',
'calculate_tax_by_amount',
'frequency_id',
'last_sent_date',
'next_send_date',
'remaining_cycles',
];
protected $casts = [
'is_deleted' => 'boolean',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
protected $touches = [];
public function getEntityType()
{
return self::class;
}
public function documents()
{
return $this->morphMany(Document::class, 'documentable');
}
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
public function assigned_user()
{
return $this->belongsTo(User::class, 'assigned_user_id', 'id');
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function vendor()
{
return $this->belongsTo(Vendor::class);
}
public function client()
{
return $this->belongsTo(Client::class);
}
/**
* Service entry points.
*/
public function service() :RecurringService
{
return new RecurringService($this);
}
public function nextSendDate() :?Carbon
{
if (!$this->next_send_date) {
return null;
}
switch ($this->frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->startOfDay()->addDay();
case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek();
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2);
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4);
case RecurringInvoice::FREQUENCY_MONTHLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow();
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2);
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3);
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4);
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6);
case RecurringInvoice::FREQUENCY_ANNUALLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addYear();
case RecurringInvoice::FREQUENCY_TWO_YEARS:
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2);
case RecurringInvoice::FREQUENCY_THREE_YEARS:
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3);
default:
return null;
}
}
public function remainingCycles() : int
{
if ($this->remaining_cycles == 0) {
return 0;
} elseif ($this->remaining_cycles == -1) {
return -1;
} else {
return $this->remaining_cycles - 1;
}
}
}

View File

@ -11,56 +11,73 @@
namespace App\Models;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Models\Presenters\RecurringQuotePresenter;
use App\Models\Quote;
use App\Models\RecurringQuoteInvitation;
use App\Services\Recurring\RecurringService;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Recurring\HasRecurrence;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Laracasts\Presenter\PresentableTrait;
/**
* Class for Recurring Invoices.
* Class for Recurring Quotes.
*/
class RecurringQuote extends BaseModel
{
use MakesHash;
use SoftDeletes;
use Filterable;
use MakesDates;
use HasRecurrence;
use PresentableTrait;
protected $presenter = RecurringQuotePresenter::class;
/**
* Invoice Statuses.
* Quote Statuses.
*/
const STATUS_DRAFT = 2;
const STATUS_ACTIVE = 3;
const STATUS_DRAFT = 1;
const STATUS_ACTIVE = 2;
const STATUS_PAUSED = 3;
const STATUS_COMPLETED = 4;
const STATUS_PENDING = -1;
const STATUS_COMPLETED = -2;
const STATUS_CANCELLED = -3;
/**
* Recurring intervals.
* Quote Frequencies.
*/
const FREQUENCY_WEEKLY = 1;
const FREQUENCY_TWO_WEEKS = 2;
const FREQUENCY_FOUR_WEEKS = 3;
const FREQUENCY_MONTHLY = 4;
const FREQUENCY_TWO_MONTHS = 5;
const FREQUENCY_THREE_MONTHS = 6;
const FREQUENCY_FOUR_MONTHS = 7;
const FREQUENCY_SIX_MONTHS = 8;
const FREQUENCY_ANNUALLY = 9;
const FREQUENCY_TWO_YEARS = 10;
const FREQUENCY_DAILY = 1;
const FREQUENCY_WEEKLY = 2;
const FREQUENCY_TWO_WEEKS = 3;
const FREQUENCY_FOUR_WEEKS = 4;
const FREQUENCY_MONTHLY = 5;
const FREQUENCY_TWO_MONTHS = 6;
const FREQUENCY_THREE_MONTHS = 7;
const FREQUENCY_FOUR_MONTHS = 8;
const FREQUENCY_SIX_MONTHS = 9;
const FREQUENCY_ANNUALLY = 10;
const FREQUENCY_TWO_YEARS = 11;
const FREQUENCY_THREE_YEARS = 12;
const RECURS_INDEFINITELY = -1;
protected $fillable = [
'client_id',
'quote_number',
'project_id',
'number',
'discount',
'is_amount_discount',
'po_number',
'quote_date',
'valid_until',
'date',
'due_date',
'due_date_days',
'line_items',
'settings',
'footer',
'public_note',
'public_notes',
'private_notes',
'terms',
'tax_name1',
@ -74,26 +91,42 @@ class RecurringQuote extends BaseModel
'custom_value3',
'custom_value4',
'amount',
'partial',
'frequency_id',
'due_date_days',
'next_send_date',
'remaining_cycles',
'auto_bill',
'auto_bill_enabled',
'design_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_surcharge_tax1',
'custom_surcharge_tax2',
'custom_surcharge_tax3',
'custom_surcharge_tax4',
'design_id',
'assigned_user_id',
'exchange_rate',
];
protected $touches = [];
protected $casts = [
'settings' => 'object',
'line_items' => 'object',
'backup' => 'object',
'settings' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
];
protected $with = [
// 'client',
// 'company',
protected $appends = [
'hashed_id',
'status',
];
protected $touches = [];
public function getEntityType()
{
return self::class;
@ -126,6 +159,16 @@ class RecurringQuote extends BaseModel
return $value;
}
public function activities()
{
return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50);
}
public function history()
{
return $this->hasManyThrough(Backup::class, Activity::class);
}
public function company()
{
return $this->belongsTo(Company::class);
@ -136,6 +179,11 @@ class RecurringQuote extends BaseModel
return $this->belongsTo(Client::class)->withTrashed();
}
public function project()
{
return $this->belongsTo(Project::class)->withTrashed();
}
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
@ -146,8 +194,299 @@ class RecurringQuote extends BaseModel
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();
}
public function quotes()
{
return $this->hasMany(Quote::class, 'recurring_id', 'id')->withTrashed();
}
public function invitations()
{
$this->morphMany(RecurringQuoteInvitation::class);
return $this->hasMany(RecurringQuoteInvitation::class);
}
public function documents()
{
return $this->morphMany(Document::class, 'documentable');
}
public function getStatusAttribute()
{
if ($this->status_id == self::STATUS_ACTIVE && Carbon::parse($this->next_send_date)->isFuture()) {
return self::STATUS_PENDING;
} else {
return $this->status_id;
}
}
public function nextSendDate() :?Carbon
{
if (!$this->next_send_date) {
return null;
}
$offset = $this->client->timezone_offset();
/*
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
to add ON a day - a day = 86400 seconds
*/
if($offset < 0)
$offset += 86400;
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY:
return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}
}
public function nextDateByFrequency($date)
{
$offset = $this->client->timezone_offset();
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($date)->startOfDay()->addDay()->addSeconds($offset);
case self::FREQUENCY_WEEKLY:
return Carbon::parse($date)->startOfDay()->addWeek()->addSeconds($offset);
case self::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->startOfDay()->addWeeks(2)->addSeconds($offset);
case self::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->startOfDay()->addWeeks(4)->addSeconds($offset);
case self::FREQUENCY_MONTHLY:
return Carbon::parse($date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset);
case self::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset);
case self::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset);
case self::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset);
case self::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6)->addSeconds($offset);
case self::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->startOfDay()->addYear()->addSeconds($offset);
case self::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->startOfDay()->addYears(2)->addSeconds($offset);
case self::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->startOfDay()->addYears(3)->addSeconds($offset);
default:
return null;
}
}
public function remainingCycles() : int
{
if ($this->remaining_cycles == 0) {
return 0;
} elseif ($this->remaining_cycles == -1) {
return -1;
} else {
return $this->remaining_cycles - 1;
}
}
public function setCompleted() : void
{
$this->status_id = self::STATUS_COMPLETED;
$this->next_send_date = null;
$this->remaining_cycles = 0;
$this->save();
}
public static function badgeForStatus(int $status)
{
switch ($status) {
case self::STATUS_DRAFT:
return '<h4><span class="badge badge-light">'.ctrans('texts.draft').'</span></h4>';
break;
case self::STATUS_PENDING:
return '<h4><span class="badge badge-primary">'.ctrans('texts.pending').'</span></h4>';
break;
case self::STATUS_ACTIVE:
return '<h4><span class="badge badge-primary">'.ctrans('texts.active').'</span></h4>';
break;
case self::STATUS_COMPLETED:
return '<h4><span class="badge badge-success">'.ctrans('texts.status_completed').'</span></h4>';
break;
case self::STATUS_PAUSED:
return '<h4><span class="badge badge-danger">'.ctrans('texts.paused').'</span></h4>';
break;
default:
// code...
break;
}
}
public static function frequencyForKey(int $frequency_id) :string
{
switch ($frequency_id) {
case self::FREQUENCY_DAILY:
return ctrans('texts.freq_daily');
break;
case self::FREQUENCY_WEEKLY:
return ctrans('texts.freq_weekly');
break;
case self::FREQUENCY_TWO_WEEKS:
return ctrans('texts.freq_two_weeks');
break;
case self::FREQUENCY_FOUR_WEEKS:
return ctrans('texts.freq_four_weeks');
break;
case self::FREQUENCY_MONTHLY:
return ctrans('texts.freq_monthly');
break;
case self::FREQUENCY_TWO_MONTHS:
return ctrans('texts.freq_two_months');
break;
case self::FREQUENCY_THREE_MONTHS:
return ctrans('texts.freq_three_months');
break;
case self::FREQUENCY_FOUR_MONTHS:
return ctrans('texts.freq_four_months');
break;
case self::FREQUENCY_SIX_MONTHS:
return ctrans('texts.freq_six_months');
break;
case self::FREQUENCY_ANNUALLY:
return ctrans('texts.freq_annually');
break;
case self::FREQUENCY_TWO_YEARS:
return ctrans('texts.freq_two_years');
break;
default:
// code...
break;
}
}
public function calc()
{
$invoice_calc = null;
if ($this->uses_inclusive_taxes) {
$invoice_calc = new InvoiceSumInclusive($this);
} else {
$invoice_calc = new InvoiceSum($this);
}
return $invoice_calc->build();
}
/*
* Important to note when playing with carbon dates - in order
* not to modify the original instance, always use a `->copy()`
*
*/
public function recurringDates()
{
/* Return early if nothing to send back! */
if ($this->status_id == self::STATUS_COMPLETED ||
$this->remaining_cycles == 0 ||
!$this->next_send_date) {
return [];
}
/* Endless - lets send 10 back*/
$iterations = $this->remaining_cycles;
if ($this->remaining_cycles == -1) {
$iterations = 10;
}
$data = [];
if (!Carbon::parse($this->next_send_date)) {
return $data;
}
$next_send_date = Carbon::parse($this->next_send_date)->copy();
for ($x=0; $x<$iterations; $x++) {
// we don't add the days... we calc the day of the month!!
$next_due_date = $this->calculateDueDate($next_send_date->copy()->format('Y-m-d'));
$next_due_date_string = $next_due_date ? $next_due_date->format('Y-m-d') : '';
$next_send_date = Carbon::parse($next_send_date);
$data[] = [
'send_date' => $next_send_date->format('Y-m-d'),
'due_date' => $next_due_date_string
];
/* Fixes the timeshift in case the offset is negative which cause a infinite loop due to UTC +0*/
if($this->client->timezone_offset() < 0){
$next_send_date = $this->nextDateByFrequency($next_send_date->addDay()->format('Y-m-d'));
}
else
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
}
return $data;
}
public function calculateDueDate($date)
{
switch ($this->due_date_days) {
case 'terms':
return $this->calculateDateFromTerms($date);
break;
default:
return $this->setDayOfMonth($date, $this->due_date_days);
break;
}
}
/**
* Calculates a date based on the client payment terms.
*
* @param Carbon $date A given date
* @return NULL|Carbon The date
*/
public function calculateDateFromTerms($date)
{
$new_date = Carbon::parse($date);
$client_payment_terms = $this->client->getSetting('payment_terms');
if ($client_payment_terms == '') {//no due date! return null;
return null;
}
return $new_date->addDays($client_payment_terms); //add the number of days in the payment terms to the date
}
/**
* Service entry points.
*/
public function service() :RecurringService
{
return new RecurringService($this);
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* Invoice Ninja (https://quoteninja.com).
*
* @link https://github.com/quoteninja/quoteninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Models;
use App\Models\RecurringQuote;
use App\Utils\Traits\Inviteable;
use App\Utils\Traits\MakesDates;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class RecurringQuoteInvitation extends BaseModel
{
use MakesDates;
use SoftDeletes;
use Inviteable;
protected $fillable = ['client_contact_id'];
protected $touches = ['recurring_quote'];
protected $with = [
'company',
'contact',
];
public function getEntityType()
{
return self::class;
}
public function entityType()
{
return RecurringQuote::class;
}
/**
* @return mixed
*/
public function recurring_quote()
{
return $this->belongsTo(RecurringQuote::class)->withTrashed();
}
/**
* @return mixed
*/
public function contact()
{
return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
}
/**
* @return mixed
*/
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
/**
* @return BelongsTo
*/
public function company()
{
return $this->belongsTo(Company::class);
}
public function markViewed()
{
$this->viewed_date = now();
$this->save();
}
public function markOpened()
{
$this->opened_date = now();
$this->save();
}
}

View File

@ -52,6 +52,7 @@ class SystemLog extends Model
const EVENT_MAIL_DELIVERY = 34;
const EVENT_WEBHOOK_RESPONSE = 40;
const EVENT_WEBHOOK_SUCCESS = 41;
const EVENT_PDF_RESPONSE = 50;
const EVENT_AUTHENTICATION_FAILURE = 60;

View File

@ -102,11 +102,11 @@ class AuthorizeCreditCard
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($cgt->gateway_customer_reference, $cgt->token, $amount);
/*Refactor and push to BaseDriver*/
if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') {
$response = $data['response'];
// if ($response != null && $response->getMessages()->getResultCode() == 'Ok') {
if ($response != null && $response->getMessages() != null) {
$this->storePayment($payment_hash, $data);
$vars = [

View File

@ -45,7 +45,8 @@ class AuthorizeTransactions
$response = $controller->executeWithApiResponse($this->authorize->mode());
if (($response != null) && ($response->getMessages()->getResultCode() == 'Ok')) {
// if (($response != null) && ($response->getMessages()->getResultCode() == 'Ok')) {
if ($response != null && $response->getMessages() != null) {
nlog('SUCCESS: Transaction Status:'.$response->getTransaction()->getTransactionStatus());
nlog(' Auth Amount:'.$response->getTransaction()->getAuthAmount());
nlog(' Trans ID:'.$response->getTransaction()->getTransId());

View File

@ -61,6 +61,13 @@ class CreditCard
$data['gateway'] = $this->braintree;
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) {
/** https://developer.paypal.com/braintree/docs/reference/request/client-token/generate#merchant_account_id */
$data['client_token'] = $this->braintree->gateway->clientToken()->generate([
'merchantAccountId' => $this->braintree->company_gateway->getConfigField('merchantAccountId')
]);
}
return render('gateways.braintree.credit_card.pay', $data);
}
@ -136,13 +143,15 @@ class CreditCard
$gateway_response = \json_decode($data['gateway_response']);
$response = $this->braintree->gateway->paymentMethod()->create([
$data = [
'customerId' => $customerId,
'paymentMethodNonce' => $gateway_response->nonce,
'options' => [
'verifyCard' => true,
],
]);
];
$response = $this->braintree->gateway->paymentMethod()->create($data);
if ($response->success) {
return $response->paymentMethod->token;

View File

@ -42,6 +42,7 @@ class SOFORT
$data['return_url'] = $this->buildReturnUrl();
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
$data['client'] = $this->stripe->client;
$data['customer'] = $this->stripe->findOrCreateCustomer()->id;
$data['country'] = $this->stripe->client->country->iso_3166_2;
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
@ -85,7 +86,7 @@ class SOFORT
'gateway_type_id' => GatewayType::SOFORT,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
$this->stripe->createPayment($data, Payment::STATUS_PENDING);
SystemLogger::dispatch(
['response' => $this->stripe->payment_hash->data, 'data' => $data],
@ -96,7 +97,7 @@ class SOFORT
$this->stripe->client->company,
);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
return redirect()->route('client.payments.index');
}
public function processUnsuccessfulPayment()

View File

@ -22,6 +22,7 @@ use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\Alipay;
@ -422,7 +423,8 @@ class StripePaymentDriver extends BaseDriver
// Allow app to catch up with webhook request.
sleep(2);
if ($request->type === 'charge.succeeded' || $request->type === 'source.chargeable') {
if ($request->type === 'charge.succeeded') {
foreach ($request->data as $transaction) {
$payment = Payment::query()
->where('transaction_reference', $transaction['id'])
@ -434,6 +436,28 @@ class StripePaymentDriver extends BaseDriver
$payment->save();
}
}
} elseif ($request->type === 'source.chargeable') {
$this->init();
foreach ($request->data as $transaction) {
$charge = \Stripe\Charge::create([
'amount' => $request->data['object']['amount'],
'currency' => $request->data['object']['currency'],
'source' => $request->data['object']['id'],
], $this->stripe_connect_auth);
if ($charge->captured) {
$payment = Payment::query()
->where('transaction_reference', $transaction['id'])
->where('company_id', $request->getCompany()->id)
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
}
}
}
return response()->json([], 200);
@ -540,6 +564,11 @@ class StripePaymentDriver extends BaseDriver
return Account::all();
}
public function setClientFromCustomer($customer)
{
$this->client = ClientGatewayToken::where('gateway_customer_reference', $customer)->client;
}
/**
* Pull all client payment methods and update
* the respective tokens in the system.

View File

@ -0,0 +1,31 @@
<?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 App\Policies;
use App\Models\User;
/**
* Class RecurringExpensePolicy.
*/
class RecurringExpensePolicy extends EntityPolicy
{
/**
* Checks if the user has create permissions.
*
* @param User $user
* @return bool
*/
public function create(User $user) : bool
{
return $user->isAdmin() || $user->hasPermission('create_recurring_expense') || $user->hasPermission('create_all');
}
}

View File

@ -12,7 +12,6 @@
namespace App\Providers;
use App\Models\Activity;
use App\Models\Subscription;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
@ -29,8 +28,10 @@ use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\Models\Subscription;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
@ -38,7 +39,6 @@ use App\Models\User;
use App\Models\Vendor;
use App\Models\Webhook;
use App\Policies\ActivityPolicy;
use App\Policies\SubscriptionPolicy;
use App\Policies\ClientPolicy;
use App\Policies\CompanyGatewayPolicy;
use App\Policies\CompanyPolicy;
@ -55,8 +55,10 @@ use App\Policies\PaymentTermPolicy;
use App\Policies\ProductPolicy;
use App\Policies\ProjectPolicy;
use App\Policies\QuotePolicy;
use App\Policies\RecurringExpensePolicy;
use App\Policies\RecurringInvoicePolicy;
use App\Policies\RecurringQuotePolicy;
use App\Policies\SubscriptionPolicy;
use App\Policies\TaskPolicy;
use App\Policies\TaskStatusPolicy;
use App\Policies\TaxRatePolicy;
@ -92,6 +94,7 @@ class AuthServiceProvider extends ServiceProvider
Product::class => ProductPolicy::class,
Project::class => ProjectPolicy::class,
Quote::class => QuotePolicy::class,
RecurringExpense::class => RecurringExpensePolicy::class,
RecurringInvoice::class => RecurringInvoicePolicy::class,
RecurringQuote::class => RecurringQuotePolicy::class,
Webhook::class => WebhookPolicy::class,

View File

@ -68,11 +68,21 @@ use App\Events\Quote\QuoteWasEmailed;
use App\Events\Quote\QuoteWasRestored;
use App\Events\Quote\QuoteWasUpdated;
use App\Events\Quote\QuoteWasViewed;
use App\Events\RecurringExpense\RecurringExpenseWasArchived;
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
use App\Events\RecurringExpense\RecurringExpenseWasRestored;
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
use App\Events\RecurringInvoice\RecurringInvoiceWasArchived;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted;
use App\Events\RecurringInvoice\RecurringInvoiceWasRestored;
use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
use App\Events\RecurringQuote\RecurringQuoteWasArchived;
use App\Events\RecurringQuote\RecurringQuoteWasCreated;
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
use App\Events\RecurringQuote\RecurringQuoteWasRestored;
use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
use App\Events\Subscription\SubscriptionWasArchived;
use App\Events\Subscription\SubscriptionWasCreated;
use App\Events\Subscription\SubscriptionWasDeleted;
@ -170,11 +180,21 @@ use App\Listeners\Quote\QuoteEmailedNotification;
use App\Listeners\Quote\QuoteRestoredActivity;
use App\Listeners\Quote\QuoteViewedActivity;
use App\Listeners\Quote\ReachWorkflowSettings;
use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity;
use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity;
use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity;
use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity;
use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity;
use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity;
use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity;
use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity;
use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity;
use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity;
use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity;
use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity;
use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity;
use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity;
use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity;
use App\Listeners\SendVerificationNotification;
use App\Listeners\User\ArchivedUserActivity;
use App\Listeners\User\CreatedUserActivity;
@ -412,6 +432,36 @@ class EventServiceProvider extends ServiceProvider
QuoteWasRestored::class => [
QuoteRestoredActivity::class,
],
RecurringExpenseWasCreated::class => [
CreatedRecurringExpenseActivity::class,
],
RecurringExpenseWasUpdated::class => [
RecurringExpenseUpdatedActivity::class,
],
RecurringExpenseWasArchived::class => [
RecurringExpenseArchivedActivity::class,
],
RecurringExpenseWasDeleted::class => [
RecurringExpenseDeletedActivity::class,
],
RecurringExpenseWasRestored::class => [
RecurringExpenseRestoredActivity::class
],
RecurringQuoteWasUpdated::class => [
UpdateRecurringQuoteActivity::class,
],
RecurringQuoteWasCreated::class => [
CreateRecurringQuoteActivity::class,
],
RecurringQuoteWasDeleted::class => [
RecurringQuoteDeletedActivity::class,
],
RecurringQuoteWasArchived::class => [
RecurringQuoteArchivedActivity::class,
],
RecurringQuoteWasRestored::class => [
RecurringQuoteRestoredActivity::class,
],
RecurringInvoiceWasUpdated::class => [
UpdateRecurringInvoiceActivity::class,
],

View File

@ -168,7 +168,9 @@ class InvoiceMigrationRepository extends BaseRepository
$model->save();
}
if($data['deleted_at'])
if($data['deleted_at'] == '0000-00-00 00:00:00.000000')
$model->deleted_at = null;
else if($data['deleted_at'])
$model->delete();
$model->save();

View File

@ -0,0 +1,60 @@
<?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 App\Repositories;
use App\Factory\RecurringExpenseFactory;
use App\Models\RecurringExpense;
use App\Utils\Traits\GeneratesCounter;
/**
* RecurringExpenseRepository.
*/
class RecurringExpenseRepository extends BaseRepository
{
use GeneratesCounter;
/**
* Saves the recurring_expense and its contacts.
*
* @param array $data The data
* @param \App\Models\RecurringExpense $recurring_expense The recurring_expense
*
* @return \App\Models\RecurringExpense|Null recurring_expense Object
*/
public function save(array $data, RecurringExpense $recurring_expense) : ?RecurringExpense
{
$recurring_expense->fill($data);
$recurring_expense->number = empty($recurring_expense->number) ? $this->getNextRecurringExpenseNumber($recurring_expense) : $recurring_expense->number;
$recurring_expense->save();
if (array_key_exists('documents', $data)) {
$this->saveDocuments($data['documents'], $recurring_expense);
}
return $recurring_expense;
}
/**
* Store recurring_expenses in bulk.
*
* @param array $recurring_expense
* @return \App\Models\RecurringExpense|null
*/
public function create($recurring_expense): ?RecurringExpense
{
return $this->save(
$recurring_expense,
RecurringExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id)
);
}
}

View File

@ -16,6 +16,7 @@ use App\Jobs\Util\UnlinkFile;
use App\Models\Invoice;
use App\Models\Quote;
use App\Repositories\QuoteRepository;
use App\Services\Quote\TriggeredActions;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
@ -177,6 +178,13 @@ class QuoteService
return $this;
}
public function triggeredActions($request)
{
$this->quote = (new TriggeredActions($this->quote, $request))->run();
return $this;
}
public function deletePdf()
{
$this->quote->invitations->each(function ($invitation){

View File

@ -0,0 +1,66 @@
<?php
/**
* Quote Ninja (https://quoteninja.com).
*
* @link https://github.com/quoteninja/quoteninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Quote;
use App\Events\Quote\QuoteWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Models\Quote;
use App\Services\AbstractService;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Http\Request;
class TriggeredActions extends AbstractService
{
use GeneratesCounter;
private $request;
private $quote;
public function __construct(Quote $quote, Request $request)
{
$this->request = $request;
$this->quote = $quote;
}
public function run()
{
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->sendEmail();
}
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
$this->quote = $this->quote->service()->markSent()->save();
}
return $this->quote;
}
private function sendEmail()
{
$reminder_template = $this->quote->calculateTemplate('quote');
//$reminder_template = 'payment';
$this->quote->invitations->load('contact.client.country', 'quote.client.country', 'quote.company')->each(function ($invitation) use ($reminder_template) {
EmailEntity::dispatch($invitation, $this->quote->company, $reminder_template);
});
if ($this->quote->invitations->count() > 0) {
event(new QuoteWasEmailed($this->quote->invitations->first(), $this->quote->company, Ninja::eventVars(), 'quote'));
}
}
}

View File

@ -13,7 +13,6 @@ namespace App\Transformers;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Subscription;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
@ -31,13 +30,16 @@ use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\Subscription;
use App\Models\SystemLog;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Webhook;
use App\Transformers\RecurringExpenseTransformer;
use App\Utils\Traits\MakesHash;
use stdClass;
@ -92,6 +94,7 @@ class CompanyTransformer extends EntityTransformer
'expense_categories',
'task_statuses',
'subscriptions',
'recurring_expenses',
];
/**
@ -296,6 +299,13 @@ class CompanyTransformer extends EntityTransformer
return $this->includeCollection($company->recurring_invoices, $transformer, RecurringInvoice::class);
}
public function includeRecurringExpenses(Company $company)
{
$transformer = new RecurringExpenseTransformer($this->serializer);
return $this->includeCollection($company->recurring_expenses, $transformer, RecurringExpense::class);
}
public function includeQuotes(Company $company)
{
$transformer = new QuoteTransformer($this->serializer);

View File

@ -94,6 +94,7 @@ class ExpenseTransformer extends EntityTransformer
'tax_amount3' => (float) $expense->tax_amount3,
'uses_inclusive_taxes' => (bool) $expense->uses_inclusive_taxes,
'calculate_tax_by_amount' => (bool) $expense->calculate_tax_by_amount,
'entity_type' => 'expense',
];
}
}

View File

@ -0,0 +1,106 @@
<?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 App\Transformers;
use App\Models\Document;
use App\Models\RecurringExpense;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* class RecurringExpenseTransformer.
*/
class RecurringExpenseTransformer extends EntityTransformer
{
use MakesHash;
use SoftDeletes;
protected $defaultIncludes = [
'documents',
];
/**
* @var array
*/
protected $availableIncludes = [
'documents',
];
public function includeDocuments(RecurringExpense $recurring_expense)
{
$transformer = new DocumentTransformer($this->serializer);
return $this->includeCollection($recurring_expense->documents, $transformer, Document::class);
}
/**
* @param RecurringExpense $recurring_expense
*
* @return array
*/
public function transform(RecurringExpense $recurring_expense)
{
return [
'id' => $this->encodePrimaryKey($recurring_expense->id),
'user_id' => $this->encodePrimaryKey($recurring_expense->user_id),
'assigned_user_id' => $this->encodePrimaryKey($recurring_expense->assigned_user_id),
'status_id' => (string) ($recurring_expense->status_id ?: 1),
'vendor_id' => $this->encodePrimaryKey($recurring_expense->vendor_id),
'invoice_id' => $this->encodePrimaryKey($recurring_expense->invoice_id),
'client_id' => $this->encodePrimaryKey($recurring_expense->client_id),
'bank_id' => (string) $recurring_expense->bank_id ?: '',
'invoice_currency_id' => (string) $recurring_expense->invoice_currency_id ?: '',
'recurring_expense_currency_id' => '', //todo remove redundant in 5.0.25
'currency_id' => (string) $recurring_expense->currency_id ?: '',
'category_id' => $this->encodePrimaryKey($recurring_expense->category_id),
'payment_type_id' => (string) $recurring_expense->payment_type_id ?: '',
'recurring_recurring_expense_id' => (string) $recurring_expense->recurring_recurring_expense_id ?: '',
'is_deleted' => (bool) $recurring_expense->is_deleted,
'should_be_invoiced' => (bool) $recurring_expense->should_be_invoiced,
'invoice_documents' => (bool) $recurring_expense->invoice_documents,
'amount' => (float) $recurring_expense->amount ?: 0,
'foreign_amount' => (float) $recurring_expense->foreign_amount ?: 0,
'exchange_rate' => (float) $recurring_expense->exchange_rate ?: 0,
'tax_name1' => $recurring_expense->tax_name1 ? $recurring_expense->tax_name1 : '',
'tax_rate1' => (float) $recurring_expense->tax_rate1,
'tax_name2' => $recurring_expense->tax_name2 ? $recurring_expense->tax_name2 : '',
'tax_rate2' => (float) $recurring_expense->tax_rate2,
'tax_name3' => $recurring_expense->tax_name3 ? $recurring_expense->tax_name3 : '',
'tax_rate3' => (float) $recurring_expense->tax_rate3,
'private_notes' => (string) $recurring_expense->private_notes ?: '',
'public_notes' => (string) $recurring_expense->public_notes ?: '',
'transaction_reference' => (string) $recurring_expense->transaction_reference ?: '',
'transaction_id' => (string) $recurring_expense->transaction_id ?: '',
'date' => $recurring_expense->date ?: '',
'number' => (string)$recurring_expense->number ?: '',
'payment_date' => $recurring_expense->payment_date ?: '',
'custom_value1' => $recurring_expense->custom_value1 ?: '',
'custom_value2' => $recurring_expense->custom_value2 ?: '',
'custom_value3' => $recurring_expense->custom_value3 ?: '',
'custom_value4' => $recurring_expense->custom_value4 ?: '',
'updated_at' => (int) $recurring_expense->updated_at,
'archived_at' => (int) $recurring_expense->deleted_at,
'created_at' => (int) $recurring_expense->created_at,
'project_id' => $this->encodePrimaryKey($recurring_expense->project_id),
'tax_amount1' => (float) $recurring_expense->tax_amount1,
'tax_amount2' => (float) $recurring_expense->tax_amount2,
'tax_amount3' => (float) $recurring_expense->tax_amount3,
'uses_inclusive_taxes' => (bool) $recurring_expense->uses_inclusive_taxes,
'calculate_tax_by_amount' => (bool) $recurring_expense->calculate_tax_by_amount,
'entity_type' => 'recurringExpense',
'frequency_id' => (string) $recurring_expense->frequency_id,
'remaining_cycles' => (int) $recurring_expense->remaining_cycles,
'last_sent_date' => $recurring_expense->last_sent_date ?: '',
'next_send_date' => $recurring_expense->next_send_date ?: '',
];
}
}

View File

@ -34,36 +34,6 @@ class RecurringInvoiceTransformer extends EntityTransformer
'activities',
];
/*
public function includeInvoiceItems(Invoice $invoice)
{
$transformer = new InvoiceItemTransformer($this->serializer);
return $this->includeCollection($invoice->invoice_items, $transformer, ENTITY_INVOICE_ITEM);
}
public function includeInvitations(Invoice $invoice)
{
$transformer = new InvitationTransformer($this->account, $this->serializer);
return $this->includeCollection($invoice->invitations, $transformer, ENTITY_INVITATION);
}
public function includePayments(Invoice $invoice)
{
$transformer = new PaymentTransformer($this->account, $this->serializer, $invoice);
return $this->includeCollection($invoice->payments, $transformer, ENTITY_PAYMENT);
}
public function includeClient(Invoice $invoice)
{
$transformer = new ClientTransformer($this->account, $this->serializer);
return $this->includeItem($invoice->client, $transformer, ENTITY_CLIENT);
}
*/
public function includeHistory(RecurringInvoice $invoice)
{
$transformer = new InvoiceHistoryTransformer($this->serializer);

View File

@ -0,0 +1,38 @@
<?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 App\Transformers;
use App\Models\RecurringQuoteInvitation;
use App\Utils\Traits\MakesHash;
class RecurringQuoteInvitationTransformer extends EntityTransformer
{
use MakesHash;
public function transform(RecurringQuoteInvitation $invitation)
{
return [
'id' => $this->encodePrimaryKey($invitation->id),
'client_contact_id' => $this->encodePrimaryKey($invitation->client_contact_id),
'key' => $invitation->key,
'link' => $invitation->getLink() ?: '',
'sent_date' => $invitation->sent_date ?: '',
'viewed_date' => $invitation->viewed_date ?: '',
'opened_date' => $invitation->opened_date ?: '',
'updated_at' => (int) $invitation->updated_at,
'archived_at' => (int) $invitation->deleted_at,
'created_at' => (int) $invitation->created_at,
'email_status' => $invitation->email_status ?: '',
'email_error' => (string)$invitation->email_error,
];
}
}

View File

@ -11,7 +11,14 @@
namespace App\Transformers;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Document;
use App\Models\Quote;
use App\Models\RecurringQuote;
use App\Models\RecurringQuoteInvitation;
use App\Transformers\ActivityTransformer;
use App\Transformers\QuoteHistoryTransformer;
use App\Utils\Traits\MakesHash;
class RecurringQuoteTransformer extends EntityTransformer
@ -19,107 +26,110 @@ class RecurringQuoteTransformer extends EntityTransformer
use MakesHash;
protected $defaultIncludes = [
// 'invoice_items',
'invitations',
'documents',
];
protected $availableIncludes = [
// 'invitations',
// 'payments',
'invitations',
'documents',
'activities',
// 'history',
// 'client',
// 'documents',
];
/*
public function includeInvoiceItems(Invoice $quote)
public function includeHistory(RecurringQuote $quote)
{
$transformer = new InvoiceItemTransformer($this->serializer);
$transformer = new QuoteHistoryTransformer($this->serializer);
return $this->includeCollection($quote->invoice_items, $transformer, ENTITY_INVOICE_ITEM);
return $this->includeCollection($quote->history, $transformer, Backup::class);
}
public function includeInvitations(Invoice $quote)
public function includeActivities(RecurringQuote $quote)
{
$transformer = new InvitationTransformer($this->account, $this->serializer);
$transformer = new ActivityTransformer($this->serializer);
return $this->includeCollection($quote->invitations, $transformer, ENTITY_INVITATION);
return $this->includeCollection($quote->activities, $transformer, Activity::class);
}
public function includePayments(Invoice $quote)
public function includeInvitations(RecurringQuote $quote)
{
$transformer = new PaymentTransformer($this->account, $this->serializer, $quote);
$transformer = new RecurringQuoteInvitationTransformer($this->serializer);
return $this->includeCollection($quote->payments, $transformer, ENTITY_PAYMENT);
return $this->includeCollection($quote->invitations, $transformer, RecurringQuoteInvitation::class);
}
public function includeClient(Invoice $quote)
public function includeDocuments(RecurringQuote $quote)
{
$transformer = new ClientTransformer($this->account, $this->serializer);
$transformer = new DocumentTransformer($this->serializer);
return $this->includeItem($quote->client, $transformer, ENTITY_CLIENT);
return $this->includeCollection($quote->documents, $transformer, Document::class);
}
public function includeExpenses(Invoice $quote)
{
$transformer = new ExpenseTransformer($this->account, $this->serializer);
return $this->includeCollection($quote->expenses, $transformer, ENTITY_EXPENSE);
}
public function includeDocuments(Invoice $quote)
{
$transformer = new DocumentTransformer($this->account, $this->serializer);
$quote->documents->each(function ($document) use ($quote) {
$document->setRelation('invoice', $quote);
});
return $this->includeCollection($quote->documents, $transformer, ENTITY_DOCUMENT);
}
*/
public function transform(RecurringQuote $quote)
{
return [
'id' => $this->encodePrimaryKey($quote->id),
'user_id' => $this->encodePrimaryKey($quote->user_id),
'project_id' => $this->encodePrimaryKey($quote->project_id),
'assigned_user_id' => $this->encodePrimaryKey($quote->assigned_user_id),
'amount' => (float) $quote->amount ?: '',
'balance' => (float) $quote->balance ?: '',
'client_id' => (string) $quote->client_id,
'amount' => (float) $quote->amount,
'balance' => (float) $quote->balance,
'client_id' => (string) $this->encodePrimaryKey($quote->client_id),
'vendor_id' => (string) $this->encodePrimaryKey($quote->vendor_id),
'status_id' => (string) ($quote->status_id ?: 1),
'design_id' => (string) $this->encodePrimaryKey($quote->design_id),
'created_at' => (int) $quote->created_at,
'updated_at' => (int) $quote->updated_at,
'archived_at' => (int) $quote->deleted_at,
'discount' => (float) $quote->discount ?: '',
'is_deleted' => (bool) $quote->is_deleted,
'number' => $quote->number ?: '',
'discount' => (float) $quote->discount,
'po_number' => $quote->po_number ?: '',
'quote_date' => $quote->quote_date ?: '',
'valid_until' => $quote->valid_until ?: '',
'date' => $quote->date ?: '',
'last_sent_date' => $quote->last_sent_date ?: '',
'next_send_date' => $quote->next_send_date ?: '',
'due_date' => $quote->due_date ?: '',
'terms' => $quote->terms ?: '',
'public_notes' => $quote->public_notes ?: '',
'private_notes' => $quote->private_notes ?: '',
'is_deleted' => (bool) $quote->is_deleted,
'uses_inclusive_taxes' => (bool) $quote->uses_inclusive_taxes,
'tax_name1' => $quote->tax_name1 ? $quote->tax_name1 : '',
'tax_rate1' => (float) $quote->tax_rate1 ?: '',
'tax_rate1' => (float) $quote->tax_rate1,
'tax_name2' => $quote->tax_name2 ? $quote->tax_name2 : '',
'tax_rate2' => (float) $quote->tax_rate2 ?: '',
'tax_rate2' => (float) $quote->tax_rate2,
'tax_name3' => $quote->tax_name3 ? $quote->tax_name3 : '',
'tax_rate3' => (float) $quote->tax_rate3 ?: '',
'tax_rate3' => (float) $quote->tax_rate3,
'total_taxes' => (float) $quote->total_taxes,
'is_amount_discount' => (bool) ($quote->is_amount_discount ?: false),
'quote_footer' => $quote->quote_footer ?: '',
'footer' => $quote->footer ?: '',
'partial' => (float) ($quote->partial ?: 0.0),
'partial_due_date' => $quote->partial_due_date ?: '',
'custom_value1' => (float) $quote->custom_value1 ?: '',
'custom_value2' => (float) $quote->custom_value2 ?: '',
'custom_taxes1' => (bool) $quote->custom_taxes1 ?: '',
'custom_taxes2' => (bool) $quote->custom_taxes2 ?: '',
'custom_value1' => (string) $quote->custom_value1 ?: '',
'custom_value2' => (string) $quote->custom_value2 ?: '',
'custom_value3' => (string) $quote->custom_value3 ?: '',
'custom_value4' => (string) $quote->custom_value4 ?: '',
'has_tasks' => (bool) $quote->has_tasks,
'has_expenses' => (bool) $quote->has_expenses,
'custom_text_value1' => $quote->custom_text_value1 ?: '',
'custom_text_value2' => $quote->custom_text_value2 ?: '',
'settings' => $quote->settings ?: '',
'frequency_id' => (int) $quote->frequency_id,
'last_sent_date' => $quote->last_sent_date ?: '',
'next_send_date' => $quote->next_send_date ?: '',
'custom_surcharge1' => (float) $quote->custom_surcharge1,
'custom_surcharge2' => (float) $quote->custom_surcharge2,
'custom_surcharge3' => (float) $quote->custom_surcharge3,
'custom_surcharge4' => (float) $quote->custom_surcharge4,
'exchange_rate' => (float) $quote->exchange_rate,
'custom_surcharge_tax1' => (bool) $quote->custom_surcharge_tax1,
'custom_surcharge_tax2' => (bool) $quote->custom_surcharge_tax2,
'custom_surcharge_tax3' => (bool) $quote->custom_surcharge_tax3,
'custom_surcharge_tax4' => (bool) $quote->custom_surcharge_tax4,
'line_items' => $quote->line_items ?: (array) [],
'entity_type' => 'recurringQuote',
'frequency_id' => (string) $quote->frequency_id,
'remaining_cycles' => (int) $quote->remaining_cycles,
'recurring_dates' => (array) $quote->recurringDates(),
'auto_bill' => (string) $quote->auto_bill,
'auto_bill_enabled' => (bool) $quote->auto_bill_enabled,
'due_date_days' => (string) $quote->due_date_days ?: '',
'paid_to_date' => (float) $quote->paid_to_date,
'subscription_id' => (string)$this->encodePrimaryKey($quote->subscription_id),
];
}
}

View File

@ -175,6 +175,7 @@ class HtmlEngine
$data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.discount')];
$data['$discount'] = &$data['$invoice.discount'];
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
if($this->entity->uses_inclusive_taxes)
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.net_subtotal')];
@ -228,6 +229,8 @@ class HtmlEngine
$data['$invoice.taxes'] = &$data['$taxes'];
$data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')];
$data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')];
$data['$user.last_name'] = ['value' => $this->entity->user->last_name, 'label' => ctrans('texts.last_name')];
$data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
@ -378,6 +381,7 @@ class HtmlEngine
$data['$product.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
$data['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
$data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')];
$data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')];
$data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')];
@ -397,6 +401,7 @@ class HtmlEngine
$data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$task.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$task.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
$data['$task.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
$data['$task.service'] = ['value' => '', 'label' => ctrans('texts.service')];
$data['$task.task1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task1')];
$data['$task.task2'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'task2')];

View File

@ -19,7 +19,9 @@ use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Project;
use App\Models\Quote;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\RecurringQuote;
use App\Models\Task;
use App\Models\Timezone;
use App\Models\Vendor;
@ -135,6 +137,12 @@ trait GeneratesCounter
case RecurringInvoice::class:
return 'recurring_invoice_number_counter';
break;
case RecurringQuote::class:
return 'recurring_quote_number_counter';
break;
case RecurringExpense::class:
return 'recurring_expense_number_counter';
break;
case Payment::class:
return 'payment_number_counter';
break;
@ -196,6 +204,11 @@ trait GeneratesCounter
return $this->getNextEntityNumber(RecurringInvoice::class, $client);
}
public function getNextRecurringQuoteNumber(Client $client)
{
return $this->getNextEntityNumber(RecurringQuote::class, $client);
}
/**
* Gets the next Payment number.
*
@ -312,6 +325,27 @@ trait GeneratesCounter
return $expense_number;
}
/**
* Gets the next expense number.
*
* @param RecurringExpense $expense The expense
* @return string The next expense number.
*/
public function getNextRecurringExpenseNumber(RecurringExpense $expense) :string
{
$this->resetCompanyCounters($expense->company);
$counter = $expense->company->settings->recurring_expense_number_counter;
$setting_entity = $expense->company->settings->recurring_expense_number_counter;
$expense_number = $this->checkEntityNumber(RecurringExpense::class, $expense, $counter, $expense->company->settings->counter_padding, $expense->company->settings->recurring_expense_number_pattern);
$this->incrementCounter($expense->company, 'recurring_expense_number_counter');
return $expense_number;
}
/**
* Determines if it has shared counter.
*

View File

@ -311,6 +311,7 @@ trait MakesInvoiceValues
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
$data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->client);
$data[$key][$table_type.'.gross_line_total'] = Number::formatMoney($item->gross_line_total, $this->client);
if (isset($item->discount) && $item->discount > 0) {
if ($item->is_amount_discount) {

View File

@ -51,6 +51,7 @@
"hashids/hashids": "^4.0",
"hedii/laravel-gelf-logger": "^6.0",
"intervention/image": "^2.5",
"invoiceninja/inspector": "dev-main",
"laracasts/presenter": "^0.2.1",
"laravel/framework": "^8.0",
"laravel/slack-notification-channel": "^2.2",

68
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0b4a7e813c3e563d631164bd24968964",
"content-hash": "b5675738fe061ea09b0bbd89e553d074",
"packages": [
{
"name": "asm/php-ansible",
@ -3326,6 +3326,69 @@
],
"time": "2021-07-22T14:31:53+00:00"
},
{
"name": "invoiceninja/inspector",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/invoiceninja/inspector.git",
"reference": "3f5beb9854c0d1d6f65bc0d9ba847503e6e84583"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/invoiceninja/inspector/zipball/3f5beb9854c0d1d6f65bc0d9ba847503e6e84583",
"reference": "3f5beb9854c0d1d6f65bc0d9ba847503e6e84583",
"shasum": ""
},
"require": {
"doctrine/dbal": "^3.1",
"illuminate/support": "^8.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"orchestra/testbench": "^6.0",
"phpunit/phpunit": "^9.0"
},
"default-branch": true,
"type": "library",
"extra": {
"laravel": {
"providers": [
"InvoiceNinja\\Inspector\\InspectorServiceProvider"
],
"aliases": {
"Inspector": "InvoiceNinja\\Inspector\\InspectorFacade"
}
}
},
"autoload": {
"psr-4": {
"InvoiceNinja\\Inspector\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Benjamin Beganović",
"email": "benjamin.beganovic4@outlook.com",
"role": "Developer"
}
],
"description": "Simplified database records management",
"homepage": "https://github.com/invoiceninja/inspector",
"keywords": [
"inspector",
"invoiceninja"
],
"support": {
"issues": "https://github.com/invoiceninja/inspector/issues",
"source": "https://github.com/invoiceninja/inspector/tree/main"
},
"time": "2021-09-11T11:35:02+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.0.4",
@ -15598,6 +15661,7 @@
"minimum-stability": "dev",
"stability-flags": {
"asm/php-ansible": 20,
"invoiceninja/inspector": 20,
"webpatser/laravel-countries": 20
},
"prefer-stable": true,
@ -15611,5 +15675,5 @@
"platform-dev": {
"php": "^7.3|^7.4|^8.0"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

Some files were not shown because too many files have changed in this diff Show More