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

View File

@ -409,7 +409,7 @@ class CompanyController extends BaseController
public function update(UpdateCompanyRequest $request, Company $company) 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); $this->removeLogo($company);
$company = $this->company_repo->save($request->all(), $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\StoreCreditRequest;
use App\Http\Requests\Credit\UpdateCreditRequest; use App\Http\Requests\Credit\UpdateCreditRequest;
use App\Http\Requests\Invoice\EditInvoiceRequest; use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Jobs\Credit\StoreCredit;
use App\Jobs\Entity\EmailEntity; use App\Jobs\Entity\EmailEntity;
use App\Jobs\Invoice\EmailCredit; use App\Jobs\Invoice\EmailCredit;
use App\Jobs\Invoice\MarkInvoicePaid; 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 = $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())); 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())); 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); return $this->itemResponse($invoice);
} }
@ -790,4 +793,57 @@ class InvoiceController extends BaseController
return response()->download($file_path, basename($file_path)); 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( * @OA\License(
* name="Attribution Assurance License", * name="Attribution Assurance License",
* url="https://opensource.org/licenses/AAL" * url="https://opensource.org/licenses/AAL"
* ) * ),
* ), * ),
* @OA\Server( * @OA\Server(
* description="Example InvoiceNinja base url", * description="Example InvoiceNinja base url",
* url="https://ninja.test" * url="https://ninja.test",
* ), * ),
* @OA\ExternalDocumentation( * @OA\ExternalDocumentation(
* description="http://docs.invoiceninja.com", * description="http://docs.invoiceninja.com",
* url="http://docs.invoiceninja.com" * url="http://docs.invoiceninja.com"
* ) * ),
* ) * ),
*/ */

View File

@ -82,8 +82,8 @@ class QuoteController extends BaseController
* tags={"quotes"}, * tags={"quotes"},
* summary="Gets a list of quotes", * summary="Gets a list of quotes",
* description="Lists quotes, search and filters allow fine grained lists to be generated. * 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-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"), * @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"), * @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 = $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())); event(new QuoteWasCreated($quote, $quote->company, Ninja::eventVars()));
return $this->itemResponse($quote); 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->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); $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\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceValues; use App\Utils\Traits\MakesInvoiceValues;
use App\Utils\Traits\MakesReminders;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -37,6 +38,7 @@ class Credit extends BaseModel
use SoftDeletes; use SoftDeletes;
use PresentableTrait; use PresentableTrait;
use MakesInvoiceValues; use MakesInvoiceValues;
use MakesReminders;
protected $presenter = CreditPresenter::class; protected $presenter = CreditPresenter::class;

View File

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

View File

@ -16,9 +16,12 @@ use App\Services\Credit\ApplyPayment;
use App\Services\Credit\CreateInvitations; use App\Services\Credit\CreateInvitations;
use App\Services\Credit\MarkSent; use App\Services\Credit\MarkSent;
use App\Services\Credit\SendEmail; use App\Services\Credit\SendEmail;
use App\Utils\Traits\MakesHash;
class CreditService class CreditService
{ {
use MakesHash;
protected $credit; protected $credit;
public function __construct($credit) public function __construct($credit)
@ -98,6 +101,24 @@ class CreditService
return $this; 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. * Saves the credit.
* @return Credit object * @return Credit object

View File

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

View File

@ -143,6 +143,11 @@ class InvoiceService
return (new GetInvoicePdf($this->invoice, $contact))->run(); return (new GetInvoicePdf($this->invoice, $contact))->run();
} }
public function getInvoiceDeliveryNote($contact = null)
{
//stubbed
}
public function sendEmail($contact = null) public function sendEmail($contact = null)
{ {
$send_email = new SendEmail($this->invoice, null, $contact); $send_email = new SendEmail($this->invoice, null, $contact);
@ -357,6 +362,24 @@ class InvoiceService
return $this; 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. * Saves the invoice.
* @return Invoice object * @return Invoice object

View File

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

View File

@ -19,9 +19,12 @@ use App\Repositories\QuoteRepository;
use App\Services\Quote\CreateInvitations; use App\Services\Quote\CreateInvitations;
use App\Services\Quote\GetQuotePdf; use App\Services\Quote\GetQuotePdf;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
class QuoteService class QuoteService
{ {
use MakesHash;
protected $quote; protected $quote;
public $invoice; public $invoice;
@ -158,6 +161,23 @@ class QuoteService
return true; 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. * Saves the quote.
* @return Quote|null * @return Quote|null

View File

@ -311,7 +311,11 @@ class HtmlEngine
$data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$task.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$task.tax_name3'] = ['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['$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['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')];
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; $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 //if invoice is currently a draft, or being marked as sent, this will be the initial email
$client = $this->client; $client = $this->client;
if($entity_string != 'invoice')
return $entity_string;
//if the invoice //if the invoice
if ($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow( if ($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow(
$client->getSetting('schedule_reminder1'), $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::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('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf'); Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');

View File

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