diff --git a/app/Console/Commands/SendTestEmails.php b/app/Console/Commands/SendTestEmails.php index b9c62c1cde..3f3d73ce11 100644 --- a/app/Console/Commands/SendTestEmails.php +++ b/app/Console/Commands/SendTestEmails.php @@ -2,10 +2,13 @@ namespace App\Console\Commands; +use App\DataMapper\DefaultSettings; use App\Factory\ClientFactory; +use App\Factory\CompanyUserFactory; use App\Factory\InvoiceFactory; use App\Factory\InvoiceInvitationFactory; use App\Helpers\Email\InvoiceEmail; +use App\Jobs\Invoice\CreateInvoicePdf; use App\Mail\TemplateEmail; use App\Models\Client; use App\Models\ClientContact; @@ -49,9 +52,7 @@ class SendTestEmails extends Command public function handle() { $this->sendTemplateEmails('plain'); - sleep(5); $this->sendTemplateEmails('light'); - sleep(5); $this->sendTemplateEmails('dark'); } @@ -68,24 +69,46 @@ class SendTestEmails extends Command $user = User::whereEmail('user@example.com')->first(); - $account = factory(\App\Models\Account::class)->create(); - - $company = factory(\App\Models\Company::class)->create([ - 'account_id' => $account->id, - ]); - - $client = Client::all()->first(); if (!$user) { + $user = factory(\App\Models\User::class)->create([ 'confirmation_code' => '123', 'email' => $faker->safeEmail, 'first_name' => 'John', 'last_name' => 'Doe', ]); + + $account = factory(\App\Models\Account::class)->create(); + + + $company = factory(\App\Models\Company::class)->create([ + 'account_id' => $account->id, + ]); + + $user->companies()->attach($company->id, [ + 'account_id' => $account->id, + 'is_owner' => 1, + 'is_admin' => 1, + 'is_locked' => 0, + 'permissions' => '', + 'settings' => DefaultSettings::userSettings(), + ]); + + } + else + { + $company = $user->company_users->first()->company; + $account = $company->account; } + + + $client = Client::all()->first(); + + if (!$client) { + $client = ClientFactory::create($company->id, $user->id); $client->save(); @@ -118,13 +141,18 @@ class SendTestEmails extends Command $ii->save(); $invoice->setRelation('invitations', $ii); + $invoice->service()->markSent()->save(); - $invoice->save(); + CreateInvoicePdf::dispatch($invoice, $company, $client->primary_contact()->first()); $cc_emails = [config('ninja.testvars.test_email')]; $bcc_emails = [config('ninja.testvars.test_email')]; - $email_builder = (new InvoiceEmail())->build($invoice, null, $client->primary_contact()->first()); + $email_builder = (new InvoiceEmail())->build($ii, 'invoice'); + + $email_builder->setFooter($message['footer']) + ->setSubject($message['subject']) + ->setBody($message['body']); Mail::to(config('ninja.testvars.test_email'), 'Mr Test') ->cc($cc_emails) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index c3f4dd8175..1fa9337213 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -33,6 +33,8 @@ class CompanySettings extends BaseSettings { public $document_email_attachment = false; public $send_portal_password = false; + public $portal_design_id = '1'; + public $timezone_id = ''; public $date_format_id = ''; public $military_time = false; @@ -218,6 +220,7 @@ class CompanySettings extends BaseSettings { public $invoice_variables = []; public static $casts = [ + 'portal_design_id' => 'string', 'late_fee_endless_percent' => 'float', 'late_fee_endless_amount' => 'float', 'auto_email_invoice' => 'bool', diff --git a/app/Helpers/Email/EmailBuilder.php b/app/Helpers/Email/EmailBuilder.php index b449e9f249..4869ecae8e 100644 --- a/app/Helpers/Email/EmailBuilder.php +++ b/app/Helpers/Email/EmailBuilder.php @@ -1,15 +1,13 @@ parseTemplate($body, true); + $this->body = $this->parseTemplate($body, true); return $this; } diff --git a/app/Helpers/Email/InvoiceEmail.php b/app/Helpers/Email/InvoiceEmail.php index 2f8f3c5ce4..8e603168be 100644 --- a/app/Helpers/Email/InvoiceEmail.php +++ b/app/Helpers/Email/InvoiceEmail.php @@ -10,23 +10,27 @@ namespace App\Helpers\Email; use App\Models\Invoice; +use App\Models\InvoiceInvitation; class InvoiceEmail extends EmailBuilder { - public function build(Invoice $invoice, $reminder_template, $contact = null) + public function build(InvoiceInvitation $invitation, $reminder_template) { - $client = $invoice->client; + $client = $invitation->contact->client; + $invoice = $invitation->invoice; + $contact = $invitation->contact; if(!$reminder_template) $reminder_template = $invoice->calculateTemplate(); $body_template = $client->getSetting('email_template_' . $reminder_template); + /* Use default translations if a custom message has not been set*/ if (iconv_strlen($body_template) == 0) { $body_template = trans('texts.invoice_message', - ['amount' => $invoice->present()->amount(), 'company' => $invoice->company->present()->name()], null, + ['invoice' => $invoice->number, 'company' => $invoice->company->present()->name()], null, $invoice->client->locale()); } @@ -36,26 +40,26 @@ class InvoiceEmail extends EmailBuilder if ($reminder_template == 'quote') { $subject_template = trans('texts.invoice_subject', [ - 'number' => $invoice->present()->invoice_number(), - 'company' => $invoice->company->present()->name() + 'invoice' => $invoice->present()->invoice_number(), + 'account' => $invoice->company->present()->name() ], null, $invoice->client->locale()); } else { $subject_template = trans('texts.reminder_subject', [ - 'number' => $invoice->present()->invoice_number(), - 'company' => $invoice->company->present()->name() + 'invoice' => $invoice->present()->invoice_number(), + 'account' => $invoice->company->present()->name() ], null, $invoice->client->locale()); } } - + $this->setTemplate($invoice->client->getSetting('email_style')) ->setContact($contact) ->setVariables($invoice->makeValues($contact)) ->setSubject($subject_template) ->setBody($body_template) - ->setFooter("Invoice Link"); + ->setFooter("{$invitation->getLink()}"); if ($client->getSetting('pdf_email_attachment') !== false) { $this->setAttachments($invoice->pdf_file_path()); diff --git a/app/Helpers/Email/QuoteEmail.php b/app/Helpers/Email/QuoteEmail.php index a586199aca..e68cb34d2d 100644 --- a/app/Helpers/Email/QuoteEmail.php +++ b/app/Helpers/Email/QuoteEmail.php @@ -9,15 +9,19 @@ namespace App\Helpers\Email; use App\Models\Quote; +use App\Models\QuoteInvitation; class QuoteEmail extends EmailBuilder { - public function build(Quote $quote, $reminder_template, $contact = null) + public function build(QuoteInvitation $invitation, $reminder_template) { - $client = $quote->client; - $this->template_style = $quote->client->getSetting('email_style'); + $client = $invitation->client; + $quote = $invitation->quote; + $contact = $invitation->contact; + + $this->template_style = $client->getSetting('email_style'); $body_template = $client->getSetting('email_template_' . $reminder_template); @@ -44,7 +48,7 @@ class QuoteEmail extends EmailBuilder $this->setTemplate($quote->client->getSetting('email_style')) ->setContact($contact) - ->setFooter("Invoice Link") + ->setFooter("Quote Link") ->setVariables($quote->makeValues($contact)) ->setSubject($subject_template) ->setBody($body_template); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 60ab152836..6610c16713 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -666,7 +666,9 @@ class InvoiceController extends BaseController { case 'email': $invoice->invitations->each(function ($invitation) use($invoice){ - EmailInvoice::dispatch((new InvoiceEmail)->build($invoice, null, null), $invitation, $invoice->company); + $email_builder = (new InvoiceEmail())->build($invitation, $this->reminder_template); + + EmailInvoice::dispatch($email_builder, $invitation, $invoice->company); }); diff --git a/app/Http/Controllers/OpenAPI/ClientSchema.php b/app/Http/Controllers/OpenAPI/ClientSchema.php index b96e84220e..1bfa27aa44 100644 --- a/app/Http/Controllers/OpenAPI/ClientSchema.php +++ b/app/Http/Controllers/OpenAPI/ClientSchema.php @@ -6,7 +6,6 @@ * @OA\Property(property="id", type="string", example="Opnel5aKBz", description="_________"), * @OA\Property(property="user_id", type="string", example="", description="__________"), * @OA\Property(property="company_id", type="string", example="", description="________"), - * @OA\Property(property="client_id", type="string", example="", description="________"), * @OA\Property( * property="contacts", * type="array", diff --git a/app/Http/Middleware/Cors.php b/app/Http/Middleware/Cors.php index 88c4c613f1..93a97c80d6 100644 --- a/app/Http/Middleware/Cors.php +++ b/app/Http/Middleware/Cors.php @@ -36,7 +36,7 @@ class Cors $response->headers->set('Access-Control-Allow-Origin', '*'); $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - $response->headers->set('Access-Control-Allow-Headers', 'X-API-SECRET,X-API-TOKEN,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'); + $response->headers->set('Access-Control-Allow-Headers', 'X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'); return $response; } diff --git a/app/Http/Middleware/PasswordProtection.php b/app/Http/Middleware/PasswordProtection.php index 2efe9c74a1..9b4ec933f8 100644 --- a/app/Http/Middleware/PasswordProtection.php +++ b/app/Http/Middleware/PasswordProtection.php @@ -40,6 +40,10 @@ class PasswordProtection return response()->json($error, 403); } } elseif (Cache::get(auth()->user()->email."_logged_in")) { + + Cache::pull(auth()->user()->email."_logged_in"); + Cache::add(auth()->user()->email."_logged_in", Str::random(64), now()->addMinutes(10)); + return $next($request); } else { $error = [ diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index 37f985c6c5..48e982ae9a 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -40,7 +40,9 @@ class StoreCreditRequest extends FormRequest { $input = $this->all(); - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + if($input['client_id']) + $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; //$input['line_items'] = json_encode($input['line_items']); $this->replace($input); diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index a9c96e4c79..dcfecee7dc 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -46,7 +46,9 @@ class StoreInvoiceRequest extends Request { $input = $this->all(); - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + if($input['client_id']) + $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; //$input['line_items'] = json_encode($input['line_items']); $this->replace($input); diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index 4a87a84c69..7f1f565a1e 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -45,7 +45,9 @@ class StoreRecurringInvoiceRequest extends Request { $input = $this->all(); - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + if($input['client_id']) + $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; //$input['line_items'] = json_encode($input['line_items']); $this->replace($input); diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php index b955795a3b..ef0e777dd0 100644 --- a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -45,7 +45,9 @@ class StoreRecurringQuoteRequest extends Request { $input = $this->all(); - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + if($input['client_id']) + $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; //$input['line_items'] = json_encode($input['line_items']); $this->replace($input); diff --git a/app/Jobs/Invoice/ZipInvoices.php b/app/Jobs/Invoice/ZipInvoices.php index c653addb51..78906910b1 100644 --- a/app/Jobs/Invoice/ZipInvoices.php +++ b/app/Jobs/Invoice/ZipInvoices.php @@ -12,6 +12,7 @@ namespace App\Jobs\Invoice; use App\Libraries\MultiDB; +use App\Mail\DownloadInvoices; use App\Models\Company; use App\Models\Invoice; use Illuminate\Bus\Queueable; @@ -19,15 +20,16 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Storage; use ZipStream\Option\Archive; use ZipStream\ZipStream; -use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Mail; class ZipInvoices implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $invoice; + public $invoices; private $company; @@ -66,7 +68,7 @@ class ZipInvoices implements ShouldQueue $zip = new ZipStream($file_name, $options); - foreach ($invoices as $invoice) { + foreach ($this->invoices as $invoice) { $zip->addFileFromPath(basename($invoice->pdf_file_path()), public_path($invoice->pdf_file_path())); } @@ -76,9 +78,9 @@ class ZipInvoices implements ShouldQueue fclose($tempStream); - //fire email here - return Storage::disk(config('filesystems.default'))->url($path . $file_name); + Mail::to(config('ninja.contact.ninja_official_contact')) + ->send(new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path . $file_name))); } } diff --git a/app/Listeners/Invoice/CreateInvoiceHtmlBackup.php b/app/Listeners/Invoice/CreateInvoiceHtmlBackup.php index 4e015245d4..3c05676aa4 100644 --- a/app/Listeners/Invoice/CreateInvoiceHtmlBackup.php +++ b/app/Listeners/Invoice/CreateInvoiceHtmlBackup.php @@ -38,6 +38,7 @@ class CreateInvoiceHtmlBackup implements ShouldQueue */ public function handle($event) { + MultiDB::setDB($event->company->db); $fields = new \stdClass; diff --git a/app/Mail/DownloadInvoices.php b/app/Mail/DownloadInvoices.php new file mode 100644 index 0000000000..b7102f1394 --- /dev/null +++ b/app/Mail/DownloadInvoices.php @@ -0,0 +1,36 @@ +file_path = $file_path; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + + return $this->from(config('mail.from.address')) //todo this needs to be fixed to handle the hosted version + ->subject(ctrans('texts.download_documents',['size'=>''])) + ->markdown('email.admin.download_files', [ + 'file_path' => $this->file_path + ]); + } +} diff --git a/app/Models/Client.php b/app/Models/Client.php index 207c8f2b9a..3ef8d16dce 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -56,7 +56,7 @@ class Client extends BaseModel implements HasLocalePreference 'user_id', 'company_id', 'backup', - 'settings', +// 'settings', 'last_login', 'private_notes' ]; @@ -245,6 +245,7 @@ class Client extends BaseModel implements HasLocalePreference */ public function getMergedSettings() :object { + if ($this->group_settings !== null) { $group_settings = ClientSettings::buildClientSettings($this->group_settings->settings, $this->settings); @@ -431,7 +432,7 @@ class Client extends BaseModel implements HasLocalePreference $languages = Cache::get('languages'); return $languages->filter(function ($item) { - return $item->id == $this->client->getSetting('language_id'); + return $item->id == $this->getSetting('language_id'); })->first()->locale; //$lang = Language::find($this->client->getSetting('language_id')); diff --git a/app/Services/AbstractService.php b/app/Services/AbstractService.php new file mode 100644 index 0000000000..5fd7e0b279 --- /dev/null +++ b/app/Services/AbstractService.php @@ -0,0 +1,19 @@ +customer = $customer; + $this->client = $client; } - public function __invoke($credit) + public function run($credit) { if ($credit->number != '') { return $credit; } - $credit->number = $this->getNextCreditNumber($this->customer); + $credit->number = $this->getNextCreditNumber($this->client); return $credit; diff --git a/app/Services/Credit/CreateInvitations.php b/app/Services/Credit/CreateInvitations.php index 07ae23cf6f..56b7249523 100644 --- a/app/Services/Credit/CreateInvitations.php +++ b/app/Services/Credit/CreateInvitations.php @@ -3,18 +3,19 @@ namespace App\Services\Credit; use App\Factory\CreditInvitationFactory; use App\Models\CreditInvitation; +use App\Services\AbstractService; -class CreateInvitations +class CreateInvitations extends AbstractService { public function __construct() { } - public function __invoke($credit) + public function run($credit) { - $contacts = $credit->customer->contacts; + $contacts = $credit->client->contacts; $contacts->each(function ($contact) use($credit){ $invitation = CreditInvitation::whereCompanyId($credit->account_id) @@ -23,11 +24,11 @@ class CreateInvitations ->first(); if (!$invitation) { - $ii = CreditInvitationFactory::create($credit->account_id, $credit->user_id); + $ii = CreditInvitationFactory::create($credit->company_id, $credit->user_id); $ii->credit_id = $credit->id; $ii->client_contact_id = $contact->id; $ii->save(); - } elseif ($invitation && !$contact->send_credit) { + } elseif ($invitation && !$contact->send_email) { $invitation->delete(); } }); diff --git a/app/Services/Credit/GetCreditPdf.php b/app/Services/Credit/GetCreditPdf.php index 0a8f7619df..9d3c11a58f 100644 --- a/app/Services/Credit/GetCreditPdf.php +++ b/app/Services/Credit/GetCreditPdf.php @@ -3,9 +3,10 @@ namespace App\Services\Credit; use App\Jobs\Invoice\CreateInvoicePdf; +use App\Services\AbstractService; use Illuminate\Support\Facades\Storage; -class GetCreditPdf +class GetCreditPdf extends AbstractService { public function __construct() @@ -15,16 +16,16 @@ class GetCreditPdf public function __invoke($credit, $contact = null) { if (!$contact) { - $contact = $credit->customer->primary_contact()->first(); + $contact = $credit->client->primary_contact()->first(); } - $path = 'public/' . $credit->customer->id . '/credits/'; + $path = 'public/' . $credit->client->id . '/credits/'; $file_path = $path . $credit->number . '.pdf'; $disk = config('filesystems.default'); $file = Storage::disk($disk)->exists($file_path); if (!$file) { - $file_path = CreateInvoicePdf::dispatchNow($this, $credit->account, $contact); + $file_path = CreateInvoicePdf::dispatchNow($this, $credit->company, $contact); } return Storage::disk($disk)->url($file_path); diff --git a/app/Services/Invoice/ApplyNumber.php b/app/Services/Invoice/ApplyNumber.php index c0e1348c67..97cfb2467d 100644 --- a/app/Services/Invoice/ApplyNumber.php +++ b/app/Services/Invoice/ApplyNumber.php @@ -14,35 +14,41 @@ namespace App\Services\Invoice; use App\Events\Payment\PaymentWasCreated; use App\Factory\PaymentFactory; use App\Jobs\Company\UpdateCompanyLedgerWithPayment; +use App\Models\Client; use App\Models\Invoice; use App\Models\Payment; +use App\Services\AbstractService; use App\Services\Client\ClientService; use App\Services\Payment\PaymentService; use App\Utils\Traits\GeneratesCounter; -class ApplyNumber +class ApplyNumber extends AbstractService { use GeneratesCounter; private $client; - public function __construct($client) + private $invoice; + + public function __construct(Client $client, Invoice $invoice) { $this->client = $client; + + $this->invoice = $invoice; } - public function run($invoice) + public function run() { - if ($invoice->number != '') - return $invoice; + if ($this->invoice->number != '') + return $this->invoice; switch ($this->client->getSetting('counter_number_applied')) { case 'when_saved': - $invoice->number = $this->getNextInvoiceNumber($this->client); + $this->invoice->number = $this->getNextInvoiceNumber($this->client); break; case 'when_sent': - if ($invoice->status_id == Invoice::STATUS_SENT) { - $invoice->number = $this->getNextInvoiceNumber($this->client); + if ($this->invoice->status_id == Invoice::STATUS_SENT) { + $this->invoice->number = $this->getNextInvoiceNumber($this->client); } break; @@ -51,6 +57,6 @@ class ApplyNumber break; } - return $invoice; + return $this->invoice; } } diff --git a/app/Services/Invoice/ApplyPayment.php b/app/Services/Invoice/ApplyPayment.php index f8f07cd191..a8ec2b0ad4 100644 --- a/app/Services/Invoice/ApplyPayment.php +++ b/app/Services/Invoice/ApplyPayment.php @@ -14,46 +14,53 @@ namespace App\Services\Invoice; use App\Jobs\Company\UpdateCompanyLedgerWithPayment; use App\Models\Invoice; use App\Models\Payment; +use App\Services\AbstractService; use App\Services\Client\ClientService; -class ApplyPayment +class ApplyPayment extends AbstractService { private $invoice; - public function __construct($invoice) + private $payment; + + private $payment_amount; + + public function __construct($invoice, $payment, $payment_amount) { $this->invoice = $invoice; + $this->payment = $payment; + $this->payment_amount = $payment_amount; } - public function run($payment, $payment_amount) + public function run() { - UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($payment_amount*-1), $payment->company); + UpdateCompanyLedgerWithPayment::dispatchNow($this->payment, ($this->payment_amount*-1), $this->payment->company); - $payment->client->service()->updateBalance($payment_amount*-1)->save(); + $this->payment->client->service()->updateBalance($this->payment_amount*-1)->save(); /* Update Pivot Record amount */ - $payment->invoices->each(function ($inv) use($payment_amount){ + $this->payment->invoices->each(function ($inv){ if ($inv->id == $this->invoice->id) { - $inv->pivot->amount = $payment_amount; + $inv->pivot->amount = $this->payment_amount; $inv->pivot->save(); } }); if ($this->invoice->hasPartial()) { //is partial and amount is exactly the partial amount - if ($this->invoice->partial == $payment_amount) { - $this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($payment_amount*-1); - } elseif ($this->invoice->partial > 0 && $this->invoice->partial > $payment_amount) { //partial amount exists, but the amount is less than the partial amount - $this->invoice->service()->updatePartial($payment_amount*-1)->updateBalance($payment_amount*-1); - } elseif ($this->invoice->partial > 0 && $this->invoice->partial < $payment_amount) { //partial exists and the amount paid is GREATER than the partial amount - $this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($payment_amount*-1); + if ($this->invoice->partial == $this->payment_amount) { + $this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount*-1); + } elseif ($this->invoice->partial > 0 && $this->invoice->partial > $this->payment_amount) { //partial amount exists, but the amount is less than the partial amount + $this->invoice->service()->updatePartial($this->payment_amount*-1)->updateBalance($this->payment_amount*-1); + } elseif ($this->invoice->partial > 0 && $this->invoice->partial < $this->payment_amount) { //partial exists and the amount paid is GREATER than the partial amount + $this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount*-1); } - } elseif ($payment_amount == $this->invoice->balance) { //total invoice paid. - $this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($payment_amount*-1); - } elseif($payment_amount < $this->invoice->balance) { //partial invoice payment made - $this->invoice->service()->clearPartial()->updateBalance($payment_amount*-1); + } elseif ($this->payment_amount == $this->invoice->balance) { //total invoice paid. + $this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($this->payment_amount*-1); + } elseif($this->payment_amount < $this->invoice->balance) { //partial invoice payment made + $this->invoice->service()->clearPartial()->updateBalance($this->payment_amount*-1); } return $this->invoice; diff --git a/app/Services/Invoice/CreateInvitations.php b/app/Services/Invoice/CreateInvitations.php index 42d2dc3bc8..b1f6c09fec 100644 --- a/app/Services/Invoice/CreateInvitations.php +++ b/app/Services/Invoice/CreateInvitations.php @@ -15,29 +15,31 @@ namespace App\Services\Invoice; use App\Factory\InvoiceInvitationFactory; use App\Models\Invoice; use App\Models\InvoiceInvitation; +use App\Services\AbstractService; -class CreateInvitations +class CreateInvitations extends AbstractService { - public function __construct() + private $invoice; + + public function __construct(Invoice $invoice) { - // .. + $this->invoice = $invoice; } - public function run($invoice) + public function run() { - $contacts = $invoice->client->contacts; + $this->invoice->client->contacts->each(function ($contact){ - $contacts->each(function ($contact) use($invoice){ - $invitation = InvoiceInvitation::whereCompanyId($invoice->company_id) + $invitation = InvoiceInvitation::whereCompanyId($this->invoice->company_id) ->whereClientContactId($contact->id) - ->whereInvoiceId($invoice->id) + ->whereInvoiceId($this->invoice->id) ->first(); if (!$invitation && $contact->send) { - $ii = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id); - $ii->invoice_id = $invoice->id; + $ii = InvoiceInvitationFactory::create($this->invoice->company_id, $this->invoice->user_id); + $ii->invoice_id = $this->invoice->id; $ii->client_contact_id = $contact->id; $ii->save(); } elseif ($invitation && !$contact->send) { @@ -45,6 +47,6 @@ class CreateInvitations } }); - return $invoice; + return $this->invoice; } } diff --git a/app/Services/Invoice/GetInvoicePdf.php b/app/Services/Invoice/GetInvoicePdf.php index 2eb6ad69cd..3c835b3fc0 100644 --- a/app/Services/Invoice/GetInvoicePdf.php +++ b/app/Services/Invoice/GetInvoicePdf.php @@ -12,20 +12,32 @@ namespace App\Services\Invoice; use App\Jobs\Invoice\CreateInvoicePdf; +use App\Models\ClientContact; +use App\Models\Invoice; +use App\Services\AbstractService; use Illuminate\Support\Facades\Storage; -class GetInvoicePdf +class GetInvoicePdf extends AbstractService { - public function run($invoice, $contact = null) + public function __construct(Invoice $invoice, ClientContact $contact = null) + { + + $this->invoice = $invoice; + + $this->contact = $contact; + + } + + public function run() { - if(!$contact) - $contact = $invoice->client->primary_contact()->first(); + if(!$this->contact) + $this->contact = $this->invoice->client->primary_contact()->first(); - $path = $invoice->client->invoice_filepath(); + $path = $this->invoice->client->invoice_filepath(); - $file_path = $path . $invoice->number . '.pdf'; + $file_path = $path . $this->invoice->number . '.pdf'; $disk = config('filesystems.default'); @@ -34,7 +46,7 @@ class GetInvoicePdf if(!$file) { - $file_path = CreateInvoicePdf::dispatchNow($invoice, $invoice->company, $contact); + $file_path = CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->contact); } diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index a30b57a371..eef9a57ecc 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -44,9 +44,9 @@ class InvoiceService */ public function markPaid() { - $mark_invoice_paid = new MarkPaid($this->client_service); + $mark_invoice_paid = new MarkPaid($this->client_service, $this->invoice); - $this->invoice = $mark_invoice_paid->run($this->invoice); + $this->invoice = $mark_invoice_paid->run(); return $this; } @@ -57,9 +57,9 @@ class InvoiceService */ public function applyNumber() { - $apply_number = new ApplyNumber($this->invoice->client); + $apply_number = new ApplyNumber($this->invoice->client, $this->invoice); - $this->invoice = $apply_number->run($this->invoice); + $this->invoice = $apply_number->run(); return $this; } @@ -72,9 +72,9 @@ class InvoiceService */ public function applyPayment(Payment $payment, float $payment_amount) { - $apply_payment = new ApplyPayment($this->invoice); + $apply_payment = new ApplyPayment($this->invoice, $payment, $payment_amount); - $this->invoice = $apply_payment->run($payment, $payment_amount); + $this->invoice = $apply_payment->run(); return $this; } @@ -88,27 +88,27 @@ class InvoiceService */ public function updateBalance($balance_adjustment) { - $update_balance = new UpdateBalance($this->invoice); + $update_balance = new UpdateBalance($this->invoice, $balance_adjustment); - $this->invoice = $update_balance->run($balance_adjustment); + $this->invoice = $update_balance->run(); return $this; } public function createInvitations() { - $create_invitation = new CreateInvitations(); + $create_invitation = new CreateInvitations($this->invoice); - $this->invoice = $create_invitation->run($this->invoice); + $this->invoice = $create_invitation->run(); return $this; } public function markSent() - { - $mark_sent = new MarkSent($this->invoice->client); + { + $mark_sent = new MarkSent($this->invoice->client, $this->invoice); - $this->invoice = $mark_sent->run($this->invoice); + $this->invoice = $mark_sent->run(); return $this; } @@ -116,16 +116,16 @@ class InvoiceService public function getInvoicePdf($contact) { - $get_invoice_pdf = new GetInvoicePdf(); + $get_invoice_pdf = new GetInvoicePdf($this->invoice, $contact); - return $get_invoice_pdf->run($this->invoice, $contact); + return $get_invoice_pdf->run(); } public function sendEmail($contact) { - $send_email = new SendEmail($this->invoice); + $send_email = new SendEmail($this->invoice, null, $contact); - return $send_email->run(null, $contact); + return $send_email->run(); } public function markViewed() diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 0b994b15de..42879a1c17 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -16,39 +16,44 @@ use App\Factory\PaymentFactory; use App\Jobs\Company\UpdateCompanyLedgerWithPayment; use App\Models\Invoice; use App\Models\Payment; +use App\Services\AbstractService; use App\Services\Client\ClientService; use App\Services\Payment\PaymentService; -class MarkPaid +class MarkPaid extends AbstractService { private $client_service; - public function __construct($client_service) + private $invoice; + + public function __construct(ClientService $client_service, Invoice $invoice) { $this->client_service = $client_service; + + $this->invoice = $invoice; } - public function run($invoice) + public function run() { - if($invoice->status_id == Invoice::STATUS_DRAFT) - $invoice->service()->markSent(); + if($this->invoice->status_id == Invoice::STATUS_DRAFT) + $this->invoice->service()->markSent(); /* Create Payment */ - $payment = PaymentFactory::create($invoice->company_id, $invoice->user_id); + $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); - $payment->amount = $invoice->balance; + $payment->amount = $this->invoice->balance; $payment->status_id = Payment::STATUS_COMPLETED; - $payment->client_id = $invoice->client_id; + $payment->client_id = $this->invoice->client_id; $payment->transaction_reference = ctrans('texts.manual_entry'); /* Create a payment relationship to the invoice entity */ $payment->save(); - $payment->invoices()->attach($invoice->id, [ + $payment->invoices()->attach($this->invoice->id, [ 'amount' => $payment->amount ]); - $invoice->service() + $this->invoice->service() ->updateBalance($payment->amount*-1) ->setStatus(Invoice::STATUS_PAID) ->save(); @@ -63,7 +68,7 @@ class MarkPaid ->updatePaidToDate($payment->amount) ->save(); - return $invoice; + return $this->invoice; } } diff --git a/app/Services/Invoice/MarkSent.php b/app/Services/Invoice/MarkSent.php index 0f49d87619..3b5dac621f 100644 --- a/app/Services/Invoice/MarkSent.php +++ b/app/Services/Invoice/MarkSent.php @@ -14,38 +14,42 @@ namespace App\Services\Invoice; use App\Events\Invoice\InvoiceWasMarkedSent; use App\Jobs\Company\UpdateCompanyLedgerWithInvoice; use App\Models\Invoice; +use App\Services\AbstractService; -class MarkSent +class MarkSent extends AbstractService { private $client; - public function __construct($client) + private $invoice; + + public function __construct($client, $invoice) { $this->client = $client; + $this->invoice = $invoice; } - public function run($invoice) + public function run() { /* Return immediately if status is not draft */ - if ($invoice->status_id != Invoice::STATUS_DRAFT) { - return $invoice; + if ($this->invoice->status_id != Invoice::STATUS_DRAFT) { + return $this->invoice; } - $invoice->markInvitationsSent(); + $this->invoice->markInvitationsSent(); - $invoice->setReminder(); + $this->invoice->setReminder(); - event(new InvoiceWasMarkedSent($invoice, $invoice->company)); + event(new InvoiceWasMarkedSent($this->invoice, $this->invoice->company)); - $this->client->service()->updateBalance($invoice->balance)->save(); + $this->client->service()->updateBalance($this->invoice->balance)->save(); - $invoice->service()->setStatus(Invoice::STATUS_SENT)->applyNumber()->save(); + $this->invoice->service()->setStatus(Invoice::STATUS_SENT)->applyNumber()->save(); - UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance, $invoice->company); + UpdateCompanyLedgerWithInvoice::dispatchNow($this->invoice, $this->invoice->balance, $this->invoice->company); - return $invoice; + return $this->invoice; } } diff --git a/app/Services/Invoice/SendEmail.php b/app/Services/Invoice/SendEmail.php index 91ebdf81a0..5cf4331bfa 100644 --- a/app/Services/Invoice/SendEmail.php +++ b/app/Services/Invoice/SendEmail.php @@ -13,17 +13,23 @@ namespace App\Services\Invoice; use App\Helpers\Email\InvoiceEmail; use App\Jobs\Invoice\EmailInvoice; +use App\Models\ClientContact; use App\Models\Invoice; +use App\Services\AbstractService; use Illuminate\Support\Carbon; -class SendEmail +class SendEmail extends AbstractService { protected $invoice; - public function __construct(Invoice $invoice) + public function __construct(Invoice $invoice, $reminder_template = null, ClientContact $contact = null) { $this->invoice = $invoice; + + $this->reminder_template = $reminder_template; + + $this->contact = $contact; } @@ -32,15 +38,17 @@ class SendEmail * @param string $reminder_template The template name ie reminder1 * @return array */ - public function run($reminder_template = null, $contact = null): array + public function run() { - if (!$reminder_template) { - $reminder_template = $this->invoice->status_id == Invoice::STATUS_DRAFT || Carbon::parse($this->invoice->due_date) > now() ? 'invoice' : $this->invoice->calculateTemplate(); + + if (!$this->reminder_template) { + $this->reminder_template = $this->invoice->calculateTemplate(); } - $email_builder = (new InvoiceEmail())->build($this->invoice, $reminder_template, $contact); + $this->invoice->invitations->each(function ($invitation){ + + $email_builder = (new InvoiceEmail())->build($invitation, $this->reminder_template); - $this->invoice->invitations->each(function ($invitation) use ($email_builder) { if ($invitation->contact->send && $invitation->contact->email) { EmailInvoice::dispatch($email_builder, $invitation, $invitation->company); } diff --git a/app/Services/Invoice/UpdateBalance.php b/app/Services/Invoice/UpdateBalance.php index f80296e308..7b6d466bcf 100644 --- a/app/Services/Invoice/UpdateBalance.php +++ b/app/Services/Invoice/UpdateBalance.php @@ -12,31 +12,33 @@ namespace App\Services\Invoice; use App\Models\Invoice; +use App\Services\AbstractService; -class UpdateBalance +class UpdateBalance extends AbstractService { private $invoice; - public function __construct($invoice) + private $balance_adjustment; + + public function __construct($invoice, $balance_adjustment) { $this->invoice = $invoice; + $this->balance_adjustment = $balance_adjustment; } - public function run($balance_adjustment) + public function run() { if ($this->invoice->is_deleted) { return; } - $balance_adjustment = floatval($balance_adjustment); - - $this->invoice->balance += $balance_adjustment; + $this->invoice->balance += floatval($this->balance_adjustment); if ($this->invoice->balance == 0) { - $this->status_id = Invoice::STATUS_PAID; + $this->invoice->status_id = Invoice::STATUS_PAID; // $this->save(); // event(new InvoiceWasPaid($this, $this->company)); diff --git a/app/Services/Quote/SendEmail.php b/app/Services/Quote/SendEmail.php index 765d868cb2..a14a4cb5e6 100644 --- a/app/Services/Quote/SendEmail.php +++ b/app/Services/Quote/SendEmail.php @@ -27,10 +27,13 @@ class SendEmail $reminder_template = $this->quote->status_id == Quote::STATUS_DRAFT || Carbon::parse($this->quote->due_date) > now() ? 'invoice' : $this->quote->calculateTemplate(); } - $email_builder = (new QuoteEmail())->build($this->quote, $reminder_template, $contact); + $this->quote->invitations->each(function ($invitation){ + + if ($invitation->contact->send && $invitation->contact->email) + { + + $email_builder = (new QuoteEmail())->build($invitation, $reminder_template); - $this->quote->invitations->each(function ($invitation) use ($email_builder) { - if ($invitation->contact->send && $invitation->contact->email) { EmailQuote::dispatchNow($email_builder, $invitation); } }); diff --git a/app/Utils/Traits/MakesInvoiceHtml.php b/app/Utils/Traits/MakesInvoiceHtml.php index 83ee61b63a..dd0abc35af 100644 --- a/app/Utils/Traits/MakesInvoiceHtml.php +++ b/app/Utils/Traits/MakesInvoiceHtml.php @@ -34,10 +34,11 @@ trait MakesInvoiceHtml { //$variables = array_merge($invoice->makeLabels(), $invoice->makeValues()); //$design = str_replace(array_keys($variables), array_values($variables), $design); - if(!$contact) - $contact = $invoice->client->primary_contact()->first(); + $invoice->load('client'); - App::setLocale($contact->preferredLocale()); + $client = $invoice->client; + + App::setLocale($client->preferredLocale()); $labels = $invoice->makeLabels(); $values = $invoice->makeValues($contact); diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index e81ddcc59e..b735681fc4 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -576,8 +576,11 @@ trait MakesInvoiceValues * @param array $items The array of invoice items * @return array The formatted array of invoice items */ - private function transformLineItems(array $items) :array + private function transformLineItems($items) :array { + if(!is_array($items)) + return []; + foreach ($items as $item) { $item->cost = Number::formatMoney($item->cost, $this->client); $item->line_total = Number::formatMoney($item->line_total, $this->client); diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index b410ed1650..9f70d4108f 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -378,7 +378,7 @@ class CreateUsersTable extends Migration $table->string('password'); $table->string('token')->nullable(); $table->boolean('is_locked')->default(false); - $table->boolean('send')->default(true); + $table->boolean('send_email')->default(true); $table->string('contact_key')->nullable(); $table->rememberToken(); $table->timestamps(6); diff --git a/resources/views/email/admin/download_files.blade.php b/resources/views/email/admin/download_files.blade.php new file mode 100644 index 0000000000..fb872093d7 --- /dev/null +++ b/resources/views/email/admin/download_files.blade.php @@ -0,0 +1,20 @@ +@component('mail::layout') + +{{-- Header --}} +@slot('header') +@component('mail::header', ['url' => config('app.url')]) +Download +@endcomponent +@endslot + +{{-- Body --}} +{{ $file_path }} + +{{-- Footer --}} +@slot('footer') +@component('mail::footer') +© {{ date('Y') }} {{ config('ninja.app_name') }}. +@endcomponent +@endslot + +@endcomponent diff --git a/resources/views/email/template/plain.blade.php b/resources/views/email/template/plain.blade.php index 06b60a05f9..a5154f26a0 100644 --- a/resources/views/email/template/plain.blade.php +++ b/resources/views/email/template/plain.blade.php @@ -1,13 +1,12 @@ - + - -{!! $body !!} - - -{!! $footer !!} + {!! $body !!} + \ No newline at end of file diff --git a/resources/views/portal/default/auth/login.blade.php b/resources/views/portal/default/auth/login.blade.php index ffb5994860..569a1a4017 100644 --- a/resources/views/portal/default/auth/login.blade.php +++ b/resources/views/portal/default/auth/login.blade.php @@ -87,7 +87,7 @@

trans('texts.sign_up_now')

trans('texts.not_a_member_yet')

- trans('texts.login_create_an_account') + trans('texts.login_create_an_account')
diff --git a/tests/Feature/InvoiceEmailTest.php b/tests/Feature/InvoiceEmailTest.php index 64e26a1c17..78bcb27f60 100644 --- a/tests/Feature/InvoiceEmailTest.php +++ b/tests/Feature/InvoiceEmailTest.php @@ -41,7 +41,7 @@ class InvoiceEmailTest extends TestCase $this->makeTestData(); } - public function test_initial_email_sends() + public function test_initial_email_send_emails() { $this->invoice->date = now(); @@ -50,15 +50,14 @@ class InvoiceEmailTest extends TestCase $this->invoice->client_id = $this->client->id; $this->invoice->setRelation('client', $this->client); + $this->invoice->save(); - $invitations = InvoiceInvitation::whereInvoiceId($this->invoice->id)->get(); + $this->invoice->invitations->each(function ($invitation) { - $email_builder = (new InvoiceEmail())->build($this->invoice, null, null); + if ($invitation->contact->send_email && $invitation->contact->email) { - $invitations->each(function ($invitation) use ($email_builder) { - - if ($invitation->contact->send && $invitation->contact->email) { + $email_builder = (new InvoiceEmail())->build($invitation, null); EmailInvoice::dispatch($email_builder, $invitation, $invitation->company); diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index 65b99e8777..1f691f0b22 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -364,8 +364,18 @@ class PaymentTest extends TestCase $this->invoice = null; $client = ClientFactory::create($this->company->id, $this->user->id); + $client->setRelation('company', $this->company); $client->save(); + $client_contact = factory(\App\Models\ClientContact::class, 1)->create([ + 'user_id' => $this->user->id, + 'client_id' => $client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1 + ]); + + $client->setRelation('contacts', $client_contact); + $this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id $this->invoice->client_id = $client->id; @@ -379,6 +389,8 @@ class PaymentTest extends TestCase $this->invoice_calc->build(); $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->company->setRelation('company', $this->company); + $this->invoice->company->setRelation('client', $client); $this->invoice->save(); $this->invoice->service()->markSent()->save(); $this->invoice->is_deleted = false; diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 66044aaccf..a8fdaa1f2c 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -139,14 +139,14 @@ trait MockAccountData 'client_id' => $this->client->id, 'company_id' => $this->company->id, 'is_primary' => 1, - 'send' => true, + 'send_email' => true, ]); factory(\App\Models\ClientContact::class,1)->create([ 'user_id' => $this->user->id, 'client_id' => $this->client->id, 'company_id' => $this->company->id, - 'send' => true + 'send_email' => true ]); @@ -176,6 +176,9 @@ trait MockAccountData $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice->setRelation('client', $this->client); + $this->invoice->setRelation('company', $this->company); + $this->invoice->save(); $this->invoice->service()->markSent(); @@ -200,13 +203,13 @@ trait MockAccountData ->whereInvoiceId($this->invoice->id) ->first(); - if(!$invitation && $contact->send) { + if(!$invitation && $contact->send_email) { $ii = InvoiceInvitationFactory::create($this->invoice->company_id, $this->invoice->user_id); $ii->invoice_id = $this->invoice->id; $ii->client_contact_id = $contact->id; $ii->save(); } - else if($invitation && !$contact->send) { + else if($invitation && !$contact->send_email) { $invitation->delete(); }