mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #6865 from turbo124/v5-develop
Fixes for converting quotes to invoices - invitations
This commit is contained in:
commit
0c23c058c5
@ -781,7 +781,9 @@ class BaseController extends Controller
|
|||||||
case 'next':
|
case 'next':
|
||||||
return 'main.next.dart.js';
|
return 'main.next.dart.js';
|
||||||
case 'profile':
|
case 'profile':
|
||||||
return 'main.profile.dart.js';
|
return 'main.profile.dart.js';
|
||||||
|
case 'html':
|
||||||
|
return 'main.html.dart.js';
|
||||||
default:
|
default:
|
||||||
return 'main.foss.dart.js';
|
return 'main.foss.dart.js';
|
||||||
|
|
||||||
|
@ -13,12 +13,14 @@ namespace App\Jobs\RecurringInvoice;
|
|||||||
|
|
||||||
use App\DataMapper\Analytics\SendRecurringFailure;
|
use App\DataMapper\Analytics\SendRecurringFailure;
|
||||||
use App\Events\Invoice\InvoiceWasEmailed;
|
use App\Events\Invoice\InvoiceWasEmailed;
|
||||||
|
use App\Factory\InvoiceInvitationFactory;
|
||||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
||||||
use App\Jobs\Entity\EmailEntity;
|
use App\Jobs\Entity\EmailEntity;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\RecurringInvoice;
|
use App\Models\RecurringInvoice;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Utils\Traits\GeneratesCounter;
|
use App\Utils\Traits\GeneratesCounter;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
use App\Utils\Traits\MakesInvoiceValues;
|
use App\Utils\Traits\MakesInvoiceValues;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@ -32,7 +34,8 @@ class SendRecurring implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
use GeneratesCounter;
|
use GeneratesCounter;
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
public $recurring_invoice;
|
public $recurring_invoice;
|
||||||
|
|
||||||
protected $db;
|
protected $db;
|
||||||
@ -58,16 +61,6 @@ class SendRecurring implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle() : void
|
public function handle() : void
|
||||||
{
|
{
|
||||||
//reset all contacts here
|
|
||||||
// $this->recurring_invoice->client->contacts()->update(['send_email' => false]);
|
|
||||||
|
|
||||||
// $this->recurring_invoice->invitations->each(function ($invitation){
|
|
||||||
|
|
||||||
// $contact = $invitation->contact;
|
|
||||||
// $contact->send_email = true;
|
|
||||||
// $contact->save();
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Generate Standard Invoice
|
// Generate Standard Invoice
|
||||||
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client);
|
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client);
|
||||||
@ -86,10 +79,12 @@ class SendRecurring implements ShouldQueue
|
|||||||
$invoice = $invoice->service()
|
$invoice = $invoice->service()
|
||||||
->markSent()
|
->markSent()
|
||||||
->applyNumber()
|
->applyNumber()
|
||||||
->createInvitations() //need to only link invitations to those in the recurring invoice
|
// ->createInvitations() //need to only link invitations to those in the recurring invoice
|
||||||
->fillDefaults()
|
->fillDefaults()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
$invoice = $this->createRecurringInvitations($invoice);
|
||||||
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
||||||
@ -154,6 +149,28 @@ class SendRecurring implements ShouldQueue
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only create the invitations that are defined on the recurring invoice.
|
||||||
|
* @param Invoice $invoice
|
||||||
|
* @return Invoice $invoice
|
||||||
|
*/
|
||||||
|
private function createRecurringInvitations($invoice) :Invoice
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->recurring_invoice->invitations->each(function ($recurring_invitation) use($invoice){
|
||||||
|
|
||||||
|
$ii = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
|
||||||
|
$ii->key = $this->createDbHash(config('database.default'));
|
||||||
|
$ii->invoice_id = $invoice->id;
|
||||||
|
$ii->client_contact_id = $recurring_invitation->client_contact_id;
|
||||||
|
$ii->save();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return $invoice->fresh();
|
||||||
|
}
|
||||||
|
|
||||||
public function failed($exception = null)
|
public function failed($exception = null)
|
||||||
{
|
{
|
||||||
nlog('the job failed');
|
nlog('the job failed');
|
||||||
|
@ -69,7 +69,7 @@ class SupportMessageSent extends Mailable
|
|||||||
if(Ninja::isHosted())
|
if(Ninja::isHosted())
|
||||||
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia');
|
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated} :: {$plan} :: ".date('M jS, g:ia');
|
||||||
else
|
else
|
||||||
$subject = "{$priority}Self Hosted :: {$plan} :: {$platform} :: ".date('M jS, g:ia');
|
$subject = "{$priority}Self Hosted :: {$plan} :: {$is_large}{$platform}{$migrated} :: ".date('M jS, g:ia');
|
||||||
|
|
||||||
return $this->from(config('mail.from.address'), $user->present()->name())
|
return $this->from(config('mail.from.address'), $user->present()->name())
|
||||||
->replyTo($user->email, $user->present()->name())
|
->replyTo($user->email, $user->present()->name())
|
||||||
|
@ -308,7 +308,7 @@ class BaseDriver extends AbstractPaymentDriver
|
|||||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||||
|
|
||||||
$invoices->each(function ($invoice) {
|
$invoices->each(function ($invoice) {
|
||||||
$invoice->service()->removeUnpaidGatewayFees();
|
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,15 +222,15 @@ class BaseRepository
|
|||||||
$this->saveDocuments($data['documents'], $model);
|
$this->saveDocuments($data['documents'], $model);
|
||||||
|
|
||||||
/* Marks whether the client contact should receive emails based on the send_email property */
|
/* Marks whether the client contact should receive emails based on the send_email property */
|
||||||
if (isset($data['client_contacts'])) {
|
// if (isset($data['client_contacts'])) {
|
||||||
foreach ($data['client_contacts'] as $contact) {
|
// foreach ($data['client_contacts'] as $contact) {
|
||||||
if ($contact['send_email'] == 1 && is_string($contact['id'])) {
|
// if ($contact['send_email'] == 1 && is_string($contact['id'])) {
|
||||||
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
|
// $client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
|
||||||
$client_contact->send_email = true;
|
// $client_contact->send_email = true;
|
||||||
$client_contact->save();
|
// $client_contact->save();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/* If invitations are present we need to filter existing invitations with the new ones */
|
/* If invitations are present we need to filter existing invitations with the new ones */
|
||||||
if (isset($data['invitations'])) {
|
if (isset($data['invitations'])) {
|
||||||
@ -285,10 +285,8 @@ class BaseRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$model->load('invitations');
|
|
||||||
|
|
||||||
/* If no invitations have been created, this is our fail safe to maintain state*/
|
/* If no invitations have been created, this is our fail safe to maintain state*/
|
||||||
if ($model->invitations->count() == 0)
|
if ($model->invitations()->count() == 0)
|
||||||
$model->service()->createInvitations();
|
$model->service()->createInvitations();
|
||||||
|
|
||||||
/* Recalculate invoice amounts */
|
/* Recalculate invoice amounts */
|
||||||
|
@ -494,6 +494,6 @@ class InvoiceService
|
|||||||
{
|
{
|
||||||
$this->invoice->saveQuietly();
|
$this->invoice->saveQuietly();
|
||||||
|
|
||||||
return $this->invoice;
|
return $this->invoice->fresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,8 +86,8 @@ class MarkPaid extends AbstractService
|
|||||||
->deletePdf()
|
->deletePdf()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
if ($this->invoice->client->getSetting('client_manual_payment_notification'))
|
// if ($this->invoice->client->getSetting('client_manual_payment_notification'))
|
||||||
$payment->service()->sendEmail();
|
// $payment->service()->sendEmail();
|
||||||
|
|
||||||
/* Update Invoice balance */
|
/* Update Invoice balance */
|
||||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
@ -33,15 +33,18 @@ class MarkSent extends AbstractService
|
|||||||
{
|
{
|
||||||
|
|
||||||
/* Return immediately if status is not draft */
|
/* Return immediately if status is not draft */
|
||||||
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
|
if ($this->invoice->fresh()->status_id != Invoice::STATUS_DRAFT) {
|
||||||
return $this->invoice;
|
return $this->invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->invoice->markInvitationsSent();
|
/*Set status*/
|
||||||
|
|
||||||
$this->invoice
|
$this->invoice
|
||||||
->service()
|
->service()
|
||||||
->setStatus(Invoice::STATUS_SENT)
|
->setStatus(Invoice::STATUS_SENT)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
$this->invoice
|
||||||
|
->service()
|
||||||
->applyNumber()
|
->applyNumber()
|
||||||
->setDueDate()
|
->setDueDate()
|
||||||
->updateBalance($this->invoice->amount)
|
->updateBalance($this->invoice->amount)
|
||||||
@ -49,9 +52,18 @@ class MarkSent extends AbstractService
|
|||||||
->setReminder()
|
->setReminder()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
$this->client->service()->updateBalance($this->invoice->balance)->save();
|
$this->invoice->markInvitationsSent();
|
||||||
|
|
||||||
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance, "Invoice {$this->invoice->number} marked as sent.");
|
/*Adjust client balance*/
|
||||||
|
$this->client
|
||||||
|
->service()
|
||||||
|
->updateBalance($this->invoice->balance)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
/*Update ledger*/
|
||||||
|
$this->invoice
|
||||||
|
->ledger()
|
||||||
|
->updateInvoiceBalance($this->invoice->balance, "Invoice {$this->invoice->number} marked as sent.");
|
||||||
|
|
||||||
event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
namespace App\Services\Quote;
|
namespace App\Services\Quote;
|
||||||
|
|
||||||
use App\Factory\CloneQuoteToInvoiceFactory;
|
use App\Factory\CloneQuoteToInvoiceFactory;
|
||||||
|
use App\Factory\InvoiceInvitationFactory;
|
||||||
|
use App\Models\Invoice;
|
||||||
use App\Models\Quote;
|
use App\Models\Quote;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
@ -39,14 +41,20 @@ class ConvertQuote
|
|||||||
{
|
{
|
||||||
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id);
|
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id);
|
||||||
$invoice->design_id = $this->decodePrimaryKey($this->client->getSetting('invoice_design_id'));
|
$invoice->design_id = $this->decodePrimaryKey($this->client->getSetting('invoice_design_id'));
|
||||||
$invoice = $this->invoice_repo->save($invoice->toArray(), $invoice);
|
|
||||||
|
//create invitations here before the repo save()
|
||||||
|
//we need to do this here otherwise the repo_save will create
|
||||||
|
//invitations for ALL contacts
|
||||||
|
$invites = $this->createConversionInvitations($invoice, $quote);
|
||||||
|
$invoice_array = $invoice->toArray();
|
||||||
|
$invoice_array['invitations'] = $invites;
|
||||||
|
|
||||||
|
$invoice = $this->invoice_repo->save($invoice_array, $invoice);
|
||||||
|
|
||||||
$invoice->fresh();
|
$invoice->fresh();
|
||||||
|
|
||||||
$invoice->service()
|
$invoice->service()
|
||||||
->fillDefaults()
|
->fillDefaults()
|
||||||
// ->markSent()
|
|
||||||
// ->createInvitations()
|
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
$quote->invoice_id = $invoice->id;
|
$quote->invoice_id = $invoice->id;
|
||||||
@ -56,4 +64,26 @@ class ConvertQuote
|
|||||||
// maybe should return invoice here
|
// maybe should return invoice here
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only create the invitations that are defined on the quote.
|
||||||
|
*
|
||||||
|
* @return Invoice $invoice
|
||||||
|
*/
|
||||||
|
private function createConversionInvitations($invoice, $quote)
|
||||||
|
{
|
||||||
|
$invites = [];
|
||||||
|
|
||||||
|
foreach($quote->invitations as $quote_invitation){
|
||||||
|
|
||||||
|
$ii = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
|
||||||
|
$ii->key = $this->createDbHash(config('database.default'));
|
||||||
|
$ii->client_contact_id = $quote_invitation->client_contact_id;
|
||||||
|
|
||||||
|
$invites[] = $ii;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $invites;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Services\Quote;
|
namespace App\Services\Quote;
|
||||||
|
|
||||||
use App\Events\Quote\QuoteWasApproved;
|
use App\Events\Quote\QuoteWasApproved;
|
||||||
|
use App\Factory\InvoiceInvitationFactory;
|
||||||
use App\Jobs\Util\UnlinkFile;
|
use App\Jobs\Util\UnlinkFile;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\Quote;
|
use App\Models\Quote;
|
||||||
@ -117,7 +118,6 @@ class QuoteService
|
|||||||
$this->invoice
|
$this->invoice
|
||||||
->service()
|
->service()
|
||||||
->markSent()
|
->markSent()
|
||||||
->createInvitations()
|
|
||||||
->deletePdf()
|
->deletePdf()
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
@ -54,8 +54,6 @@ class RecurringService
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// $this->createInvitations()->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
|
||||||
|
|
||||||
$this->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
$this->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -4328,6 +4328,9 @@ $LANG = array(
|
|||||||
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
|
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
|
||||||
'eps' => 'EPS',
|
'eps' => 'EPS',
|
||||||
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
|
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
|
||||||
|
'clone_to_expense' => 'Clone to expense',
|
||||||
|
'checkout' => 'Checkout',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return $LANG;
|
return $LANG;
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
<div class="col-span-12 xl:col-span-4 bg-white flex flex-col items-center lg:h-screen">
|
<div class="col-span-12 xl:col-span-4 bg-white flex flex-col items-center lg:h-screen">
|
||||||
<div class="w-full p-10 md:p-24 xl:mt-32 md:max-w-3xl">
|
<div class="w-full p-10 md:p-24 xl:mt-32 md:max-w-3xl">
|
||||||
<div class="col-span-12 w-full xl:col-span-9">
|
<div class="col-span-12 w-full xl:col-span-9">
|
||||||
<h2 class="text-2xl font-bold tracking-wide">{{ $heading_text ?? ctrans('texts.login') }}</h2>
|
<h2 class="text-2xl font-bold tracking-wide">{{ $heading_text ?? ctrans('texts.checkout') }}</h2>
|
||||||
@if (session()->has('message'))
|
@if (session()->has('message'))
|
||||||
@component('portal.ninja2020.components.message')
|
@component('portal.ninja2020.components.message')
|
||||||
{{ session('message') }}
|
{{ session('message') }}
|
||||||
|
@ -215,7 +215,7 @@ Route::match(['get', 'post'], 'payment_notification_webhook/{company_key}/{compa
|
|||||||
->middleware(['guest'])
|
->middleware(['guest'])
|
||||||
->name('payment_notification_webhook');
|
->name('payment_notification_webhook');
|
||||||
|
|
||||||
Route::post('api/v1/postmark_webhook', 'PostMarkController@webhook');
|
Route::post('api/v1/postmark_webhook', 'PostMarkController@webhook')->middleware(['throttle:5000,1']);
|
||||||
Route::get('token_hash_router', 'OneTimeTokenController@router');
|
Route::get('token_hash_router', 'OneTimeTokenController@router');
|
||||||
Route::get('webcron', 'WebCronController@index');
|
Route::get('webcron', 'WebCronController@index');
|
||||||
Route::post('api/v1/get_migration_account', 'HostedMigrationController@getAccount')->middleware('guest');
|
Route::post('api/v1/get_migration_account', 'HostedMigrationController@getAccount')->middleware('guest');
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
|
use App\DataMapper\ClientRegistrationFields;
|
||||||
use App\DataMapper\ClientSettings;
|
use App\DataMapper\ClientSettings;
|
||||||
use App\DataMapper\CompanySettings;
|
use App\DataMapper\CompanySettings;
|
||||||
use App\Factory\CompanyUserFactory;
|
use App\Factory\CompanyUserFactory;
|
||||||
@ -173,6 +174,8 @@ trait MockAccountData
|
|||||||
'account_id' => $this->account->id,
|
'account_id' => $this->account->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$this->company->client_registration_fields = ClientRegistrationFields::generate();
|
||||||
|
|
||||||
Storage::makeDirectory($this->company->company_key.'/documents', 0755, true);
|
Storage::makeDirectory($this->company->company_key.'/documents', 0755, true);
|
||||||
Storage::makeDirectory($this->company->company_key.'/images', 0755, true);
|
Storage::makeDirectory($this->company->company_key.'/images', 0755, true);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user