1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-21 08:51:34 +02:00

Merge pull request #4211 from turbo124/v5-develop

Refactor PDF generation
This commit is contained in:
David Bomba 2020-10-27 07:53:14 +11:00 committed by GitHub
commit fe46bba383
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 448 additions and 151 deletions

View File

@ -242,6 +242,8 @@ class CreateSingleAccount extends Command
$settings = $client->settings;
$settings->currency_id = "1";
$settings->use_credits_payment = "always";
$client->settings = $settings;
$country = Country::all()->random();

View File

@ -18,7 +18,7 @@ use App\Factory\CompanyUserFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Helpers\Email\InvoiceEmail;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Invoice\CreateEntityPdf;
use App\Mail\TemplateEmail;
use App\Models\Account;
use App\Models\Client;
@ -149,7 +149,7 @@ class SendTestEmails extends Command
$invoice->setRelation('invitations', $ii);
$invoice->service()->markSent()->save();
CreateInvoicePdf::dispatch($invoice->invitations()->first());
CreateEntityPdf::dispatch($invoice->invitations()->first());
$cc_emails = [config('ninja.testvars.test_email')];
$bcc_emails = [config('ninja.testvars.test_email')];

View File

@ -253,7 +253,10 @@ class CompanySettings extends BaseSettings
public $client_portal_under_payment_minimum = 0;
public $client_portal_allow_over_payment = false;
public $use_credits_payment = 'off'; //always, option, off
public static $casts = [
'use_credits_payment' => 'string',
'recurring_invoice_number_pattern' => 'string',
'recurring_invoice_number_counter' => 'int',
'client_portal_under_payment_minimum'=> 'float',

View File

@ -60,12 +60,15 @@ class BaseController extends Controller
private $first_load = [
'account',
'user.company_user',
'token.company_user',
'company.activities',
'company.designs.company',
'company.task_statuses',
'company.expense_categories',
'company.documents',
'company.users.company_users',
'company.clients.contacts',
'company.users.company_user',
'company.clients.contacts.company',
'company.clients.gateway_tokens',
'company.clients.documents',
'company.company_gateways.gateway',
@ -90,11 +93,11 @@ class BaseController extends Controller
'company.quotes.invitations.contact',
'company.quotes.invitations.company',
'company.quotes.documents',
'company.tasks',
'company.tasks.documents',
'company.tax_rates',
'company.tokens_hashed',
'company.vendors.contacts',
'company.vendors.contacts.company',
'company.vendors.documents',
'company.webhooks',
];
@ -257,6 +260,12 @@ class BaseController extends Controller
'company.vendors'=> function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at)->with('contacts','documents' );
},
'company.expense_categories'=> function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at);
},
'company.task_statuses'=> function ($query) use ($updated_at) {
$query->where('updated_at', '>=', $updated_at);
},
]
);
@ -418,7 +427,8 @@ class BaseController extends Controller
public function flutterRoute()
{
if ((bool) $this->checkAppSetup() !== false && Schema::hasTable('accounts') && $account = Account::first()) {
// if ((bool) $this->checkAppSetup() !== false && Schema::hasTable('accounts') && $account = Account::first()) {
if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) {
if (config('ninja.require_https') && ! request()->isSecure()) {
return redirect()->secure(request()->getRequestUri());
}

View File

@ -175,7 +175,7 @@ class PaymentController extends Controller
$payment_method_id = $request->input('payment_method_id');
$invoice_totals = $payable_invoices->sum('amount');
$first_invoice = $invoices->first();
$credit_totals = $first_invoice->company->use_credits_payment == 'off' ? 0 : $first_invoice->client->service()->getCreditBalance();
$credit_totals = $first_invoice->client->getSetting('use_credits_payment') == 'off' ? 0 : $first_invoice->client->service()->getCreditBalance();
$starting_invoice_amount = $first_invoice->amount;
if($gateway)

View File

@ -28,7 +28,6 @@ use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Http\Requests\Invoice\ShowInvoiceRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Invoice\UpdateInvoiceRequest;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Invoice\EmailInvoice;
use App\Jobs\Invoice\StoreInvoice;
use App\Jobs\Invoice\ZipInvoices;

View File

@ -14,7 +14,6 @@ namespace App\Http\Controllers;
use App\Designs\Custom;
use App\Designs\Designer;
use App\Factory\InvoiceFactory;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Util\PreviewPdf;
use App\Models\Client;
use App\Models\ClientContact;

View File

@ -16,6 +16,7 @@ use App\Http\Requests\Setup\CheckDatabaseRequest;
use App\Http\Requests\Setup\CheckMailRequest;
use App\Http\Requests\Setup\StoreSetupRequest;
use App\Jobs\Account\CreateAccount;
use App\Jobs\Util\VersionCheck;
use App\Models\Account;
use App\Utils\SystemHealth;
use Illuminate\Http\Response;
@ -124,6 +125,8 @@ class SetupController extends Controller
CreateAccount::dispatchNow($request->all());
}
VersionCheck::dispatchNow();
return redirect('/');
} catch (\Exception $e) {
info($e->getMessage());

View File

@ -33,8 +33,6 @@ class ContactKeyLogin
*/
public function handle($request, Closure $next)
{
info($request->segment(3));
info($request->route('contact_key'));
if(Auth::guard('contact')->check())
Auth::guard('contact')->logout();

View File

@ -42,12 +42,8 @@ class StoreProjectRequest extends Request
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($this->all());
if (array_key_exists('client_id', $input) && is_string($input['client_id'])) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
$this->replace($input);
}

View File

@ -36,12 +36,8 @@ class UpdateProjectRequest extends Request
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($this->all());
if (array_key_exists('client_id', $input) && is_string($input['client_id'])) {
unset($input['client_id']);
}
$this->replace($input);
}
}

