Merge pull request #4267 from turbo124/v5-develop

Company Settings Implmentation
This commit is contained in:
David Bomba 2020-11-05 07:49:10 +11:00 committed by GitHub
commit 100207f4cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 794 additions and 244 deletions

View File

@ -0,0 +1,52 @@
<?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 App\Console\Commands;
use App\Jobs\Ninja\SendReminders;
use Illuminate\Console\Command;
class SendRemindersCron extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:send-reminders';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Force send all reminders';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
SendReminders::dispatchNow();
}
}

View File

@ -24,177 +24,176 @@ class CompanySettings extends BaseSettings
/*Group settings based on functionality*/
/*Invoice*/
public $auto_archive_invoice = false;
public $auto_archive_invoice = false; // @implemented
public $lock_invoices = 'off'; //off,when_sent,when_paid
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
public $enable_client_portal_tasks = false;
public $enable_client_portal_password = false;
public $enable_client_portal = true; //implemented
public $enable_client_portal_dashboard = true; //implemented
public $signature_on_pdf = false;
public $document_email_attachment = false;
//public $send_portal_password = false;
public $enable_client_portal_tasks = false; //@ben to implement
public $enable_client_portal_password = false; //@implemented
public $enable_client_portal = true; //@implemented
public $enable_client_portal_dashboard = true; // @TODO There currently is no dashboard so this is pending
public $signature_on_pdf = false; //@implemented
public $document_email_attachment = false; //@TODO I assume this is 3rd party attachments on the entity to be included
public $portal_design_id = '1';
public $portal_design_id = '1'; //?@deprecated
public $timezone_id = '';
public $date_format_id = '';
public $military_time = false;
public $timezone_id = ''; //@implemented
public $date_format_id = ''; //@implemented
public $military_time = false; // @TODOImplemented in Tasks only?
public $language_id = '';
public $show_currency_code = false;
public $language_id = ''; //@implemented
public $show_currency_code = false; //@implemented
public $company_gateway_ids = '';
public $company_gateway_ids = ''; //@implemented
public $currency_id = '1';
public $currency_id = '1'; //@implemented
public $custom_value1 = '';
public $custom_value2 = '';
public $custom_value3 = '';
public $custom_value4 = '';
public $custom_value1 = ''; //@implemented
public $custom_value2 = ''; //@implemented
public $custom_value3 = ''; //@implemented
public $custom_value4 = ''; //@implemented
public $default_task_rate = 0;
public $default_task_rate = 0; // @TODO Where do we inject this?
public $payment_terms = '';
public $send_reminders = false;
public $payment_terms = ''; //@implemented
public $send_reminders = false; //@TODO
public $custom_message_dashboard = '';
public $custom_message_unpaid_invoice = '';
public $custom_message_paid_invoice = '';
public $custom_message_unapproved_quote = '';
public $auto_archive_quote = false;
public $auto_convert_quote = true;
public $auto_email_invoice = true;
public $custom_message_dashboard = ''; // @TODO There currently is no dashboard so this is pending
public $custom_message_unpaid_invoice = ''; //@ben to implement
public $custom_message_paid_invoice = ''; //@ben to implement
public $custom_message_unapproved_quote = ''; //@ben to implement
public $auto_archive_quote = false; //@implemented
public $auto_convert_quote = true; //@implemented
public $auto_email_invoice = true; //@only used for Recurring Invoices, if set to false, we never send?
public $inclusive_taxes = false;
public $quote_footer = '';
public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented
public $translations;
public $translations; //@TODO not used anywhere
public $counter_number_applied = 'when_saved'; // when_saved , when_sent
public $quote_number_applied = 'when_saved'; // when_saved , when_sent
public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
/* Counters */
public $invoice_number_pattern = '';
public $invoice_number_counter = 1;
public $invoice_number_pattern = ''; //@implemented
public $invoice_number_counter = 1; //@implemented
public $recurring_invoice_number_pattern = '';
public $recurring_invoice_number_counter = 1;
public $recurring_invoice_number_pattern = ''; //@implemented
public $recurring_invoice_number_counter = 1; //@implemented
public $quote_number_pattern = '';
public $quote_number_counter = 1;
public $quote_number_pattern = ''; //@implemented
public $quote_number_counter = 1; //@implemented
public $client_number_pattern = '';
public $client_number_counter = 1;
public $client_number_pattern = ''; //@implemented
public $client_number_counter = 1; //@implemented
public $credit_number_pattern = '';
public $credit_number_counter = 1;
public $credit_number_pattern = ''; //@implemented
public $credit_number_counter = 1; //@implemented
public $task_number_pattern = '';
public $task_number_counter = 1;
public $task_number_pattern = ''; //@implemented
public $task_number_counter = 1; //@implemented
public $expense_number_pattern = '';
public $expense_number_counter = 1;
public $expense_number_pattern = ''; //@implemented
public $expense_number_counter = 1; //@implemented
public $vendor_number_pattern = '';
public $vendor_number_counter = 1;
public $vendor_number_pattern = ''; //@implemented
public $vendor_number_counter = 1; //@implemented
public $ticket_number_pattern = '';
public $ticket_number_counter = 1;
public $ticket_number_pattern = ''; //@implemented
public $ticket_number_counter = 1; //@implemented
public $payment_number_pattern = '';
public $payment_number_counter = 1;
public $payment_number_pattern = ''; //@implemented
public $payment_number_counter = 1; //@implemented
public $project_number_pattern = '';
public $project_number_counter = 1;
public $project_number_pattern = ''; //@implemented
public $project_number_counter = 1; //@implemented
public $shared_invoice_quote_counter = false;
public $recurring_number_prefix = 'R';
public $reset_counter_frequency_id = '0';
public $reset_counter_date = '';
public $counter_padding = 4;
public $shared_invoice_quote_counter = false; //@implemented
public $recurring_number_prefix = 'R'; //@implemented
public $reset_counter_frequency_id = '0'; //@implemented
public $reset_counter_date = ''; //@implemented
public $counter_padding = 4; //@implemented
public $auto_bill = 'off'; //off,always,optin,optout
public $auto_bill_date = 'on_due_date'; // on_due_date , on_send_date
public $auto_bill = 'off'; //off,always,optin,optout //@implemented
public $auto_bill_date = 'on_due_date'; // on_due_date , on_send_date //@implemented
public $design = 'views/pdf/design1.blade.php';
//public $design = 'views/pdf/design1.blade.php'; //@deprecated - never used
public $invoice_terms = '';
public $quote_terms = '';
public $invoice_taxes = 0;
public $invoice_terms = ''; //@implemented
public $quote_terms = ''; //@implemented
public $invoice_taxes = 0; // ? used in AP only?
// public $enabled_item_tax_rates = 0;
public $invoice_design_id = 'VolejRejNm';
public $quote_design_id = 'VolejRejNm';
public $credit_design_id = 'VolejRejNm';
public $invoice_footer = '';
public $credit_footer = '';
public $credit_terms = '';
public $invoice_labels = '';
public $tax_name1 = '';
public $tax_rate1 = 0;
public $tax_name2 = '';
public $tax_rate2 = 0;
public $tax_name3 = '';
public $tax_rate3 = 0;
public $payment_type_id = '0';
public $invoice_fields = '';
public $invoice_design_id = 'VolejRejNm'; //@implemented
public $quote_design_id = 'VolejRejNm'; //@implemented
public $credit_design_id = 'VolejRejNm'; //@implemented
public $invoice_footer = ''; //@implemented
public $credit_footer = ''; //@implemented
public $credit_terms = ''; //@implemented
public $invoice_labels = ''; //@TODO used in AP only?
public $tax_name1 = ''; //@TODO where do we use this?
public $tax_rate1 = 0; //@TODO where do we use this?
public $tax_name2 = ''; //@TODO where do we use this?
public $tax_rate2 = 0; //@TODO where do we use this?
public $tax_name3 = ''; //@TODO where do we use this?
public $tax_rate3 = 0; //@TODO where do we use this?
public $payment_type_id = '0'; //@TODO where do we use this?
public $invoice_fields = ''; //@TODO is this redundant, we store this in the custom_fields on the company?
public $show_accept_invoice_terms = false;
public $show_accept_quote_terms = false;
public $require_invoice_signature = false;
public $require_quote_signature = false;
public $show_accept_invoice_terms = false; //@TODO ben to confirm
public $show_accept_quote_terms = false; //@TODO ben to confirm
public $require_invoice_signature = false; //@TODO ben to confirm
public $require_quote_signature = false; //@TODO ben to confirm
//email settings
public $email_sending_method = 'default'; //enum 'default','gmail'
public $gmail_sending_user_id = '0';
public $email_sending_method = 'default'; //enum 'default','gmail' //@implemented
public $gmail_sending_user_id = '0'; //@implemented
public $reply_to_email = '';
public $bcc_email = '';
public $pdf_email_attachment = false;
public $ubl_email_attachment = false;
public $reply_to_email = ''; //@TODO
public $bcc_email = ''; //@TODO
public $pdf_email_attachment = false; //@implemented
public $ubl_email_attachment = false; //@implemented
public $email_style = 'light'; //plain, light, dark, custom
public $email_style_custom = ''; //the template itself
public $email_subject_invoice = '';
public $email_subject_quote = '';
public $email_subject_credit = '';
public $email_subject_payment = '';
public $email_subject_payment_partial = '';
public $email_subject_statement = '';
public $email_template_invoice = '';
public $email_template_credit = '';
public $email_template_quote = '';
public $email_template_payment = '';
public $email_template_payment_partial = '';
public $email_template_statement = '';
public $email_subject_reminder1 = '';
public $email_subject_reminder2 = '';
public $email_subject_reminder3 = '';
public $email_subject_reminder_endless = '';
public $email_template_reminder1 = '';
public $email_template_reminder2 = '';
public $email_template_reminder3 = '';
public $email_template_reminder_endless = '';
public $email_signature = '';
public $enable_email_markup = true;
public $email_style = 'light'; //plain, light, dark, custom //@implemented
public $email_style_custom = ''; //the template itself //@implemented
public $email_subject_invoice = ''; //@implemented
public $email_subject_quote = ''; //@implemented
public $email_subject_credit = ''; //@implemented
public $email_subject_payment = ''; //@implemented
public $email_subject_payment_partial = ''; //@implemented
public $email_subject_statement = ''; //@implemented
public $email_template_invoice = ''; //@implemented
public $email_template_credit = ''; //@implemented
public $email_template_quote = ''; //@implemented
public $email_template_payment = ''; //@implemented
public $email_template_payment_partial = ''; //@implemented
public $email_template_statement = ''; //@implemented
public $email_subject_reminder1 = ''; //@implemented
public $email_subject_reminder2 = ''; //@implemented
public $email_subject_reminder3 = ''; //@implemented
public $email_subject_reminder_endless = ''; //@implemented
public $email_template_reminder1 = ''; //@implemented
public $email_template_reminder2 = ''; //@implemented
public $email_template_reminder3 = ''; //@implemented
public $email_template_reminder_endless = ''; //@implemented
public $email_signature = ''; //@implemented
public $enable_email_markup = true; //@TODO
public $email_subject_custom1 = '';
public $email_subject_custom2 = '';
public $email_subject_custom3 = '';
public $email_subject_custom1 = ''; //@TODO
public $email_subject_custom2 = ''; //@TODO
public $email_subject_custom3 = ''; //@TODO
public $email_template_custom1 = '';
public $email_template_custom2 = '';
public $email_template_custom3 = '';
public $email_template_custom1 = ''; //@TODO
public $email_template_custom2 = ''; //@TODO
public $email_template_custom3 = ''; //@TODO
public $enable_reminder1 = false;
public $enable_reminder2 = false;
public $enable_reminder3 = false;
public $enable_reminder_endless = false;
public $enable_reminder1 = false; //@partially implmemented
public $enable_reminder2 = false; //@partially implmemented
public $enable_reminder3 = false; //@partially implmemented
public $enable_reminder_endless = false; //@partially implmemented
public $num_days_reminder1 = 0;
public $num_days_reminder2 = 0;
public $num_days_reminder3 = 0;
public $num_days_reminder1 = 0;//@partially implmemented
public $num_days_reminder2 = 0;//@partially implmemented
public $num_days_reminder3 = 0;//@partially implmemented
public $schedule_reminder1 = ''; // (enum: after_invoice_date, before_due_date, after_due_date)
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date)
@ -202,32 +201,36 @@ class CompanySettings extends BaseSettings
public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders
public $late_fee_amount1 = 0;
public $late_fee_amount2 = 0;
public $late_fee_amount3 = 0;
public $late_fee_amount1 = 0; //@TODO
public $late_fee_amount2 = 0; //@TODO
public $late_fee_amount3 = 0; //@TODO
public $endless_reminder_frequency_id = '0';
public $late_fee_endless_amount = 0;
public $late_fee_endless_percent = 0;
public $late_fee_percent1 = 0; //@TODO
public $late_fee_percent2 = 0; //@TODO
public $late_fee_percent3 = 0; //@TODO
public $endless_reminder_frequency_id = '0'; //@implemented
public $late_fee_endless_amount = 0; //@TODO
public $late_fee_endless_percent = 0; //@TODO
public $client_online_payment_notification = true; //@todo implement in notifications
public $client_manual_payment_notification = true; //@todo implement in notifications
/* Company Meta data that we can use to build sub companies*/
public $name = '';
public $company_logo = '';
public $website = '';
public $address1 = '';
public $address2 = '';
public $city = '';
public $state = '';
public $postal_code = '';
public $phone = '';
public $email = '';
public $country_id;
public $vat_number = '';
public $id_number = '';
public $name = ''; //@implemented
public $company_logo = ''; //@implemented
public $website = ''; //@implemented
public $address1 = ''; //@implemented
public $address2 = ''; //@implemented
public $city = ''; //@implemented
public $state = ''; //@implemented
public $postal_code = ''; //@implemented
public $phone = ''; //@implemented
public $email = ''; //@implemented
public $country_id; //@implemented
public $vat_number = ''; //@implemented
public $id_number = ''; //@implemented
public $page_size = 'A4'; //Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6
public $font_size = 9;
@ -236,26 +239,26 @@ class CompanySettings extends BaseSettings
public $primary_color = '#4caf50';
public $secondary_color = '#2196f3';
public $hide_paid_to_date = false;
public $embed_documents = false;
public $all_pages_header = false;
public $all_pages_footer = false;
public $pdf_variables = '';
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO
public $all_pages_header = false; //@implemented
public $all_pages_footer = false; //@implemented
public $pdf_variables = ''; //@implemented
public $portal_custom_head = '';
public $portal_custom_css = '';
public $portal_custom_footer = '';
public $portal_custom_js = '';
public $portal_custom_head = ''; //@TODO
public $portal_custom_css = ''; //@TODO
public $portal_custom_footer = ''; //@TODO
public $portal_custom_js = ''; //@TODO
public $client_can_register = false;
public $client_portal_terms = '';
public $client_portal_privacy_policy = '';
public $client_portal_enable_uploads = false;
public $client_portal_allow_under_payment = false;
public $client_portal_under_payment_minimum = 0;
public $client_portal_allow_over_payment = false;
public $client_can_register = false; //@implemented
public $client_portal_terms = ''; //@TODO
public $client_portal_privacy_policy = ''; //@TODO
public $client_portal_enable_uploads = false; //@implemented
public $client_portal_allow_under_payment = false; //@implemented
public $client_portal_under_payment_minimum = 0; //@implemented
public $client_portal_allow_over_payment = false; //@implemented
public $use_credits_payment = 'off'; //always, option, off
public $use_credits_payment = 'off'; //always, option, off //@implemented
public static $casts = [
'enable_reminder_endless' => 'bool',
@ -301,6 +304,9 @@ class CompanySettings extends BaseSettings
'late_fee_amount1' => 'float',
'late_fee_amount2' => 'float',
'late_fee_amount3' => 'float',
'late_fee_percent1' => 'float',
'late_fee_percent2' => 'float',
'late_fee_percent3' => 'float',
'endless_reminder_frequency_id' => 'integer',
'client_online_payment_notification' => 'bool',
'client_manual_payment_notification' => 'bool',
@ -312,7 +318,6 @@ class CompanySettings extends BaseSettings
'email_template_statement' => 'string',
'email_subject_statement' => 'string',
'signature_on_pdf' => 'bool',
// 'send_portal_password' => 'bool',
'quote_footer' => 'string',
'page_size' => 'string',
'font_size' => 'int',
@ -431,7 +436,7 @@ class CompanySettings extends BaseSettings
'auto_convert_quote' => 'bool',
'shared_invoice_quote_counter' => 'bool',
'counter_padding' => 'integer',
'design' => 'string',
//'design' => 'string',
'website' => 'string',
'pdf_variables' => 'object',
'portal_custom_head' => 'string',

View File

@ -409,7 +409,7 @@ class CompanyController extends BaseController
public function update(UpdateCompanyRequest $request, Company $company)
{
if($request->hasFile('company_logo') || !array_key_exists('company_logo', $request->input('settings')))
if($request->hasFile('company_logo') || (is_array($request->input('settings')) && !array_key_exists('company_logo', $request->input('settings'))))
$this->removeLogo($company);
$company = $this->company_repo->save($request->all(), $company);

View File

@ -16,7 +16,6 @@ use App\Http\Requests\Credit\ShowCreditRequest;
use App\Http\Requests\Credit\StoreCreditRequest;
use App\Http\Requests\Credit\UpdateCreditRequest;
use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Jobs\Credit\StoreCredit;
use App\Jobs\Entity\EmailEntity;
use App\Jobs\Invoice\EmailCredit;
use App\Jobs\Invoice\MarkInvoicePaid;
@ -188,7 +187,9 @@ class CreditController extends BaseController
$credit = $this->credit_repository->save($request->all(), CreditFactory::create(auth()->user()->company()->id, auth()->user()->id));
$credit = StoreCredit::dispatchNow($credit, $request->all(), $credit->company);
$credit = $credit->service()
->fillDefaults()
->save();
event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars()));

