Allow conversion of quotes to invoices (#3760)

This commit is contained in:
David Bomba 2020-05-27 14:46:19 +10:00 committed by GitHub
parent 8512db6b1e
commit c72d38ca4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 106 additions and 54 deletions

View File

@ -28,14 +28,14 @@ class CloneQuoteToInvoiceFactory
unset($quote_array['hashed_id']); unset($quote_array['hashed_id']);
unset($quote_array['invoice_id']); unset($quote_array['invoice_id']);
unset($quote_array['id']); unset($quote_array['id']);
foreach($quote_array as $key => $value) foreach($quote_array as $key => $value)
$invoice->{$key} = $value; $invoice->{$key} = $value;
$invoice->status_id = Invoice::STATUS_DRAFT;
$invoice->due_date = null; $invoice->due_date = null;
$invoice->partial_due_date = null; $invoice->partial_due_date = null;
$invoice->number = null; $invoice->number = null;
$invoice->status_id = null;
return $invoice; return $invoice;

View File

@ -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 * Sorts the list based on $sort
* *

View File

@ -41,7 +41,7 @@ class InvitationController extends Controller
if ((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false) { if ((bool)$invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
$this->middleware('auth:contact'); $this->middleware('auth:contact');
} else { } else {
auth()->guard('contact')->login($invitation->contact, false); auth()->guard('contact')->login($invitation->contact, true);
} }
if (!request()->has('silent')) { if (!request()->has('silent')) {

View File

@ -23,13 +23,13 @@ class SwitchCompanyController extends Controller
public function __invoke(string $contact) 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)) ->where('id', $this->transformKeys($contact))
->first(); ->first();
Auth::guard('contact')->login($client_contact, true); Auth::guard('contact')->login($client_contact, true);
return back(); return redirect('/client/dashboard');
} }
} }

View File