View File

@ -37,49 +37,17 @@ class StoreTaskRequest extends Request
public function rules()
{
$rules = [];
/* Ensure we have a client name, and that all emails are unique*/
//$rules['name'] = 'required|min:1';
//$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
// $rules['number'] = new UniqueTaskNumberRule($this->all());
return $rules;
return $this->globalRules($rules);
}
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->all();
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
}
$input = $this->decodePrimaryKeys($this->all());
if (array_key_exists('client_id', $input) && is_string($input['client_id'])) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (array_key_exists('project_id', $input) && is_string($input['project_id'])) {
$input['project_id'] = $this->decodePrimaryKey($input['project_id']);
}
if (array_key_exists('invoice_id', $input) && is_string($input['invoice_id'])) {
$input['invoice_id'] = $this->decodePrimaryKey($input['invoice_id']);
}
$this->replace($input);
$this->replace($input);
}
// public function messages()
// {
// // return [
// // 'unique' => ctrans('validation.unique', ['attribute' => 'email']),
// // //'required' => trans('validation.required', ['attribute' => 'email']),
// // 'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
// // ];
// }
}

View File

@ -43,7 +43,7 @@ class UpdateTaskRequest extends Request
$rules['number'] = 'unique:tasks,number,'.$this->id.',id,company_id,'.$this->taskss->company_id;
}
return $rules;
return $this->globalRules($rules);
}
// public function messages()
@ -58,28 +58,9 @@ class UpdateTaskRequest extends Request
protected function prepareForValidation()
{
$input = $this->all();
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
}
$input = $this->decodePrimaryKeys($this->all());
if (array_key_exists('client_id', $input) && is_string($input['client_id'])) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (array_key_exists('project_id', $input) && is_string($input['project_id'])) {
$input['project_id'] = $this->decodePrimaryKey($input['project_id']);
}
if (array_key_exists('invoice_id', $input) && is_string($input['invoice_id'])) {
$input['invoice_id'] = $this->decodePrimaryKey($input['invoice_id']);
}
$this->replace($input);
$this->replace($input);
}
}

View File

