mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Modifications to Designs (#3353)
* Working on Quotes * Naming refactor for Quotes * Quote Actions * Quote Pdfs * Quote PDFs * Refunds in Stripe * Fixes tests * Company Ledger work
This commit is contained in:
parent
4a41685e94
commit
9e9cd37b87
@ -521,7 +521,7 @@ class CreateTestData extends Command
|
||||
$invoice_calc = new InvoiceSum($credit);
|
||||
$invoice_calc->build();
|
||||
|
||||
$credit = $invoice_calc->getInvoice();
|
||||
$credit = $invoice_calc->getCredit();
|
||||
|
||||
$credit->save();
|
||||
|
||||
@ -533,8 +533,7 @@ class CreateTestData extends Command
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$quote = QuoteFactory::create($client->company->id, $client->user->id);//stub the company and user_id
|
||||
$quote->client_id = $client->id;
|
||||
$quote =factory(\App\Models\Quote::class)->create(['user_id' => $client->user->id, 'company_id' => $client->company->id, 'client_id' => $client->id]);
|
||||
$quote->date = $faker->date();
|
||||
|
||||
$quote->line_items = $this->buildLineItems(rand(1,10));
|
||||
@ -560,9 +559,8 @@ class CreateTestData extends Command
|
||||
$quote_calc = new InvoiceSum($quote);
|
||||
$quote_calc->build();
|
||||
|
||||
$quote = $quote_calc->getInvoice();
|
||||
|
||||
$quote->save();
|
||||
$quote = $quote_calc->getQuote();
|
||||
$quote->service()->markSent()->save();
|
||||
|
||||
CreateQuoteInvitations::dispatch($quote, $quote->company);
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ class CompanySettings extends BaseSettings {
|
||||
public $embed_documents = false;
|
||||
public $all_pages_header = true;
|
||||
public $all_pages_footer = true;
|
||||
public $invoice_variables = [];
|
||||
public $pdf_variables = [];
|
||||
|
||||
public static $casts = [
|
||||
'portal_design_id' => 'string',
|
||||
@ -372,7 +372,7 @@ class CompanySettings extends BaseSettings {
|
||||
'counter_padding' => 'integer',
|
||||
'design' => 'string',
|
||||
'website' => 'string',
|
||||
'invoice_variables' => 'object',
|
||||
'pdf_variables' => 'object',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -415,7 +415,7 @@ class CompanySettings extends BaseSettings {
|
||||
$data->date_format_id = (string) config('ninja.i18n.date_format_id');
|
||||
$data->country_id = (string) config('ninja.i18n.country_id');
|
||||
$data->translations = (object) [];
|
||||
$data->invoice_variables = (array) self::getInvoiceVariableDefaults();
|
||||
$data->pdf_variables = (array) self::getEntityVariableDefaults();
|
||||
|
||||
// $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject();
|
||||
// $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate();
|
||||
@ -456,7 +456,7 @@ class CompanySettings extends BaseSettings {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private static function getInvoiceVariableDefaults() {
|
||||
private static function getEntityVariableDefaults() {
|
||||
$variables = [
|
||||
'client_details' => [
|
||||
'name',
|
||||
@ -490,6 +490,21 @@ class CompanySettings extends BaseSettings {
|
||||
'balance_due',
|
||||
'invoice_total',
|
||||
],
|
||||
'quote_details' => [
|
||||
'quote_number',
|
||||
'po_number',
|
||||
'date',
|
||||
'valid_until',
|
||||
'balance_due',
|
||||
'quote_total',
|
||||
],
|
||||
'credit_details' => [
|
||||
'credit_number',
|
||||
'po_number',
|
||||
'date',
|
||||
'credit_balance',
|
||||
'credit_amount',
|
||||
],
|
||||
'table_columns' => [
|
||||
'product_key',
|
||||
'notes',
|
||||
|
@ -63,10 +63,10 @@ class Bold extends AbstractDesign
|
||||
<div class="w-1/2">
|
||||
<div class="w-full bg-teal-600 px-5 py-3 text-white flex">
|
||||
<div class="w-48 flex flex-col text-white">
|
||||
$invoice_details_labels
|
||||
$entity_labels
|
||||
</div>
|
||||
<div class="w-32 flex flex-col text-white">
|
||||
$invoice_details
|
||||
$entity_details
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,10 +65,10 @@ class Clean extends AbstractDesign
|
||||
<div class="ml-4 py-4">
|
||||
<div class="flex">
|
||||
<div class="w-40 flex flex-col">
|
||||
$invoice_details_labels
|
||||
$entity_labels
|
||||
</div>
|
||||
<div class="w-48 flex flex-col">
|
||||
$invoice_details
|
||||
$entity_details
|
||||
</div>
|
||||
<div class="w-56 flex flex-col">
|
||||
$client_details
|
||||
|
@ -24,6 +24,8 @@ class Designer {
|
||||
|
||||
protected $html;
|
||||
|
||||
protected $entity_string;
|
||||
|
||||
private static $custom_fields = [
|
||||
'invoice1',
|
||||
'invoice2',
|
||||
@ -47,10 +49,15 @@ class Designer {
|
||||
'company4',
|
||||
];
|
||||
|
||||
public function __construct($design, $input_variables) {
|
||||
public function __construct($design, $input_variables, $entity_string)
|
||||
{
|
||||
|
||||
$this->design = $design;
|
||||
|
||||
$this->input_variables = (array) $input_variables;
|
||||
|
||||
$this->entity_string = $entity_string;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,21 +65,24 @@ class Designer {
|
||||
* formatted HTML
|
||||
* @return string The HTML design built
|
||||
*/
|
||||
public function build(Invoice $invoice):Designer {
|
||||
public function build($entity):Designer
|
||||
{
|
||||
|
||||
$this->exportVariables($invoice)
|
||||
$this->exportVariables($entity)
|
||||
->setDesign($this->getSection('header'))
|
||||
->setDesign($this->getSection('body'))
|
||||
->setDesign($this->getTable($invoice))
|
||||
->setDesign($this->getTable($entity))
|
||||
->setDesign($this->getSection('footer'));
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function getTable(Invoice $invoice):string {
|
||||
public function getTable($entity):string
|
||||
{
|
||||
|
||||
$table_header = $invoice->table_header($this->input_variables['table_columns'], $this->design->table_styles());
|
||||
$table_body = $invoice->table_body($this->input_variables['table_columns'], $this->design->table_styles());
|
||||
$table_header = $entity->table_header($this->input_variables['table_columns'], $this->design->table_styles());
|
||||
$table_body = $entity->table_body($this->input_variables['table_columns'], $this->design->table_styles());
|
||||
|
||||
$data = str_replace('$table_header', $table_header, $this->getSection('table'));
|
||||
$data = str_replace('$table_body', $table_body, $data);
|
||||
@ -81,11 +91,13 @@ class Designer {
|
||||
|
||||
}
|
||||
|
||||
public function getHtml():string {
|
||||
public function getHtml():string
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
private function setDesign($section) {
|
||||
private function setDesign($section)
|
||||
{
|
||||
|
||||
$this->html .= $section;
|
||||
|
||||
@ -99,23 +111,40 @@ class Designer {
|
||||
* @param string $section the method name to be executed ie header/body/table/footer
|
||||
* @return string The HTML of the template section
|
||||
*/
|
||||
public function getSection($section):string {
|
||||
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{ $section}());
|
||||
public function getSection($section):string
|
||||
{
|
||||
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{$section}());
|
||||
}
|
||||
|
||||
private function exportVariables($invoice) {
|
||||
$company = $invoice->company;
|
||||
private function exportVariables($entity)
|
||||
{
|
||||
|
||||
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
|
||||
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
|
||||
$this->exported_variables['$company_address'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_address']), $this->companyAddress($company));
|
||||
$this->exported_variables['$invoice_details_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
|
||||
$this->exported_variables['$invoice_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
|
||||
$company = $entity->company;
|
||||
|
||||
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
|
||||
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
|
||||
$this->exported_variables['$company_address'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_address']), $this->companyAddress($company));
|
||||
|
||||
if($this->entity_string == 'invoice')
|
||||
{
|
||||
$this->exported_variables['$entity_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
|
||||
$this->exported_variables['$entity_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
|
||||
}
|
||||
elseif($this->entity_string == 'credit')
|
||||
{
|
||||
$this->exported_variables['$entity_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['credit_details']), $this->creditDetails($company));
|
||||
$this->exported_variables['$entity_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['credit_details']), $this->creditDetails($company));
|
||||
}
|
||||
elseif($this->entity_string == 'quote')
|
||||
{
|
||||
$this->exported_variables['$entity_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['quote_details']), $this->quoteDetails($company));
|
||||
$this->exported_variables['$entity_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['quote_details']), $this->quoteDetails($company));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function processVariables($input_variables, $variables):string {
|
||||
private function processVariables($input_variables, $variables):string
|
||||
{
|
||||
|
||||
$output = '';
|
||||
|
||||
@ -126,7 +155,8 @@ class Designer {
|
||||
|
||||
}
|
||||
|
||||
private function processLabels($input_variables, $variables):string {
|
||||
private function processLabels($input_variables, $variables):string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach ($input_variables as $value) {
|
||||
@ -142,8 +172,8 @@ class Designer {
|
||||
// private function exportVariables()
|
||||
// {
|
||||
// /*
|
||||
// * $invoice_details_labels
|
||||
// * $invoice_details
|
||||
// * $entity_labels
|
||||
// * $entity_details
|
||||
// */
|
||||
// $header = $this->design->header();
|
||||
|
||||
@ -168,7 +198,8 @@ class Designer {
|
||||
// $footer = $this->design->footer();
|
||||
// }
|
||||
|
||||
private function clientDetails(Company $company) {
|
||||
private function clientDetails(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => '<p>$client.name</p>',
|
||||
@ -193,7 +224,9 @@ class Designer {
|
||||
return $this->processCustomFields($company, $data);
|
||||
}
|
||||
|
||||
private function companyDetails(Company $company) {
|
||||
private function companyDetails(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'company_name' => '<span>$company.company_name</span>',
|
||||
'id_number' => '<span>$company.id_number</span>',
|
||||
@ -208,9 +241,11 @@ class Designer {
|
||||
];
|
||||
|
||||
return $this->processCustomFields($company, $data);
|
||||
|
||||
}
|
||||
|
||||
private function companyAddress(Company $company) {
|
||||
private function companyAddress(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'address1' => '<span>$company.address1</span>',
|
||||
@ -225,9 +260,11 @@ class Designer {
|
||||
];
|
||||
|
||||
return $this->processCustomFields($company, $data);
|
||||
|
||||
}
|
||||
|
||||
private function invoiceDetails(Company $company) {
|
||||
private function invoiceDetails(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'invoice_number' => '<span>$invoice_number</span>',
|
||||
@ -248,9 +285,60 @@ class Designer {
|
||||
];
|
||||
|
||||
return $this->processCustomFields($company, $data);
|
||||
|
||||
}
|
||||
|
||||
private function processCustomFields(Company $company, $data) {
|
||||
private function quoteDetails(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'quote_number' => '<span>$quote_number</span>',
|
||||
'po_number' => '<span>$po_number</span>',
|
||||
'date' => '<span>$date</span>',
|
||||
'valid_until' => '<span>$valid_until</span>',
|
||||
'balance_due' => '<span>$balance_due</span>',
|
||||
'quote_total' => '<span>$quote_total</span>',
|
||||
'partial_due' => '<span>$partial_due</span>',
|
||||
'quote1' => '<span>$quote1</span>',
|
||||
'quote2' => '<span>$quote2</span>',
|
||||
'quote3' => '<span>$quote3</span>',
|
||||
'quote4' => '<span>$quote4</span>',
|
||||
'surcharge1' => '<span>$surcharge1</span>',
|
||||
'surcharge2' => '<span>$surcharge2</span>',
|
||||
'surcharge3' => '<span>$surcharge3</span>',
|
||||
'surcharge4' => '<span>$surcharge4</span>',
|
||||
];
|
||||
|
||||
return $this->processCustomFields($company, $data);
|
||||
|
||||
}
|
||||
|
||||
private function creditDetails(Company $company)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'credit_number' => '<span>$credit_number</span>',
|
||||
'po_number' => '<span>$po_number</span>',
|
||||
'date' => '<span>$date</span>',
|
||||
'credit_balance' => '<span>$credit_balance</span>',
|
||||
'credit_amount' => '<span>$credit_amount</span>',
|
||||
'partial_due' => '<span>$partial_due</span>',
|
||||
'invoice1' => '<span>$invoice1</span>',
|
||||
'invoice2' => '<span>$invoice2</span>',
|
||||
'invoice3' => '<span>$invoice3</span>',
|
||||
'invoice4' => '<span>$invoice4</span>',
|
||||
'surcharge1' => '<span>$surcharge1</span>',
|
||||
'surcharge2' => '<span>$surcharge2</span>',
|
||||
'surcharge3' => '<span>$surcharge3</span>',
|
||||
'surcharge4' => '<span>$surcharge4</span>',
|
||||
];
|
||||
|
||||
return $this->processCustomFields($company, $data);
|
||||
|
||||
}
|
||||
|
||||
private function processCustomFields(Company $company, $data)
|
||||
{
|
||||
|
||||
$custom_fields = $company->custom_fields;
|
||||
|
||||
@ -270,7 +358,8 @@ class Designer {
|
||||
|
||||
}
|
||||
|
||||
private function processInputVariables($company, $variables) {
|
||||
private function processInputVariables($company, $variables)
|
||||
{
|
||||
|
||||
$custom_fields = $company->custom_fields;
|
||||
|
||||
@ -291,4 +380,5 @@ class Designer {
|
||||
return $variables;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -40,10 +40,10 @@ class Modern extends AbstractDesign
|
||||
</div>
|
||||
<div class="w-1/2 flex justify-end">
|
||||
<div class="w-56 flex flex-col text-white">
|
||||
$invoice_details_labels
|
||||
$entity_labels
|
||||
</div>
|
||||
<div class="w-32 flex flex-col text-left text-white">
|
||||
$invoice_details
|
||||
$entity_details
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,8 +49,8 @@ class Photo extends AbstractDesign
|
||||
|
||||
<div class="px-16 py-10">
|
||||
<div class="flex justify-end">
|
||||
<span class="text-orange-700">$invoice_number_label</span>
|
||||
<span class="ml-6">$invoice_number</span>
|
||||
<span class="text-orange-700">$entity_labels</span>
|
||||
<span class="ml-6">$entity_details</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<div ref="logo" class="h-14">
|
||||
|
@ -47,10 +47,10 @@ class Plain extends AbstractDesign
|
||||
<div class="h-14">$company_logo</div>
|
||||
<div class="flex px-3 mt-6">
|
||||
<section class="w-1/2 flex flex-col">
|
||||
$invoice_details_labels
|
||||
$entity_labels
|
||||
</section>
|
||||
<section class="flex flex-col">
|
||||
$invoice_details
|
||||
$entity_details
|
||||
</section>
|
||||
</div>
|
||||
<section class="flex bg-gray-300 px-3">
|
||||
|
@ -26,17 +26,17 @@ class PaymentWasRefunded
|
||||
*/
|
||||
public $payment;
|
||||
|
||||
public $refundAmount;
|
||||
public $refund_amount;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Payment $payment
|
||||
* @param $refundAmount
|
||||
* @param $refund_amount
|
||||
*/
|
||||
public function __construct(Payment $payment, $refundAmount)
|
||||
public function __construct(Payment $payment, $refund_amount)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
$this->refundAmount = $refundAmount;
|
||||
$this->refund_amount = $refund_amount;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use App\Models\Invoice;
|
||||
|
||||
class CloneInvoiceFactory
|
||||
{
|
||||
public static function create(Invoice $invoice, $user_id) : ?Invoice
|
||||
public static function create($invoice, $user_id)
|
||||
{
|
||||
$clone_invoice = $invoice->replicate();
|
||||
$clone_invoice->status_id = Invoice::STATUS_DRAFT;
|
||||
@ -25,6 +25,7 @@ class CloneInvoiceFactory
|
||||
$clone_invoice->partial_due_date = null;
|
||||
$clone_invoice->user_id = $user_id;
|
||||
$clone_invoice->balance = $invoice->amount;
|
||||
$clone_invoice->amount = $invoice->amount;
|
||||
$clone_invoice->line_items = $invoice->line_items;
|
||||
$clone_invoice->backup = null;
|
||||
|
||||
|
@ -19,9 +19,6 @@ class CloneInvoiceToQuoteFactory
|
||||
public static function create(Invoice $invoice, $user_id) : ?Quote
|
||||
{
|
||||
$quote = new Quote();
|
||||
$quote->client_id = $invoice->client_id;
|
||||
$quote->user_id = $user_id;
|
||||
$quote->company_id = $invoice->company_id;
|
||||
$quote->discount = $invoice->discount;
|
||||
$quote->is_amount_discount = $invoice->is_amount_discount;
|
||||
$quote->po_number = $invoice->po_number;
|
||||
@ -35,12 +32,14 @@ class CloneInvoiceToQuoteFactory
|
||||
$quote->tax_rate1 = $invoice->tax_rate1;
|
||||
$quote->tax_name2 = $invoice->tax_name2;
|
||||
$quote->tax_rate2 = $invoice->tax_rate2;
|
||||
$quote->tax_rate3 = $invoice->tax_rate3;
|
||||
$quote->tax_rate3 = $invoice->tax_rate3;
|
||||
$quote->custom_value1 = $invoice->custom_value1;
|
||||
$quote->custom_value2 = $invoice->custom_value2;
|
||||
$quote->custom_value3 = $invoice->custom_value3;
|
||||
$quote->custom_value4 = $invoice->custom_value4;
|
||||
$quote->amount = $invoice->amount;
|
||||
$quote->balance = $invoice->balance;
|
||||
$quote->balance = $invoice->amount;
|
||||
$quote->partial = $invoice->partial;
|
||||
$quote->partial_due_date = $invoice->partial_due_date;
|
||||
$quote->last_viewed = $invoice->last_viewed;
|
||||
@ -50,7 +49,6 @@ class CloneInvoiceToQuoteFactory
|
||||
$quote->date = null;
|
||||
$quote->due_date = null;
|
||||
$quote->partial_due_date = null;
|
||||
$quote->balance = $invoice->amount;
|
||||
$quote->line_items = $invoice->line_items;
|
||||
|
||||
return $quote;
|
||||
|
34
app/Factory/CloneQuoteFactory.php
Normal file
34
app/Factory/CloneQuoteFactory.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* quote Ninja (https://quoteninja.com)
|
||||
*
|
||||
* @link https://github.com/quoteninja/quoteninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. quote Ninja LLC (https://quoteninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\quote;
|
||||
|
||||
class CloneQuoteFactory
|
||||
{
|
||||
public static function create($quote, $user_id)
|
||||
{
|
||||
$clone_quote = $quote->replicate();
|
||||
$clone_quote->status_id = quote::STATUS_DRAFT;
|
||||
$clone_quote->number = null;
|
||||
$clone_quote->date = null;
|
||||
$clone_quote->due_date = null;
|
||||
$clone_quote->partial_due_date = null;
|
||||
$clone_quote->user_id = $user_id;
|
||||
$clone_quote->balance = $quote->amount;
|
||||
$clone_quote->amount = $quote->amount;
|
||||
$clone_quote->line_items = $quote->line_items;
|
||||
$clone_quote->backup = null;
|
||||
|
||||
return $clone_quote;
|
||||
}
|
||||
}
|
@ -6,14 +6,14 @@ use App\Models\Quote;
|
||||
|
||||
class CloneQuoteToInvoiceFactory
|
||||
{
|
||||
public function create(Quote $quote, $user_id, $company_id) : ?Invoice
|
||||
public static function create(Quote $quote, $user_id) : ?Invoice
|
||||
{
|
||||
$invoice = new Invoice();
|
||||
$invoice->company_id = $company_id;
|
||||
$invoice->client_id = $quote->client_id;
|
||||
$invoice->user_id = $user_id;
|
||||
$invoice->po_number = $quote->po_number;
|
||||
$invoice->footer = $quote->footer;
|
||||
$invoice->line_items = $quote->line_items;
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,25 @@ class InvoiceSum
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
public function getQuote()
|
||||
{
|
||||
|
||||
$this->setCalculatedAttributes();
|
||||
$this->invoice->save();
|
||||
|
||||
return $this->invoice;
|
||||
|
||||
}
|
||||
|
||||
public function getCredit()
|
||||
{
|
||||
|
||||
$this->setCalculatedAttributes();
|
||||
$this->invoice->save();
|
||||
|
||||
return $this->invoice;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Build $this->invoice variables after
|
||||
|
@ -12,6 +12,7 @@
|
||||
* @OA\Property(property="date", type="string", example="1-1-2014", description="The Payment date"),
|
||||
* @OA\Property(property="transaction_reference", type="string", example="xcsSxcs124asd", description="The transaction reference as defined by the payment gateway"),
|
||||
* @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="______"),
|
||||
* @OA\Property(property="private_notes", type="string", example="The payment was refunded due to error", description="______"),
|
||||
* @OA\Property(property="is_manual", type="boolean", example=true, description="______"),
|
||||
* @OA\Property(property="is_deleted", type="boolean", example=true, description="______"),
|
||||
* @OA\Property(property="amount", type="number", example=10.00, description="The amount of this payment"),
|
||||
|
@ -11,6 +11,10 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\CloneInvoiceFactory;
|
||||
use App\Factory\CloneInvoiceToQuoteFactory;
|
||||
use App\Factory\CloneQuoteFactory;
|
||||
use App\Factory\CloneQuoteToInvoiceFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Filters\QuoteFilters;
|
||||
use App\Http\Requests\Quote\ActionQuoteRequest;
|
||||
@ -20,8 +24,10 @@ use App\Http\Requests\Quote\EditQuoteRequest;
|
||||
use App\Http\Requests\Quote\ShowQuoteRequest;
|
||||
use App\Http\Requests\Quote\StoreQuoteRequest;
|
||||
use App\Http\Requests\Quote\UpdateQuoteRequest;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Transformers\InvoiceTransformer;
|
||||
use App\Transformers\QuoteTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
@ -578,12 +584,16 @@ class QuoteController extends BaseController
|
||||
{
|
||||
switch ($action) {
|
||||
case 'clone_to_invoice':
|
||||
//$quote = CloneInvoiceFactory::create($quote, auth()->user()->id);
|
||||
return $this->itemResponse($quote);
|
||||
|
||||
$this->entity_type = Invoice::class;
|
||||
$this->entity_transformer = InvoiceTransformer::class;
|
||||
|
||||
$invoice = CloneQuoteToInvoiceFactory::create($quote, auth()->user()->id);
|
||||
return $this->itemResponse($invoice);
|
||||
break;
|
||||
case 'clone_to_quote':
|
||||
//$quote = CloneInvoiceToQuoteFactory::create($quote, auth()->user()->id);
|
||||
// todo build the quote transformer and return response here
|
||||
$quote = CloneQuoteFactory::create($quote, auth()->user()->id);
|
||||
return $this->itemResponse($quote);
|
||||
break;
|
||||
case 'history':
|
||||
# code...
|
||||
|
@ -27,10 +27,4 @@ class EditInvoiceRequest extends Request
|
||||
return auth()->user()->can('edit', $this->invoice);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,18 @@ class StoreInvoiceRequest extends Request
|
||||
if($input['client_id'])
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
|
||||
if(isset($input['client_contacts']))
|
||||
{
|
||||
foreach($input['client_contacts'] as $key => $contact)
|
||||
{
|
||||
if(!array_key_exists('send_email', $contact) || !array_key_exists('id', $contact))
|
||||
{
|
||||
unset($input['client_contacts'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
//$input['line_items'] = json_encode($input['line_items']);
|
||||
$this->replace($input);
|
||||
|
@ -27,20 +27,5 @@ class EditQuoteRequest extends Request
|
||||
return auth()->user()->can('edit', $this->quote);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
//$input['id'] = $this->encodePrimaryKey($input['id']);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,18 @@ class StoreQuoteRequest extends Request
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
if(isset($input['client_contacts']))
|
||||
{
|
||||
foreach($input['client_contacts'] as $key => $contact)
|
||||
{
|
||||
if(!array_key_exists('send_email', $contact) || !array_key_exists('id', $contact))
|
||||
{
|
||||
unset($input['client_contacts'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -46,9 +46,10 @@ class UpdateQuoteRequest extends Request
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
// if(isset($input['client_id']))
|
||||
// $input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
|
||||
if (isset($input['client_id'])) {
|
||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||
}
|
||||
|
||||
if (isset($input['line_items'])) {
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
@ -19,6 +19,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -32,7 +33,7 @@ use Spatie\Browsershot\Browsershot;
|
||||
|
||||
class CreateInvoicePdf implements ShouldQueue {
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker;
|
||||
|
||||
public $invoice;
|
||||
|
||||
@ -47,7 +48,7 @@ class CreateInvoicePdf implements ShouldQueue {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Invoice $invoice, Company $company, ClientContact $contact = null, $disk = 'public')
|
||||
public function __construct($invoice, Company $company, ClientContact $contact = null)
|
||||
{
|
||||
|
||||
$this->invoice = $invoice;
|
||||
@ -73,7 +74,6 @@ class CreateInvoicePdf implements ShouldQueue {
|
||||
|
||||
$path = $this->invoice->client->invoice_filepath();
|
||||
|
||||
//$file_path = $path . $this->invoice->number . '-' . $this->contact->contact_key .'.pdf';
|
||||
$file_path = $path . $this->invoice->number . '.pdf';
|
||||
|
||||
$design = Design::find($this->invoice->client->getSetting('invoice_design_id'));
|
||||
@ -86,7 +86,7 @@ class CreateInvoicePdf implements ShouldQueue {
|
||||
$invoice_design = new $class();
|
||||
}
|
||||
|
||||
$designer = new Designer($invoice_design, $this->invoice->client->getSetting('invoice_variables'));
|
||||
$designer = new Designer($invoice_design, $this->invoice->client->getSetting('pdf_variables'), 'invoice');
|
||||
|
||||
//get invoice design
|
||||
$html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice, $this->contact);
|
||||
@ -95,7 +95,6 @@ class CreateInvoicePdf implements ShouldQueue {
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
||||
//\Log::error($html);
|
||||
//create pdf
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
|
||||
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
@ -105,24 +104,5 @@ class CreateInvoicePdf implements ShouldQueue {
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PDF stream
|
||||
*
|
||||
* @param string $header Header to be included in PDF
|
||||
* @param string $footer Footer to be included in PDF
|
||||
* @param string $html The HTML object to be converted into PDF
|
||||
*
|
||||
* @return string The PDF string
|
||||
*/
|
||||
private function makePdf($header, $footer, $html) {
|
||||
return Browsershot::html($html)
|
||||
//->showBrowserHeaderAndFooter()
|
||||
//->headerHtml($header)
|
||||
//->footerHtml($footer)
|
||||
->deviceScaleFactor(1)
|
||||
->showBackground()
|
||||
->waitUntilNetworkIdle(true) ->pdf();
|
||||
//->margins(10,10,10,10)
|
||||
//->savePdf('test.pdf');
|
||||
}
|
||||
|
||||
}
|
||||
|
127
app/Jobs/Quote/CreateQuotePdf.php
Normal file
127
app/Jobs/Quote/CreateQuotePdf.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?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\Quote;
|
||||
|
||||
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\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
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 CreateQuotePdf implements ShouldQueue {
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker;
|
||||
|
||||
public $quote;
|
||||
|
||||
public $company;
|
||||
|
||||
public $contact;
|
||||
|
||||
private $disk;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote, Company $company, ClientContact $contact = null)
|
||||
{
|
||||
|
||||
$this->quote = $quote;
|
||||
|
||||
$this->company = $company;
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
$this->disk = $disk ?? config('filesystems.default');
|
||||
|
||||
}
|
||||
|
||||
public function handle() {
|
||||
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$this->quote->load('client');
|
||||
|
||||
if(!$this->contact)
|
||||
$this->contact = $this->quote->client->primary_contact()->first();
|
||||
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
|
||||
$path = $this->quote->client->quote_filepath();
|
||||
|
||||
$file_path = $path . $this->quote->number . '.pdf';
|
||||
|
||||
$design = Design::find($this->quote->client->getSetting('quote_design_id'));
|
||||
|
||||
if($design->is_custom){
|
||||
$quote_design = new Custom($design->design);
|
||||
}
|
||||
else{
|
||||
$class = 'App\Designs\\'.$design->name;
|
||||
$quote_design = new $class();
|
||||
}
|
||||
|
||||
$designer = new Designer($quote_design, $this->quote->client->getSetting('pdf_variables'), 'quote');
|
||||
|
||||
//get invoice design
|
||||
$html = $this->generateInvoiceHtml($designer->build($this->quote)->getHtml(), $this->quote, $this->contact);
|
||||
|
||||
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
|
||||
Storage::makeDirectory($path, 0755);
|
||||
|
||||
//\Log::error($html);
|
||||
$pdf = $this->makePdf(null, null, $html);
|
||||
|
||||
$instance = Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
//$instance= Storage::disk($this->disk)->path($file_path);
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PDF stream
|
||||
*
|
||||
* @param string $header Header to be included in PDF
|
||||
* @param string $footer Footer to be included in PDF
|
||||
* @param string $html The HTML object to be converted into PDF
|
||||
*
|
||||
* @return string The PDF string
|
||||
*/
|
||||
private function makePdf($header, $footer, $html) {
|
||||
return Browsershot::html($html)
|
||||
//->showBrowserHeaderAndFooter()
|
||||
//->headerHtml($header)
|
||||
//->footerHtml($footer)
|
||||
->deviceScaleFactor(1)
|
||||
->showBackground()
|
||||
->waitUntilNetworkIdle(true) ->pdf();
|
||||
//->margins(10,10,10,10)
|
||||
//->savePdf('test.pdf');
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use App\Exceptions\ProcessingMigrationArchiveFailed;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@ -49,6 +50,9 @@ class StartMigration implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($this->filepath);
|
||||
|
||||
|
50
app/Listeners/Activity/PaymentRefundedActivity.php
Normal file
50
app/Listeners/Activity/PaymentRefundedActivity.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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\Listeners\Activity;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class PaymentRefundedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
$fields = new \stdClass;
|
||||
|
||||
$fields->client_id = $event->payment->id;
|
||||
$fields->user_id = $event->payment->user_id;
|
||||
$fields->company_id = $event->payment->company_id;
|
||||
$fields->activity_type_id = Activity::REFUNDED_PAYMENT;
|
||||
$fields->payment_id = $event->payment->id;
|
||||
|
||||
$this->activity_repo->save($fields, $event->client);
|
||||
}
|
||||
}
|
50
app/Listeners/Activity/PaymentVoidedActivity.php
Normal file
50
app/Listeners/Activity/PaymentVoidedActivity.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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\Listeners\Activity;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class PaymentVoidedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
$fields = new \stdClass;
|
||||
|
||||
$fields->client_id = $event->payment->id;
|
||||
$fields->user_id = $event->payment->user_id;
|
||||
$fields->company_id = $event->payment->company_id;
|
||||
$fields->activity_type_id = Activity::VOIDED_PAYMENT;
|
||||
$fields->payment_id = $event->payment->id;
|
||||
|
||||
$this->activity_repo->save($fields, $event->client);
|
||||
}
|
||||
}
|
@ -445,4 +445,13 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
return $this->client_hash . '/invoices/';
|
||||
}
|
||||
|
||||
public function quote_filepath()
|
||||
{
|
||||
return $this->client_hash . '/quotes/';
|
||||
}
|
||||
|
||||
public function credit_filepath()
|
||||
{
|
||||
return $this->client_hash . '/credits/';
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,11 @@ class Credit extends BaseModel
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function company_ledger()
|
||||
{
|
||||
return $this->morphMany(CompanyLedger::class, 'company_ledgerable');
|
||||
}
|
||||
|
||||
/**
|
||||
* The invoice/s which the credit has
|
||||
* been applied to.
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Credit;
|
||||
use App\Models\DateFormat;
|
||||
@ -181,5 +182,67 @@ class Payment extends BaseModel
|
||||
return $this->processRefund($data);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCompletedAmount() :float
|
||||
{
|
||||
return $this->amount - $this->refunded;
|
||||
}
|
||||
|
||||
public function recordRefund($amount = null)
|
||||
{
|
||||
//do i need $this->isRefunded() here?
|
||||
if ($this->isVoided()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//if no refund specified
|
||||
if (! $amount) {
|
||||
$amount = $this->amount;
|
||||
}
|
||||
|
||||
$new_refund = min($this->amount, $this->refunded + $amount);
|
||||
$refund_change = $new_refund - $this->refunded;
|
||||
|
||||
if ($refund_change) {
|
||||
$this->refunded = $new_refund;
|
||||
$this->status_id = $this->refunded == $this->amount ? self::STATUS_REFUNDED : self::STATUS_PARTIALLY_REFUNDED;
|
||||
$this->save();
|
||||
|
||||
event(new PaymentWasRefunded($this, $refund_change));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isVoided()
|
||||
{
|
||||
return $this->status_id == self::STATUS_VOIDED;
|
||||
}
|
||||
|
||||
public function isPartiallyRefunded()
|
||||
{
|
||||
return $this->status_id == self::STATUS_PARTIALLY_REFUNDED;
|
||||
}
|
||||
|
||||
public function isRefunded()
|
||||
{
|
||||
return $this->status_id == self::STATUS_REFUNDED;
|
||||
}
|
||||
|
||||
public function markVoided()
|
||||
{
|
||||
if ($this->isVoided() || $this->isPartiallyRefunded() || $this->isRefunded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->refunded = $this->amount;
|
||||
$this->status_id = self::STATUS_VOIDED;
|
||||
$this->save();
|
||||
|
||||
event(new PaymentWasVoided($this));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,13 +13,17 @@ namespace App\Models;
|
||||
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
use App\Jobs\Quote\CreateQuotePdf;
|
||||
use App\Models\Filterable;
|
||||
use App\Services\Quote\QuoteService;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceValues;
|
||||
use App\Utils\Traits\MakesReminders;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
class Quote extends BaseModel
|
||||
{
|
||||
@ -28,6 +32,7 @@ class Quote extends BaseModel
|
||||
use SoftDeletes;
|
||||
use MakesReminders;
|
||||
use PresentableTrait;
|
||||
use MakesInvoiceValues;
|
||||
|
||||
protected $presenter = 'App\Models\Presenters\QuotePresenter';
|
||||
|
||||
@ -135,7 +140,23 @@ class Quote extends BaseModel
|
||||
}
|
||||
|
||||
public function service(): QuoteService
|
||||
{
|
||||
return new QuoteService($this);
|
||||
}
|
||||
{
|
||||
return new QuoteService($this);
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
{
|
||||
$storage_path = 'storage/' . $this->client->quote_filepath() . $this->number . '.pdf';
|
||||
|
||||
if (Storage::exists($storage_path))
|
||||
return $storage_path;
|
||||
|
||||
if(!$invitation)
|
||||
CreateQuotePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
|
||||
else
|
||||
CreateQuotePdf::dispatchNow($invitation->quote, $invitation->company, $invitation->contact);
|
||||
|
||||
return $storage_path;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -141,8 +141,46 @@ class BasePaymentDriver
|
||||
* Refunds a given payment
|
||||
* @return void
|
||||
*/
|
||||
public function refundPayment()
|
||||
public function refundPayment($payment, $amount = 0)
|
||||
{
|
||||
if ($amount) {
|
||||
$amount = min($amount, $payment->getCompletedAmount());
|
||||
} else {
|
||||
$amount = $payment->getCompletedAmount();
|
||||
}
|
||||
|
||||
if ($payment->is_deleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $amount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($payment->type_id == Payment::TYPE_CREDIT_CARD) {
|
||||
return $payment->recordRefund($amount);
|
||||
}
|
||||
|
||||
$details = $this->refundDetails($payment, $amount);
|
||||
$response = $this->gateway()->refund($details)->send();
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
return $payment->recordRefund($amount);
|
||||
} elseif ($this->attemptVoidPayment($response, $payment, $amount)) {
|
||||
$details = ['transactionReference' => $payment->transaction_reference];
|
||||
$response = $this->gateway->void($details)->send();
|
||||
if ($response->isSuccessful()) {
|
||||
return $payment->markVoided();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function attemptVoidPayment($response, $payment, $amount)
|
||||
{
|
||||
// Partial refund not allowed for unsettled transactions
|
||||
return $amount == $payment->amount;
|
||||
}
|
||||
|
||||
public function authorizeCreditCardView(array $data)
|
||||
@ -246,4 +284,6 @@ class BasePaymentDriver
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,15 @@ use App\Events\Invoice\InvoiceWasPaid;
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Events\Payment\PaymentWasDeleted;
|
||||
use App\Events\Payment\PaymentWasRefunded;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Events\User\UserLoggedIn;
|
||||
use App\Events\User\UserWasCreated;
|
||||
use App\Listeners\Activity\CreatedClientActivity;
|
||||
use App\Listeners\Activity\PaymentCreatedActivity;
|
||||
use App\Listeners\Activity\PaymentDeletedActivity;
|
||||
use App\Listeners\Activity\PaymentRefundedActivity;
|
||||
use App\Listeners\Activity\PaymentVoidedActivity;
|
||||
use App\Listeners\Contact\UpdateContactLastLogin;
|
||||
use App\Listeners\Invoice\CreateInvoiceActivity;
|
||||
use App\Listeners\Invoice\CreateInvoiceHtmlBackup;
|
||||
@ -75,6 +79,12 @@ class EventServiceProvider extends ServiceProvider
|
||||
PaymentWasDeleted::class => [
|
||||
PaymentDeletedActivity::class,
|
||||
],
|
||||
PaymentWasRefunded::class => [
|
||||
PaymentRefundedActivity::class,
|
||||
],
|
||||
PaymentWasVoided::class => [
|
||||
PaymentVoidedActivity::class,
|
||||
],
|
||||
'App\Events\ClientWasArchived' => [
|
||||
'App\Listeners\ActivityListener@archivedClient',
|
||||
],
|
||||
|
@ -86,7 +86,7 @@ class CreditRepository extends BaseRepository
|
||||
* credit note
|
||||
*/
|
||||
|
||||
$credit = $credit->calc()->getInvoice();
|
||||
$credit = $credit->calc()->getCredit();
|
||||
|
||||
$credit->save();
|
||||
|
||||
|
@ -56,8 +56,8 @@ class InvoiceRepository extends BaseRepository {
|
||||
|
||||
if (isset($data['client_contacts'])) {
|
||||
foreach ($data['client_contacts'] as $contact) {
|
||||
if ($contact['send_email'] == 1) {
|
||||
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
|
||||
if ($contact['send_email'] == 1 && is_string($contact['id'])) {
|
||||
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
|
||||
$client_contact->send_email = true;
|
||||
$client_contact->save();
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class QuoteRepository extends BaseRepository
|
||||
|
||||
if (isset($data['client_contacts'])) {
|
||||
foreach ($data['client_contacts'] as $contact) {
|
||||
if ($contact['send_email'] == 1) {
|
||||
if ($contact['send_email'] == 1 && is_string($contact['id'])) {
|
||||
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
|
||||
$client_contact->send_email = true;
|
||||
$client_contact->save();
|
||||
@ -59,26 +59,29 @@ class QuoteRepository extends BaseRepository
|
||||
if (isset($data['invitations'])) {
|
||||
$invitations = collect($data['invitations']);
|
||||
|
||||
/* Get array of Keyss which have been removed from the invitations array and soft delete each invitation */
|
||||
/* Get array of Keys which have been removed from the invitations array and soft delete each invitation */
|
||||
collect($quote->invitations->pluck('key'))->diff($invitations->pluck('key'))->each(function ($invitation) {
|
||||
QuoteInvitation::destroy($invitation);
|
||||
});
|
||||
|
||||
$this->getInvitationByKey($invitation)->delete();
|
||||
});
|
||||
|
||||
foreach ($data['invitations'] as $invitation) {
|
||||
$inv = false;
|
||||
|
||||
if (array_key_exists('key', $invitation)) {
|
||||
$inv = QuoteInvitation::whereKey($invitation['key'])->first();
|
||||
$inv = $this->getInvitationByKey([$invitation['key']])->first();
|
||||
}
|
||||
|
||||
if (!$inv) {
|
||||
|
||||
if (isset($invitation['id'])) {
|
||||
unset($invitation['id']);
|
||||
}
|
||||
|
||||
$new_invitation = QuoteInvitationFactory::create($quote->company_id, $quote->user_id);
|
||||
$new_invitation->fill($invitation);
|
||||
$new_invitation->quote_id = $quote->id;
|
||||
$new_invitation->client_contact_id = $this->decodePrimaryKey($invitation['client_contact_id']);
|
||||
$new_invitation->save();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -88,7 +91,7 @@ class QuoteRepository extends BaseRepository
|
||||
$quote->service()->createInvitations();
|
||||
}
|
||||
|
||||
$quote = $quote->calc()->getInvoice();
|
||||
$quote = $quote->calc()->getQuote();
|
||||
|
||||
$quote->save();
|
||||
|
||||
@ -98,4 +101,10 @@ class QuoteRepository extends BaseRepository
|
||||
|
||||
return $quote->fresh();
|
||||
}
|
||||
|
||||
public function getInvitationByKey($key) :QuoteInvitation
|
||||
{
|
||||
return QuoteInvitation::whereRaw("BINARY `key`= ?", [$key])->first();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class RecurringQuoteRepository extends BaseRepository
|
||||
|
||||
$quote_calc = new InvoiceSum($quote, $quote->settings);
|
||||
|
||||
$quote = $quote_calc->build()->getInvoice();
|
||||
$quote = $quote_calc->build()->getQuote();
|
||||
|
||||
//fire events here that cascading from the saving of an Quote
|
||||
//ie. client balance update...
|
||||
|
65
app/Services/Ledger/LedgerService.php
Normal file
65
app/Services/Ledger/LedgerService.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?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\Services\Ledger;
|
||||
|
||||
use App\Models\CompanyLedger;
|
||||
|
||||
class LedgerService
|
||||
{
|
||||
|
||||
private $entity;
|
||||
|
||||
public function __construct($entity)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
public function updateInvoiceBalance($adjustment)
|
||||
{
|
||||
$balance = 0;
|
||||
|
||||
if ($this->ledger()) {
|
||||
$balance = $this->ledger->balance;
|
||||
}
|
||||
|
||||
$adjustment = $balance + $adjustment;
|
||||
|
||||
$company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id);
|
||||
$company_ledger->client_id = $this->entity->client_id;
|
||||
$company_ledger->adjustment = $adjustment;
|
||||
$company_ledger->balance = $balance + $adjustment;
|
||||
$company_ledger->save();
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function ledger() :CompanyLedger
|
||||
{
|
||||
|
||||
return CompanyLedger::whereClientId($this->entity->client_id)
|
||||
->whereCompanyId($this->entity->company_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
|
||||
$this->entity->save();
|
||||
|
||||
return $this->entity;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -77,6 +77,7 @@ class PaymentTransformer extends EntityTransformer
|
||||
'is_deleted' => (bool) $payment->is_deleted,
|
||||
'type_id' => (string) $payment->payment_type_id ?: '',
|
||||
'invitation_id' => (string) $payment->invitation_id ?: '',
|
||||
'private_notes' => (string) $payment->private_notes ?: '',
|
||||
'number' => (string) $payment->number ?: '',
|
||||
'client_id' => (string) $this->encodePrimaryKey($payment->client_id),
|
||||
'client_contact_id' => (string) $this->encodePrimaryKey($payment->client_contact_id),
|
||||
|
@ -298,10 +298,15 @@ trait GeneratesCounter
|
||||
*/
|
||||
private function incrementCounter($entity, string $counter_name) :void
|
||||
{
|
||||
|
||||
$settings = $entity->settings;
|
||||
$settings->$counter_name = $settings->$counter_name + 1;
|
||||
|
||||
$settings->{$counter_name} = $settings->{$counter_name} + 1;
|
||||
|
||||
$entity->settings = $settings;
|
||||
|
||||
$entity->save();
|
||||
|
||||
}
|
||||
|
||||
private function prefixCounter($counter, $prefix) : string
|
||||
|
@ -47,6 +47,9 @@ trait MakesInvoiceValues
|
||||
* @var array
|
||||
*/
|
||||
private static $labels = [
|
||||
'credit_balance',
|
||||
'credit_amount',
|
||||
'quote_total',
|
||||
'invoice',
|
||||
'date',
|
||||
'due_date',
|
||||
@ -312,8 +315,18 @@ trait MakesInvoiceValues
|
||||
// $data['$your_invoice'] = ;
|
||||
// $data['$quote'] = ;
|
||||
// $data['$your_quote'] = ;
|
||||
// $data['$quote_date'] = ;
|
||||
// $data['$quote_number'] = ;
|
||||
$data['$quote_date'] = &$data['$date'];
|
||||
$data['$quote_number'] = &$data['$number'];
|
||||
$data['$quote_no'] = &$data['$quote_number'];
|
||||
$data['$quote.quote_no'] = &$data['$quote_number'];
|
||||
$data['$valid_until'] = $this->due_date;
|
||||
$data['$quote_total'] = &$data['$total'];
|
||||
|
||||
|
||||
$data['$credit_amount'] = &$data['$total'];
|
||||
$data['$credit_balance'] = &$data['$balance'];
|
||||
$data['$credit.amount'] = &$data['$total'];
|
||||
|
||||
// $data['$invoice_issued_to'] = ;
|
||||
// $data['$quote_issued_to'] = ;
|
||||
// $data['$rate'] = ;
|
||||
@ -325,8 +338,6 @@ trait MakesInvoiceValues
|
||||
// $data['$details'] = ;
|
||||
$data['$invoice_no'] = $this->number ?: ' ';
|
||||
$data['$invoice.invoice_no'] = &$data['$invoice_no'];
|
||||
// $data['$quote_no'] = ;
|
||||
// $data['$valid_until'] = ;
|
||||
$data['$client1'] = $this->client->custom_value1 ?: ' ';
|
||||
$data['$client2'] = $this->client->custom_value2 ?: ' ';
|
||||
$data['$client3'] = $this->client->custom_value3 ?: ' ';
|
||||
@ -529,11 +540,13 @@ trait MakesInvoiceValues
|
||||
return str_replace(
|
||||
[
|
||||
'tax_name1',
|
||||
'tax_name2'
|
||||
'tax_name2',
|
||||
'tax_name3'
|
||||
],
|
||||
[
|
||||
'tax',
|
||||
'tax',
|
||||
'tax'
|
||||
],
|
||||
$columns
|
||||
);
|
||||
@ -558,7 +571,8 @@ trait MakesInvoiceValues
|
||||
'custom_invoice_label3',
|
||||
'custom_invoice_label4',
|
||||
'tax_name1',
|
||||
'tax_name2'
|
||||
'tax_name2',
|
||||
'tax_name3'
|
||||
],
|
||||
[
|
||||
'custom_invoice_value1',
|
||||
@ -566,7 +580,8 @@ trait MakesInvoiceValues
|
||||
'custom_invoice_value3',
|
||||
'custom_invoice_value4',
|
||||
'tax_rate1',
|
||||
'tax_rate2'
|
||||
'tax_rate2',
|
||||
'tax_rate3'
|
||||
],
|
||||
$columns
|
||||
);
|
||||
|
32
app/Utils/Traits/Pdf/PdfMaker.php
Normal file
32
app/Utils/Traits/Pdf/PdfMaker.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Utils\Traits\Pdf;
|
||||
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
|
||||
trait PdfMaker
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a PDF stream
|
||||
*
|
||||
* @param string $header Header to be included in PDF
|
||||
* @param string $footer Footer to be included in PDF
|
||||
* @param string $html The HTML object to be converted into PDF
|
||||
*
|
||||
* @return string The PDF string
|
||||
*/
|
||||
public function makePdf($header, $footer, $html) {
|
||||
return Browsershot::html($html)
|
||||
//->showBrowserHeaderAndFooter()
|
||||
//->headerHtml($header)
|
||||
//->footerHtml($footer)
|
||||
->deviceScaleFactor(1)
|
||||
->showBackground()
|
||||
->waitUntilNetworkIdle(true) ->pdf();
|
||||
//->margins(10,10,10,10)
|
||||
//->savePdf('test.pdf');
|
||||
}
|
||||
|
||||
}
|
@ -7,7 +7,7 @@ use Faker\Generator as Faker;
|
||||
$factory->define(App\Models\Quote::class, function (Faker $faker) {
|
||||
return [
|
||||
'status_id' => App\Models\Quote::STATUS_DRAFT,
|
||||
'number' => $faker->text(256),
|
||||
'number' => '',
|
||||
'discount' => $faker->numberBetween(1,10),
|
||||
'is_amount_discount' => $faker->boolean(),
|
||||
'tax_name1' => 'GST',
|
||||
|
@ -949,6 +949,7 @@ class CreateUsersTable extends Migration
|
||||
$t->string('transaction_reference')->nullable();
|
||||
$t->string('payer_id')->nullable();
|
||||
$t->string('number')->nullable();
|
||||
$t->text('private_notes')->nullable();
|
||||
$t->timestamps(6);
|
||||
$t->softDeletes('deleted_at', 6);
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
|
@ -207,7 +207,7 @@ class RandomDataSeeder extends Seeder
|
||||
else
|
||||
$credit_calc = new InvoiceSum($credit);
|
||||
|
||||
$credit = $credit_calc->build()->getInvoice();
|
||||
$credit = $credit_calc->build()->getCredit();
|
||||
|
||||
$credit->save();
|
||||
|
||||
|
@ -58,6 +58,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
|
||||
Route::resource('quotes', 'QuoteController');// name = (quotes. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('quotes/{quote}/{action}', 'QuoteController@action')->name('quotes.action');
|
||||
|
||||
Route::post('quotes/bulk', 'QuoteController@bulk')->name('quotes.bulk');
|
||||
|
||||
Route::resource('recurring_invoices', 'RecurringInvoiceController');// name = (recurring_invoices. index / create / show / update / destroy / edit
|
||||
|
@ -15,6 +15,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@ -27,6 +28,7 @@ class QuoteTest extends TestCase
|
||||
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
@ -39,67 +41,16 @@ class QuoteTest extends TestCase
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
}
|
||||
|
||||
public function testQuoteList()
|
||||
{
|
||||
$data = [
|
||||
'first_name' => $this->faker->firstName,
|
||||
'last_name' => $this->faker->lastName,
|
||||
'name' => $this->faker->company,
|
||||
'email' => $this->faker->unique()->safeEmail,
|
||||
'password' => 'ALongAndBrilliantPassword123',
|
||||
'_token' => csrf_token(),
|
||||
'privacy_policy' => 1,
|
||||
'terms_of_service' => 1
|
||||
];
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
])->post('/api/v1/signup?include=account', $data);
|
||||
|
||||
$acc = $response->json();
|
||||
|
||||
$account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id']));
|
||||
|
||||
$company_token = $account->default_company->tokens()->first();
|
||||
$token = $company_token->token;
|
||||
$company = $company_token->company;
|
||||
|
||||
$user = $company_token->user;
|
||||
|
||||
$this->assertNotNull($company_token);
|
||||
$this->assertNotNull($token);
|
||||
$this->assertNotNull($user);
|
||||
$this->assertNotNull($company);
|
||||
//$this->assertNotNull($user->token->company);
|
||||
|
||||
factory(\App\Models\Client::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
|
||||
|
||||
factory(\App\Models\ClientContact::class,1)->create([
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $c->id,
|
||||
'company_id' => $company->id,
|
||||
'is_primary' => 1
|
||||
]);
|
||||
|
||||
factory(\App\Models\ClientContact::class,1)->create([
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $c->id,
|
||||
'company_id' => $company->id
|
||||
]);
|
||||
|
||||
});
|
||||
$client = Client::all()->first();
|
||||
|
||||
factory(\App\Models\Quote::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/quotes');
|
||||
|
||||
$response->assertStatus(200);
|
||||
@ -108,72 +59,18 @@ class QuoteTest extends TestCase
|
||||
|
||||
public function testQuoteRESTEndPoints()
|
||||
{
|
||||
$data = [
|
||||
'first_name' => $this->faker->firstName,
|
||||
'last_name' => $this->faker->lastName,
|
||||
'name' => $this->faker->company,
|
||||
'email' => $this->faker->unique()->safeEmail,
|
||||
'password' => 'ALongAndBrilliantPassword123',
|
||||
'_token' => csrf_token(),
|
||||
'privacy_policy' => 1,
|
||||
'terms_of_service' => 1
|
||||
];
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
])->post('/api/v1/signup?include=account', $data);
|
||||
|
||||
$acc = $response->json();
|
||||
|
||||
$account = Account::find($this->decodePrimaryKey($acc['data'][0]['account']['id']));
|
||||
|
||||
$company_token = $account->default_company->tokens()->first();
|
||||
$token = $company_token->token;
|
||||
$company = $company_token->company;
|
||||
|
||||
$user = $company_token->user;
|
||||
|
||||
$this->assertNotNull($company_token);
|
||||
$this->assertNotNull($token);
|
||||
$this->assertNotNull($user);
|
||||
$this->assertNotNull($company);
|
||||
//$this->assertNotNull($user->token->company);
|
||||
|
||||
factory(\App\Models\Client::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
|
||||
|
||||
factory(\App\Models\ClientContact::class,1)->create([
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $c->id,
|
||||
'company_id' => $company->id,
|
||||
'is_primary' => 1
|
||||
]);
|
||||
|
||||
factory(\App\Models\ClientContact::class,1)->create([
|
||||
'user_id' => $user->id,
|
||||
'client_id' => $c->id,
|
||||
'company_id' => $company->id
|
||||
]);
|
||||
|
||||
});
|
||||
$client = Client::all()->first();
|
||||
|
||||
factory(\App\Models\Quote::class, 1)->create(['user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id]);
|
||||
|
||||
$quote = Quote::where('user_id',$user->id)->first();
|
||||
$quote->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
])->get('/api/v1/quotes/'.$this->encodePrimaryKey($quote->id));
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/quotes/'.$this->encodePrimaryKey($this->quote->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
])->get('/api/v1/quotes/'.$this->encodePrimaryKey($quote->id).'/edit');
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/quotes/'.$this->encodePrimaryKey($this->quote->id).'/edit');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
@ -182,26 +79,26 @@ class QuoteTest extends TestCase
|
||||
// 'client_id' => $this->encodePrimaryKey($quote->client_id),
|
||||
];
|
||||
|
||||
$this->assertNotNull($quote);
|
||||
$this->assertNotNull($this->quote);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
])->put('/api/v1/quotes/'.$this->encodePrimaryKey($quote->id), $quote_update);
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/quotes/'.$this->encodePrimaryKey($this->quote->id), $quote_update);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
])->delete('/api/v1/quotes/'.$this->encodePrimaryKey($quote->id));
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->delete('/api/v1/quotes/'.$this->encodePrimaryKey($this->quote->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$client_contact = ClientContact::whereClientId($client->id)->first();
|
||||
$client_contact = ClientContact::whereClientId($this->client->id)->first();
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->encodePrimaryKey($client->id),
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
'date' => "2019-12-14",
|
||||
'line_items' => [],
|
||||
'invitations' => [
|
||||
@ -212,7 +109,7 @@ class QuoteTest extends TestCase
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/quotes', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
73
tests/Integration/DesignTest.php
Normal file
73
tests/Integration/DesignTest.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Integration;
|
||||
|
||||
use App\Designs\Designer;
|
||||
use App\Designs\Modern;
|
||||
use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
use App\Jobs\Quote\CreateQuotePdf;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Designs\Designer
|
||||
*/
|
||||
class DesignTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testInvoiceDesignExists()
|
||||
{
|
||||
|
||||
$modern = new Modern();
|
||||
|
||||
$designer = new Designer($modern, $this->company->settings->pdf_variables, 'quote');
|
||||
|
||||
$html = $designer->build($this->invoice)->getHtml();
|
||||
|
||||
$this->assertNotNull($html);
|
||||
|
||||
//\Log::error($html);
|
||||
|
||||
$settings = $this->invoice->client->settings;
|
||||
$settings->invoice_design_id = "4";
|
||||
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
|
||||
}
|
||||
|
||||
public function testQuoteDesignExists()
|
||||
{
|
||||
|
||||
$modern = new Modern();
|
||||
|
||||
$designer = new Designer($modern, $this->company->settings->pdf_variables, 'quote');
|
||||
|
||||
$html = $designer->build($this->quote)->getHtml();
|
||||
|
||||
$this->assertNotNull($html);
|
||||
|
||||
//\Log::error($html);
|
||||
|
||||
$settings = $this->invoice->client->settings;
|
||||
$settings->quote_design_id = "4";
|
||||
|
||||
$this->client->settings = $settings;
|
||||
$this->client->save();
|
||||
|
||||
CreateQuotePdf::dispatchNow($this->quote, $this->quote->company, $this->quote->client->primary_contact()->first());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Integration;
|
||||
|
||||
use App\Designs\Designer;
|
||||
use App\Designs\Modern;
|
||||
use App\Jobs\Invoice\CreateInvoicePdf;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Designs\Designer
|
||||
*/
|
||||
class InvoiceDesignTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testDesignExists()
|
||||
{
|
||||
|
||||
$modern = new Modern();
|
||||
|
||||
$input_variables = [
|
||||
'client_details' => [
|
||||
'name',
|
||||
'id_number',
|
||||
'vat_number',
|
||||
'address1',
|
||||
'address2',
|
||||
'city_state_postal',
|
||||
'postal_city_state',
|
||||
'country',
|
||||
'email',
|
||||
'client1',
|
||||
'client2',
|
||||
'client3',
|
||||
'client4',
|
||||
'contact1',
|
||||
'contact2',
|
||||
'contact3',
|
||||
'contact4',
|
||||
],
|
||||
'company_details' => [
|
||||
'company_name',
|
||||
'id_number',
|
||||
'vat_number',
|
||||
'website',
|
||||
'email',
|
||||
'phone',
|
||||
'company1',
|
||||
'company2',
|
||||
'company3',
|
||||
'company4',
|
||||
],
|
||||
'company_address' => [
|
||||
'address1',
|
||||
'address2',
|
||||
'city_state_postal',
|
||||
'postal_city_state',
|
||||
'country',
|
||||
'company1',
|
||||
'company2',
|
||||
'company3',
|
||||
'company4',
|
||||
],
|
||||
'invoice_details' => [
|
||||
'invoice_number',
|
||||
'po_number',
|
||||
'date',
|
||||
'due_date',
|
||||
'balance_due',
|
||||
'invoice_total',
|
||||
'partial_due',
|
||||
'invoice1',
|
||||
'invoice2',
|
||||
'invoice3',
|
||||
'invoice4',
|
||||
'surcharge1',
|
||||
'surcharge2',
|
||||
'surcharge3',
|
||||
'surcharge4',
|
||||
],
|
||||
'table_columns' => [
|
||||
'product_key',
|
||||
'notes',
|
||||
'cost',
|
||||
'quantity',
|
||||
'discount',
|
||||
'tax_name1',
|
||||
'line_total'
|
||||
],
|
||||
];
|
||||
|
||||
$designer = new Designer($modern, $input_variables);
|
||||
|
||||
$html = $designer->build($this->invoice)->getHtml();
|
||||
|
||||
$this->assertNotNull($html);
|
||||
|
||||
//\Log::error($html);
|
||||
|
||||
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,10 @@ trait MockAccountData
|
||||
|
||||
public $token;
|
||||
|
||||
public $invoice;
|
||||
|
||||
public $quote;
|
||||
|
||||
public function makeTestData()
|
||||
{
|
||||
|
||||
@ -206,6 +210,29 @@ trait MockAccountData
|
||||
|
||||
$this->invoice->service()->markSent();
|
||||
|
||||
$this->quote = factory(\App\Models\Quote::class)->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$this->quote->line_items = $this->buildLineItems();
|
||||
$this->quote->uses_inclusive_taxes = false;
|
||||
|
||||
$this->quote->save();
|
||||
|
||||
$this->quote_calc = new InvoiceSum($this->quote);
|
||||
$this->quote_calc->build();
|
||||
|
||||
$this->quote = $this->quote_calc->getQuote();
|
||||
|
||||
$this->quote->number = $this->getNextQuoteNumber($this->client);
|
||||
|
||||
$this->quote->setRelation('client', $this->client);
|
||||
$this->quote->setRelation('company', $this->company);
|
||||
|
||||
$this->quote->save();
|
||||
|
||||
$this->credit = CreditFactory::create($this->company->id,$this->user->id);
|
||||
$this->credit->client_id = $this->client->id;
|
||||
|
||||
|
@ -89,11 +89,11 @@ class GeneratesCounterTest extends TestCase
|
||||
|
||||
$quote_number = $this->getNextQuoteNumber($this->client);
|
||||
|
||||
$this->assertEquals($quote_number, 0001);
|
||||
$this->assertEquals($quote_number, 0002);
|
||||
|
||||
$quote_number = $this->getNextQuoteNumber($this->client);
|
||||
|
||||
$this->assertEquals($quote_number, '0002');
|
||||
$this->assertEquals($quote_number, '0003');
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user