View File

@ -213,7 +213,10 @@ class InvoiceController extends BaseController
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
$invoice = $invoice->service()->triggeredActions($request)->save();
$invoice = $invoice->service()
->fillDefaults()
->triggeredActions($request)
->save();
return $this->itemResponse($invoice);
}
@ -790,4 +793,57 @@ class InvoiceController extends BaseController
return response()->download($file_path, basename($file_path));
}
/**
* @OA\Get(
* path="/api/v1/invoices/{id}/delivery_note",
* operationId="deliveryNote",
* tags={"invoices"},
* summary="Download a specific invoice delivery notes",
* description="Downloads a specific invoice delivery notes",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Invoice Hahsed Id",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the invoice delivery note pdf",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
*
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param $invoice
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
{
$file_path = $invoice->service()->getInvoiceDeliveryNote($invoice->invitations->first()->contact);
return response()->download($file_path, basename($file_path));
}
}

View File

@ -12,15 +12,15 @@
* @OA\License(
* name="Attribution Assurance License",
* url="https://opensource.org/licenses/AAL"
* )
* ),
* ),
* @OA\Server(
* description="Example InvoiceNinja base url",
* url="https://ninja.test"
* url="https://ninja.test",
* ),
* @OA\ExternalDocumentation(
* description="http://docs.invoiceninja.com",
* url="http://docs.invoiceninja.com"
* )
* )
* ),
* ),
*/

