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:
David Bomba 2020-02-20 07:44:12 +11:00 committed by GitHub
parent 4a41685e94
commit 9e9cd37b87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 918 additions and 378 deletions

View File

@ -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);
}

View File

@ -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',

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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"),

View File

@ -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...

View File

@ -27,10 +27,4 @@ class EditInvoiceRequest extends Request
return auth()->user()->can('edit', $this->invoice);
}
public function rules()
{
$rules = [];
return $rules;
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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']) : [];

View File

@ -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');
}
}

View 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');
}
}

View File

@ -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);

View 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);
}
}

View 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);
}
}

View File

@ -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/';
}
}

View File

@ -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.

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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',
],

View File

@ -86,7 +86,7 @@ class CreditRepository extends BaseRepository
* credit note
*/
$credit = $credit->calc()->getInvoice();
$credit = $credit->calc()->getCredit();
$credit->save();

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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...

View 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;
}
}

View File

@ -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),

View File

@ -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

View File

@ -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 ?: '&nbsp;';
$data['$invoice.invoice_no'] = &$data['$invoice_no'];
// $data['$quote_no'] = ;
// $data['$valid_until'] = ;
$data['$client1'] = $this->client->custom_value1 ?: '&nbsp;';
$data['$client2'] = $this->client->custom_value2 ?: '&nbsp;';
$data['$client3'] = $this->client->custom_value3 ?: '&nbsp;';
@ -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
);

View 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');
}
}

View File

@ -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',

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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);

View 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());
}
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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');
}