mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Allow conversion of quotes to invoices (#3760)
This commit is contained in:
parent
8512db6b1e
commit
c72d38ca4f
@ -28,14 +28,14 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['hashed_id']);
|
||||
unset($quote_array['invoice_id']);
|
||||
unset($quote_array['id']);
|
||||
|
||||
|
||||
foreach($quote_array as $key => $value)
|
||||
$invoice->{$key} = $value;
|
||||
|
||||
$invoice->status_id = Invoice::STATUS_DRAFT;
|
||||
$invoice->due_date = null;
|
||||
$invoice->partial_due_date = null;
|
||||
$invoice->number = null;
|
||||
$invoice->status_id = null;
|
||||
|
||||
return $invoice;
|
||||
|
||||
|
@ -82,6 +82,11 @@ class QuoteFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
public function number($number = '')
|
||||
{
|
||||
return $this->builder->where('number', 'like', '%'.$number.'%');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort
|
||||
*
|
||||
|
@ -41,7 +41,7 @@ class InvitationController extends Controller
|
||||
if ((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
|
||||
$this->middleware('auth:contact');
|
||||
} else {
|
||||
auth()->guard('contact')->login($invitation->contact, false);
|
||||
auth()->guard('contact')->login($invitation->contact, true);
|
||||
}
|
||||
|
||||
if (!request()->has('silent')) {
|
||||
|
@ -23,13 +23,13 @@ class SwitchCompanyController extends Controller
|
||||
|
||||
public function __invoke(string $contact)
|
||||
{
|
||||
$client_contact = ClientContact::query()
|
||||
->where('user_id', auth()->user()->id)
|
||||
|
||||
$client_contact = ClientContact::where('email', auth()->user()->email)
|
||||
->where('id', $this->transformKeys($contact))
|
||||
->first();
|
||||
|
||||
Auth::guard('contact')->login($client_contact, true);
|
||||
|
||||
return back();
|
||||
return redirect('/client/dashboard');
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ class MigrationController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $company);
|
||||
StartMigration::dispatch(base_path("storage/app/public/$migration_file"), $user, $company)->delay(now()->addSeconds(60));
|
||||
|
||||
return response()->json([
|
||||
'_id' => Str::uuid(),
|
||||
|
@ -49,7 +49,8 @@ class PortalComposer
|
||||
$data['company'] = auth()->user()->company;
|
||||
$data['client'] = auth()->user()->client;
|
||||
$data['settings'] = auth()->user()->client->getMergedSettings();
|
||||
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->get();
|
||||
|
||||
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->whereNotNull('email')->distinct('company_id')->get();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -55,8 +55,12 @@ class EntityPaidMailer extends BaseMailerJob implements ShouldQueue
|
||||
{
|
||||
info("entity paid mailer");
|
||||
//Set DB
|
||||
//
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if($this->company->company_users->first()->is_migrating)
|
||||
return true;
|
||||
|
||||
//if we need to set an email driver do it now
|
||||
$this->setMailDriver($this->payment->client->getSetting('email_sending_method'));
|
||||
|
||||
|
@ -31,6 +31,7 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\MigrationCompleted;
|
||||
use App\Mail\MigrationFailed;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
@ -147,7 +148,7 @@ class Import implements ShouldQueue
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :bool
|
||||
{
|
||||
|
||||
set_time_limit(0);
|
||||
@ -156,7 +157,6 @@ class Import implements ShouldQueue
|
||||
if (! in_array($key, $this->available_imports)) {
|
||||
//throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration.");
|
||||
info("Resource {$key} is not available for migration.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -170,6 +170,8 @@ class Import implements ShouldQueue
|
||||
Mail::to($this->user)->send(new MigrationCompleted());
|
||||
|
||||
info('Completed🚀🚀🚀🚀🚀 at '.now());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -368,7 +370,9 @@ class Import implements ShouldQueue
|
||||
unset($modified_contacts[$key]['id']);
|
||||
}
|
||||
|
||||
$contact_repository->save($modified_contacts, $client);
|
||||
$saveable_contacts['contacts'] = $modified_contacts;
|
||||
|
||||
$contact_repository->save($saveable_contacts, $client);
|
||||
}
|
||||
|
||||
$key = "clients_{$resource['id']}";
|
||||
|
@ -74,6 +74,9 @@ class StartMigration implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
auth()->login($this->user, false);
|
||||
@ -96,7 +99,7 @@ class StartMigration implements ShouldQueue
|
||||
$zip->close();
|
||||
|
||||
if (app()->environment() == 'testing') {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->company->setMigration(true);
|
||||
@ -110,6 +113,9 @@ class StartMigration implements ShouldQueue
|
||||
$data = json_decode(file_get_contents($file), 1);
|
||||
|
||||
Import::dispatchNow($data, $this->company, $this->user);
|
||||
|
||||
$this->company->setMigration(false);
|
||||
|
||||
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
|
||||
$this->company->setMigration(false);
|
||||
|
||||
@ -119,6 +125,11 @@ class StartMigration implements ShouldQueue
|
||||
info($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//always make sure we unset the migration as running
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function failed($exception = null)
|
||||
|
@ -37,18 +37,27 @@ class SubscriptionHandler implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :bool
|
||||
{
|
||||
|
||||
if(!$this->entity->company || $this->entity->company->company_users->first()->is_migrating)
|
||||
return true;
|
||||
|
||||
info("i got past the check");
|
||||
|
||||
$subscriptions = Subscription::where('company_id', $this->entity->company_id)
|
||||
->where('event_id', $this->event_id)
|
||||
->get();
|
||||
|
||||
if(!$subscriptions || $subscriptions->count() == 0)
|
||||
return;
|
||||
return true;
|
||||
|
||||
$subscriptions->each(function($subscription) {
|
||||
$this->process($subscription);
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private function process($subscription)
|
||||
|
@ -48,6 +48,9 @@ class PaymentNotification implements ShouldQueue
|
||||
/*User notifications*/
|
||||
foreach ($payment->company->company_users as $company_user) {
|
||||
|
||||
if($company_user->is_migrating)
|
||||
return true;
|
||||
|
||||
$user = $company_user->user;
|
||||
|
||||
$methods = $this->findUserEntityNotificationType($payment, $company_user, ['all_notifications']);
|
||||
|
@ -406,9 +406,13 @@ class Company extends BaseModel
|
||||
|
||||
public function setMigration($status)
|
||||
{
|
||||
$this->company_users->each(function ($cu) use ($status) {
|
||||
$company_users = CompanyUser::where('company_id', $this->id)->get();
|
||||
|
||||
foreach($company_users as $cu)
|
||||
{
|
||||
$cu->is_migrating=$status;
|
||||
$cu->save();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ class ClientContactRepository extends BaseRepository
|
||||
{
|
||||
public function save(array $data, Client $client) : void
|
||||
{
|
||||
|
||||
if (isset($data['contacts'])) {
|
||||
$contacts = collect($data['contacts']);
|
||||
} else {
|
||||
@ -66,17 +67,20 @@ class ClientContactRepository extends BaseRepository
|
||||
}
|
||||
|
||||
$update_contact->save();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
//always made sure we have one blank contact to maintain state
|
||||
if ($contacts->count() == 0) {
|
||||
if ($client->contacts->count() == 0) {
|
||||
|
||||
info("no contacts found");
|
||||
|
||||
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$new_contact->client_id = $client->id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ class ConvertQuote
|
||||
private $client;
|
||||
private $invoice_repo;
|
||||
|
||||
public function __construct($client, InvoiceRepository $invoice_repo)
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->invoice_repo = $invoice_repo;
|
||||
$this->invoice_repo = new InvoiceRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -23,9 +23,19 @@ class ConvertQuote
|
||||
public function run($quote)
|
||||
{
|
||||
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id, $quote->company_id);
|
||||
$this->invoice_repo->save([], $invoice);
|
||||
$invoice = $this->invoice_repo->save([], $invoice);
|
||||
|
||||
$invoice->fresh();
|
||||
|
||||
$invoice->service()
|
||||
->markSent()
|
||||
->createInvitations()
|
||||
->save();
|
||||
|
||||
$quote->invoice_id = $invoice->id;
|
||||
$quote->save();
|
||||
|
||||
// maybe should return invoice here
|
||||
return $quote;
|
||||
return $invoice;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ class QuoteService
|
||||
{
|
||||
protected $quote;
|
||||
|
||||
public $invoice;
|
||||
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
@ -38,17 +40,28 @@ class QuoteService
|
||||
public function markApproved()
|
||||
{
|
||||
$mark_approved = new MarkApproved($this->quote->client);
|
||||
|
||||
$this->quote = $mark_approved->run($this->quote);
|
||||
|
||||
if ($this->quote->client->getSetting('auto_convert_quote') === true) {
|
||||
$convert_quote = new ConvertQuote($this->quote->client);
|
||||
$this->quote = $convert_quote->run($this->quote);
|
||||
$this->convert();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function convert() :QuoteService
|
||||
{
|
||||
if($this->quote->invoice_id)
|
||||
return $this;
|
||||
|
||||
$convert_quote = new ConvertQuote($this->quote->client);
|
||||
$this->invoice = $convert_quote->run($this->quote);
|
||||
|
||||
$this->quote->fresh();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuotePdf($contact = null)
|
||||
{
|
||||
$get_invoice_pdf = new GetQuotePdf();
|
||||
@ -101,8 +114,7 @@ class QuoteService
|
||||
$invoice = null;
|
||||
|
||||
if ($this->quote->client->getSetting('auto_convert_quote')) {
|
||||
$invoice = $this->convertToInvoice();
|
||||
$this->linkInvoiceToQuote($invoice)->save();
|
||||
$this->convert();
|
||||
}
|
||||
|
||||
if ($this->quote->client->getSetting('auto_archive_quote')) {
|
||||
@ -113,32 +125,16 @@ class QuoteService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where we convert a quote to an invoice we link the two entities via the invoice_id parameter on the quote table
|
||||
* @param object $invoice The Invoice object
|
||||
* @return object QuoteService
|
||||
*/
|
||||
public function linkInvoiceToQuote($invoice) :QuoteService
|
||||
{
|
||||
$this->quote->invoice_id = $invoice->id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function convertToInvoice() :Invoice
|
||||
{
|
||||
|
||||
$invoice = CloneQuoteToInvoiceFactory::create($this->quote, $this->quote->user_id);
|
||||
$invoice->status_id = Invoice::STATUS_SENT;
|
||||
$invoice->due_date = null;
|
||||
$invoice->number = null;
|
||||
$invoice->save();
|
||||
//to prevent circular references we need to explicit call this here.
|
||||
$mark_approved = new MarkApproved($this->quote->client);
|
||||
$this->quote = $mark_approved->run($this->quote);
|
||||
|
||||
return $invoice->service()
|
||||
->markSent()
|
||||
->createInvitations()
|
||||
->save();
|
||||
$this->convert();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,6 +144,7 @@ class QuoteService
|
||||
public function save() : ?Quote
|
||||
{
|
||||
$this->quote->save();
|
||||
|
||||
return $this->quote;
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ class QuoteTransformer extends EntityTransformer
|
||||
'client_id' => (string) $this->encodePrimaryKey($quote->client_id),
|
||||
'status_id' => (string)$quote->status_id,
|
||||
'design_id' => (string) $this->encodePrimaryKey($quote->design_id),
|
||||
'invoice_id' => (string)$quote->invoice_id,
|
||||
'invoice_id' => (string)$this->encodePrimaryKey($quote->invoice_id),
|
||||
'updated_at' => (int)$quote->updated_at,
|
||||
'archived_at' => (int)$quote->deleted_at,
|
||||
'created_at' => (int)$quote->created_at,
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"video": false,
|
||||
"baseUrl": "http://127.0.0.1:8002/"
|
||||
"baseUrl": "http://ninja.test:8000/"
|
||||
}
|
||||
|
@ -124,7 +124,7 @@
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="street" class="input-label">@lang('texts.name')</label>
|
||||
<input id="name" class="input w-full" name="name"
|
||||
value="{{ auth()->user()->client->name }}"/>
|
||||
value="{{ auth()->user()->client->present()->name }}"/>
|
||||
@error('name')
|
||||
<div class="validation validation-fail">
|
||||
{{ $message }}
|
||||
@ -229,7 +229,7 @@
|
||||
<select id="country" class="input w-full form-select" name="country">
|
||||
@foreach($countries as $country)
|
||||
<option
|
||||
{{ $country == auth()->user()->client->country->id ? 'selected' : null }} value="{{ $country->id }}">
|
||||
{{ $country == isset(auth()->user()->client->country->id) ? 'selected' : null }} value="{{ $country->id }}">
|
||||
{{ $country->iso_3166_2 }}
|
||||
({{ $country->name }})
|
||||
</option>
|
||||
@ -333,7 +333,7 @@
|
||||
<select id="shipping_country" class="input w-full form-select" name="shipping_country">
|
||||
@foreach($countries as $country)
|
||||
<option
|
||||
{{ $country == auth()->user()->client->shipping_country->id ? 'selected' : null }} value="{{ $country->id }}">
|
||||
{{ $country == isset(auth()->user()->client->shipping_country->id) ? 'selected' : null }} value="{{ $country->id }}">
|
||||
{{ $country->iso_3166_2 }}
|
||||
({{ $country->name }})
|
||||
</option>
|
||||
|
Loading…
x
Reference in New Issue
Block a user