@ -366,7 +366,7 @@ class MigrationController extends BaseController
return; 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([ return response()->json([
'_id' => Str::uuid(), '_id' => Str::uuid(),

View File

@ -49,7 +49,8 @@ class PortalComposer
$data['company'] = auth()->user()->company; $data['company'] = auth()->user()->company;
$data['client'] = auth()->user()->client; $data['client'] = auth()->user()->client;
$data['settings'] = auth()->user()->client->getMergedSettings(); $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; return $data;
} }

View File

@ -55,8 +55,12 @@ class EntityPaidMailer extends BaseMailerJob implements ShouldQueue
{ {
info("entity paid mailer"); info("entity paid mailer");
//Set DB //Set DB
//
MultiDB::setDb($this->company->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 //if we need to set an email driver do it now
$this->setMailDriver($this->payment->client->getSetting('email_sending_method')); $this->setMailDriver($this->payment->client->getSetting('email_sending_method'));

View File

@ -31,6 +31,7 @@ use App\Libraries\MultiDB;
use App\Mail\MigrationCompleted; use App\Mail\MigrationCompleted;
use App\Mail\MigrationFailed; use App\Mail\MigrationFailed;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
@ -147,7 +148,7 @@ class Import implements ShouldQueue
* @return void * @return void
* @throws \Exception * @throws \Exception
*/ */
public function handle() public function handle() :bool
{ {
set_time_limit(0); set_time_limit(0);
@ -156,7 +157,6 @@ class Import implements ShouldQueue
if (! in_array($key, $this->available_imports)) { if (! in_array($key, $this->available_imports)) {
//throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration."); //throw new ResourceNotAvailableForMigration("Resource {$key} is not available for migration.");
info("Resource {$key} is not available for migration."); info("Resource {$key} is not available for migration.");
continue; continue;
} }
@ -170,6 +170,8 @@ class Import implements ShouldQueue
Mail::to($this->user)->send(new MigrationCompleted()); Mail::to($this->user)->send(new MigrationCompleted());
info('Completed🚀🚀🚀🚀🚀 at '.now()); info('Completed🚀🚀🚀🚀🚀 at '.now());
return true;
} }
/** /**
@ -368,7 +370,9 @@ class Import implements ShouldQueue
unset($modified_contacts[$key]['id']); 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']}"; $key = "clients_{$resource['id']}";

View File

@ -74,6 +74,9 @@ class StartMigration implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
set_time_limit(0);
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
auth()->login($this->user, false); auth()->login($this->user, false);
@ -96,7 +99,7 @@ class StartMigration implements ShouldQueue
$zip->close(); $zip->close();
if (app()->environment() == 'testing') { if (app()->environment() == 'testing') {
return; return true;
} }
$this->company->setMigration(true); $this->company->setMigration(true);
@ -110,6 +113,9 @@ class StartMigration implements ShouldQueue
$data = json_decode(file_get_contents($file), 1); $data = json_decode(file_get_contents($file), 1);
Import::dispatchNow($data, $this->company, $this->user); Import::dispatchNow($data, $this->company, $this->user);
$this->company->setMigration(false);
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) { } catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
$this->company->setMigration(false); $this->company->setMigration(false);
@ -119,6 +125,11 @@ class StartMigration implements ShouldQueue
info($e->getMessage()); info($e->getMessage());
} }
} }
//always make sure we unset the migration as running
return true;
} }
public function failed($exception = null) public function failed($exception = null)

View File

@ -37,18 +37,27 @@ class SubscriptionHandler implements ShouldQueue
* *
* @return void * @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) $subscriptions = Subscription::where('company_id', $this->entity->company_id)
->where('event_id', $this->event_id) ->where('event_id', $this->event_id)
->get(); ->get();
if(!$subscriptions || $subscriptions->count() == 0) if(!$subscriptions || $subscriptions->count() == 0)
return; return true;
$subscriptions->each(function($subscription) { $subscriptions->each(function($subscription) {
$this->process($subscription); $this->process($subscription);
}); });
return true;
} }
private function process($subscription) private function process($subscription)

View File

@ -48,6 +48,9 @@ class PaymentNotification implements ShouldQueue
/*User notifications*/ /*User notifications*/
foreach ($payment->company->company_users as $company_user) { foreach ($payment->company->company_users as $company_user) {
if($company_user->is_migrating)
return true;
$user = $company_user->user; $user = $company_user->user;
$methods = $this->findUserEntityNotificationType($payment, $company_user, ['all_notifications']); $methods = $this->findUserEntityNotificationType($payment, $company_user, ['all_notifications']);

View File

@ -406,9 +406,13 @@ class Company extends BaseModel
public function setMigration($status) 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->is_migrating=$status;
$cu->save(); $cu->save();
}); }
} }
} }

View File

@ -24,6 +24,7 @@ class ClientContactRepository extends BaseRepository
{ {
public function save(array $data, Client $client) : void public function save(array $data, Client $client) : void
{ {
if (isset($data['contacts'])) { if (isset($data['contacts'])) {
$contacts = collect($data['contacts']); $contacts = collect($data['contacts']);
} else { } else {
@ -66,17 +67,20 @@ class ClientContactRepository extends BaseRepository
} }
$update_contact->save(); $update_contact->save();
}); });
//always made sure we have one blank contact to maintain state //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 = ClientContactFactory::create($client->company_id, $client->user_id);
$new_contact->client_id = $client->id; $new_contact->client_id = $client->id;
$new_contact->contact_key = Str::random(40); $new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true; $new_contact->is_primary = true;
$new_contact->save(); $new_contact->save();
} }
} }
} }

View File

