mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
f4aeee59fa
@ -116,6 +116,9 @@ class CompanySettings extends BaseSettings
|
||||
public $project_number_pattern = ''; //@implemented
|
||||
public $project_number_counter = 1; //@implemented
|
||||
|
||||
public $purchase_order_number_pattern = ''; //@implemented
|
||||
public $purchase_order_number_counter = 1; //@implemented
|
||||
|
||||
public $shared_invoice_quote_counter = false; //@implemented
|
||||
public $shared_invoice_credit_counter = false; //@implemented
|
||||
public $recurring_number_prefix = ''; //@implemented
|
||||
@ -133,6 +136,13 @@ class CompanySettings extends BaseSettings
|
||||
public $invoice_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
public $quote_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
public $credit_design_id = 'Wpmbk5ezJn'; //@implemented
|
||||
|
||||
public $purchase_order_design_id = 'Wpmbk5ezJn';
|
||||
public $purchase_order_footer = ''; //@implemented
|
||||
public $purchase_order_terms = ''; //@implemented
|
||||
public $purchase_order_public_notes = ''; //@implemented
|
||||
public $require_purchase_order_signature = false; //@TODO ben to confirm
|
||||
|
||||
public $invoice_footer = ''; //@implemented
|
||||
public $credit_footer = ''; //@implemented
|
||||
public $credit_terms = ''; //@implemented
|
||||
@ -170,6 +180,8 @@ class CompanySettings extends BaseSettings
|
||||
public $email_subject_payment = ''; //@implemented
|
||||
public $email_subject_payment_partial = ''; //@implemented
|
||||
public $email_subject_statement = ''; //@implemented
|
||||
public $email_subject_purchase_order = ''; //@implemented
|
||||
public $email_template_purchase_order = ''; //@implemented
|
||||
public $email_template_invoice = ''; //@implemented
|
||||
public $email_template_credit = ''; //@implemented
|
||||
public $email_template_quote = ''; //@implemented
|
||||
@ -276,10 +288,16 @@ class CompanySettings extends BaseSettings
|
||||
public $email_from_name = '';
|
||||
public $auto_archive_invoice_cancelled = false;
|
||||
|
||||
|
||||
public $purchase_order_number_counter = 1; //TODO
|
||||
|
||||
public static $casts = [
|
||||
'email_subject_purchase_order' => 'string',
|
||||
'email_template_purchase_order' => 'string',
|
||||
'require_purchase_order_signature' => 'bool',
|
||||
'purchase_order_public_notes' => 'string',
|
||||
'purchase_order_terms' => 'string',
|
||||
'purchase_order_design_id' => 'string',
|
||||
'purchase_order_footer' => 'string',
|
||||
'purchase_order_number_pattern' => 'string',
|
||||
'purchase_order_number_counter' => 'int',
|
||||
'page_numbering_alignment' => 'string',
|
||||
'page_numbering' => 'bool',
|
||||
'auto_archive_invoice_cancelled' => 'bool',
|
||||
@ -531,6 +549,7 @@ class CompanySettings extends BaseSettings
|
||||
'invoice_design_id',
|
||||
'quote_design_id',
|
||||
'credit_design_id',
|
||||
'purchase_order_design_id',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -629,6 +648,25 @@ class CompanySettings extends BaseSettings
|
||||
'$client.phone',
|
||||
'$contact.email',
|
||||
],
|
||||
'vendor_details' => [
|
||||
'$vendor.name',
|
||||
'$vendor.number',
|
||||
'$vendor.vat_number',
|
||||
'$vendor.address1',
|
||||
'$vendor.address2',
|
||||
'$vendor.city_state_postal',
|
||||
'$vendor.country',
|
||||
'$vendor.phone',
|
||||
'$contact.email',
|
||||
],
|
||||
'purchase_order_details' => [
|
||||
'$purchase_order.number',
|
||||
'$purchase_order.po_number',
|
||||
'$purchase_order.date',
|
||||
'$purchase_order.due_date',
|
||||
'$purchase_order.total',
|
||||
'$purchase_order.balance_due',
|
||||
],
|
||||
'company_details' => [
|
||||
'$company.name',
|
||||
'$company.id_number',
|
||||
|
@ -12,7 +12,6 @@
|
||||
namespace App\Events\Invoice;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
@ -35,12 +34,12 @@ class InvoiceWasEmailedAndFailed
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param InvoiceInvitation $invitation
|
||||
* @param $invitation
|
||||
* @param Company $company
|
||||
* @param string $errors
|
||||
* @param array $event_vars
|
||||
*/
|
||||
public function __construct(InvoiceInvitation $invitation, Company $company, string $message, string $template, array $event_vars)
|
||||
public function __construct($invitation, Company $company, string $message, string $template, array $event_vars)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
|
||||
|
@ -52,7 +52,10 @@ class InvoiceItemSum
|
||||
|
||||
$this->invoice = $invoice;
|
||||
|
||||
if($this->invoice->client)
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
else
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
|
||||
$this->line_items = [];
|
||||
}
|
||||
|
@ -46,7 +46,10 @@ class InvoiceItemSumInclusive
|
||||
|
||||
$this->invoice = $invoice;
|
||||
|
||||
if($this->invoice->client)
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
else
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
|
||||
$this->line_items = [];
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ class InvoiceSum
|
||||
|
||||
private $gross_sub_total;
|
||||
|
||||
private $precision;
|
||||
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
*
|
||||
@ -53,8 +55,10 @@ class InvoiceSum
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
// if(!$this->invoice->relationLoaded('client'))
|
||||
// $this->invoice->load('client');
|
||||
if($this->invoice->client)
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
else
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
|
||||
$this->tax_map = new Collection;
|
||||
}
|
||||
@ -224,11 +228,19 @@ class InvoiceSum
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
public function getPurchaseOrder()
|
||||
{
|
||||
$this->setCalculatedAttributes();
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
public function getRecurringInvoice()
|
||||
{
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
@ -247,13 +259,13 @@ class InvoiceSum
|
||||
if ($this->invoice->amount != $this->invoice->balance) {
|
||||
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
|
||||
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
|
||||
} else {
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
|
||||
}
|
||||
}
|
||||
/* Set new calculated total */
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
|
||||
|
@ -41,6 +41,7 @@ class InvoiceSumInclusive
|
||||
|
||||
private $sub_total;
|
||||
|
||||
private $precision;
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
*
|
||||
@ -50,6 +51,11 @@ class InvoiceSumInclusive
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
if($this->invoice->client)
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
else
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
|
||||
$this->tax_map = new Collection;
|
||||
}
|
||||
|
||||
@ -164,32 +170,15 @@ class InvoiceSumInclusive
|
||||
|
||||
private function calculateTotals()
|
||||
{
|
||||
//$this->total += $this->total_taxes;
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value1)) {
|
||||
// $this->total += $this->invoice->custom_value1;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value2)) {
|
||||
// $this->total += $this->invoice->custom_value2;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value3)) {
|
||||
// $this->total += $this->invoice->custom_value3;
|
||||
// }
|
||||
|
||||
// if (is_numeric($this->invoice->custom_value4)) {
|
||||
// $this->total += $this->invoice->custom_value4;
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRecurringInvoice()
|
||||
{
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
@ -229,6 +218,15 @@ class InvoiceSumInclusive
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
public function getPurchaseOrder()
|
||||
{
|
||||
//Build invoice values here and return Invoice
|
||||
$this->setCalculatedAttributes();
|
||||
$this->invoice->saveQuietly();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build $this->invoice variables after
|
||||
* calculations have been performed.
|
||||
@ -240,14 +238,14 @@ class InvoiceSumInclusive
|
||||
if ($this->invoice->amount != $this->invoice->balance) {
|
||||
$paid_to_date = $this->invoice->amount - $this->invoice->balance;
|
||||
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision) - $paid_to_date;
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision) - $paid_to_date;
|
||||
} else {
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->balance = $this->formatValue($this->getTotal(), $this->precision);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set new calculated total */
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->invoice->client->currency()->precision);
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
|
||||
|
@ -709,7 +709,6 @@ class InvoiceController extends BaseController
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
$this->invoice_repo->restore($invoice);
|
||||
|
@ -14,12 +14,14 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
use App\Filters\PurchaseOrderFilters;
|
||||
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
|
||||
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
|
||||
use App\Jobs\Invoice\ZipInvoices;
|
||||
use App\Models\Client;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
@ -174,8 +176,6 @@ class PurchaseOrderController extends BaseController
|
||||
public function store(StorePurchaseOrderRequest $request)
|
||||
{
|
||||
|
||||
$client = Client::find($request->get('client_id'));
|
||||
|
||||
$purchase_order = $this->purchase_order_repository->save($request->all(), PurchaseOrderFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
$purchase_order = $purchase_order->service()
|
||||
@ -410,4 +410,221 @@ class PurchaseOrderController extends BaseController
|
||||
|
||||
return $this->itemResponse($purchase_order->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk actions on the list view.
|
||||
*
|
||||
* @return Collection
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/purchase_orders/bulk",
|
||||
* operationId="bulkPurchaseOrderss",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Performs bulk actions on an array of purchase_orders",
|
||||
* description="",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\RequestBody(
|
||||
* description="Purchase Order IDS",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* description="Array of hashed IDs to be bulk 'actioned",
|
||||
* example="[0,1,2,3]",
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Bulk Action response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$purchase_orders = PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
|
||||
|
||||
if (! $invoices) {
|
||||
return response()->json(['message' => 'No Purchase Orders Found']);
|
||||
}
|
||||
|
||||
/*
|
||||
* Download Purchase Order/s
|
||||
*/
|
||||
|
||||
if ($action == 'bulk_download' && $purchase_orders->count() > 1) {
|
||||
$purchase_orders->each(function ($purchase_order) {
|
||||
if (auth()->user()->cannot('view', $purchase_order)) {
|
||||
nlog("access denied");
|
||||
return response()->json(['message' => ctrans('text.access_denied')]);
|
||||
}
|
||||
});
|
||||
|
||||
ZipInvoices::dispatch($purchase_orders, $purchase_orders->first()->company, auth()->user());
|
||||
|
||||
return response()->json(['message' => ctrans('texts.sent_message')], 200);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send the other actions to the switch
|
||||
*/
|
||||
$purchase_orders->each(function ($purchase_order, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $purchase_order)) {
|
||||
$this->performAction($purchase_order, $action, true);
|
||||
}
|
||||
});
|
||||
|
||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
||||
|
||||
return $this->listResponse(PurchaseOrder::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/purchase_orders/{id}/{action}",
|
||||
* operationId="actionPurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Performs a custom action on an purchase order",
|
||||
* description="Performs a custom action on an purchase order.
|
||||
*
|
||||
* The current range of actions are as follows
|
||||
* - mark_paid
|
||||
* - download
|
||||
* - archive
|
||||
* - delete
|
||||
* - email",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Purchase Order Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="action",
|
||||
* in="path",
|
||||
* description="The action string to be performed",
|
||||
* example="clone_to_quote",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the invoice object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Invoice"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param ActionPurchaseOrderRequest $request
|
||||
* @param PurchaseOrder $purchase_order
|
||||
* @param $action
|
||||
* @return \App\Http\Controllers\Response|\Illuminate\Http\JsonResponse|Response|mixed|\Symfony\Component\HttpFoundation\StreamedResponse
|
||||
*/
|
||||
public function action(ActionPurchaseOrderRequest $request, PurchaseOrder $purchase_order, $action)
|
||||
{
|
||||
return $this->performAction($invoice, $action);
|
||||
}
|
||||
|
||||
private function performAction(PurchaseOrder $purchase_order, $action, $bulk = false)
|
||||
{
|
||||
/*If we are using bulk actions, we don't want to return anything */
|
||||
switch ($action) {
|
||||
case 'mark_sent':
|
||||
$purchase_order->service()->markSent()->save();
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->itemResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
case 'download':
|
||||
|
||||
$file = $purchase_order->service()->getPurchaseOrderPdf();
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
$this->purchase_order_repository->restore($purchase_order);
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->listResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
case 'archive':
|
||||
$this->purchase_order_repository->archive($purchase_order);
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->listResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
|
||||
$this->purchase_order_repository->delete($purchase_order);
|
||||
|
||||
if (! $bulk) {
|
||||
return $this->listResponse($purchase_order);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
|
||||
default:
|
||||
return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\PurchaseOrder;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class ActionPurchaseOrderRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private $error_msg;
|
||||
|
||||
// private $invoice;
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->purchase_order);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'action' => 'required'
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if($this->action){
|
||||
$input['action'] = $this->action;
|
||||
} elseif (!array_key_exists('action', $input) ) {
|
||||
$this->error_msg = 'Action is a required field';
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'action' => $this->error_msg,
|
||||
];
|
||||
}
|
||||
}
|
@ -38,8 +38,7 @@ class StorePurchaseOrderRequest extends Request
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = 'required';
|
||||
|
||||
$rules['vendor_id'] = 'required';
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', auth()->user()->company()->id)];
|
||||
$rules['discount'] = 'sometimes|numeric';
|
||||
|
@ -55,7 +55,6 @@ class UpdatePurchaseOrderRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
|
||||
$input['id'] = $this->purchase_order->id;
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Entity Ninja (https://entityninja.com).
|
||||
* Invoice Ninja (https://entityninja.com).
|
||||
*
|
||||
* @link https://github.com/entityninja/entityninja source repository
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Entity Ninja LLC (https://entityninja.com)
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
@ -49,8 +49,8 @@ class SystemMaintenance implements ShouldQueue
|
||||
|
||||
nlog("Starting System Maintenance");
|
||||
|
||||
// if(Ninja::isHosted())
|
||||
// return;
|
||||
if(Ninja::isHosted())
|
||||
return;
|
||||
|
||||
$delete_pdf_days = config('ninja.maintenance.delete_pdfs');
|
||||
|
||||
|
221
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
Normal file
221
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Vendor;
|
||||
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\Pdf\PDF;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
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\Lang;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use setasign\Fpdi\PdfParser\StreamReader;
|
||||
|
||||
class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
|
||||
|
||||
public $entity;
|
||||
|
||||
public $company;
|
||||
|
||||
public $contact;
|
||||
|
||||
private $disk;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public $entity_string = '';
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param $invitation
|
||||
*/
|
||||
public function __construct($invitation, $disk = 'public')
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->entity = $invitation->purchase_order;
|
||||
$this->entity_string = 'purchase_order';
|
||||
|
||||
$this->contact = $invitation->contact;
|
||||
|
||||
$this->vendor = $invitation->contact->vendor;
|
||||
$this->vendor->load('company');
|
||||
|
||||
$this->disk = Ninja::isHosted() ? config('filesystems.default') : $disk;
|
||||
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
/* Forget the singleton*/
|
||||
App::forgetInstance('translator');
|
||||
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->locale());
|
||||
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->generate($this->invitation);
|
||||
}
|
||||
|
||||
$entity_design_id = '';
|
||||
|
||||
$path = $this->vendor->purchase_order_filepath($this->invitation);
|
||||
$entity_design_id = 'purchase_order_design_id';
|
||||
|
||||
$file_path = $path.$this->entity->numberFormatter().'.pdf';
|
||||
|
||||
$entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn');
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if(!$design)
|
||||
$design = Design::find(2);
|
||||
|
||||
$html = new VendorHtmlEngine($this->invitation);
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true)
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => null,
|
||||
'vendor' => $this->vendor,
|
||||
'entity' => $this->entity,
|
||||
'pdf_variables' => (array) $this->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
]),
|
||||
'variables' => $variables,
|
||||
'options' => [
|
||||
'all_pages_header' => $this->entity->company->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $this->entity->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, $this->company);
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
|
||||
if (config('ninja.log_pdf_html')) {
|
||||
info($maker->getCompiledHTML());
|
||||
}
|
||||
|
||||
if ($pdf) {
|
||||
|
||||
try{
|
||||
|
||||
if(!Storage::disk($this->disk)->exists($path))
|
||||
Storage::disk($this->disk)->makeDirectory($path, 0775);
|
||||
|
||||
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
|
||||
|
||||
}
|
||||
catch(\Exception $e)
|
||||
{
|
||||
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
public function failed($e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
42
app/Models/Presenters/VendorContactPresenter.php
Normal file
42
app/Models/Presenters/VendorContactPresenter.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models\Presenters;
|
||||
|
||||
/**
|
||||
* Class VendorContactPresenter.
|
||||
*/
|
||||
class VendorContactPresenter extends EntityPresenter
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
$contact_name = $this->entity->first_name.' '.$this->entity->last_name;
|
||||
|
||||
if (strlen($contact_name) > 1) {
|
||||
return $contact_name;
|
||||
}
|
||||
|
||||
return $this->entity->vendor->present()->name();
|
||||
}
|
||||
|
||||
public function first_name()
|
||||
{
|
||||
return $this->entity->first_name ?: '';
|
||||
}
|
||||
|
||||
public function last_name()
|
||||
{
|
||||
return $this->entity->last_name ?: '';
|
||||
}
|
||||
}
|
@ -12,7 +12,10 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Services\PurchaseOrder\PurchaseOrderService;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -159,20 +162,20 @@ class PurchaseOrder extends BaseModel
|
||||
if(!$invitation)
|
||||
throw new \Exception('Hard fail, could not create an invitation - is there a valid contact?');
|
||||
|
||||
$file_path = $this->client->credit_filepath($invitation).$this->numberFormatter().'.pdf';
|
||||
$file_path = $this->vendor->purchase_order_filepath($invitation).$this->numberFormatter().'.pdf';
|
||||
|
||||
if(Ninja::isHosted() && $portal && Storage::disk(config('filesystems.default'))->exists($file_path)){
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
elseif(Ninja::isHosted() && $portal){
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation,config('filesystems.default'));
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if(Storage::disk('public')->exists($file_path))
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
||||
$file_path = CreateEntityPdf::dispatchNow($invitation);
|
||||
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation);
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
@ -211,4 +214,17 @@ class PurchaseOrder extends BaseModel
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
public function calc()
|
||||
{
|
||||
$purchase_order_calc = null;
|
||||
|
||||
if ($this->uses_inclusive_taxes) {
|
||||
$purchase_order_calc = new InvoiceSumInclusive($this);
|
||||
} else {
|
||||
$purchase_order_calc = new InvoiceSum($this);
|
||||
}
|
||||
|
||||
return $purchase_order_calc->build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Carbon\Carbon;
|
||||
@ -77,5 +78,30 @@ class PurchaseOrderInvitation extends BaseModel
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function getPortalLink() :string
|
||||
{
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$domain = $this->company->domain();
|
||||
else
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
case 'subdomain':
|
||||
return $domain.'/vendor/';
|
||||
break;
|
||||
case 'iframe':
|
||||
return $domain.'/vendor/';
|
||||
break;
|
||||
case 'domain':
|
||||
return $domain.'/vendor/';
|
||||
break;
|
||||
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,9 +11,13 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\Presenters\VendorPresenter;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
class Vendor extends BaseModel
|
||||
@ -22,6 +26,7 @@ class Vendor extends BaseModel
|
||||
use Filterable;
|
||||
use GeneratesCounter;
|
||||
use PresentableTrait;
|
||||
use AppSetup;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
@ -95,6 +100,22 @@ class Vendor extends BaseModel
|
||||
return $this->hasMany(Activity::class);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
$currencies = Cache::get('currencies');
|
||||
|
||||
if(!$currencies)
|
||||
$this->buildCache(true);
|
||||
|
||||
if(!$this->currency_id)
|
||||
$this->currency_id = 1;
|
||||
|
||||
return $currencies->filter(function ($item) {
|
||||
return $item->id == $this->currency_id;
|
||||
})->first();
|
||||
}
|
||||
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
@ -109,4 +130,54 @@ class Vendor extends BaseModel
|
||||
{
|
||||
return ctrans('texts.vendor');
|
||||
}
|
||||
|
||||
public function setCompanyDefaults($data, $entity_name) :array
|
||||
{
|
||||
$defaults = [];
|
||||
|
||||
if (! (array_key_exists('terms', $data) && strlen($data['terms']) > 1)) {
|
||||
$defaults['terms'] = $this->getSetting($entity_name.'_terms');
|
||||
} elseif (array_key_exists('terms', $data)) {
|
||||
$defaults['terms'] = $data['terms'];
|
||||
}
|
||||
|
||||
if (! (array_key_exists('footer', $data) && strlen($data['footer']) > 1)) {
|
||||
$defaults['footer'] = $this->getSetting($entity_name.'_footer');
|
||||
} elseif (array_key_exists('footer', $data)) {
|
||||
$defaults['footer'] = $data['footer'];
|
||||
}
|
||||
|
||||
if (strlen($this->public_notes) >= 1) {
|
||||
$defaults['public_notes'] = $this->public_notes;
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
public function getSetting($setting)
|
||||
{
|
||||
if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) {
|
||||
return $this->company->settings->{$setting};
|
||||
}
|
||||
|
||||
elseif( property_exists(CompanySettings::defaults(), $setting) ) {
|
||||
return CompanySettings::defaults()->{$setting};
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function purchase_order_filepath($invitation)
|
||||
{
|
||||
$contact_key = $invitation->contact->contact_key;
|
||||
|
||||
return $this->company->company_key.'/'.$this->vendor_hash.'/'.$contact_key.'/purchase_orders/';
|
||||
}
|
||||
|
||||
public function country()
|
||||
{
|
||||
return $this->belongsTo(Country::class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Presenters\VendorContactPresenter;
|
||||
use App\Notifications\ClientContactResetPassword;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
@ -35,6 +36,8 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
|
||||
protected $touches = ['vendor'];
|
||||
|
||||
protected $presenter = VendorContactPresenter::class;
|
||||
|
||||
/* Allow microtime timestamps */
|
||||
protected $dateFormat = 'Y-m-d H:i:s.u';
|
||||
|
||||
|
@ -11,9 +11,10 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
|
||||
use App\Factory\PurchaseOrderInvitationFactory;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderRepository extends BaseRepository
|
||||
@ -27,13 +28,81 @@ class PurchaseOrderRepository extends BaseRepository
|
||||
public function save(array $data, PurchaseOrder $purchase_order) : ?PurchaseOrder
|
||||
{
|
||||
$purchase_order->fill($data);
|
||||
|
||||
$purchase_order->save();
|
||||
|
||||
if (isset($data['invitations'])) {
|
||||
$invitations = collect($data['invitations']);
|
||||
|
||||
/* Get array of Keys which have been removed from the invitations array and soft delete each invitation */
|
||||
$purchase_order->invitations->pluck('key')->diff($invitations->pluck('key'))->each(function ($invitation) {
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('key', $invitation)->first();
|
||||
|
||||
if ($invitation)
|
||||
$invitation->delete();
|
||||
|
||||
});
|
||||
|
||||
foreach ($data['invitations'] as $invitation) {
|
||||
|
||||
//if no invitations are present - create one.
|
||||
if (! $this->getInvitation($invitation)) {
|
||||
|
||||
if (isset($invitation['id']))
|
||||
unset($invitation['id']);
|
||||
|
||||
//make sure we are creating an invite for a contact who belongs to the client only!
|
||||
$contact = VendorContact::find($invitation['vendor_contact_id']);
|
||||
|
||||
if ($contact && $purchase_order->vendor_id == $contact->vendor_id) {
|
||||
|
||||
$new_invitation = PurchaseOrderInvitation::withTrashed()
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $purchase_order->id)
|
||||
->first();
|
||||
|
||||
if ($new_invitation && $new_invitation->trashed()) {
|
||||
|
||||
$new_invitation->restore();
|
||||
|
||||
} else {
|
||||
|
||||
$new_invitation = PurchaseOrderInvitationFactory::create($purchase_order->company_id, $purchase_order->user_id);
|
||||
$new_invitation->purchase_order_id = $purchase_order->id;
|
||||
$new_invitation->vendor_contact_id = $contact->id;
|
||||
$new_invitation->key = $this->createDbHash($purchase_order->company->db);
|
||||
$new_invitation->save();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If no invitations have been created, this is our fail safe to maintain state*/
|
||||
if ($purchase_order->invitations()->count() == 0)
|
||||
$purchase_order->service()->createInvitations();
|
||||
|
||||
/* Recalculate invoice amounts */
|
||||
$purchase_order = $purchase_order->calc()->getPurchaseOrder();
|
||||
|
||||
return $purchase_order;
|
||||
}
|
||||
|
||||
public function getInvitationByKey($key) :?PurchaseOrderInvitation
|
||||
{
|
||||
return PurchaseOrderInvitation::where('key', $key)->first();
|
||||
}
|
||||
|
||||
public function getInvitation($invitation, $resource=null)
|
||||
{
|
||||
if (is_array($invitation) && ! array_key_exists('key', $invitation))
|
||||
return false;
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('key', $invitation['key'])->first();
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ class Design extends BaseDesign
|
||||
/** @var App\Models\Client */
|
||||
public $client;
|
||||
|
||||
/** @var App\Models\Vendor */
|
||||
public $vendor;
|
||||
|
||||
/** Global state of the design, @var array */
|
||||
public $context;
|
||||
|
||||
@ -69,6 +72,8 @@ class Design extends BaseDesign
|
||||
|
||||
const DELIVERY_NOTE = 'delivery_note';
|
||||
const STATEMENT = 'statement';
|
||||
const PURCHASE_ORDER = 'purchase_order';
|
||||
|
||||
|
||||
public function __construct(string $design = null, array $options = [])
|
||||
{
|
||||
@ -113,6 +118,10 @@ class Design extends BaseDesign
|
||||
'id' => 'client-details',
|
||||
'elements' => $this->clientDetails(),
|
||||
],
|
||||
'vendor-details' => [
|
||||
'id' => 'vendor-details',
|
||||
'elements' => $this->vendorDetails(),
|
||||
],
|
||||
'entity-details' => [
|
||||
'id' => 'entity-details',
|
||||
'elements' => $this->entityDetails(),
|
||||
@ -188,10 +197,29 @@ class Design extends BaseDesign
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function vendorDetails(): array
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
if(!$this->vendor)
|
||||
return $elements;
|
||||
|
||||
$variables = $this->context['pdf_variables']['vendor_details'];
|
||||
|
||||
foreach ($variables as $variable) {
|
||||
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function clientDetails(): array
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
if(!$this->client)
|
||||
return $elements;
|
||||
|
||||
if ($this->type == self::DELIVERY_NOTE) {
|
||||
$elements = [
|
||||
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
|
||||
@ -224,6 +252,8 @@ class Design extends BaseDesign
|
||||
|
||||
public function entityDetails(): array
|
||||
{
|
||||
|
||||
|
||||
if ($this->type === 'statement') {
|
||||
|
||||
$s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
|
||||
@ -258,6 +288,12 @@ class Design extends BaseDesign
|
||||
$variables = $this->context['pdf_variables']['credit_details'];
|
||||
}
|
||||
|
||||
if($this->vendor){
|
||||
|
||||
$variables = $this->context['pdf_variables']['purchase_order_details'];
|
||||
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
|
||||
// We don't want to show account balance or invoice total on PDF.. or any amount with currency.
|
||||
|
@ -30,6 +30,10 @@ trait DesignHelpers
|
||||
{
|
||||
$this->syncPdfVariables();
|
||||
|
||||
if (isset($this->context['vendor'])) {
|
||||
$this->vendor = $this->context['vendor'];
|
||||
}
|
||||
|
||||
if (isset($this->context['client'])) {
|
||||
$this->client = $this->context['client'];
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
@ -15,15 +22,15 @@ class ApplyNumber extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private Client $client;
|
||||
public Vendor $vendor;
|
||||
|
||||
private PurchaseOrder $purchase_order;
|
||||
|
||||
private bool $completed = true;
|
||||
|
||||
public function __construct(Client $client, PurchaseOrder $purchase_order)
|
||||
public function __construct(Vendor $vendor, PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->vendor = $vendor;
|
||||
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
@ -43,7 +50,7 @@ class ApplyNumber extends AbstractService
|
||||
$x=1;
|
||||
do{
|
||||
try{
|
||||
$this->purchase_order->number = $this->getNextPurchaseOrderNumber($this->client, $this->purchase_order);
|
||||
$this->purchase_order->number = $this->getNextPurchaseOrderNumber($this->purchase_order);
|
||||
$this->purchase_order->saveQuietly();
|
||||
$this->completed = false;
|
||||
}
|
||||
|
@ -1,10 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Factory\PurchaseOrderInvitationFactory;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Services\AbstractService;
|
||||
@ -21,17 +30,19 @@ class CreateInvitations extends AbstractService
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
private function createBlankContact()
|
||||
{
|
||||
$new_contact = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$new_contact->client_id = $this->purchase_order->client_id;
|
||||
$new_contact = VendorContactFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$new_contact->vendor_id = $this->purchase_order->vendor_id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->save();
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$contacts = $this->purchase_order->vendor->contacts;
|
||||
$contacts = $this->purchase_order->vendor->contacts()->where('send_email', true)->get();
|
||||
|
||||
if($contacts->count() == 0){
|
||||
$this->createBlankContact();
|
||||
@ -40,19 +51,25 @@ class CreateInvitations extends AbstractService
|
||||
$contacts = $this->purchase_order->vendor->contacts;
|
||||
}
|
||||
|
||||
|
||||
$contacts->each(function ($contact) {
|
||||
$invitation = PurchaseOrderInvitation::whereCompanyId($this->purchase_order->company_id)
|
||||
->whereClientContactId($contact->id)
|
||||
->whereCreditId($this->purchase_order->id)
|
||||
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $this->purchase_order->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
if (! $invitation) {
|
||||
$ii = PurchaseOrderInvitation::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
try{
|
||||
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii->key = $this->createDbHash($this->purchase_order->company->db);
|
||||
$ii->purchase_order_id = $this->purchase_order->id;
|
||||
$ii->vendor_contact_id = $contact->id;
|
||||
$ii->save();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
} elseif (! $contact->send_email) {
|
||||
$invitation->delete();
|
||||
}
|
||||
@ -66,7 +83,7 @@ class CreateInvitations extends AbstractService
|
||||
else{
|
||||
$contact = $contacts->first();
|
||||
|
||||
$invitation = PurchaseOrder::where('company_id', $this->purchase_order->company_id)
|
||||
$invitation = PurchaseOrderInvitation::where('company_id', $this->purchase_order->company_id)
|
||||
->where('vendor_contact_id', $contact->id)
|
||||
->where('purchase_order_id', $this->purchase_order->id)
|
||||
->withTrashed()
|
||||
@ -78,14 +95,13 @@ class CreateInvitations extends AbstractService
|
||||
}
|
||||
}
|
||||
|
||||
$ii = PurchaseOrderInvitation::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii = PurchaseOrderInvitationFactory::create($this->purchase_order->company_id, $this->purchase_order->user_id);
|
||||
$ii->key = $this->createDbHash($this->purchase_order->company->db);
|
||||
$ii->purchase_order_id = $this->purchase_order->id;
|
||||
$ii->vendor_contact_id = $contact->id;
|
||||
$ii->save();
|
||||
}
|
||||
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
}
|
||||
|
58
app/Services/PurchaseOrder/GetPurchaseOrderPdf.php
Normal file
58
app/Services/PurchaseOrder/GetPurchaseOrderPdf.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\VendorContact;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\TempFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class GetPurchaseOrderPdf extends AbstractService
|
||||
{
|
||||
public function __construct(PurchaseOrder $purchase_order, VendorContact $contact = null)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
|
||||
$this->contact = $contact;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
if (! $this->contact) {
|
||||
$this->contact = $this->purchase_order->vendor->contacts()->where('send_email', true)->first();
|
||||
}
|
||||
|
||||
$invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first();
|
||||
|
||||
if(!$invitation)
|
||||
$invitation = $this->purchase_order->invitations()->first();
|
||||
|
||||
$path = $this->purchase_order->vendor->purchase_order_filepath($invitation);
|
||||
|
||||
$file_path = $path.$this->purchase_order->numberFormatter().'.pdf';
|
||||
|
||||
// $disk = 'public';
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
if (! $file) {
|
||||
$file_path = CreatePurchaseOrderPdf::dispatchNow($invitation);
|
||||
}
|
||||
|
||||
return $file_path;
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
@ -10,13 +18,13 @@ use App\Utils\Ninja;
|
||||
|
||||
class MarkSent
|
||||
{
|
||||
private $client;
|
||||
private $vendor;
|
||||
|
||||
private $purchase_order;
|
||||
|
||||
public function __construct($client, $purchase_order)
|
||||
public function __construct($vendor, $purchase_order)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->vendor = $vendor;
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,9 @@ namespace App\Services\PurchaseOrder;
|
||||
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Services\PurchaseOrder\ApplyNumber;
|
||||
use App\Services\PurchaseOrder\CreateInvitations;
|
||||
use App\Services\PurchaseOrder\GetPurchaseOrderPdf;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PurchaseOrderService
|
||||
@ -21,11 +24,63 @@ class PurchaseOrderService
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct($purchase_order)
|
||||
public function __construct(PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function createInvitations()
|
||||
{
|
||||
|
||||
$this->purchase_order = (new CreateInvitations($this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applyNumber()
|
||||
{
|
||||
$this->invoice = (new ApplyNumber($this->purchase_order->vendor, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
// $settings = $this->purchase_order->client->getMergedSettings();
|
||||
|
||||
// //TODO implement design, footer, terms
|
||||
|
||||
|
||||
// /* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
|
||||
// if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
|
||||
// $this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
|
||||
|
||||
// if (!isset($this->purchase_order->public_notes))
|
||||
// $this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPurchaseOrderPdf($contact = null)
|
||||
{
|
||||
return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run();
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->purchase_order->status_id = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function markSent()
|
||||
{
|
||||
$this->purchase_order = (new MarkSent($this->purchase_order->vendor, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the purchase order.
|
||||
* @return \App\Models\PurchaseOrder object
|
||||
@ -37,45 +92,4 @@ class PurchaseOrderService
|
||||
return $this->purchase_order;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
$settings = $this->purchase_order->client->getMergedSettings();
|
||||
|
||||
//TODO implement design, footer, terms
|
||||
|
||||
|
||||
/* If client currency differs from the company default currency, then insert the client exchange rate on the model.*/
|
||||
if (!isset($this->purchase_order->exchange_rate) && $this->purchase_order->client->currency()->id != (int)$this->purchase_order->company->settings->currency_id)
|
||||
$this->purchase_order->exchange_rate = $this->purchase_order->client->currency()->exchange_rate;
|
||||
|
||||
if (!isset($this->purchase_order->public_notes))
|
||||
$this->purchase_order->public_notes = $this->purchase_order->client->public_notes;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->purchase_order->status_id = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function markSent()
|
||||
{
|
||||
$this->purchase_order = (new MarkSent($this->purchase_order->client, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Applies the purchase order number.
|
||||
* @return $this PurchaseOrderService object
|
||||
*/
|
||||
public function applyNumber()
|
||||
{
|
||||
$this->purchase_order = (new ApplyNumber($this->purchase_order->client, $this->purchase_order))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class Helpers
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function formatCustomFieldValue($custom_fields, $field, $value, Client $client = null): ?string
|
||||
public function formatCustomFieldValue($custom_fields, $field, $value, $entity = null): ?string
|
||||
{
|
||||
$custom_field = '';
|
||||
|
||||
@ -67,7 +67,7 @@ class Helpers
|
||||
|
||||
switch ($custom_field) {
|
||||
case 'date':
|
||||
return is_null($client) ? $value : $this->translateDate($value, $client->date_format(), $client->locale());
|
||||
return is_null($entity) ? $value : $this->translateDate($value, $entity->date_format(), $entity->locale());
|
||||
break;
|
||||
|
||||
case 'switch':
|
||||
@ -104,15 +104,15 @@ class Helpers
|
||||
* Process reserved keywords on PDF.
|
||||
*
|
||||
* @param string $value
|
||||
* @param Client $client
|
||||
* @param Client|Company $entity
|
||||
* @return null|string
|
||||
*/
|
||||
public static function processReservedKeywords(?string $value, Client $client): ?string
|
||||
public static function processReservedKeywords(?string $value, $entity): ?string
|
||||
{
|
||||
if(!$value)
|
||||
return '';
|
||||
|
||||
Carbon::setLocale($client->locale());
|
||||
Carbon::setLocale($entity->locale());
|
||||
|
||||
$replacements = [
|
||||
'literal' => [
|
||||
@ -121,21 +121,21 @@ class Helpers
|
||||
':QUARTER' => 'Q' . now()->quarter,
|
||||
':WEEK_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subDays(7)->translatedFormat($client->date_format()),
|
||||
Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->translatedFormat($client->date_format())
|
||||
Carbon::now()->translatedFormat($entity->date_format())
|
||||
),
|
||||
':WEEK_AHEAD' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->addDays(7)->translatedFormat($client->date_format()),
|
||||
Carbon::now()->addDays(7)->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(14)->translatedFormat($client->date_format())
|
||||
Carbon::now()->addDays(14)->translatedFormat($entity->date_format())
|
||||
),
|
||||
':WEEK' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($client->date_format()),
|
||||
Carbon::now()->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(7)->translatedFormat($client->date_format())
|
||||
Carbon::now()->addDays(7)->translatedFormat($entity->date_format())
|
||||
),
|
||||
],
|
||||
'raw' => [
|
||||
|
@ -158,6 +158,10 @@ trait GeneratesCounter
|
||||
return 'purchase_order_number_counter';
|
||||
break;
|
||||
|
||||
case PurchaseOrder::class:
|
||||
return 'purchase_order_number_counter';
|
||||
break;
|
||||
|
||||
default:
|
||||
return 'default_number_counter';
|
||||
break;
|
||||
@ -192,20 +196,6 @@ trait GeneratesCounter
|
||||
|
||||
return $this->replaceUserVars($credit, $entity_number);
|
||||
|
||||
}
|
||||
/**
|
||||
* Gets the next purchase order number.
|
||||
*
|
||||
* @param PurchaseOrder $purchase_order The purchase order
|
||||
*
|
||||
* @return string The next purchase order number.
|
||||
*/
|
||||
public function getNextPurchaseOrderNumber(Client $client, ?PurchaseOrder $purchase_order) :string
|
||||
{
|
||||
$entity_number = $this->getNextEntityNumber(PurchaseOrder::class, $client);
|
||||
|
||||
return $this->replaceUserVars($purchase_order, $entity_number);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -363,6 +353,23 @@ trait GeneratesCounter
|
||||
|
||||
}
|
||||
|
||||
public function getNextPurchaseOrderNumber(PurchaseOrder $purchase_order) :string
|
||||
{
|
||||
$this->resetCompanyCounters($purchase_order->company);
|
||||
|
||||
$counter = $purchase_order->company->settings->purchase_order_number_counter;
|
||||
$setting_entity = $purchase_order->company->settings->purchase_order_number_counter;
|
||||
|
||||
$purchase_order_number = $this->checkEntityNumber(PurchaseOrder::class, $purchase_order, $counter, $purchase_order->company->settings->counter_padding, $purchase_order->company->settings->purchase_order_number_pattern);
|
||||
|
||||
$this->incrementCounter($purchase_order->company, 'purchase_order_number_counter');
|
||||
|
||||
$entity_number = $purchase_order_number;
|
||||
|
||||
return $this->replaceUserVars($purchase_order, $entity_number);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next expense number.
|
||||
*
|
||||
|
714
app/Utils/VendorHtmlEngine.php
Normal file
714
app/Utils/VendorHtmlEngine.php
Normal file
@ -0,0 +1,714 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\transformTranslations;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class VendorHtmlEngine
|
||||
{
|
||||
use MakesDates;
|
||||
|
||||
public $entity;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public $vendor;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $company;
|
||||
|
||||
public $settings;
|
||||
|
||||
public $entity_calc;
|
||||
|
||||
public $entity_string;
|
||||
|
||||
private $helpers;
|
||||
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
|
||||
$this->entity_string = $this->resolveEntityString();
|
||||
|
||||
$this->entity = $invitation->purchase_order;
|
||||
|
||||
$this->company = $invitation->company;
|
||||
|
||||
$this->contact = $invitation->contact->load('vendor');
|
||||
|
||||
$this->vendor = $this->contact->vendor->load('company','country');
|
||||
|
||||
$this->entity->load('vendor');
|
||||
|
||||
$this->settings = $this->company->settings;
|
||||
|
||||
$this->entity_calc = $this->entity->calc();
|
||||
|
||||
$this->helpers = new Helpers();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private function resolveEntityString()
|
||||
{
|
||||
switch ($this->invitation) {
|
||||
case ($this->invitation instanceof InvoiceInvitation):
|
||||
return 'invoice';
|
||||
break;
|
||||
case ($this->invitation instanceof CreditInvitation):
|
||||
return 'credit';
|
||||
break;
|
||||
case ($this->invitation instanceof QuoteInvitation):
|
||||
return 'quote';
|
||||
break;
|
||||
case ($this->invitation instanceof RecurringInvoiceInvitation):
|
||||
return 'recurring_invoice';
|
||||
break;
|
||||
case ($this->invitation instanceof PurchaseOrderInvitation):
|
||||
return 'purchase_order';
|
||||
break;
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function buildEntityDataArray() :array
|
||||
{
|
||||
if (! $this->vendor->currency()) {
|
||||
throw new Exception(debug_backtrace()[1]['function'], 1);
|
||||
exit;
|
||||
}
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($this->company->locale());
|
||||
$t->replace(Ninja::transformTranslations($this->settings));
|
||||
|
||||
$data = [];
|
||||
$data['$global_margin'] = ['value' => '6.35mm', 'label' => ''];
|
||||
$data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => ''];
|
||||
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
|
||||
$data['$to'] = ['value' => '', 'label' => ctrans('texts.to')];
|
||||
$data['$total_tax_labels'] = ['value' => $this->totalTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$total_tax_values'] = ['value' => $this->totalTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.due_date')];
|
||||
|
||||
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
$data['$dueDate'] = &$data['$due_date'];
|
||||
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
$data['$poNumber'] = ['value' => $this->entity->po_number, 'label' => ctrans('texts.po_number')];
|
||||
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format(), $this->company->locale()), 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')];
|
||||
$data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')];
|
||||
|
||||
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.purchase_order_number_short')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$paymentButton'] = &$data['$payment_button'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$purchase_order.number'] = &$data['$number'];
|
||||
$data['$purchase_order.date'] = &$data['$date'];
|
||||
$data['$purchase_order.po_number'] = &$data['$poNumber'];
|
||||
$data['$purchase_order.due_date'] = &$data['$due_date'];
|
||||
|
||||
$data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>''];
|
||||
|
||||
$data['$entity_number'] = &$data['$number'];
|
||||
$data['$discount'] = ['value' => $this->entity->discount, 'label' => ctrans('texts.discount')];
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
|
||||
if($this->entity->uses_inclusive_taxes)
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
else
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->vendor) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
} else {
|
||||
|
||||
if($this->entity->status_id == 1){
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
else{
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->vendor) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
}
|
||||
|
||||
// $data['$balance_due'] = $data['$balance_due'];
|
||||
$data['$outstanding'] = &$data['$balance_due'];
|
||||
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$partial'] = &$data['$partial_due'];
|
||||
|
||||
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->vendor) ?: ' ', 'label' => ctrans('texts.total')];
|
||||
|
||||
$data['$purchase_order.total'] = &$data['$total'];
|
||||
|
||||
$data['$amount'] = &$data['$total'];
|
||||
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->vendor) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
|
||||
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->vendor) ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
|
||||
$data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')];
|
||||
$data['$user.last_name'] = ['value' => $this->entity->user->last_name, 'label' => ctrans('texts.last_name')];
|
||||
$data['$created_by_user'] = &$data['$user.name'];
|
||||
$data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')];
|
||||
|
||||
$data['$public_notes'] = ['value' => $this->entity->public_notes, 'label' => ctrans("texts.public_notes")];
|
||||
$data['$entity.public_notes'] = &$data['$public_notes'];
|
||||
$data['$notes'] = &$data['$public_notes'];
|
||||
|
||||
$data['$purchase_order.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order1', $this->entity->custom_value1, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order1')];
|
||||
$data['$purchase_order.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order2', $this->entity->custom_value2, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order2')];
|
||||
$data['$purchase_order.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order3', $this->entity->custom_value3, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order3')];
|
||||
$data['$purchase_order.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'purchase_order4', $this->entity->custom_value4, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'purchase_order4')];
|
||||
|
||||
$data['$vendor1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor1', $this->vendor->custom_value1, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor1')];
|
||||
$data['$vendor2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor2', $this->vendor->custom_value2, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor2')];
|
||||
$data['$vendor3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor3', $this->vendor->custom_value3, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor3')];
|
||||
$data['$vendor4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor4', $this->vendor->custom_value4, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor4')];
|
||||
$data['$vendor.custom1'] = &$data['$vendor1'];
|
||||
$data['$vendor.custom2'] = &$data['$vendor2'];
|
||||
$data['$vendor.custom3'] = &$data['$vendor3'];
|
||||
$data['$vendor.custom4'] = &$data['$vendor4'];
|
||||
$data['$address1'] = ['value' => $this->vendor->address1 ?: ' ', 'label' => ctrans('texts.address1')];
|
||||
$data['$address2'] = ['value' => $this->vendor->address2 ?: ' ', 'label' => ctrans('texts.address2')];
|
||||
$data['$id_number'] = ['value' => $this->vendor->id_number ?: ' ', 'label' => ctrans('texts.id_number')];
|
||||
$data['$vendor.number'] = ['value' => $this->vendor->number ?: ' ', 'label' => ctrans('texts.number')];
|
||||
$data['$vat_number'] = ['value' => $this->vendor->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')];
|
||||
$data['$website'] = ['value' => $this->vendor->present()->website() ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$phone'] = ['value' => $this->vendor->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')];
|
||||
$data['$country'] = ['value' => isset($this->vendor->country->name) ? ctrans('texts.country_' . $this->vendor->country->name) : '', 'label' => ctrans('texts.country')];
|
||||
$data['$country_2'] = ['value' => isset($this->vendor->country) ? $this->vendor->country->iso_3166_2 : '', 'label' => ctrans('texts.country')];
|
||||
$data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')];
|
||||
|
||||
if(str_contains($data['$email']['value'], 'example.com'))
|
||||
$data['$email'] = ['value' => '', 'label' => ctrans('texts.email')];
|
||||
|
||||
$data['$vendor_name'] = ['value' => $this->vendor->present()->name() ?: ' ', 'label' => ctrans('texts.vendor_name')];
|
||||
$data['$vendor.name'] = &$data['$vendor_name'];
|
||||
$data['$vendor'] = &$data['$vendor_name'];
|
||||
|
||||
$data['$vendor.address1'] = &$data['$address1'];
|
||||
$data['$vendor.address2'] = &$data['$address2'];
|
||||
$data['$vendor_address'] = ['value' => $this->vendor->present()->address() ?: ' ', 'label' => ctrans('texts.address')];
|
||||
$data['$vendor.address'] = &$data['$vendor_address'];
|
||||
$data['$vendor.postal_code'] = ['value' => $this->vendor->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')];
|
||||
$data['$vendor.public_notes'] = ['value' => $this->vendor->public_notes ?: ' ', 'label' => ctrans('texts.notes')];
|
||||
$data['$vendor.city'] = ['value' => $this->vendor->city ?: ' ', 'label' => ctrans('texts.city')];
|
||||
$data['$vendor.state'] = ['value' => $this->vendor->state ?: ' ', 'label' => ctrans('texts.state')];
|
||||
$data['$vendor.id_number'] = &$data['$id_number'];
|
||||
$data['$vendor.vat_number'] = &$data['$vat_number'];
|
||||
$data['$vendor.website'] = &$data['$website'];
|
||||
$data['$vendor.phone'] = &$data['$phone'];
|
||||
$data['$city_state_postal'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, $this->vendor->state, $this->vendor->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')];
|
||||
$data['$vendor.city_state_postal'] = &$data['$city_state_postal'];
|
||||
$data['$postal_city_state'] = ['value' => $this->vendor->present()->cityStateZip($this->vendor->city, $this->vendor->state, $this->vendor->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')];
|
||||
$data['$vendor.postal_city_state'] = &$data['$postal_city_state'];
|
||||
$data['$vendor.country'] = &$data['$country'];
|
||||
$data['$vendor.email'] = &$data['$email'];
|
||||
|
||||
$data['$vendor.billing_address'] = &$data['$vendor_address'];
|
||||
$data['$vendor.billing_address1'] = &$data['$vendor.address1'];
|
||||
$data['$vendor.billing_address2'] = &$data['$vendor.address2'];
|
||||
$data['$vendor.billing_city'] = &$data['$vendor.city'];
|
||||
$data['$vendor.billing_state'] = &$data['$vendor.state'];
|
||||
$data['$vendor.billing_postal_code'] = &$data['$vendor.postal_code'];
|
||||
$data['$vendor.billing_country'] = &$data['$vendor.country'];
|
||||
|
||||
$data['$vendor.currency'] = ['value' => $this->vendor->currency()->code, 'label' => ''];
|
||||
|
||||
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->vendor), 'label' => ctrans('texts.paid_to_date')];
|
||||
|
||||
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$contact'] = &$data['$contact.full_name'];
|
||||
|
||||
$data['$contact.email'] = &$data['$email'];
|
||||
$data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')];
|
||||
|
||||
$data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->vendor->present()->name(), 'label' => ctrans('texts.contact_name')];
|
||||
$data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')];
|
||||
$data['$firstName'] = &$data['$contact.first_name'];
|
||||
|
||||
$data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')];
|
||||
|
||||
$data['$contact.custom1'] = ['value' => isset($this->contact) ? $this->contact->custom_value1 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact1')];
|
||||
$data['$contact.custom2'] = ['value' => isset($this->contact) ? $this->contact->custom_value2 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact2')];
|
||||
$data['$contact.custom3'] = ['value' => isset($this->contact) ? $this->contact->custom_value3 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact3')];
|
||||
$data['$contact.custom4'] = ['value' => isset($this->contact) ? $this->contact->custom_value4 : ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'contact4')];
|
||||
|
||||
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')];
|
||||
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')];
|
||||
$data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')];
|
||||
$data['$account'] = &$data['$company.name'];
|
||||
|
||||
$data['$company.address1'] = ['value' => $this->settings->address1 ?: ' ', 'label' => ctrans('texts.address1')];
|
||||
$data['$company.address2'] = ['value' => $this->settings->address2 ?: ' ', 'label' => ctrans('texts.address2')];
|
||||
$data['$company.city'] = ['value' => $this->settings->city ?: ' ', 'label' => ctrans('texts.city')];
|
||||
$data['$company.state'] = ['value' => $this->settings->state ?: ' ', 'label' => ctrans('texts.state')];
|
||||
$data['$company.postal_code'] = ['value' => $this->settings->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')];
|
||||
$data['$company.country'] = ['value' => $this->getCountryName(), 'label' => ctrans('texts.country')];
|
||||
$data['$company.country_2'] = ['value' => $this->getCountryCode(), 'label' => ctrans('texts.country')];
|
||||
$data['$company.phone'] = ['value' => $this->settings->phone ?: ' ', 'label' => ctrans('texts.phone')];
|
||||
$data['$company.email'] = ['value' => $this->settings->email ?: ' ', 'label' => ctrans('texts.email')];
|
||||
$data['$company.vat_number'] = ['value' => $this->settings->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')];
|
||||
$data['$company.id_number'] = ['value' => $this->settings->id_number ?: ' ', 'label' => ctrans('texts.id_number')];
|
||||
$data['$company.website'] = ['value' => $this->settings->website ?: ' ', 'label' => ctrans('texts.website')];
|
||||
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: ' ', 'label' => ctrans('texts.address')];
|
||||
|
||||
$data['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => ''];
|
||||
$data['$emailSignature'] = &$data['$signature'];
|
||||
|
||||
$logo = $this->company->present()->logo_base64($this->settings);
|
||||
|
||||
$data['$company.logo'] = ['value' => $logo ?: ' ', 'label' => ctrans('texts.logo')];
|
||||
$data['$company_logo'] = &$data['$company.logo'];
|
||||
$data['$company1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
|
||||
$data['$company2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company2', $this->settings->custom_value2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company2')];
|
||||
$data['$company3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company3', $this->settings->custom_value3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company3')];
|
||||
$data['$company4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company4', $this->settings->custom_value4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')];
|
||||
|
||||
$data['$company.custom1'] = &$data['$company1'];
|
||||
$data['$company.custom2'] = &$data['$company2'];
|
||||
$data['$company.custom3'] = &$data['$company3'];
|
||||
$data['$company.custom4'] = &$data['$company4'];
|
||||
|
||||
$data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
|
||||
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
|
||||
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
|
||||
$data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->vendor) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')];
|
||||
|
||||
$data['$product.item'] = ['value' => '', 'label' => ctrans('texts.item')];
|
||||
$data['$product.date'] = ['value' => '', 'label' => ctrans('texts.date')];
|
||||
$data['$product.discount'] = ['value' => '', 'label' => ctrans('texts.discount')];
|
||||
$data['$product.product_key'] = ['value' => '', 'label' => ctrans('texts.product_key')];
|
||||
$data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
$data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')];
|
||||
$data['$product.quantity'] = ['value' => '', 'label' => ctrans('texts.quantity')];
|
||||
$data['$product.tax_name1'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax_name2'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.tax_name3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$product.line_total'] = ['value' => '', 'label' => ctrans('texts.line_total')];
|
||||
$data['$product.gross_line_total'] = ['value' => '', 'label' => ctrans('texts.gross_line_total')];
|
||||
$data['$product.description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
$data['$product.unit_cost'] = ['value' => '', 'label' => ctrans('texts.unit_cost')];
|
||||
$data['$product.product1'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product1')];
|
||||
$data['$product.product2'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product2')];
|
||||
$data['$product.product3'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product3')];
|
||||
$data['$product.product4'] = ['value' => '', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'product4')];
|
||||
|
||||
if ($this->settings->signature_on_pdf) {
|
||||
$data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')];
|
||||
} else {
|
||||
$data['$contact.signature'] = ['value' => '', 'label' => ''];
|
||||
}
|
||||
|
||||
$data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')];
|
||||
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
|
||||
$data['$to'] = ['value' => '', 'label' => ctrans('texts.to')];
|
||||
$data['$details'] = ['value' => '', 'label' => ctrans('texts.details')];
|
||||
|
||||
$data['_rate1'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['_rate2'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['_rate3'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
|
||||
$data['$font_size'] = ['value' => $this->settings->font_size . 'px', 'label' => ''];
|
||||
$data['$font_name'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['name'], 'label' => ''];
|
||||
$data['$font_url'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['url'], 'label' => ''];
|
||||
|
||||
$data['$invoiceninja.whitelabel'] = ['value' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', 'label' => ''];
|
||||
|
||||
$data['$primary_color'] = ['value' => $this->settings->primary_color, 'label' => ''];
|
||||
$data['$secondary_color'] = ['value' => $this->settings->secondary_color, 'label' => ''];
|
||||
|
||||
$data['$item'] = ['value' => '', 'label' => ctrans('texts.item')];
|
||||
$data['$description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
|
||||
$data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer), $this->company), 'label' => ''];
|
||||
$data['$footer'] = &$data['$entity_footer'];
|
||||
|
||||
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
|
||||
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
|
||||
|
||||
$data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => ''];
|
||||
$data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => ''];
|
||||
$data['$auto_bill'] = &$data['$autoBill'];
|
||||
|
||||
/*Payment Aliases*/
|
||||
$data['$paymentLink'] = &$data['$payment_link'];
|
||||
$data['$payment_url'] = &$data['$payment_link'];
|
||||
$data['$portalButton'] = &$data['$paymentLink'];
|
||||
|
||||
$data['$dir'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'rtl' : 'ltr', 'label' => ''];
|
||||
$data['$dir_text_align'] = ['value' => optional($this->company->language())->locale === 'ar' ? 'right' : 'left', 'label' => ''];
|
||||
|
||||
$data['$payment.date'] = ['value' => ' ', 'label' => ctrans('texts.payment_date')];
|
||||
$data['$method'] = ['value' => ' ', 'label' => ctrans('texts.method')];
|
||||
|
||||
$data['$statement_amount'] = ['value' => '', 'label' => ctrans('texts.amount')];
|
||||
$data['$statement'] = ['value' => '', 'label' => ctrans('texts.statement')];
|
||||
|
||||
$data['$entity_images'] = ['value' => $this->generateEntityImagesMarkup(), 'label' => ''];
|
||||
|
||||
$data['$payments'] = ['value' => '', 'label' => ctrans('texts.payments')];
|
||||
|
||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function makeValues() :array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$values = $this->buildEntityDataArray();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$data[$key] = $value['value'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function generateLabelsAndValues()
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$values = $this->buildEntityDataArray();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$data['values'][$key] = $value['value'];
|
||||
$data['labels'][$key.'_label'] = $value['label'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function totalTaxLabels() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (! $this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<span>'.$tax['name'].'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function totalTaxValues() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (! $this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $this->vendor).'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function lineTaxLabels() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<span>'.$tax['name'].'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getCountryName() :string
|
||||
{
|
||||
$country = Country::find($this->settings->country_id);
|
||||
|
||||
if ($country) {
|
||||
return ctrans('texts.country_' . $country->name);
|
||||
}
|
||||
|
||||
return ' ';
|
||||
}
|
||||
|
||||
|
||||
private function getCountryCode() :string
|
||||
{
|
||||
$country = Country::find($this->settings->country_id);
|
||||
|
||||
if($country)
|
||||
return $country->iso_3166_2;
|
||||
// if ($country) {
|
||||
// return ctrans('texts.country_' . $country->iso_3166_2);
|
||||
// }
|
||||
|
||||
return ' ';
|
||||
}
|
||||
/**
|
||||
* Due to the way we are compiling the blade template we
|
||||
* have no ability to iterate, so in the case
|
||||
* of line taxes where there are multiple rows,
|
||||
* we use this function to format a section of rows.
|
||||
*
|
||||
* @return string a collection of <tr> rows with line item
|
||||
* aggregate data
|
||||
*/
|
||||
private function makeLineTaxes() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<tr class="line_taxes">';
|
||||
$data .= '<td>'.$tax['name'].'</td>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $this->vendor).'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function lineTaxValues() :string
|
||||
{
|
||||
$tax_map = $this->entity_calc->getTaxMap();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach ($tax_map as $tax) {
|
||||
$data .= '<span>'.Number::formatMoney($tax['total'], $this->vendor).'</span>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function makeTotalTaxes() :string
|
||||
{
|
||||
$data = '';
|
||||
|
||||
if (! $this->entity_calc->getTotalTaxMap()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->entity_calc->getTotalTaxMap() as $tax) {
|
||||
$data .= '<tr>';
|
||||
$data .= '<td colspan="{ count($this->entity->company->settings->pdf_variables->total_columns) - 2 }"></td>';
|
||||
$data .= '<td>'.$tax['name'].'</td>';
|
||||
$data .= '<td>'.Number::formatMoney($tax['total'], $this->vendor).'</td></tr>';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function parseLabelsAndValues($labels, $values, $section) :string
|
||||
{
|
||||
$section = strtr($section, $labels);
|
||||
|
||||
return strtr($section, $values);
|
||||
}
|
||||
|
||||
/*
|
||||
| Ensures the URL doesn't have duplicated trailing slash
|
||||
*/
|
||||
public function generateAppUrl()
|
||||
{
|
||||
//return rtrim(config('ninja.app_url'), "/");
|
||||
return config('ninja.app_url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds CSS to assist with the generation
|
||||
* of Repeating headers and footers on the PDF.
|
||||
* @return string The css string
|
||||
*/
|
||||
private function generateCustomCSS() :string
|
||||
{
|
||||
$header_and_footer = '
|
||||
.header, .header-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer, .footer-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
thead {display: table-header-group;}
|
||||
tfoot {display: table-footer-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
|
||||
$header = '
|
||||
.header, .header-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0mm;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
thead {display: table-header-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
|
||||
$footer = '
|
||||
|
||||
.footer, .footer-space {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
tfoot {display: table-footer-group;}
|
||||
button {display: none;}
|
||||
body {margin: 0;}
|
||||
}';
|
||||
$css = '';
|
||||
|
||||
if ($this->settings->all_pages_header && $this->settings->all_pages_footer) {
|
||||
$css .= $header_and_footer;
|
||||
} elseif ($this->settings->all_pages_header && ! $this->settings->all_pages_footer) {
|
||||
$css .= $header;
|
||||
} elseif (! $this->settings->all_pages_header && $this->settings->all_pages_footer) {
|
||||
$css .= $footer;
|
||||
}
|
||||
|
||||
$css .= '
|
||||
.page {
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0mm
|
||||
}
|
||||
|
||||
html {
|
||||
';
|
||||
|
||||
$css .= 'font-size:'.$this->settings->font_size.'px;';
|
||||
// $css .= 'font-size:14px;';
|
||||
|
||||
$css .= '}';
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate markup for HTML images on entity.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
protected function generateEntityImagesMarkup()
|
||||
{
|
||||
if ($this->company->getSetting('embed_documents') === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
$container = $dom->createElement('div');
|
||||
$container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);');
|
||||
|
||||
foreach ($this->entity->documents as $document) {
|
||||
if (!$document->isImage()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$image = $dom->createElement('img');
|
||||
|
||||
$image->setAttribute('src', $document->generateUrl());
|
||||
$image->setAttribute('style', 'max-height: 100px; margin-top: 20px;');
|
||||
|
||||
$container->appendChild($image);
|
||||
}
|
||||
|
||||
$dom->appendChild($container);
|
||||
|
||||
return $dom->saveHTML();
|
||||
}
|
||||
}
|
@ -12,10 +12,12 @@ namespace Database\Factories;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\Company;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class CompanyFactory extends Factory
|
||||
{
|
||||
use MakesHash;
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
@ -41,6 +43,7 @@ class CompanyFactory extends Factory
|
||||
'enabled_modules' => config('ninja.enabled_modules'),
|
||||
'custom_fields' => (object) [
|
||||
],
|
||||
'company_key' => $this->createHash(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace Database\Factories;
|
||||
|
||||
use App\Models\Vendor;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class VendorFactory extends Factory
|
||||
{
|
||||
@ -45,6 +46,8 @@ class VendorFactory extends Factory
|
||||
'state' => $this->faker->state,
|
||||
'postal_code' => $this->faker->postcode,
|
||||
'country_id' => 4,
|
||||
'vendor_hash' => Str::random(40),
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -15,10 +16,10 @@ class CreatePurchaseOrderInvitationsTable extends Migration
|
||||
{
|
||||
Schema::create('purchase_order_invitations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('company_id')->index();
|
||||
$table->unsignedInteger('company_id');
|
||||
$table->unsignedInteger('user_id');
|
||||
$table->unsignedInteger('vendor_contact_id')->unique();
|
||||
$table->unsignedBigInteger('purchase_order_id')->index()->unique();
|
||||
$table->unsignedInteger('vendor_contact_id');
|
||||
$table->unsignedBigInteger('purchase_order_id')->index();
|
||||
$table->string('key')->index();
|
||||
$table->string('transaction_reference')->nullable();
|
||||
$table->string('message_id')->nullable()->index();
|
||||
@ -37,8 +38,32 @@ class CreatePurchaseOrderInvitationsTable extends Migration
|
||||
|
||||
$table->timestamps(6);
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
$table->unique(['vendor_contact_id', 'purchase_order_id'], 'vendor_purchase_unique');
|
||||
$table->index(['deleted_at', 'purchase_order_id', 'company_id'], 'vendor_purchase_company_index');
|
||||
|
||||
});
|
||||
|
||||
Schema::table('purchase_orders', function (Blueprint $table) {
|
||||
$table->unsignedInteger('client_id')->nullable()->change();
|
||||
});
|
||||
|
||||
|
||||
Company::cursor()->each(function ($company){
|
||||
|
||||
$settings = $company->settings;
|
||||
|
||||
$settings->purchase_order_design_id = 'Wpmbk5ezJn';
|
||||
$settings->purchase_order_footer = ''; //@implemented
|
||||
$settings->purchase_order_terms = ''; //@implemented
|
||||
$settings->purchase_order_public_notes = ''; //@implemented
|
||||
$settings->purchase_order_number_pattern = ''; //@implemented
|
||||
$settings->purchase_order_number_counter = 1; //@implemented
|
||||
$settings->email_subject_purchase_order = '';
|
||||
$settings->email_template_purchase_order = '';
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,6 +73,5 @@ class CreatePurchaseOrderInvitationsTable extends Migration
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('purchase_order_invitations');
|
||||
}
|
||||
}
|
||||
|
@ -4607,7 +4607,9 @@ $LANG = array(
|
||||
'enable_tooltips_help' => 'Show tooltips when hovering the mouse',
|
||||
'multiple_client_error' => 'Error: records belong to more than one client',
|
||||
'login_label' => 'Login to an existing account',
|
||||
|
||||
'purchase_order' => 'Purchase Order',
|
||||
'purchase_order_number' => 'Purchase Order Number',
|
||||
'purchase_order_number_short' => 'Purchase Order #',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -331,6 +331,7 @@
|
||||
<div class="client-wrapper-left-side">
|
||||
<h4 class="entity-label">$entity_label</h4>
|
||||
<div id="client-details" cellspacing="0"></div>
|
||||
<div id="vendor-details" cellspacing="0"></div>
|
||||
</div>
|
||||
|
||||
<div class="entity-details-wrapper-right-side">
|
||||
@ -380,7 +381,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'vendor-details', 'client-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -324,6 +324,7 @@
|
||||
<p class="entity-issued-to">$entity_issued_to_label:</p>
|
||||
<div class="client-and-entity-wrapper">
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
<div class="entity-details-wrapper">
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
</div>
|
||||
@ -365,6 +366,7 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'vendor-details','client-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -295,6 +295,7 @@
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
</div>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
<table id="task-table" cellspacing="0" data-ref="table"></table>
|
||||
@ -333,7 +334,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -267,6 +267,7 @@
|
||||
<div id="body">
|
||||
<div class="header-wrapper">
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
<div class="company-info-wrapper">
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
@ -322,7 +323,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'vendor-details', 'client-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -279,6 +279,7 @@
|
||||
<div class="text-with-client">
|
||||
<h2 class="wrapper-info-text">$to_label</h2>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
<div class="company-info">
|
||||
<h2 class="wrapper-info-text">$from_label</h2>
|
||||
@ -328,7 +329,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -297,6 +297,7 @@
|
||||
<div class="header-right-side-wrapper-left">
|
||||
<p class="header-text-label">$to_label:</p>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
<div class="header-right-side-wrapper-right">
|
||||
<img
|
||||
@ -369,7 +370,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'vendor-details','client-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -320,6 +320,7 @@
|
||||
<div class="logo-client-wrapper">
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo"/>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
@ -364,7 +365,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -270,6 +270,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
<table id="task-table" cellspacing="0" data-ref="table"></table>
|
||||
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
|
||||
@ -306,7 +307,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -349,6 +349,7 @@
|
||||
<div class="contact-wrapper-right-side">
|
||||
<p class="contact-label">$to_label:</p>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||
@ -409,7 +410,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -321,6 +321,7 @@
|
||||
<div class="client-details">
|
||||
<span class="client-details-to-label">$from_label:</span>
|
||||
<div id="client-details"></div>
|
||||
<div id="vendor-details"></div>
|
||||
</div>
|
||||
<div class="company-details">
|
||||
<span class="client-details-to-label">$to_label:</span>
|
||||
@ -369,7 +370,8 @@ $entity_images
|
||||
let tables = [
|
||||
'product-table', 'task-table', 'delivery-note-table',
|
||||
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table'
|
||||
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||
'client-details','vendor-details'
|
||||
];
|
||||
|
||||
tables.forEach((tableIdentifier) => {
|
||||
|
@ -207,6 +207,8 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale
|
||||
Route::put('vendors/{vendor}/upload', 'VendorController@upload');
|
||||
|
||||
Route::resource('purchase_orders', 'PurchaseOrderController');
|
||||
Route::post('purchase_orders/bulk', 'PurchaseOrderController@bulk')->name('purchase_orders.bulk');
|
||||
Route::get('purchase_orders/{purchase_order}/{action}', 'PurchaseOrderController@action')->name('purchase_orders.action');
|
||||
|
||||
Route::get('users', 'UserController@index');
|
||||
Route::get('users/{user}', 'UserController@show')->middleware('password_protected');
|
||||
|
@ -78,10 +78,15 @@ class CompanySettingsTest extends TestCase
|
||||
|
||||
$this->company->saveSettings($settings, $this->company);
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-Token' => $this->token,
|
||||
])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray());
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
@ -19,6 +19,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PurchaseOrderTest extends TestCase
|
||||
{
|
||||
@ -40,6 +41,45 @@ class PurchaseOrderTest extends TestCase
|
||||
|
||||
}
|
||||
|
||||
public function testPostNewPurchaseOrderPdf()
|
||||
{
|
||||
$purchase_order = [
|
||||
'status_id' => 1,
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => 1,
|
||||
'number' => Str::random(10),
|
||||
'po_number' => Str::random(5),
|
||||
'due_date' => '2022-01-01',
|
||||
'date' => '2022-01-01',
|
||||
'balance' => 100,
|
||||
'amount' => 100,
|
||||
'public_notes' => 'notes',
|
||||
'is_deleted' => 0,
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'vendor_id' => $this->encodePrimaryKey($this->vendor->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/purchase_orders/', $purchase_order)
|
||||
->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$purchase_order = PurchaseOrder::find($this->decodePrimaryKey($arr['data']['id']));
|
||||
|
||||
$this->assertNotNull($purchase_order);
|
||||
|
||||
$x = $purchase_order->service()->markSent()->getPurchaseOrderPdf();
|
||||
|
||||
nlog($x);
|
||||
}
|
||||
|
||||
public function testPurchaseOrderRest()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
@ -83,7 +123,7 @@ class PurchaseOrderTest extends TestCase
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
'vendor_id' => $this->encodePrimaryKey($this->vendor->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
@ -117,7 +157,7 @@ class PurchaseOrderTest extends TestCase
|
||||
'custom_value3' => 0,
|
||||
'custom_value4' => 0,
|
||||
'status' => 1,
|
||||
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||
'vendor_id' => $this->encodePrimaryKey($this->vendor->id),
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
@ -13,6 +13,7 @@ namespace Tests\Integration;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
@ -57,6 +58,8 @@ class HtmlGenerationTest extends TestCase
|
||||
$entity_design_id = 'quote_design_id';
|
||||
} elseif ($entity instanceof Credit) {
|
||||
$entity_design_id = 'credit_design_id';
|
||||
} elseif ($entity instanceof PurchaseOrder) {
|
||||
$entity_design_id = 'purchase_order_design_id';
|
||||
}
|
||||
|
||||
$entity_design_id = $entity->design_id ? $entity->design_id : $this->decodePrimaryKey($entity->client->getSetting($entity_design_id));
|
||||
|
@ -56,6 +56,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class MockAccountData.
|
||||
@ -294,6 +295,7 @@ trait MockAccountData
|
||||
$this->vendor = Vendor::factory()->create([
|
||||
'user_id' => $user_id,
|
||||
'company_id' => $this->company->id,
|
||||
'currency_id' => 1
|
||||
]);
|
||||
|
||||
|
||||
@ -450,22 +452,12 @@ trait MockAccountData
|
||||
|
||||
$this->quote->save();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$this->purchase_order = PurchaseOrderFactory::create($this->company->id, $user_id);
|
||||
$this->purchase_order->client_id = $this->client->id;
|
||||
|
||||
$this->purchase_order->vendor_id = $this->vendor->id;
|
||||
|
||||
$this->purchase_order->amount = 10;
|
||||
$this->purchase_order->balance = 10;
|
||||
|
||||
// $this->credit->due_date = now()->addDays(200);
|
||||
|
||||
$this->purchase_order->tax_name1 = '';
|
||||
$this->purchase_order->tax_name2 = '';
|
||||
$this->purchase_order->tax_name3 = '';
|
||||
|
Loading…
x
Reference in New Issue
Block a user