View File

@ -82,8 +82,8 @@ class QuoteController extends BaseController
* tags={"quotes"},
* summary="Gets a list of quotes",
* description="Lists quotes, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available",
*
* Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
@ -207,6 +207,8 @@ class QuoteController extends BaseController
$quote = $this->quote_repo->save($request->all(), QuoteFactory::create(auth()->user()->company()->id, auth()->user()->id));
$quote = $quote->service()->fillDefaults()->save();
event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars()));
return $this->itemResponse($quote);

View File

@ -1,55 +0,0 @@
<?php
namespace App\Jobs\Credit;
use App\Jobs\Payment\PaymentNotification;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Credit;
use App\Repositories\CreditRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StoreCredit implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $credit;
protected $data;
/**
* Create a new job instance.
*
* @param Credit $credit
* @param array $data
*/
public function __construct(Credit $credit, array $data)
{
$this->credit = $credit;
$this->data = $data;
}
/**
* Execute the job.
*
* @param CreditRepository $credit_repository
* @return Credit|null
*/
public function handle(CreditRepository $credit_repository): ?Credit
{
// MultiDB::setDB($this->company->db);
// $payment = false;
// if ($payment) {
// PaymentNotification::dispatch($payment, $payment->company);
// }
return $this->credit;
}
}