@ -0,0 +1,166 @@
<?php
/**
* Entity Ninja (https://entityninja.com).
*
* @link https://github.com/entityninja/entityninja source repository
*
* @copyright Copyright (c) 2020. Entity Ninja LLC (https://entityninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Entity;
use App\Designs\Custom;
use App\Designs\Designer;
use App\Designs\Modern;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Entity;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
use App\Utils\HtmlEngine;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
class CreateEntityPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash;
public $entity;
public $company;
public $contact;
private $disk;
public $invitation;
public $entity_string = '';
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($invitation)
{
$this->invitation = $invitation;
if($invitation instanceof InvoiceInvitation){
$this->entity = $invitation->invoice;
$this->entity_string = 'invoice';
}
elseif($invitation instanceof QuoteInvitation){
$this->entity = $invitation->quote;
$this->entity_string = 'quote';
}
elseif($invitation instanceof CreditInvitation){
$this->entity = $invitation->credit;
$this->entity_string = 'credit';
}
elseif($invitation instanceof RecurringInvoiceInvitation){
$this->entity = $invitation->recurring_invoice;
$this->entity_string = 'recurring_invoice';
}
$this->company = $invitation->company;
$this->contact = $invitation->contact;
$this->disk = $disk ?? config('filesystems.default');
}
public function handle()
{
if (config('ninja.phantomjs_key')) {
return (new Phantom)->generate($this->invitation);
}
App::setLocale($this->contact->preferredLocale());
$entity_design_id = '';
if($this->entity instanceof Invoice){
$path = $this->entity->client->invoice_filepath();
$entity_design_id = 'invoice_design_id';
}
elseif($this->entity instanceof Quote){
$path = $this->entity->client->quote_filepath();
$entity_design_id = 'quote_design_id';
}
elseif($this->entity instanceof Credit){
$path = $this->entity->client->credit_filepath();
$entity_design_id = 'credit_design_id';
}
$file_path = $path.$this->entity->number.'.pdf';
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id));
$design = Design::find($entity_design_id);
$html = new HtmlEngine(null, $this->invitation, $this->entity_string);
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true)
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'client' => $this->entity->client,
'entity' => $this->entity,
'pdf_variables' => (array) $this->entity->company->settings->pdf_variables,
'products' => $design->design->product,
]),
'variables' => $html->generateLabelsAndValues(),
'options' => [
'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
],
];
$maker = new PdfMakerService($state);
$maker
->design($template)
->build();
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0775);
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
return $file_path;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Jobs\Invoice;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -51,6 +52,6 @@ class InjectSignature implements ShouldQueue
$invitation->signature_base64 = $this->signature;
$invitation->save();
CreateInvoicePdf::dispatch($invitation);
CreateEntityPdf::dispatch($invitation);
}
}

View File

@ -34,7 +34,7 @@ class VersionCheck implements ShouldQueue
*/
public function handle()
{
$version_file = file_get_contents(config('ninja.version_url'));
$version_file = trim(file_get_contents(config('ninja.version_url')));
info("latest version = {$version_file}");

View File

@ -11,7 +11,7 @@
namespace App\Listeners\Invoice;
use App\Jobs\Invoice\CreateInvoicePdf as PdfCreator;
use App\Jobs\Entity\CreateEntityPdf;
use App\Libraries\MultiDB;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@ -38,7 +38,7 @@ class CreateInvoicePdf implements ShouldQueue
MultiDB::setDb($event->company->db);
$event->invoice->invitations->each(function ($invitation) {
PdfCreator::dispatch($invitation);
CreateEntityPdf::dispatch($invitation);
});
}
}

View File

