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['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;

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
*

View File

@ -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')) {

View File

@ -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');
}
}

View File

@ -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(),

View File

@ -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;
}

View File

@ -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'));

View File

@ -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']}";

View File

@ -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)

View File

@ -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)

View File

@ -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']);

View File

@ -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();
});
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -1,4 +1,4 @@
{
"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">
<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>