diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index a4292d0b0078..41517772f86e 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -55,7 +55,7 @@ class UpdateRecurringInvoiceRequest extends Request protected function prepareForValidation() { $input = $this->all(); - +info($input); if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { $input['design_id'] = $this->decodePrimaryKey($input['design_id']); } diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index bcf921576e78..692903c273b6 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -49,6 +49,8 @@ use App\Repositories\ClientRepository; use App\Repositories\CompanyRepository; use App\Repositories\CreditRepository; use App\Repositories\InvoiceRepository; +use App\Repositories\Migration\InvoiceMigrationRepository; +use App\Repositories\Migration\PaymentMigrationRepository; use App\Repositories\PaymentRepository; use App\Repositories\ProductRepository; use App\Repositories\QuoteRepository; @@ -448,7 +450,7 @@ class Import implements ShouldQueue throw new MigrationValidatorFailed(json_encode($validator->errors())); } - $invoice_repository = new InvoiceRepository(); + $invoice_repository = new InvoiceMigrationRepository(); foreach ($data as $key => $resource) { @@ -602,7 +604,7 @@ class Import implements ShouldQueue throw new MigrationValidatorFailed(json_encode($validator->errors())); } - $payment_repository = new PaymentRepository(new CreditRepository()); + $payment_repository = new PaymentMigrationRepository(new CreditRepository()); foreach ($data as $resource) { $modified = $resource; @@ -621,8 +623,8 @@ class Import implements ShouldQueue if (isset($modified['invoices'])) { - foreach ($modified['invoices'] as $invoice) { - $invoice['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']); + foreach ($modified['invoices'] as $key => $invoice) { + $modified['invoices'][$key]['invoice_id'] = $this->transformId('invoices', $invoice['invoice_id']); } } diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 72cdf83b0605..49d46d692024 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -78,6 +78,8 @@ class ClientRepository extends BaseRepository $data['name'] = $client->present()->name(); } + info("{$client->present()->name} has a balance of {$client->balance} with a paid to date of {$client->paid_to_date}"); + if (array_key_exists('documents', $data)) { $this->saveDocuments($data['documents'], $client); } diff --git a/app/Repositories/Migration/InvoiceMigrationRepository.php b/app/Repositories/Migration/InvoiceMigrationRepository.php new file mode 100644 index 000000000000..993feea57814 --- /dev/null +++ b/app/Repositories/Migration/InvoiceMigrationRepository.php @@ -0,0 +1,181 @@ +client_id); + } + + $state = []; + $resource = explode('\\', $class->name)[2]; /** This will extract 'Invoice' from App\Models\Invoice */ + $lcfirst_resource_id = lcfirst($resource) . '_id'; + + if ($class->name == Invoice::class || $class->name == Quote::class) { + $state['starting_amount'] = $model->amount; + } + + if (!$model->id) { + $company_defaults = $client->setCompanyDefaults($data, lcfirst($resource)); + $model->uses_inclusive_taxes = $client->getSetting('inclusive_taxes'); + $data = array_merge($company_defaults, $data); + } + + $tmp_data = $data; + + /* We need to unset some variable as we sometimes unguard the model */ + + if (isset($tmp_data['invitations'])) { + unset($tmp_data['invitations']); + } + + if (isset($tmp_data['client_contacts'])) { + unset($tmp_data['client_contacts']); + } + + $model->fill($tmp_data); + $model->save(); + + if (array_key_exists('documents', $data)) { + $this->saveDocuments($data['documents'], $model); + } + + $invitation_factory_class = sprintf("App\\Factory\\%sInvitationFactory", $resource); + + if (isset($data['client_contacts'])) { + foreach ($data['client_contacts'] as $contact) { + if ($contact['send_email'] == 1 && is_string($contact['id'])) { + $client_contact = ClientContact::find($this->decodePrimaryKey($contact['id'])); + $client_contact->send_email = true; + $client_contact->save(); + } + } + } + + if (isset($data['invitations'])) { + $invitations = collect($data['invitations']); + + /* Get array of Keys which have been removed from the invitations array and soft delete each invitation */ + $model->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) { + $this->getInvitation($invitation, $resource)->delete(); + }); + + foreach ($data['invitations'] as $invitation) { + + //if no invitations are present - create one. + if (! $this->getInvitation($invitation, $resource)) { + if (isset($invitation['id'])) { + unset($invitation['id']); + } + + //make sure we are creating an invite for a contact who belongs to the client only! + $contact = ClientContact::find($invitation['client_contact_id']); + + if ($contact && $model->client_id == $contact->client_id); + { + $new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id); + $new_invitation->{$lcfirst_resource_id} = $model->id; + $new_invitation->client_contact_id = $contact->id; + $new_invitation->save(); + } + } + } + } + + $model->load('invitations'); + + /* If no invitations have been created, this is our fail safe to maintain state*/ + if ($model->invitations->count() == 0) { + $model->service()->createInvitations(); + } + + $model = $model->calc()->getInvoice(); + + $state['finished_amount'] = $model->amount; + + $model = $model->service()->applyNumber()->save(); + + if ($model->company->update_products !== false) { + UpdateOrCreateProduct::dispatch($model->line_items, $model, $model->company); + } + + if ($class->name == Invoice::class) { + + if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) { + + // $model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount'])); + // $model->client->service()->updateBalance(($state['finished_amount'] - $state['starting_amount']))->save(); + } + + if(!$model->design_id) + $model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id')); + + } + + if ($class->name == Credit::class) { + $model = $model->calc()->getCredit(); + + if(!$model->design_id) + $model->design_id = $this->decodePrimaryKey($client->getSetting('credit_design_id')); + + + } + + if ($class->name == Quote::class) { + $model = $model->calc()->getQuote(); + + if(!$model->design_id) + $model->design_id = $this->decodePrimaryKey($client->getSetting('quote_design_id')); + + + + } + + $model->save(); + + return $model->fresh(); + } + +} diff --git a/app/Repositories/Migration/PaymentMigrationRepository.php b/app/Repositories/Migration/PaymentMigrationRepository.php new file mode 100644 index 000000000000..50462a4e0bfd --- /dev/null +++ b/app/Repositories/Migration/PaymentMigrationRepository.php @@ -0,0 +1,197 @@ +credit_repo = $credit_repo; + $this->activity_repo = new ActivityRepository(); + } + + public function getClassName() + { + return Payment::class; + } + + /** + * Saves and updates a payment. //todo refactor to handle refunds and payments. + * + * @param array $data the request object + * @param Payment $payment The Payment object + * @return Payment|null Payment $payment + */ + public function save(array $data, Payment $payment): ?Payment + { + if ($payment->amount >= 0) { + return $this->applyPayment($data, $payment); + } + + return $payment; + } + + /** + * Handles a positive payment request + * @param array $data The data object + * @param Payment $payment The $payment entity + * @return Payment The updated/created payment object + */ + private function applyPayment(array $data, Payment $payment): ?Payment + { + + //check currencies here and fill the exchange rate data if necessary + if (!$payment->id) { + $this->processExchangeRates($data, $payment); + + /*We only update the paid to date ONCE per payment*/ + if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) { + + if($data['amount'] == '') + $data['amount'] = array_sum(array_column($data['invoices'], 'amount')); + + } + } + + /*Fill the payment*/ + $payment->fill($data); + $payment->status_id = Payment::STATUS_COMPLETED; + $payment->save(); + + /*Ensure payment number generated*/ + if (!$payment->number || strlen($payment->number) == 0) { + $payment->number = $payment->client->getNextPaymentNumber($payment->client); + } + + $invoice_totals = 0; + $credit_totals = 0; + + /*Iterate through invoices and apply payments*/ + if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) { + + $invoice_totals = array_sum(array_column($data['invoices'], 'amount')); + + info("invoice totals = {$invoice_totals}"); + + $invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get(); + + $payment->invoices()->saveMany($invoices); + + $payment->invoices->each(function ($inv) use($invoice_totals){ + info("updating the pivot to {$invoice_totals}"); + $inv->pivot->amount = $invoice_totals; + $inv->pivot->save(); + }); + + } + + $fields = new \stdClass; + + $fields->payment_id = $payment->id; + $fields->user_id = $payment->user_id; + $fields->company_id = $payment->company_id; + $fields->activity_type_id = Activity::CREATE_PAYMENT; + + foreach ($payment->invoices as $invoice) { + $fields->invoice_id = $invoice->id; + + $this->activity_repo->save($fields, $invoice); + } + + if (count($invoices) == 0) { + $this->activity_repo->save($fields, $payment); + } + + if ($invoice_totals == $payment->amount) { + $payment->applied += $payment->amount; + } elseif ($invoice_totals < $payment->amount) { + $payment->applied += $invoice_totals; + } + + $payment->save(); + + return $payment->fresh(); + } + + + /** + * If the client is paying in a currency other than + * the company currency, we need to set a record + */ + private function processExchangeRates($data, $payment) + { + + $client = Client::find($data['client_id']); + + $client_currency = $client->getSetting('currency_id'); + $company_currency = $client->company->settings->currency_id; + + if ($company_currency != $client_currency) { + $currency = $client->currency(); + + $exchange_rate = new CurrencyApi(); + + $payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date)); + $payment->exchange_currency_id = $client_currency; + } + + return $payment; + } + + public function delete($payment) + { + //cannot double delete a payment + if($payment->is_deleted) + return; + + $payment->service()->deletePayment(); + + return parent::delete($payment); + + } + + public function restore($payment) + { + //we cannot restore a deleted payment. + if($payment->is_deleted) + return; + + return parent::restore($payment); + } +} diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 4cdbebe40bca..e2b0004c6465 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -81,6 +81,8 @@ class PaymentRepository extends BaseRepository $data['amount'] = array_sum(array_column($data['invoices'], 'amount')); $client = Client::find($data['client_id']); + info("updating client balance from {$client->balance} by this much ".$data['amount']); + $client->service()->updatePaidToDate($data['amount'])->save(); } @@ -108,6 +110,8 @@ class PaymentRepository extends BaseRepository $invoice_totals = array_sum(array_column($data['invoices'], 'amount')); $invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get(); + + info("saving this many invoices to the payment ".$invoices->count()); $payment->invoices()->saveMany($invoices); diff --git a/tests/Feature/RecurringInvoiceTest.php b/tests/Feature/RecurringInvoiceTest.php index 433f6ebd82ae..42b2a462577e 100644 --- a/tests/Feature/RecurringInvoiceTest.php +++ b/tests/Feature/RecurringInvoiceTest.php @@ -120,7 +120,7 @@ class RecurringInvoiceTest extends TestCase $RecurringInvoice_update = [ 'status_id' => RecurringInvoice::STATUS_DRAFT, - 'client_id' => $RecurringInvoice->client_id, + 'client_id' => $this->encodePrimaryKey($RecurringInvoice->client_id), ]; $this->assertNotNull($RecurringInvoice);