View File

@ -79,7 +79,7 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
$this->entity = $invitation->{$this->entity_string};
$this->reminder_template = $reminder_template ?: $this->findReminderTemplate();
$this->reminder_template = $reminder_template ?: $this->entity->calculateTemplate($this->entity_string);
$this->html_engine = new HtmlEngine($invitation);

View File

@ -0,0 +1,112 @@
<?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 App\Jobs\Ninja;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendReminders implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
info("Sending reminders ".Carbon::now()->format('Y-m-d h:i:s'));
if (! config('ninja.db.multi_db_enabled')) {
$this->sendReminderEmails();
} else {
//multiDB environment, need to
foreach (MultiDB::$dbs as $db)
{
MultiDB::setDB($db);
$this->sendReminderEmails();
}
}
}
private function chargeLateFee()
{
}
private function sendReminderEmails()
{
$invoices = Invoice::where('is_deleted', 0)
->where('balance', '>', 0)
->whereDate('next_send_date', '<=', now()->startOfDay())
->cursor();
//we only need invoices that are payable
$invoices->filter(function ($invoice){
return $invoice->isPayable();
})->each(function ($invoice){
$reminder_template = $invoice->calculateTemplate('invoice');
if($reminder_template == 'reminder1'){
}
elseif($reminder_template == 'reminder2'){
}
elseif($reminder_template == 'reminder3'){
}
elseif($reminder_template == 'endless_reminder'){
}
//@todo
});
//iterate through all the reminder emails due today
//
//determine which reminder
//
//determine late fees
//
//send
}
}

