mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Recurring Quotes
This commit is contained in:
parent
8d15e181c3
commit
ee855824db
@ -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,17 +36,39 @@ 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']) {
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('client_id', $input) && is_string($input['client_id'])) {
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
}
|
||||
|
||||
@ -51,8 +76,56 @@ class StoreRecurringQuoteRequest extends Request
|
||||
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||
}
|
||||
|
||||
if (isset($input['client_contacts'])) {
|
||||
foreach ($input['client_contacts'] as $key => $contact) {
|
||||
if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) {
|
||||
unset($input['client_contacts'][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($input['invitations'])) {
|
||||
foreach ($input['invitations'] as $key => $value) {
|
||||
if (isset($input['invitations'][$key]['id']) && is_numeric($input['invitations'][$key]['id'])) {
|
||||
unset($input['invitations'][$key]['id']);
|
||||
}
|
||||
|
||||
if (isset($input['invitations'][$key]['id']) && is_string($input['invitations'][$key]['id'])) {
|
||||
$input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']);
|
||||
}
|
||||
|
||||
if (is_string($input['invitations'][$key]['client_contact_id'])) {
|
||||
$input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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 [];
|
||||
}
|
||||
}
|
||||
|
@ -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,91 @@ 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();
|
||||
|
||||
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
|
||||
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
|
||||
}
|
||||
|
||||
if (isset($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['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
if (isset($input['invitations'])) {
|
||||
foreach ($input['invitations'] as $key => $value) {
|
||||
if (is_numeric($input['invitations'][$key]['id'])) {
|
||||
unset($input['invitations'][$key]['id']);
|
||||
}
|
||||
|
||||
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('id', $input['invitations'][$key]) && is_string($input['invitations'][$key]['id'])) {
|
||||
$input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']);
|
||||
}
|
||||
|
||||
if (is_string($input['invitations'][$key]['client_contact_id'])) {
|
||||
$input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($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 (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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
31
app/Models/Presenters/RecurringQuotePresenter.php
Normal file
31
app/Models/Presenters/RecurringQuotePresenter.php
Normal 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
|
||||
{
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
88
app/Models/RecurringQuoteInvitation.php
Normal file
88
app/Models/RecurringQuoteInvitation.php
Normal 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();
|
||||
}
|
||||
}
|
@ -37,36 +37,6 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
// 'history',
|
||||
// 'client',
|
||||
];
|
||||
|
||||
/*
|
||||
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)
|
||||
{
|
||||
|
@ -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 includeHistory(RecurringQuote $quote)
|
||||
{
|
||||
$transformer = new QuoteHistoryTransformer($this->serializer);
|
||||
|
||||
/*
|
||||
public function includeInvoiceItems(Invoice $quote)
|
||||
{
|
||||
$transformer = new InvoiceItemTransformer($this->serializer);
|
||||
return $this->includeCollection($quote->history, $transformer, Backup::class);
|
||||
}
|
||||
|
||||
public function includeActivities(RecurringQuote $quote)
|
||||
{
|
||||
$transformer = new ActivityTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($quote->invoice_items, $transformer, ENTITY_INVOICE_ITEM);
|
||||
}
|
||||
return $this->includeCollection($quote->activities, $transformer, Activity::class);
|
||||
}
|
||||
|
||||
public function includeInvitations(Invoice $quote)
|
||||
{
|
||||
$transformer = new InvitationTransformer($this->account, $this->serializer);
|
||||
public function includeInvitations(RecurringQuote $quote)
|
||||
{
|
||||
$transformer = new RecurringQuoteInvitationTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($quote->invitations, $transformer, ENTITY_INVITATION);
|
||||
}
|
||||
return $this->includeCollection($quote->invitations, $transformer, RecurringQuoteInvitation::class);
|
||||
}
|
||||
|
||||
public function includePayments(Invoice $quote)
|
||||
{
|
||||
$transformer = new PaymentTransformer($this->account, $this->serializer, $quote);
|
||||
public function includeDocuments(RecurringQuote $quote)
|
||||
{
|
||||
$transformer = new DocumentTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($quote->payments, $transformer, ENTITY_PAYMENT);
|
||||
}
|
||||
|
||||
public function includeClient(Invoice $quote)
|
||||
{
|
||||
$transformer = new ClientTransformer($this->account, $this->serializer);
|
||||
|
||||
return $this->includeItem($quote->client, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
*/
|
||||
return $this->includeCollection($quote->documents, $transformer, Document::class);
|
||||
}
|
||||
|
||||
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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -4297,7 +4297,8 @@ $LANG = array(
|
||||
'lang_Latvian' => 'Latvian',
|
||||
'expiry_date' => 'Expiry date',
|
||||
'cardholder_name' => 'Card holder name',
|
||||
|
||||
'recurring_quote_number_taken' => 'Recurring Quote number :number already taken',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
Loading…
Reference in New Issue
Block a user