header('company')); $rules = [ 'starts_at' => [ 'required', ], 'send_automatically' => [ 'required', 'boolean', ], 'customer_id' => [ 'required', ], 'exchange_rate' => [ 'nullable', ], 'discount' => [ 'numeric', 'required', ], 'discount_val' => [ 'integer', 'required', ], 'sub_total' => [ 'integer', 'required', ], 'total' => [ 'integer', 'max:999999999999', 'required', ], 'tax' => [ 'required', ], 'status' => [ 'required', ], 'frequency' => [ 'required', ], 'limit_by' => [ 'required', ], 'limit_count' => [ 'required_if:limit_by,COUNT', ], 'limit_date' => [ 'required_if:limit_by,DATE', ], 'items' => [ 'required', ], 'items.*' => [ 'required', ], 'items.*.description' => [ 'nullable', ], ]; // A contact billed in some other currency than the company's turns the // otherwise optional rate into a hard requirement. The contact is // looked up by bare id, so one belonging to another company answers // here just the same. $contact = Customer::find($this->customer_id); if ($contact && $homeCurrency && (string) $contact->currency_id !== $homeCurrency) { $rules['exchange_rate'] = [ 'required', ]; } return $rules; } /** * Reject any per-item tax row that carries an amount without a type. */ public function withValidator(Validator $validator): void { $this->validateDocumentTaxPlaceholders($validator); } /** * Fold the submission into the columns of the schedule row. * * The submitted sub-total, tax and grand total are thrown away and worked * out again from the line items, because every invoice this schedule mints * inherits them. The stored currency is always the contact's; the * submitted currency id only decides whether an exchange rate is carried * or pinned at one. */ public function getRecurringInvoicePayload() { $company = $this->header('company'); $companyCurrency = CompanySetting::getSetting('currency', $company); $submittedCurrency = $this->currency_id; $rate = $companyCurrency != $submittedCurrency ? $this->exchange_rate : 1; $contactCurrency = Customer::find($this->customer_id)->currency_id; $nextRun = RecurringInvoice::getNextInvoiceDate($this->frequency, $this->starts_at); $perItemTax = CompanySetting::getSetting('tax_per_item', $company) ?? 'NO '; $perItemDiscount = CompanySetting::getSetting('discount_per_item', $company) ?? 'NO'; $totals = DocumentTotals::compute( $this->items ?? [], $this->taxes ?? [], $this->discount_val, $perItemTax, (bool) $this->tax_included, $perItemDiscount ); $submitted = collect($this->except('items', 'taxes')); return $submitted ->merge([ 'creator_id' => $this->user()->id, 'company_id' => $company, 'next_invoice_at' => $nextRun, 'tax_per_item' => $perItemTax, 'discount_per_item' => $perItemDiscount, 'sub_total' => $totals['sub_total'], 'total' => $totals['total'], 'tax' => $totals['tax'], 'due_amount' => $totals['total'], 'exchange_rate' => $rate, 'base_sub_total' => $totals['sub_total'] * $rate, 'base_total' => $totals['total'] * $rate, 'base_tax' => $totals['tax'] * $rate, 'currency_id' => $contactCurrency, ]) ->toArray(); } }