View File

@ -23,6 +23,7 @@ use App\Utils\Ninja;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceValues;
use App\Utils\Traits\MakesReminders;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
@ -37,7 +38,8 @@ class Credit extends BaseModel
use SoftDeletes;
use PresentableTrait;
use MakesInvoiceValues;
use MakesReminders;
protected $presenter = CreditPresenter::class;
protected $fillable = [

View File

@ -301,10 +301,6 @@ class BaseRepository
if ($class->name == Quote::class) {
$model = $model->calc()->getQuote();
if (! $model->design_id) {
$model->design_id = $this->decodePrimaryKey($client->getSetting('quote_design_id'));
}
}
$model->save();

View File

@ -16,9 +16,12 @@ use App\Services\Credit\ApplyPayment;
use App\Services\Credit\CreateInvitations;
use App\Services\Credit\MarkSent;
use App\Services\Credit\SendEmail;
use App\Utils\Traits\MakesHash;
class CreditService
{
use MakesHash;
protected $credit;
public function __construct($credit)
@ -98,6 +101,24 @@ class CreditService
return $this;
}
public function fillDefaults()
{
$settings = $this->credit->client->getMergedSettings();
if(! $this->credit->design_id)
$this->credit->design_id = $this->decodePrimaryKey($settings->credit_design_id);
if(!isset($this->credit->footer))
$this->credit->footer = $settings->credit_footer;
if(!isset($this->credit->terms))
$this->credit->terms = $settings->credit_terms;
return $this;
}
/**
* Saves the credit.
* @return Credit object

View File

@ -60,8 +60,8 @@ class AutoBillInvoice extends AbstractService
if($this->client->getSetting('use_credits_payment') != 'off')
$this->applyCreditPayment();
info("partial = {$this->invoice->partial}");
info("balance = {$this->invoice->balance}");
// info("partial = {$this->invoice->partial}");
// info("balance = {$this->invoice->balance}");
/* Determine $amount */
if ($this->invoice->partial > 0)

View File

@ -143,6 +143,11 @@ class InvoiceService
return (new GetInvoicePdf($this->invoice, $contact))->run();
}
public function getInvoiceDeliveryNote($contact = null)
{
//stubbed
}
public function sendEmail($contact = null)
{
$send_email = new SendEmail($this->invoice, null, $contact);
@ -357,6 +362,24 @@ class InvoiceService
return $this;
}
public function fillDefaults()
{
$settings = $this->invoice->client->getMergedSettings();
if(! $this->invoice->design_id)
$this->invoice->design_id = $this->decodePrimaryKey($settings->invoice_design_id);
if(!isset($this->invoice->footer))
$this->invoice->footer = $settings->invoice_footer;
if(!isset($this->invoice->terms))
$this->invoice->terms = $settings->invoice_terms;
return $this;
}
/**
* Saves the invoice.
* @return Invoice object

View File

@ -66,6 +66,8 @@ class MarkPaid extends AbstractService
'amount' => $payment->amount,
]);
$this->invoice->next_send_date = null;
$this->invoice->service()
->updateBalance($payment->amount * -1)
->setStatus(Invoice::STATUS_PAID)

View File

@ -19,9 +19,12 @@ use App\Repositories\QuoteRepository;
use App\Services\Quote\CreateInvitations;
use App\Services\Quote\GetQuotePdf;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
class QuoteService
{
use MakesHash;
protected $quote;
public $invoice;
@ -158,6 +161,23 @@ class QuoteService
return true;
}
public function fillDefaults()
{
$settings = $this->quote->client->getMergedSettings();
if(! $this->quote->design_id)
$this->quote->design_id = $this->decodePrimaryKey($settings->quote_design_id);
if(!isset($this->quote->footer))
$this->quote->footer = $settings->quote_footer;
if(!isset($this->quote->terms))
$this->quote->terms = $settings->quote_terms;
return $this;
}
/**
* Saves the quote.
* @return Quote|null

View File

@ -311,7 +311,11 @@ class HtmlEngine
$data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$task.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$task.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
$data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')];
if($this->settings->signature_on_pdf)
$data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')];
else
$data['$contact.signature'] = ['value' => '', 'label' => ''];
$data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')];
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];

View File

@ -186,6 +186,9 @@ trait MakesReminders
//if invoice is currently a draft, or being marked as sent, this will be the initial email
$client = $this->client;
if($entity_string != 'invoice')
return $entity_string;
//if the invoice
if ($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow(
$client->getSetting('schedule_reminder1'),

198
config/l5-swagger.php Normal file
View File

@ -0,0 +1,198 @@
<?php
return [
'default' => 'default',
'documentations' => [
'default' => [
'api' => [
'title' => 'L5 Swagger UI',
],
'routes' => [
/*
* Route for accessing api documentation interface
*/
'api' => 'api/documentation',
],
'paths' => [
/*
* File name of the generated json documentation file
*/
'docs_json' => 'api-docs.json',
/*
* File name of the generated YAML documentation file
*/
'docs_yaml' => 'api-docs.yaml',
/*
* Absolute paths to directory containing the swagger annotations are stored.
*/
'annotations' => [
base_path('app'),
],
],
],
],
'defaults' => [
'routes' => [
/*
* Route for accessing parsed swagger annotations.
*/
'docs' => 'docs',
/*
* Route for Oauth2 authentication callback.
*/
'oauth2_callback' => 'api/oauth2-callback',
/*
* Middleware allows to prevent unexpected access to API documentation
*/
'middleware' => [
'api' => [],
'asset' => [],
'docs' => [],
'oauth2_callback' => [],
],
/*
* Route Group options
*/
'group_options' => [],
],
'paths' => [
/*
* Absolute path to location where parsed annotations will be stored
*/
'docs' => storage_path('api-docs'),
/*
* Absolute path to directory where to export views
*/
'views' => base_path('resources/views/vendor/l5-swagger'),
/*
* Edit to set the api's base path
*/
'base' => env('L5_SWAGGER_BASE_PATH', null),
/*
* Edit to set path where swagger ui assets should be stored
*/
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
/*
* Absolute path to directories that should be exclude from scanning
*/
'excludes' => [],
],
/*
* API security definitions. Will be generated into documentation file.
*/
'securityDefinitions' => [
'securitySchemes' => [
/*
* Examples of Security schemes
*/
/*
'api_key_security_example' => [ // Unique name of security
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for security scheme',
'name' => 'api_key', // The name of the header or query parameter to be used.
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
],
'oauth2_security_example' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'A short description for oauth2 security scheme.',
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
'scopes' => [
'read:projects' => 'read your projects',
'write:projects' => 'modify projects in your account',
]
],
*/
/* Open API 3.0 support
'passport' => [ // Unique name of security
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
'description' => 'Laravel passport oauth2 security.',
'in' => 'header',
'scheme' => 'https',
'flows' => [
"password" => [
"authorizationUrl" => config('app.url') . '/oauth/authorize',
"tokenUrl" => config('app.url') . '/oauth/token',
"refreshUrl" => config('app.url') . '/token/refresh',
"scopes" => []
],
],
],
*/
],
'security' => [
/*
* Examples of Securities
*/
[
/*
'oauth2_security_example' => [
'read',
'write'
],
'passport' => []
*/
],
],
],
/*
* Set this to `true` in development mode so that docs would be regenerated on each request
* Set this to `false` to disable swagger generation on production
*/
'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),
/*
* Set this to `true` to generate a copy of documentation in yaml format
*/
'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false),
/*
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
* string[]
*/
'proxy' => false,
/*
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
* See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
*/
'additional_config_url' => null,
/*
* Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
* 'method' (sort by HTTP method).
* Default is the order returned by the server unchanged.
*/
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
/*
* Pass the validatorUrl parameter to SwaggerUi init on the JS side.
* A null value here disables validation.
*/
'validator_url' => null,
/*
* Uncomment to add constants which can be used in annotations
*/
// 'constants' => [
// 'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
// ],
],
];

View File

View File

@ -0,0 +1,106 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{config('l5-swagger.documentations.'.$documentation.'.api.title')}}</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ l5_swagger_asset($documentation, 'swagger-ui.css') }}" >
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-32x32.png') }}" sizes="32x32" />
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-16x16.png') }}" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-bundle.js') }}"> </script>
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-standalone-preset.js') }}"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
url: "{!! $urlToDocs !!}",
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
configUrl: {!! isset($configUrl) ? '"' . $configUrl . '"' : 'null' !!},
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
oauth2RedirectUrl: "{{ route('l5-swagger.'.$documentation.'.oauth2_callback') }}",
requestInterceptor: function(request) {
request.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
return request;
},
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

View File

@ -35,6 +35,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
Route::get('invoices/{invoice}/delivery_note', 'InvoiceController@deliveryNote')->name('invoices.delivery_note');
Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');

View File

@ -29,7 +29,7 @@ class GroupTest extends TestCase
public function testGroupsPropertiesExistsResponses()
{
$this->assertTrue(property_exists($this->settings, 'design'));
$this->assertTrue(property_exists($this->settings, 'timezone_id'));
}
public function testPropertyValueAccessors()