@ -98,6 +98,8 @@ class Client extends BaseModel implements HasLocalePreference
];
protected $with = [
'gateway_tokens',
'documents'
//'currency',
// 'primary_contact',
// 'country',
@ -523,7 +525,7 @@ class Client extends BaseModel implements HasLocalePreference
}
}
if(($this->company->use_credits_payment == 'option' || $this->company->use_credits_payment == 'always') && $this->service()->getCreditBalance() > 0) {
if(($this->getSetting('use_credits_payment') == 'option' || $this->getSetting('use_credits_payment') == 'always') && $this->service()->getCreditBalance() > 0) {
$payment_urls[] = [
'label' => ctrans('texts.apply_credit'),
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,

View File

@ -71,7 +71,6 @@ class Company extends BaseModel
protected $fillable = [
'mark_expenses_invoiceable',
'mark_expenses_paid',
'use_credits_payment',
'enabled_item_tax_rates',
'fill_products',
'industry_id',
@ -98,6 +97,8 @@ class Company extends BaseModel
'google_analytics_key',
'client_can_register',
'enable_shop_api',
'invoice_task_timelog',
'auto_start_tasks',
];
protected $hidden = [
@ -165,14 +166,20 @@ class Company extends BaseModel
return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function expense_categories()
{
return $this->hasMany(ExpenseCategory::class)->withTrashed();
}
public function task_statuses()
{
return $this->hasMany(TaskStatus::class)->withTrashed();
}
public function clients()
{
return $this->hasMany(Client::class)->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/

View File

@ -14,7 +14,7 @@ namespace App\Models;
use App\Events\Credit\CreditWasUpdated;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Credit\CreateCreditPdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Filterable;
use App\Services\Credit\CreditService;
use App\Services\Ledger\LedgerService;
@ -244,10 +244,10 @@ class Credit extends BaseModel
if (! $invitation) {
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
CreateCreditPdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
CreateEntityPdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
} else {
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
CreateCreditPdf::dispatchNow($invitation->credit, $invitation->company, $invitation->contact);
CreateEntityPdf::dispatchNow($invitation->credit, $invitation->company, $invitation->contact);
}
return $storage_path;

View File

@ -12,7 +12,7 @@
namespace App\Models;
use App\Events\Credit\CreditWasUpdated;
use App\Jobs\Credit\CreateCreditPdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Traits\Inviteable;
@ -131,7 +131,7 @@ class CreditInvitation extends BaseModel
if (! Storage::exists($this->credit->client->credit_filepath().$this->credit->number.'.pdf')) {
event(new CreditWasUpdated($this, $this->company, Ninja::eventVars()));
CreateCreditPdf::dispatchNow($this);
CreateEntityPdf::dispatchNow($this);
}
return $storage_path;

View File

@ -18,7 +18,7 @@ use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Client\UpdateClientBalance;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Backup;
use App\Models\CompanyLedger;
use App\Models\Currency;
@ -29,8 +29,8 @@ use App\Services\Ledger\LedgerService;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\Archivable;
use App\Utils\Traits\Invoice\ActionsInvoice;
use App\Utils\Traits\InvoiceEmailBuilder;
use App\Utils\Traits\Invoice\ActionsInvoice;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesInvoiceValues;
use App\Utils\Traits\MakesReminders;
@ -395,7 +395,7 @@ class Invoice extends BaseModel
if (! Storage::exists($this->client->invoice_filepath().$this->number.'.pdf')) {
event(new InvoiceWasUpdated($this, $this->company, Ninja::eventVars()));
CreateInvoicePdf::dispatchNow($invitation);
CreateEntityPdf::dispatchNow($invitation);
}
return $storage_path;

View File

@ -12,7 +12,7 @@
namespace App\Models;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Traits\Inviteable;
@ -144,7 +144,7 @@ class InvoiceInvitation extends BaseModel
if (! Storage::exists($this->invoice->client->invoice_filepath().$this->invoice->number.'.pdf')) {
event(new InvoiceWasUpdated($this->invoice, $this->company, Ninja::eventVars()));
CreateInvoicePdf::dispatchNow($this);
CreateEntityPdf::dispatchNow($this);
}
return $storage_path;

View File

@ -14,8 +14,7 @@ namespace App\Models;
use App\Events\Quote\QuoteWasUpdated;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Quote\CreateQuotePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Filterable;
use App\Services\Quote\QuoteService;
use App\Utils\Ninja;
@ -206,7 +205,7 @@ class Quote extends BaseModel
event(new QuoteWasUpdated($this, $this->company, Ninja::eventVars()));
CreateQuotePdf::dispatchNow($invitation);
CreateEntityPdf::dispatchNow($invitation);
return $storage_path;
}

View File

@ -12,7 +12,7 @@
namespace App\Models;
use App\Events\Quote\QuoteWasUpdated;
use App\Jobs\Quote\CreateQuotePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Quote;
use App\Utils\Ninja;
use App\Utils\Traits\Inviteable;
@ -135,7 +135,7 @@ class QuoteInvitation extends BaseModel
if (! Storage::exists($this->quote->client->quote_filepath().$this->quote->number.'.pdf')) {
event(new QuoteWasUpdated($this->quote, $this->company, Ninja::eventVars()));
CreateQuotePdf::dispatchNow($this);
CreateEntityPdf::dispatchNow($this);
}
return $storage_path;

View File

@ -33,6 +33,8 @@ class Task extends BaseModel
'description',
'is_running',
'time_log',
'status_id',
'status_sort_order',
];
protected $touches = [];

View File

@ -11,8 +11,7 @@
namespace App\Services\Credit;
use App\Jobs\Credit\CreateCreditPdf;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Services\AbstractService;
@ -45,7 +44,7 @@ class GetCreditPdf extends AbstractService
$file = Storage::disk($disk)->exists($file_path);
if (! $file) {
$file_path = CreateCreditPdf::dispatchNow($this->credit, $this->credit->company, $this->contact);
$file_path = CreateEntityPdf::dispatchNow($this->credit, $this->credit->company, $this->contact);
}
return Storage::disk($disk)->path($file_path);

View File

@ -19,6 +19,7 @@ use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Services\AbstractService;
use App\Services\Client\ClientService;
use App\Services\Payment\PaymentService;
@ -56,7 +57,7 @@ class AutoBillInvoice extends AbstractService
//if the credits cover the payments, we stop here, build the payment with credits and exit early
if($this->invoice->company->use_credits_payment != 'off')
if($this->client->getSetting('use_credits_payment') != 'off')
$this->applyCreditPayment();
info("partial = {$this->invoice->partial}");
@ -116,6 +117,7 @@ class AutoBillInvoice extends AbstractService
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->date = now();
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->type_id = PaymentType::CREDIT;
$payment->service()->applyNumber()->save();
$payment->invoices()->attach($this->invoice->id, ['amount' => $amount]);

View File

@ -11,7 +11,7 @@
namespace App\Services\Invoice;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Services\AbstractService;
@ -43,7 +43,7 @@ class GetInvoicePdf extends AbstractService
$file = Storage::disk($disk)->exists($file_path);
if (! $file) {
$file_path = CreateInvoicePdf::dispatchNow($invitation);
$file_path = CreateEntityPdf::dispatchNow($invitation);
}
return Storage::disk($disk)->path($file_path);

View File

@ -11,7 +11,7 @@
namespace App\Services\Invoice;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Util\UnlinkFile;
use App\Models\CompanyGateway;
use App\Models\Invoice;
@ -295,7 +295,7 @@ class InvoiceService
public function touchPdf()
{
$this->invoice->invitations->each(function ($invitation){
CreateInvoicePdf::dispatch($invitation);
CreateEntityPdf::dispatch($invitation);
});
return $this;

View File

@ -11,7 +11,7 @@
namespace App\Services\Quote;
use App\Jobs\Quote\CreateQuotePdf;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\ClientContact;
use App\Models\Quote;
use App\Services\AbstractService;
@ -43,7 +43,7 @@ class GetQuotePdf extends AbstractService
$file = Storage::disk($disk)->exists($file_path);
if (! $file) {
$file_path = CreateQuotePdf::dispatchNow($invitation);
$file_path = CreateEntityPdf::dispatchNow($invitation);
}
return Storage::disk($disk)->path($file_path);

View File

@ -23,6 +23,7 @@ use App\Models\Credit;
use App\Models\Design;
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\GroupSetting;
use App\Models\Payment;
use App\Models\PaymentTerm;
@ -32,6 +33,7 @@ use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\SystemLog;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Webhook;
@ -40,9 +42,11 @@ use App\Transformers\CompanyTokenHashedTransformer;
use App\Transformers\CompanyTokenTransformer;
use App\Transformers\CreditTransformer;
use App\Transformers\DocumentTransformer;
use App\Transformers\ExpenseCategoryTransformer;
use App\Transformers\PaymentTermTransformer;
use App\Transformers\RecurringInvoiceTransformer;
use App\Transformers\SystemLogTransformer;
use App\Transformers\TaskStatusTransformer;
use App\Transformers\TaskTransformer;
use App\Transformers\WebhookTransformer;
use App\Utils\Traits\MakesHash;
@ -95,6 +99,8 @@ class CompanyTransformer extends EntityTransformer
'tokens',
'tokens_hashed',
'system_logs',
'expense_categories',
'task_statuses',
];
/**
@ -147,10 +153,24 @@ class CompanyTransformer extends EntityTransformer
'invoice_expense_documents' => (bool) $company->invoice_expense_documents,
'invoice_task_timelog' => (bool) $company->invoice_task_timelog,
'auto_start_tasks' => (bool) $company->auto_start_tasks,
'use_credits_payment' => (string) $company->use_credits_payment,
'invoice_task_documents' => (bool) $company->invoice_task_documents,
];
}
public function includeExpenseCategories(Company $company)
{
$transformer = new ExpenseCategoryTransformer($this->serializer);
return $this->includeCollection($company->expense_categories, $transformer, ExpenseCategory::class);
}
public function includeTaskStatuses(Company $company)
{
$transformer = new TaskStatusTransformer($this->serializer);
return $this->includeCollection($company->task_statuses, $transformer, TaskStatus::class);
}
public function includeDocuments(Company $company)
{
$transformer = new DocumentTransformer($this->serializer);

View File

@ -59,7 +59,7 @@ class ExpenseTransformer extends EntityTransformer
'bank_id' => (string) $expense->bank_id ?: '',
'invoice_currency_id' => (string) $expense->invoice_currency_id ?: '',
'expense_currency_id' => (string) $expense->expense_currency_id ?: '',
'category_id' => (string) $expense->category_id ?: '',
'category_id' => $this->encodePrimaryKey($expense->category_id),
'payment_type_id' => (string) $expense->payment_type_id ?: '',
'recurring_expense_id' => (string) $expense->recurring_expense_id ?: '',
'is_deleted' => (bool) $expense->is_deleted,

View File

@ -23,6 +23,7 @@ class TaskStatusTransformer extends EntityTransformer
return [
'id' => (string) $this->encodePrimaryKey($task_status->id),
'name' => (string) $task_status->name,
'sort_order' => (int) $task_status->sort_order,
'is_deleted' => (bool) $task_status->is_deleted,
'created_at' => (int) $task_status->created_at,
'updated_at' => (int) $task_status->updated_at,

View File

@ -50,6 +50,7 @@ class TaskTransformer extends EntityTransformer
'start_time' => (int) $task->start_time,
'description' => $task->description ?: '',
'duration' => 0,
'rate' => (float) $task->rate ?: 0,
'created_at' => (int) $task->created_at,
'updated_at' => (int) $task->updated_at,
'archived_at' => (int) $task->deleted_at,
@ -63,8 +64,8 @@ class TaskTransformer extends EntityTransformer
'custom_value2' => $task->custom_value2 ?: '',
'custom_value3' => $task->custom_value3 ?: '',
'custom_value4' => $task->custom_value4 ?: '',
'task_status_id' => $this->encodePrimaryKey($task->task_status_id),
'task_status_sort_order' => (int) $task->task_status_sort_order,
'status_id' => $this->encodePrimaryKey($task->status_id),
'status_sort_order' => (int) $task->status_sort_order,
];
}
}

View File

@ -133,7 +133,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '&nbsp;', 'label' => ctrans('texts.quote_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
}
if ($this->entity_string == 'credit') {
@ -142,6 +142,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '&nbsp;', 'label' => ctrans('texts.credit_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
}
@ -210,10 +211,10 @@ class HtmlEngine
// $data['$details'] = ;
$data['$invoice_no'] = &$data['$number'];
$data['$invoice.invoice_no'] = &$data['$number'];
$data['$client1'] = ['value' => $this->client->custom_value1 ?: '&nbsp;', 'label' => $this->makeCustomField('client1')];
$data['$client2'] = ['value' => $this->client->custom_value2 ?: '&nbsp;', 'label' => $this->makeCustomField('client2')];
$data['$client3'] = ['value' => $this->client->custom_value3 ?: '&nbsp;', 'label' => $this->makeCustomField('client3')];
$data['$client4'] = ['value' => $this->client->custom_value4 ?: '&nbsp;', 'label' => $this->makeCustomField('client4')];
$data['$client1'] = ['value' => $this->formatCustomFieldValue('client1', $this->client->custom_value1) ?: '&nbsp;', 'label' => $this->makeCustomField('client1')];
$data['$client2'] = ['value' => $this->formatCustomFieldValue('client2', $this->client->custom_value2) ?: '&nbsp;', 'label' => $this->makeCustomField('client2')];
$data['$client3'] = ['value' => $this->formatCustomFieldValue('client3', $this->client->custom_value3) ?: '&nbsp;', 'label' => $this->makeCustomField('client3')];
$data['$client4'] = ['value' => $this->formatCustomFieldValue('client4', $this->client->custom_value4) ?: '&nbsp;', 'label' => $this->makeCustomField('client4')];
$data['$address1'] = ['value' => $this->client->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$address2'] = ['value' => $this->client->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$id_number'] = ['value' => $this->client->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')];
@ -276,10 +277,10 @@ class HtmlEngine
$data['$company.logo'] = ['value' => $logo ?: '&nbsp;', 'label' => ctrans('texts.logo')];
$data['$company_logo'] = &$data['$company.logo'];
$data['$company1'] = ['value' => $this->settings->custom_value1 ?: '&nbsp;', 'label' => $this->makeCustomField('company1')];
$data['$company2'] = ['value' => $this->settings->custom_value2 ?: '&nbsp;', 'label' => $this->makeCustomField('company2')];
$data['$company3'] = ['value' => $this->settings->custom_value3 ?: '&nbsp;', 'label' => $this->makeCustomField('company3')];
$data['$company4'] = ['value' => $this->settings->custom_value4 ?: '&nbsp;', 'label' => $this->makeCustomField('company4')];
$data['$company1'] = ['value' => $this->formatCustomFieldValue('company1', $this->settings->custom_value1) ?: '&nbsp;', 'label' => $this->makeCustomField('company1')];
$data['$company2'] = ['value' => $this->formatCustomFieldValue('company2', $this->settings->custom_value2) ?: '&nbsp;', 'label' => $this->makeCustomField('company2')];
$data['$company3'] = ['value' => $this->formatCustomFieldValue('company3', $this->settings->custom_value3) ?: '&nbsp;', 'label' => $this->makeCustomField('company3')];
$data['$company4'] = ['value' => $this->formatCustomFieldValue('company4', $this->settings->custom_value4) ?: '&nbsp;', 'label' => $this->makeCustomField('company4')];
$data['$custom_surcharge1'] = ['value' => $this->entity->custom_surcharge1 ?: '&nbsp;', 'label' => $this->makeCustomField('custom_surcharge1')];
$data['$custom_surcharge2'] = ['value' => $this->entity->custom_surcharge2 ?: '&nbsp;', 'label' => $this->makeCustomField('custom_surcharge2')];

View File

@ -109,7 +109,7 @@ trait GeneratesCounter
$credit_number = $this->checkEntityNumber(Credit::class, $client, $counter, $padding, $pattern);
$this->incrementCounter($client->company, 'credit_number_counter');
$this->incrementCounter($counter_entity, 'credit_number_counter');
return $credit_number;
}

View File

@ -0,0 +1,37 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Database\Factories;
use App\Models\CreditInvitation;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class CreditInvitationFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = CreditInvitation::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'key' => Str::random(40),
];
}
}

View File

@ -15,6 +15,12 @@ class CompanyTableFields extends Migration
{
Schema::table('companies', function(Blueprint $table){
$table->boolean('invoice_task_timelog')->default(true);
$table->boolean('invoice_task_documents')->default(false);
$table->dropColumn('use_credits_payment');
});
Schema::table('task_statuses', function(Blueprint $table){
$table->unsignedInteger('status_sort_order')->default(0);
});
}

View File

@ -35,6 +35,7 @@ class GatewayTypesSeeder extends Seeder
['id' => 11, 'alias' => 'apple_pay', 'name' => 'Apple Pay'],
['id' => 12, 'alias' => 'custom2', 'name' => 'Custom'],
['id' => 13, 'alias' => 'custom3', 'name' => 'Custom'],
['id' => 14, 'alias' => 'credit', 'name' => 'Credit'],
];
foreach ($gateway_types as $gateway_type) {

View File

@ -31,6 +31,7 @@ class PaymentTypesSeeder extends Seeder
const GATEWAY_TYPE_APPLE_PAY = 11;
const GATEWAY_TYPE_CUSTOM2 = 12;
const GATEWAY_TYPE_CUSTOM3 = 13;
const GATEWAY_TYPE_CREDIT = 14;
public function run()
{
@ -69,6 +70,7 @@ class PaymentTypesSeeder extends Seeder
['name' => 'SEPA', 'gateway_type_id' => self::GATEWAY_TYPE_SEPA],
['name' => 'GoCardless', 'gateway_type_id' => self::GATEWAY_TYPE_GOCARDLESS],
['name' => 'Crypto', 'gateway_type_id' => self::GATEWAY_TYPE_CRYPTO],
['name' => 'Credit', 'gateway_type_id' => self::GATEWAY_TYPE_CREDIT],
];
$x = 1;

View File

@ -3285,5 +3285,7 @@ return [
'credit_subject' => 'New credit :number from :account',
'credit_message' => 'To view your credit for :amount, click the link below.',
'payment_type_Crypto' => 'Cryptocurrency',
'payment_type_Credit' => 'Credit',
];

View File

@ -0,0 +1,63 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature;
use App\Jobs\Entity\CreateEntityPdf;
use Faker\Factory;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Storage;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
*/
class PdfCreatorTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->makeTestData();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testCreditPdfCreated()
{
$credit_path = CreateEntityPdf::dispatchNow($this->credit->invitations->first());
$this->assertTrue(Storage::exists($this->client->credit_filepath().$this->credit->number.'.pdf'));
}
public function testInvoicePdfCreated()
{
$invoice_path = CreateEntityPdf::dispatchNow($this->invoice->invitations->first());
$this->assertTrue(Storage::exists($this->client->invoice_filepath().$this->invoice->number.'.pdf'));
}
public function testQuotePdfCreated()
{
$quote_path = CreateEntityPdf::dispatchNow($this->quote->invitations->first());
$this->assertTrue(Storage::exists($this->client->quote_filepath().$this->quote->number.'.pdf'));
}
}

View File

@ -31,6 +31,7 @@ use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyToken;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\GroupSetting;
@ -135,7 +136,8 @@ trait MockAccountData
$settings->country_id = '840';
$settings->vat_number = 'vat number';
$settings->id_number = 'id number';
$settings->use_credits_payment = 'always';
$this->company->settings = $settings;
$this->company->save();
@ -350,19 +352,45 @@ trait MockAccountData
$this->credit->tax_rate3 = 0;
$this->credit->uses_inclusive_taxes = false;
$this->credit->save();
$this->credit->service()->createInvitations()->markSent();
$this->credit_calc = new InvoiceSum($this->credit);
$this->credit_calc->build();
$this->credit = $this->credit_calc->getCredit();
$this->credit->service()->markSent();
$this->client->service()->adjustCreditBalance($this->credit->balance)->save();
$this->credit->ledger()->updateCreditBalance($this->credit->balance)->save();
$this->credit->number = $this->getNextCreditNumber($this->client);
CreditInvitation::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'client_contact_id' => $contact->id,
'credit_id' => $this->credit->id,
]);
CreditInvitation::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'client_contact_id' => $contact2->id,
'credit_id' => $this->credit->id,
]);
$invitations = CreditInvitation::whereCompanyId($this->credit->company_id)
->whereCreditId($this->credit->id);
$this->credit->setRelation('invitations', $invitations);
$this->credit->service()->markSent();
$this->credit->setRelation('client', $this->client);
$this->credit->setRelation('company', $this->company);
$this->credit->save();
$contacts = $this->invoice->client->contacts;
$contacts->each(function ($contact) {

View File

@ -33,8 +33,6 @@ class AutoBillInvoiceTest extends TestCase
public function testAutoBillFunctionality()
{
$this->company->use_credits_payment = 'always';
$this->company->save();
$this->assertEquals($this->client->balance, 10);
$this->assertEquals($this->client->paid_to_date, 0);
@ -52,23 +50,27 @@ class AutoBillInvoiceTest extends TestCase
}
public function testAutoBillSetOffFunctionality()
{
$this->company->use_credits_payment = 'off';
$this->company->save();
// public function testAutoBillSetOffFunctionality()
// {
// $settings = $this->company->settings;
// $settings->use_credits_payment = 'off';
$this->assertEquals($this->client->balance, 10);
$this->assertEquals($this->client->paid_to_date, 0);
$this->assertEquals($this->client->credit_balance, 10);
// $this->company->settings = $settings;
// $this->company->save();
$this->invoice->service()->markSent()->autoBill()->save();
// $this->assertEquals($this->client->balance, 10);
// $this->assertEquals($this->client->paid_to_date, 0);
// $this->assertEquals($this->client->credit_balance, 10);
$this->assertNotNull($this->invoice->payments());
$this->assertEquals(0, $this->invoice->payments()->sum('payments.amount'));
// $this->invoice->service()->markSent()->autoBill()->save();
$this->assertEquals($this->client->balance, 10);
$this->assertEquals($this->client->paid_to_date, 0);
$this->assertEquals($this->client->credit_balance, 10);
// $this->assertNotNull($this->invoice->payments());
// $this->assertEquals(0, $this->invoice->payments()->sum('payments.amount'));
// $this->assertEquals($this->client->balance, 10);
// $this->assertEquals($this->client->paid_to_date, 0);
// $this->assertEquals($this->client->credit_balance, 10);
}
// }
}