@ -10,10 +10,10 @@ class ConvertQuote
private $client; private $client;
private $invoice_repo; private $invoice_repo;
public function __construct($client, InvoiceRepository $invoice_repo) public function __construct($client)
{ {
$this->client = $client; $this->client = $client;
$this->invoice_repo = $invoice_repo; $this->invoice_repo = new InvoiceRepository();
} }
/** /**
@ -23,9 +23,19 @@ class ConvertQuote
public function run($quote) public function run($quote)
{ {
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id, $quote->company_id); $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 // maybe should return invoice here
return $quote; return $invoice;
} }
} }

View File

@ -21,6 +21,8 @@ class QuoteService
{ {
protected $quote; protected $quote;
public $invoice;
public function __construct($quote) public function __construct($quote)
{ {
$this->quote = $quote; $this->quote = $quote;
@ -38,17 +40,28 @@ class QuoteService
public function markApproved() public function markApproved()
{ {
$mark_approved = new MarkApproved($this->quote->client); $mark_approved = new MarkApproved($this->quote->client);
$this->quote = $mark_approved->run($this->quote); $this->quote = $mark_approved->run($this->quote);
if ($this->quote->client->getSetting('auto_convert_quote') === true) { if ($this->quote->client->getSetting('auto_convert_quote') === true) {
$convert_quote = new ConvertQuote($this->quote->client); $this->convert();
$this->quote = $convert_quote->run($this->quote);
} }
return $this; 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) public function getQuotePdf($contact = null)
{ {
$get_invoice_pdf = new GetQuotePdf(); $get_invoice_pdf = new GetQuotePdf();
@ -101,8 +114,7 @@ class QuoteService
$invoice = null; $invoice = null;
if ($this->quote->client->getSetting('auto_convert_quote')) { if ($this->quote->client->getSetting('auto_convert_quote')) {
$invoice = $this->convertToInvoice(); $this->convert();
$this->linkInvoiceToQuote($invoice)->save();
} }
if ($this->quote->client->getSetting('auto_archive_quote')) { if ($this->quote->client->getSetting('auto_archive_quote')) {
@ -113,32 +125,16 @@ class QuoteService
return $this; 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 public function convertToInvoice() :Invoice
{ {
$invoice = CloneQuoteToInvoiceFactory::create($this->quote, $this->quote->user_id); //to prevent circular references we need to explicit call this here.
$invoice->status_id = Invoice::STATUS_SENT; $mark_approved = new MarkApproved($this->quote->client);
$invoice->due_date = null; $this->quote = $mark_approved->run($this->quote);
$invoice->number = null;
$invoice->save();
return $invoice->service() $this->convert();
->markSent()
->createInvitations()
->save();
return $this->invoice;
} }
/** /**
@ -148,6 +144,7 @@ class QuoteService
public function save() : ?Quote public function save() : ?Quote
{ {
$this->quote->save(); $this->quote->save();
return $this->quote; return $this->quote;
} }
} }

View File

@ -81,7 +81,7 @@ class QuoteTransformer extends EntityTransformer
'client_id' => (string) $this->encodePrimaryKey($quote->client_id), 'client_id' => (string) $this->encodePrimaryKey($quote->client_id),
'status_id' => (string)$quote->status_id, 'status_id' => (string)$quote->status_id,
'design_id' => (string) $this->encodePrimaryKey($quote->design_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, 'updated_at' => (int)$quote->updated_at,
'archived_at' => (int)$quote->deleted_at, 'archived_at' => (int)$quote->deleted_at,
'created_at' => (int)$quote->created_at, 'created_at' => (int)$quote->created_at,

View File

@ -1,4 +1,4 @@
{ {
"video": false, "video": false,
"baseUrl": "http://127.0.0.1:8002/" "baseUrl": "http://ninja.test:8000/"
} }

View File

@ -124,7 +124,7 @@
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="street" class="input-label">@lang('texts.name')</label> <label for="street" class="input-label">@lang('texts.name')</label>
<input id="name" class="input w-full" name="name" <input id="name" class="input w-full" name="name"
value="{{ auth()->user()->client->name }}"/> value="{{ auth()->user()->client->present()->name }}"/>
@error('name') @error('name')
<div class="validation validation-fail"> <div class="validation validation-fail">
{{ $message }} {{ $message }}
@ -229,7 +229,7 @@
<select id="country" class="input w-full form-select" name="country"> <select id="country" class="input w-full form-select" name="country">
@foreach($countries as $country) @foreach($countries as $country)
<option <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->iso_3166_2 }}
({{ $country->name }}) ({{ $country->name }})
</option> </option>
@ -333,7 +333,7 @@
<select id="shipping_country" class="input w-full form-select" name="shipping_country"> <select id="shipping_country" class="input w-full form-select" name="shipping_country">
@foreach($countries as $country) @foreach($countries as $country)
<option <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->iso_3166_2 }}
({{ $country->name }}) ({{ $country->name }})
</option> </option>