diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index de8572747db0..7f5c97ebf293 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -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); } diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 1fa9337213ef..a52b397db4c1 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -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', diff --git a/app/Designs/Bold.php b/app/Designs/Bold.php index 9b0dc83c37a7..aaed1d075ddf 100644 --- a/app/Designs/Bold.php +++ b/app/Designs/Bold.php @@ -63,10 +63,10 @@ class Bold extends AbstractDesign
- $invoice_details_labels + $entity_labels
- $invoice_details + $entity_details
diff --git a/app/Designs/Clean.php b/app/Designs/Clean.php index f7012f7d4434..8fede5c9d024 100644 --- a/app/Designs/Clean.php +++ b/app/Designs/Clean.php @@ -65,10 +65,10 @@ class Clean extends AbstractDesign
- $invoice_details_labels + $entity_labels
- $invoice_details + $entity_details
$client_details diff --git a/app/Designs/Designer.php b/app/Designs/Designer.php index efbe702694d0..cfe1df77cf06 100644 --- a/app/Designs/Designer.php +++ b/app/Designs/Designer.php @@ -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' => '

$client.name

', @@ -193,7 +224,9 @@ class Designer { return $this->processCustomFields($company, $data); } - private function companyDetails(Company $company) { + private function companyDetails(Company $company) + { + $data = [ 'company_name' => '$company.company_name', 'id_number' => '$company.id_number', @@ -208,9 +241,11 @@ class Designer { ]; return $this->processCustomFields($company, $data); + } - private function companyAddress(Company $company) { + private function companyAddress(Company $company) + { $data = [ 'address1' => '$company.address1', @@ -225,9 +260,11 @@ class Designer { ]; return $this->processCustomFields($company, $data); + } - private function invoiceDetails(Company $company) { + private function invoiceDetails(Company $company) + { $data = [ 'invoice_number' => '$invoice_number', @@ -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' => '$quote_number', + 'po_number' => '$po_number', + 'date' => '$date', + 'valid_until' => '$valid_until', + 'balance_due' => '$balance_due', + 'quote_total' => '$quote_total', + 'partial_due' => '$partial_due', + 'quote1' => '$quote1', + 'quote2' => '$quote2', + 'quote3' => '$quote3', + 'quote4' => '$quote4', + 'surcharge1' => '$surcharge1', + 'surcharge2' => '$surcharge2', + 'surcharge3' => '$surcharge3', + 'surcharge4' => '$surcharge4', + ]; + + return $this->processCustomFields($company, $data); + + } + + private function creditDetails(Company $company) + { + + $data = [ + 'credit_number' => '$credit_number', + 'po_number' => '$po_number', + 'date' => '$date', + 'credit_balance' => '$credit_balance', + 'credit_amount' => '$credit_amount', + 'partial_due' => '$partial_due', + 'invoice1' => '$invoice1', + 'invoice2' => '$invoice2', + 'invoice3' => '$invoice3', + 'invoice4' => '$invoice4', + 'surcharge1' => '$surcharge1', + 'surcharge2' => '$surcharge2', + 'surcharge3' => '$surcharge3', + 'surcharge4' => '$surcharge4', + ]; + + 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; } + } \ No newline at end of file diff --git a/app/Designs/Modern.php b/app/Designs/Modern.php index c1fa9d1d1d3a..d9b62b978175 100644 --- a/app/Designs/Modern.php +++ b/app/Designs/Modern.php @@ -40,10 +40,10 @@ class Modern extends AbstractDesign
- $invoice_details_labels + $entity_labels
- $invoice_details + $entity_details
diff --git a/app/Designs/Photo.php b/app/Designs/Photo.php index 68a9e79185c1..dd7282a5deec 100644 --- a/app/Designs/Photo.php +++ b/app/Designs/Photo.php @@ -49,8 +49,8 @@ class Photo extends AbstractDesign
- $invoice_number_label - $invoice_number + $entity_labels + $entity_details
diff --git a/app/Designs/Plain.php b/app/Designs/Plain.php index 7b1bf71f574b..6b8dba9cb13c 100644 --- a/app/Designs/Plain.php +++ b/app/Designs/Plain.php @@ -47,10 +47,10 @@ class Plain extends AbstractDesign
$company_logo
- $invoice_details_labels + $entity_labels
- $invoice_details + $entity_details
diff --git a/app/Events/Payment/PaymentWasRefunded.php b/app/Events/Payment/PaymentWasRefunded.php index 4a75ded348a1..5bf2dbfa624a 100644 --- a/app/Events/Payment/PaymentWasRefunded.php +++ b/app/Events/Payment/PaymentWasRefunded.php @@ -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; } } diff --git a/app/Factory/CloneInvoiceFactory.php b/app/Factory/CloneInvoiceFactory.php index 707b1801f221..30e0bef1d929 100644 --- a/app/Factory/CloneInvoiceFactory.php +++ b/app/Factory/CloneInvoiceFactory.php @@ -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; diff --git a/app/Factory/CloneInvoiceToQuoteFactory.php b/app/Factory/CloneInvoiceToQuoteFactory.php index abee62f38e0b..73cb0b0cfcec 100644 --- a/app/Factory/CloneInvoiceToQuoteFactory.php +++ b/app/Factory/CloneInvoiceToQuoteFactory.php @@ -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; diff --git a/app/Factory/CloneQuoteFactory.php b/app/Factory/CloneQuoteFactory.php new file mode 100644 index 000000000000..fd005866ddd5 --- /dev/null +++ b/app/Factory/CloneQuoteFactory.php @@ -0,0 +1,34 @@ +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; + } +} diff --git a/app/Factory/CloneQuoteToInvoiceFactory.php b/app/Factory/CloneQuoteToInvoiceFactory.php index 56bf188ece3a..e06b92ee9285 100644 --- a/app/Factory/CloneQuoteToInvoiceFactory.php +++ b/app/Factory/CloneQuoteToInvoiceFactory.php @@ -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; } } diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index 7a7942cebc64..57c638470d24 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -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 diff --git a/app/Http/Controllers/OpenAPI/PaymentSchema.php b/app/Http/Controllers/OpenAPI/PaymentSchema.php index 654c7f4a247c..f8ca4898dab7 100644 --- a/app/Http/Controllers/OpenAPI/PaymentSchema.php +++ b/app/Http/Controllers/OpenAPI/PaymentSchema.php @@ -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"), diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 7c845addeb17..e334abb3a895 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -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... diff --git a/app/Http/Requests/Invoice/EditInvoiceRequest.php b/app/Http/Requests/Invoice/EditInvoiceRequest.php index ae679dd69afe..35fc0212f08d 100644 --- a/app/Http/Requests/Invoice/EditInvoiceRequest.php +++ b/app/Http/Requests/Invoice/EditInvoiceRequest.php @@ -27,10 +27,4 @@ class EditInvoiceRequest extends Request return auth()->user()->can('edit', $this->invoice); } - public function rules() - { - $rules = []; - - return $rules; - } } diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index dcfecee7dc76..47ed5629a3b1 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -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); diff --git a/app/Http/Requests/Quote/EditQuoteRequest.php b/app/Http/Requests/Quote/EditQuoteRequest.php index 94ebc46c918f..82afcd3a828a 100644 --- a/app/Http/Requests/Quote/EditQuoteRequest.php +++ b/app/Http/Requests/Quote/EditQuoteRequest.php @@ -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); - } } diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index bcd9d6c9676c..735970b033c7 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -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); } diff --git a/app/Http/Requests/Quote/UpdateQuoteRequest.php b/app/Http/Requests/Quote/UpdateQuoteRequest.php index 18aedba9c40f..9b761e8b1d17 100644 --- a/app/Http/Requests/Quote/UpdateQuoteRequest.php +++ b/app/Http/Requests/Quote/UpdateQuoteRequest.php @@ -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']) : []; diff --git a/app/Jobs/Invoice/CreateInvoicePdf.php b/app/Jobs/Invoice/CreateInvoicePdf.php index 1f57dd6414d6..58524715060c 100644 --- a/app/Jobs/Invoice/CreateInvoicePdf.php +++ b/app/Jobs/Invoice/CreateInvoicePdf.php @@ -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'); - } + } diff --git a/app/Jobs/Quote/CreateQuotePdf.php b/app/Jobs/Quote/CreateQuotePdf.php new file mode 100644 index 000000000000..98d991734cc4 --- /dev/null +++ b/app/Jobs/Quote/CreateQuotePdf.php @@ -0,0 +1,127 @@ +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'); + } +} diff --git a/app/Jobs/Util/StartMigration.php b/app/Jobs/Util/StartMigration.php index 95de600e4b8c..42e4066d3c66 100644 --- a/app/Jobs/Util/StartMigration.php +++ b/app/Jobs/Util/StartMigration.php @@ -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); diff --git a/app/Listeners/Activity/PaymentRefundedActivity.php b/app/Listeners/Activity/PaymentRefundedActivity.php new file mode 100644 index 000000000000..660461887c8a --- /dev/null +++ b/app/Listeners/Activity/PaymentRefundedActivity.php @@ -0,0 +1,50 @@ +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); + } +} diff --git a/app/Listeners/Activity/PaymentVoidedActivity.php b/app/Listeners/Activity/PaymentVoidedActivity.php new file mode 100644 index 000000000000..1dd6e56f4c76 --- /dev/null +++ b/app/Listeners/Activity/PaymentVoidedActivity.php @@ -0,0 +1,50 @@ +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); + } +} diff --git a/app/Models/Client.php b/app/Models/Client.php index 3ef8d16dce2f..91c333bfd248 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -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/'; + } } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index 9fb6673368bb..854eb8acd822 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -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. diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 6304a9b5887b..09d76117b17b 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -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)); + } } diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 1477e1ef5aeb..86633ecc9d11 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -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; + + } } diff --git a/app/PaymentDrivers/BasePaymentDriver.php b/app/PaymentDrivers/BasePaymentDriver.php index 32062249b5a4..0253fc1f9dbf 100644 --- a/app/PaymentDrivers/BasePaymentDriver.php +++ b/app/PaymentDrivers/BasePaymentDriver.php @@ -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; } + + } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index b32e43621de6..cbdbc97533e9 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -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', ], diff --git a/app/Repositories/CreditRepository.php b/app/Repositories/CreditRepository.php index 6f0119f62cc7..8b68219bb8aa 100644 --- a/app/Repositories/CreditRepository.php +++ b/app/Repositories/CreditRepository.php @@ -86,7 +86,7 @@ class CreditRepository extends BaseRepository * credit note */ - $credit = $credit->calc()->getInvoice(); + $credit = $credit->calc()->getCredit(); $credit->save(); diff --git a/app/Repositories/InvoiceRepository.php b/app/Repositories/InvoiceRepository.php index 00bb336327d4..a46ef46a1329 100644 --- a/app/Repositories/InvoiceRepository.php +++ b/app/Repositories/InvoiceRepository.php @@ -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(); } diff --git a/app/Repositories/QuoteRepository.php b/app/Repositories/QuoteRepository.php index c551d8b0def8..e6ec5d7b50b0 100644 --- a/app/Repositories/QuoteRepository.php +++ b/app/Repositories/QuoteRepository.php @@ -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(); + } + } diff --git a/app/Repositories/RecurringQuoteRepository.php b/app/Repositories/RecurringQuoteRepository.php index 6a21cd0328f8..ade9b554fb29 100644 --- a/app/Repositories/RecurringQuoteRepository.php +++ b/app/Repositories/RecurringQuoteRepository.php @@ -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... diff --git a/app/Services/Ledger/LedgerService.php b/app/Services/Ledger/LedgerService.php new file mode 100644 index 000000000000..6133cbbb45dc --- /dev/null +++ b/app/Services/Ledger/LedgerService.php @@ -0,0 +1,65 @@ +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; + + } + +} diff --git a/app/Transformers/PaymentTransformer.php b/app/Transformers/PaymentTransformer.php index 9dd6a5954ca9..263ac2eceb3d 100644 --- a/app/Transformers/PaymentTransformer.php +++ b/app/Transformers/PaymentTransformer.php @@ -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), diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index 81614ad4a15c..0d339280a98e 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -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 diff --git a/app/Utils/Traits/MakesInvoiceValues.php b/app/Utils/Traits/MakesInvoiceValues.php index 9f680211d4ea..0a83d67f76e7 100644 --- a/app/Utils/Traits/MakesInvoiceValues.php +++ b/app/Utils/Traits/MakesInvoiceValues.php @@ -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 ); diff --git a/app/Utils/Traits/Pdf/PdfMaker.php b/app/Utils/Traits/Pdf/PdfMaker.php new file mode 100644 index 000000000000..8a38a7c7e28d --- /dev/null +++ b/app/Utils/Traits/Pdf/PdfMaker.php @@ -0,0 +1,32 @@ +showBrowserHeaderAndFooter() + //->headerHtml($header) + //->footerHtml($footer) + ->deviceScaleFactor(1) + ->showBackground() + ->waitUntilNetworkIdle(true) ->pdf(); + //->margins(10,10,10,10) + //->savePdf('test.pdf'); + } + +} diff --git a/database/factories/QuoteFactory.php b/database/factories/QuoteFactory.php index 989654d44a52..dce38f24ceb4 100644 --- a/database/factories/QuoteFactory.php +++ b/database/factories/QuoteFactory.php @@ -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', diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index 9f70d4108f6a..728daa8d246e 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -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); diff --git a/database/seeds/RandomDataSeeder.php b/database/seeds/RandomDataSeeder.php index be3c7db89dd4..f110724058a3 100644 --- a/database/seeds/RandomDataSeeder.php +++ b/database/seeds/RandomDataSeeder.php @@ -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(); diff --git a/routes/api.php b/routes/api.php index ce8315e12826..cfb5c93c6d65 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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 diff --git a/tests/Feature/QuoteTest.php b/tests/Feature/QuoteTest.php index 0020acb573ee..427e02a6d2ea 100644 --- a/tests/Feature/QuoteTest.php +++ b/tests/Feature/QuoteTest.php @@ -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); diff --git a/tests/Integration/DesignTest.php b/tests/Integration/DesignTest.php new file mode 100644 index 000000000000..7303175068a5 --- /dev/null +++ b/tests/Integration/DesignTest.php @@ -0,0 +1,73 @@ +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()); + } + +} + + \ No newline at end of file diff --git a/tests/Integration/InvoiceDesignTest.php b/tests/Integration/InvoiceDesignTest.php deleted file mode 100644 index 41d9a4e2cec3..000000000000 --- a/tests/Integration/InvoiceDesignTest.php +++ /dev/null @@ -1,116 +0,0 @@ -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()); - } - - -} - - \ No newline at end of file diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 1bd28eca6fe5..87303517db5c 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -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; diff --git a/tests/Unit/GeneratesCounterTest.php b/tests/Unit/GeneratesCounterTest.php index 79264007134d..bbaff8e3fa8f 100644 --- a/tests/Unit/GeneratesCounterTest.php +++ b/tests/Unit/GeneratesCounterTest.php @@ -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'); }