From ee855824db47f9b59c960d3a296b13031ae7f265 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Aug 2021 12:57:46 +1000 Subject: [PATCH] Recurring Quotes --- .../StoreRecurringQuoteRequest.php | 85 +++- .../UpdateRecurringQuoteRequest.php | 82 +++- .../UploadRecurringQuoteRequest.php | 39 ++ .../UniqueRecurringQuoteNumberRule.php | 67 +++ .../Presenters/RecurringQuotePresenter.php | 31 ++ app/Models/RecurringQuote.php | 399 ++++++++++++++++-- app/Models/RecurringQuoteInvitation.php | 88 ++++ .../RecurringInvoiceTransformer.php | 30 -- .../RecurringQuoteTransformer.php | 144 ++++--- resources/lang/en/texts.php | 3 +- 10 files changed, 828 insertions(+), 140 deletions(-) create mode 100644 app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php create mode 100644 app/Http/ValidationRules/Recurring/UniqueRecurringQuoteNumberRule.php create mode 100644 app/Models/Presenters/RecurringQuotePresenter.php create mode 100644 app/Models/RecurringQuoteInvitation.php diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php index 9da24cf99b..375b076aee 100644 --- a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -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 []; + } } diff --git a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php index 4a8cc51d87..7278d8d1b2 100644 --- a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php @@ -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; + } } diff --git a/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php new file mode 100644 index 0000000000..72985f6f48 --- /dev/null +++ b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php @@ -0,0 +1,39 @@ +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; + + } +} diff --git a/app/Http/ValidationRules/Recurring/UniqueRecurringQuoteNumberRule.php b/app/Http/ValidationRules/Recurring/UniqueRecurringQuoteNumberRule.php new file mode 100644 index 0000000000..66fca8061f --- /dev/null +++ b/app/Http/ValidationRules/Recurring/UniqueRecurringQuoteNumberRule.php @@ -0,0 +1,67 @@ +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; + } +} diff --git a/app/Models/Presenters/RecurringQuotePresenter.php b/app/Models/Presenters/RecurringQuotePresenter.php new file mode 100644 index 0000000000..e69058ce8f --- /dev/null +++ b/app/Models/Presenters/RecurringQuotePresenter.php @@ -0,0 +1,31 @@ + '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 '

'.ctrans('texts.draft').'

'; + break; + case self::STATUS_PENDING: + return '

'.ctrans('texts.pending').'

'; + break; + case self::STATUS_ACTIVE: + return '

'.ctrans('texts.active').'

'; + break; + case self::STATUS_COMPLETED: + return '

'.ctrans('texts.status_completed').'

'; + break; + case self::STATUS_PAUSED: + return '

'.ctrans('texts.paused').'

'; + 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); + } + + } diff --git a/app/Models/RecurringQuoteInvitation.php b/app/Models/RecurringQuoteInvitation.php new file mode 100644 index 0000000000..feb01a14a7 --- /dev/null +++ b/app/Models/RecurringQuoteInvitation.php @@ -0,0 +1,88 @@ +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(); + } +} diff --git a/app/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php index 6d6a4eaf73..97a13f7814 100644 --- a/app/Transformers/RecurringInvoiceTransformer.php +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -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) { diff --git a/app/Transformers/RecurringQuoteTransformer.php b/app/Transformers/RecurringQuoteTransformer.php index 1e5d84d037..c74c30fb55 100644 --- a/app/Transformers/RecurringQuoteTransformer.php +++ b/app/Transformers/RecurringQuoteTransformer.php @@ -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), ]; } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 18fd55344c..25bb62e617 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -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;