diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php
index d680935af130..27e76dddccc9 100644
--- a/app/Http/Controllers/PreviewController.php
+++ b/app/Http/Controllers/PreviewController.php
@@ -388,6 +388,7 @@ class PreviewController extends BaseController
$design_object = json_decode(json_encode(request()->input('design')), 1);
$ts = (new TemplateService());
+
try {
$ts->setCompany($company)
->setTemplate($design_object)
@@ -395,7 +396,6 @@ class PreviewController extends BaseController
} catch(SyntaxError $e) {
// return response()->json(['message' => 'Twig syntax is invalid.', 'errors' => new \stdClass], 422);
-
}
$html = $ts->getHtml();
diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php
index 5ae5e13dd481..456b350fb526 100644
--- a/app/Http/Requests/Task/StoreTaskRequest.php
+++ b/app/Http/Requests/Task/StoreTaskRequest.php
@@ -54,14 +54,11 @@ class StoreTaskRequest extends Request
$rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0';
}
- $rules['time_log'] = ['bail', function ($attribute, $values, $fail) {
-
- if(is_string($values)) {
- $values = json_decode($values, 1);
- }
+ $rules['time_log'] = ['bail',function ($attribute, $values, $fail) {
- if(!is_array($values)) {
- return $fail('The '.$attribute.' is invalid. Must be an array.');
+ if(!is_array(json_decode($values, true))) {
+ $fail('The '.$attribute.' must be a valid array.');
+ return;
}
foreach ($values as $k) {
@@ -119,6 +116,10 @@ class StoreTaskRequest extends Request
}
}
+ if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}'){
+ $input['time_log'] = json_encode([]);
+ }
+
$this->replace($input);
}
}
diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php
index 7779dc0340df..f9d6099c2842 100644
--- a/app/Http/Requests/Task/UpdateTaskRequest.php
+++ b/app/Http/Requests/Task/UpdateTaskRequest.php
@@ -38,12 +38,14 @@ class UpdateTaskRequest extends Request
/** @var \App\Models\User $user */
$user = auth()->user();
+ /** @var \App\Models\User $user */
+ $user = auth()->user();
+
return $user->can('edit', $this->task);
}
public function rules()
{
-
/** @var \App\Models\User $user */
$user = auth()->user();
@@ -61,14 +63,11 @@ class UpdateTaskRequest extends Request
$rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0';
}
- $rules['time_log'] = ['bail',function ($attribute, $values, $fail) {
+ $rules['time_log'] = ['bail', function ($attribute, $values, $fail) {
- if(is_string($values)) {
- $values = json_decode($values, 1);
- }
-
- if(!is_array($values)) {
- return $fail('The '.$attribute.' is invalid. Must be an array.');
+ if(!is_array(json_decode($values, true))) {
+ $fail('The '.$attribute.' must be a valid array.');
+ return;
}
foreach ($values as $k) {
@@ -129,6 +128,10 @@ class UpdateTaskRequest extends Request
}
+ if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}') {
+ $input['time_log'] = json_encode([]);
+ }
+
$this->replace($input);
}
diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php
index 0ac0ce6070be..15cf1faff44a 100644
--- a/app/Models/PurchaseOrder.php
+++ b/app/Models/PurchaseOrder.php
@@ -268,7 +268,8 @@ class PurchaseOrder extends BaseModel
{
return $this->belongsTo(Client::class)->withTrashed();
}
- public function markInvitationsSent()
+
+ public function markInvitationsSent(): void
{
$this->invitations->each(function ($invitation) {
if (! isset($invitation->sent_date)) {
diff --git a/app/Models/Task.php b/app/Models/Task.php
index 9bbab1d15a79..df8fd59603b5 100644
--- a/app/Models/Task.php
+++ b/app/Models/Task.php
@@ -239,4 +239,23 @@ class Task extends BaseModel
return $this->company->settings->default_task_rate ?? 0;
}
+
+ public function processLogs()
+ {
+ return
+ collect($this->time_log)->map(function ($log){
+
+ $parent_entity = $this->client ?? $this->company;
+
+ if($log[0])
+ $log[0] = Carbon::createFromTimestamp($log[0])->format($parent_entity->date_format());
+
+ if($log[1] && $log[1] != 0)
+ $log[1] = Carbon::createFromTimestamp($log[1])->format($parent_entity->date_format());
+ else
+ $log[1] = ctrans('texts.running');
+
+ return $log;
+ })->toArray();
+ }
}
diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php
index 8061c014bd6d..db8438c49a55 100644
--- a/app/Services/Pdf/PdfBuilder.php
+++ b/app/Services/Pdf/PdfBuilder.php
@@ -1621,12 +1621,6 @@ class PdfBuilder
// Dom Traversal
///////////////////////////////////////
-
- public function getSectionNode(string $selector)
- {
- return $this->document->getElementById($selector);
- }
-
public function updateElementProperties() :self
{
foreach ($this->sections as $element) {
diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php
index c5b783b51399..f7e00ff5e8e3 100644
--- a/app/Services/Template/TemplateService.php
+++ b/app/Services/Template/TemplateService.php
@@ -11,12 +11,23 @@
namespace App\Services\Template;
+<<<<<<< HEAD
use App\Models\Client;
+=======
+use App\Models\Quote;
+use App\Utils\Number;
+use Twig\Error\Error;
+use App\Models\Client;
+use App\Models\Credit;
+use App\Models\Design;
+use App\Models\Vendor;
+>>>>>>> support_for_custom_statement_designs
use App\Models\Company;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Project;
+<<<<<<< HEAD
use App\Models\PurchaseOrder;
use App\Transformers\ProjectTransformer;
use App\Transformers\PurchaseOrderTransformer;
@@ -43,11 +54,28 @@ use Twig\Loader\FilesystemLoader;
use Twig\Sandbox\SecurityError;
use Twig\TwigFilter;
use Twig\TwigFunction;
+=======
+use App\Utils\HtmlEngine;
+use Twig\Error\LoaderError;
+use Twig\Error\SyntaxError;
+use Twig\Error\RuntimeError;
+use App\Models\PurchaseOrder;
+use App\Utils\VendorHtmlEngine;
+use Twig\Sandbox\SecurityError;
+use App\Models\RecurringInvoice;
+use App\Utils\PaymentHtmlEngine;
+use App\Utils\Traits\MakesDates;
+use App\Utils\HostedPDF\NinjaPdf;
+use App\Utils\Traits\Pdf\PdfMaker;
+use Twig\Extra\Intl\IntlExtension;
+use League\CommonMark\CommonMarkConverter;
+>>>>>>> support_for_custom_statement_designs
class TemplateService
{
- use MakesDates, PdfMaker;
-
+ use MakesDates;
+ use PdfMaker;
+
private \DomDocument $document;
public Environment $twig;
@@ -60,12 +88,22 @@ class TemplateService
public ?Company $company;
+ private ?Client $client;
+
+ private ?Vendor $vendor;
+
+ private Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice $entity;
+
+ private Payment $payment;
+
+ private CommonMarkConverter $commonmark;
+
public function __construct(public ?Design $template = null)
{
$this->template = $template;
$this->init();
}
-
+
/**
* Boot Dom Document
*
@@ -73,6 +111,11 @@ class TemplateService
*/
private function init(): self
{
+
+ $this->commonmark = new CommonMarkConverter([
+ 'allow_unsafe_links' => false,
+ ]);
+
$this->document = new \DOMDocument();
$this->document->validateOnParse = true;
$loader = new FilesystemLoader(storage_path());
@@ -93,15 +136,15 @@ class TemplateService
$filter = new TwigFilter('sum', function (array $array, string $column) {
return array_sum(array_column($array, $column));
});
-
+
$this->twig->addFilter($filter);
return $this;
}
-
+
/**
* Iterate through all of the
- * ninja nodes
+ * ninja nodes, and field stacks
*
* @param array $data - the payload to be passed into the template
* @return self
@@ -110,13 +153,20 @@ class TemplateService
{
$this->compose()
->processData($data)
+ ->parseGlobalStacks()
->parseNinjaBlocks()
->processVariables($data)
->parseVariables();
return $this;
}
-
+
+ /**
+ * Initialized a set of HTMLEngine variables
+ *
+ * @param array | \Illuminate\Support\Collection $data
+ * @return self
+ */
private function processVariables($data): self
{
$this->variables = $this->resolveHtmlEngine($data);
@@ -124,21 +174,28 @@ class TemplateService
return $this;
}
+ /**
+ * Returns a Mock Template
+ *
+ * @return self
+ */
public function mock(): self
{
$tm = new TemplateMock($this->company);
$tm->init();
+ $this->entity = $this->company->invoices()->first();
+
$this->data = $tm->engines;
$this->variables = $tm->variables[0];
-
$this->parseNinjaBlocks()
+ ->parseGlobalStacks()
->parseVariables();
return $this;
}
-
+
/**
* Returns the HTML as string
*
@@ -149,7 +206,12 @@ class TemplateService
return $this->compiled_html;
}
- public function getPdf(): mixed
+ /**
+ * Returns the PDF string
+ *
+ * @return string
+ */
+ public function getPdf(): string
{
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
@@ -162,11 +224,22 @@ class TemplateService
}
+ /**
+ * Get the parsed data
+ *
+ * @return array
+ */
public function getData(): array
{
return $this->data;
}
-
+
+ /**
+ * Process data variables
+ *
+ * @param array | \Illuminate\Support\Collection $data
+ * @return self
+ */
public function processData($data): self
{
@@ -187,13 +260,14 @@ class TemplateService
$contents = $this->document->getElementsByTagName('ninja');
foreach ($contents as $content) {
-
+
$template = $content->ownerDocument->saveHTML($content);
try {
$template = $this->twig->createTemplate(html_entity_decode($template));
} catch(SyntaxError $e) {
nlog($e->getMessage());
+<<<<<<< HEAD
continue;
} catch(Error $e) {
nlog("error = " .$e->getMessage());
@@ -204,6 +278,18 @@ class TemplateService
} catch(LoaderError $e) {
nlog("loader = " . $e->getMessage());
continue;
+=======
+ throw ($e);
+ } catch(Error $e) {
+ nlog("error = " . $e->getMessage());
+ throw ($e);
+ } catch(RuntimeError $e) {
+ nlog("runtime = " . $e->getMessage());
+ throw ($e);
+ } catch(LoaderError $e) {
+ nlog("loader = " . $e->getMessage());
+ throw ($e);
+>>>>>>> support_for_custom_statement_designs
} catch(SecurityError $e) {
nlog("security = " . $e->getMessage());
continue;
@@ -213,7 +299,7 @@ class TemplateService
$f = $this->document->createDocumentFragment();
$f->appendXML(html_entity_decode($template));
-
+
$replacements[] = $f;
}
@@ -227,19 +313,22 @@ class TemplateService
return $this;
}
-
+
/**
* Parses all variables in the document
*
* @return self
*/
- private function parseVariables(): self
+ public function parseVariables(): self
{
$html = $this->getHtml();
foreach($this->variables as $key => $variable) {
+<<<<<<< HEAD
+=======
+>>>>>>> support_for_custom_statement_designs
if(isset($variable['labels']) && isset($variable['values'])) {
$html = strtr($html, $variable['labels']);
$html = strtr($html, $variable['values']);
@@ -251,7 +340,7 @@ class TemplateService
return $this;
}
-
+
/**
* Saves the document and updates the compiled string.
*
@@ -286,7 +375,7 @@ class TemplateService
return $this;
}
-
+
/**
* Inject the template components
* manually
@@ -312,15 +401,20 @@ class TemplateService
* Resolves the labels and values needed to replace the string
* holders in the template.
*
+ * @param array $data
* @return array
*/
private function resolveHtmlEngine(array $data): array
{
return collect($data)->map(function ($value, $key) {
-
+
$processed = [];
+<<<<<<< HEAD
if(in_array($key, ['tasks','projects','aging']) || !$value->first()) {
+=======
+ if(in_array($key, ['tasks', 'projects', 'aging']) || !$value->first()) {
+>>>>>>> support_for_custom_statement_designs
return $processed;
}
@@ -336,13 +430,20 @@ class TemplateService
'aging' => $processed = [],
default => $processed = [],
};
-
+
return $processed;
})->toArray();
}
+ /**
+ * Pre Processes the Data Blocks into
+ * Twig consumables
+ *
+ * @param array | \Illuminate\Support\Collection $data
+ * @return array
+ */
private function preProcessDataBlocks($data): array
{
return collect($data)->map(function ($value, $key) {
@@ -366,12 +467,19 @@ class TemplateService
})->toArray();
}
+ /**
+ * Process Invoices into consumable form for Twig templates
+ *
+ * @param array | \Illuminate\Support\Collection $invoices
+ * @return array
+ */
public function processInvoices($invoices): array
{
$invoices = collect($invoices)
->map(function ($invoice) {
$payments = [];
+<<<<<<< HEAD
if($invoice->payments ?? false) {
$payments = $invoice->payments->map(function ($payment) {
@@ -437,15 +545,96 @@ class TemplateService
'line_tax_map' => $invoice->calc()->getTaxMap(),
];
+=======
+ $this->entity = $invoice;
+
+ if($invoice->payments ?? false) {
+ $payments = $invoice->payments->map(function ($payment) {
+ return $this->transformPayment($payment);
+ })->toArray();
+ }
+
+ return [
+ 'amount' => Number::formatMoney($invoice->amount, $invoice->client),
+ 'balance' => Number::formatMoney($invoice->balance, $invoice->client),
+ 'status_id' => $invoice->status_id,
+ 'status' => Invoice::stringStatus($invoice->status_id),
+ 'balance_raw' => $invoice->balance,
+ 'number' => $invoice->number ?: '',
+ 'discount' => $invoice->discount,
+ 'po_number' => $invoice->po_number ?: '',
+ 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()),
+ 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()),
+ 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()),
+ 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()),
+ 'terms' => $invoice->terms ?: '',
+ 'public_notes' => $invoice->public_notes ?: '',
+ 'private_notes' => $invoice->private_notes ?: '',
+ 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes,
+ 'tax_name1' => $invoice->tax_name1 ?? '',
+ 'tax_rate1' => (float) $invoice->tax_rate1,
+ 'tax_name2' => $invoice->tax_name2 ?? '',
+ 'tax_rate2' => (float) $invoice->tax_rate2,
+ 'tax_name3' => $invoice->tax_name3 ?? '',
+ 'tax_rate3' => (float) $invoice->tax_rate3,
+ 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client),
+ 'total_taxes_raw' => $invoice->total_taxes,
+ 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false,
+ 'footer' => $invoice->footer ?? '',
+ 'partial' => $invoice->partial ?? 0,
+ 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()),
+ 'custom_value1' => (string) $invoice->custom_value1 ?: '',
+ 'custom_value2' => (string) $invoice->custom_value2 ?: '',
+ 'custom_value3' => (string) $invoice->custom_value3 ?: '',
+ 'custom_value4' => (string) $invoice->custom_value4 ?: '',
+ 'custom_surcharge1' => (float) $invoice->custom_surcharge1,
+ 'custom_surcharge2' => (float) $invoice->custom_surcharge2,
+ 'custom_surcharge3' => (float) $invoice->custom_surcharge3,
+ 'custom_surcharge4' => (float) $invoice->custom_surcharge4,
+ 'exchange_rate' => (float) $invoice->exchange_rate,
+ 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1,
+ 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2,
+ 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3,
+ 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4,
+ 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client) : (array) [],
+ 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()),
+ 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()),
+ 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()),
+ 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()),
+ 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client),
+ 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
+ 'client' => [
+ 'name' => $invoice->client->present()->name(),
+ 'balance' => $invoice->client->balance,
+ 'payment_balance' => $invoice->client->payment_balance,
+ 'credit_balance' => $invoice->client->credit_balance,
+ ],
+ 'payments' => $payments,
+ 'total_tax_map' => $invoice->calc()->getTotalTaxMap(),
+ 'line_tax_map' => $invoice->calc()->getTaxMap(),
+ ];
+
+>>>>>>> support_for_custom_statement_designs
});
return $invoices->toArray();
}
- public function padLineItems(array $items, Client $client): array
+ /**
+ * Pads Line Items with raw and formatted content
+ *
+ * @param array $items
+ * @param Vendor | Client $client_or_vendor
+ * @return array
+ */
+ public function padLineItems(array $items, Vendor | Client $client_or_vendor): array
{
+<<<<<<< HEAD
return collect($items)->map(function ($item) use ($client) {
+=======
+ return collect($items)->map(function ($item) use ($client_or_vendor) {
+>>>>>>> support_for_custom_statement_designs
$item->cost_raw = $item->cost ?? 0;
$item->discount_raw = $item->discount ?? 0;
@@ -454,6 +643,7 @@ class TemplateService
$item->tax_amount_raw = $item->tax_amount ?? 0;
$item->product_cost_raw = $item->product_cost ?? 0;
+<<<<<<< HEAD
$item->cost = Number::formatMoney($item->cost_raw, $client);
if($item->is_amount_discount) {
@@ -464,17 +654,37 @@ class TemplateService
$item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client);
$item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client);
$item->product_cost = Number::formatMoney($item->product_cost_raw, $client);
+=======
+ $item->cost = Number::formatMoney($item->cost_raw, $client_or_vendor);
+
+ if($item->is_amount_discount) {
+ $item->discount = Number::formatMoney($item->discount_raw, $client_or_vendor);
+ }
+
+ $item->line_total = Number::formatMoney($item->line_total_raw, $client_or_vendor);
+ $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client_or_vendor);
+ $item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client_or_vendor);
+ $item->product_cost = Number::formatMoney($item->product_cost_raw, $client_or_vendor);
+>>>>>>> support_for_custom_statement_designs
return $item;
})->toArray();
}
+ /**
+ * Transforms a Payment into consumable for twig
+ *
+ * @param Payment $payment
+ * @return array
+ */
private function transformPayment(Payment $payment): array
{
$data = [];
-
+
+ $this->payment = $payment;
+
$credits = $payment->credits->map(function ($credit) use ($payment) {
return [
'credit' => $credit->number,
@@ -543,8 +753,6 @@ class TemplateService
'refund_activity' => $this->getPaymentRefundActivity($payment),
];
- nlog($data);
-
return $data;
}
@@ -596,31 +804,83 @@ class TemplateService
}
+ /**
+ *
+ *
+ * @param array | \Illuminate\Support\Collection $quotes
+ * @return array
+ */
public function processQuotes($quotes): array
{
- $it = new QuoteTransformer();
- $it->setDefaultIncludes(['client']);
- $manager = new Manager();
- $manager->parseIncludes(['client']);
- $resource = new \League\Fractal\Resource\Collection($quotes, $it, null);
- $resources = $manager->createData($resource)->toArray();
+
+ return collect($quotes)->map(function ($quote){
- foreach($resources['data'] as $key => $resource) {
+ return [
+ 'amount' => Number::formatMoney($quote->amount, $quote->client),
+ 'balance' => Number::formatMoney($quote->balance, $quote->client),
+ 'balance_raw' => (float) $quote->balance,
+ 'client' => [
+ 'name' => $quote->client->present()->name(),
+ 'balance' => $quote->client->balance,
+ 'payment_balance' => $quote->client->payment_balance,
+ 'credit_balance' => $quote->client->credit_balance,
+ ],
+ 'status_id' =>$quote->status_id,
+ 'status' => Quote::stringStatus($quote->status_id),
+ 'number' => $quote->number ?: '',
+ 'discount' => (float) $quote->discount,
+ 'po_number' => $quote->po_number ?: '',
+ 'date' => $quote->date ? $this->translateDate($quote->date, $quote->client->date_format(), $quote->client->locale()) : '',
+ 'last_sent_date' => $quote->last_sent_date ? $this->translateDate($quote->last_sent_date, $quote->client->date_format(), $quote->client->locale()) : '',
+ // 'next_send_date' => $quote->next_send_date ?: '',
+ // 'reminder1_sent' => $quote->reminder1_sent ?: '',
+ // 'reminder2_sent' => $quote->reminder2_sent ?: '',
+ // 'reminder3_sent' => $quote->reminder3_sent ?: '',
+ // 'reminder_last_sent' => $quote->reminder_last_sent ?: '',
+ 'due_date' => $quote->due_date ? $this->translateDate($quote->due_date, $quote->client->date_format(), $quote->client->locale()) : '',
+ 'terms' => $quote->terms ?: '',
+ 'public_notes' => $quote->public_notes ?: '',
+ 'private_notes' => $quote->private_notes ?: '',
+ 'is_deleted' => (bool) $quote->is_deleted,
+ 'uses_inclusive_taxes' => (bool) $quote->uses_inclusive_taxes,
+ 'tax_name1' => $quote->tax_name1 ? $quote->tax_name1 : '',
+ 'tax_rate1' => (float) $quote->tax_rate1,
+ 'tax_name2' => $quote->tax_name2 ? $quote->tax_name2 : '',
+ 'tax_rate2' => (float) $quote->tax_rate2,
+ 'tax_name3' => $quote->tax_name3 ? $quote->tax_name3 : '',
+ 'tax_rate3' => (float) $quote->tax_rate3,
+ 'total_taxes' => (float) $quote->total_taxes,
+ 'is_amount_discount' => (bool) ($quote->is_amount_discount ?: false),
+ 'footer' => $quote->footer ?: '',
+ 'partial' => (float) ($quote->partial ?: 0.0),
+ 'partial_due_date' => $quote->partial_due_date ? $this->translateDate($quote->partial_due_date, $quote->client->date_format(), $quote->client->locale()) : '',
+ 'custom_value1' => (string) $quote->custom_value1 ?: '',
+ 'custom_value2' => (string) $quote->custom_value2 ?: '',
+ 'custom_value3' => (string) $quote->custom_value3 ?: '',
+ 'custom_value4' => (string) $quote->custom_value4 ?: '',
+ 'has_expenses' => (bool) $quote->has_expenses,
+ 'custom_surcharge1' => (float) $quote->custom_surcharge1,
+ 'custom_surcharge2' => (float) $quote->custom_surcharge2,
+ 'custom_surcharge3' => (float) $quote->custom_surcharge3,
+ 'custom_surcharge4' => (float) $quote->custom_surcharge4,
+ 'custom_surcharge_tax1' => (bool) $quote->custom_surcharge_tax1,
+ 'custom_surcharge_tax2' => (bool) $quote->custom_surcharge_tax2,
+ 'custom_surcharge_tax3' => (bool) $quote->custom_surcharge_tax3,
+ 'custom_surcharge_tax4' => (bool) $quote->custom_surcharge_tax4,
+ 'line_items' => $quote->line_items ? $this->padLineItems($quote->line_items, $quote->client) : (array) [],
+ 'exchange_rate' => (float) $quote->exchange_rate,
+ 'paid_to_date' => (float) $quote->paid_to_date,
+ ];
- $resources['data'][$key]['client'] = $resource['client']['data'] ?? [];
- $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? [];
-
- }
-
- return $resources['data'];
+ })->toArray();
}
-
+
/**
* Pushes credits through the appropriate transformer
* and builds any required relationships
*
- * @param mixed $credits
+ * @param array | \Illuminate\Support\Collection $credits
* @return array
*/
public function processCredits($credits): array
@@ -628,6 +888,7 @@ class TemplateService
$credits = collect($credits)
->map(function ($credit) {
+<<<<<<< HEAD
return [
'amount' => Number::formatMoney($credit->amount, $credit->client),
'balance' => Number::formatMoney($credit->balance, $credit->client),
@@ -686,18 +947,78 @@ class TemplateService
'line_tax_map' => $credit->calc()->getTaxMap(),
];
+=======
+ $this->entity = $credit;
+
+ return [
+ 'amount' => Number::formatMoney($credit->amount, $credit->client),
+ 'balance' => Number::formatMoney($credit->balance, $credit->client),
+ 'balance_raw' => $credit->balance,
+ 'number' => $credit->number ?: '',
+ 'discount' => $credit->discount,
+ 'po_number' => $credit->po_number ?: '',
+ 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()),
+ 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()),
+ 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()),
+ 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()),
+ 'terms' => $credit->terms ?: '',
+ 'public_notes' => $credit->public_notes ?: '',
+ 'private_notes' => $credit->private_notes ?: '',
+ 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes,
+ 'tax_name1' => $credit->tax_name1 ?? '',
+ 'tax_rate1' => (float) $credit->tax_rate1,
+ 'tax_name2' => $credit->tax_name2 ?? '',
+ 'tax_rate2' => (float) $credit->tax_rate2,
+ 'tax_name3' => $credit->tax_name3 ?? '',
+ 'tax_rate3' => (float) $credit->tax_rate3,
+ 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client),
+ 'total_taxes_raw' => $credit->total_taxes,
+ 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false,
+ 'footer' => $credit->footer ?? '',
+ 'partial' => $credit->partial ?? 0,
+ 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()),
+ 'custom_value1' => (string) $credit->custom_value1 ?: '',
+ 'custom_value2' => (string) $credit->custom_value2 ?: '',
+ 'custom_value3' => (string) $credit->custom_value3 ?: '',
+ 'custom_value4' => (string) $credit->custom_value4 ?: '',
+ 'custom_surcharge1' => (float) $credit->custom_surcharge1,
+ 'custom_surcharge2' => (float) $credit->custom_surcharge2,
+ 'custom_surcharge3' => (float) $credit->custom_surcharge3,
+ 'custom_surcharge4' => (float) $credit->custom_surcharge4,
+ 'exchange_rate' => (float) $credit->exchange_rate,
+ 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1,
+ 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2,
+ 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3,
+ 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4,
+ 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client) : (array) [],
+ 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()),
+ 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()),
+ 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()),
+ 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()),
+ 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client),
+ 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled,
+ 'client' => [
+ 'name' => $credit->client->present()->name(),
+ 'balance' => $credit->client->balance,
+ 'payment_balance' => $credit->client->payment_balance,
+ 'credit_balance' => $credit->client->credit_balance,
+ ],
+ 'payments' => [],
+ 'total_tax_map' => $credit->calc()->getTotalTaxMap(),
+ 'line_tax_map' => $credit->calc()->getTaxMap(),
+ ];
+
+>>>>>>> support_for_custom_statement_designs
});
return $credits->toArray();
}
-
-
/**
* Pushes payments through the appropriate transformer
*
- * @param mixed $payments
+ * @param array | \Illuminate\Support\Collection $payments
* @return array
*/
public function processPayments($payments): array
@@ -706,77 +1027,655 @@ class TemplateService
$payments = collect($payments)->map(function ($payment) {
return $this->transformPayment($payment);
})->toArray();
-
+
return $payments;
-
}
- public function processTasks($tasks): array
+ /**
+ * @todo refactor
+ *
+ * @param mixed $tasks
+ * @return array
+ */
+ public function processTasks($tasks, bool $nested = false): array
{
- $it = new TaskTransformer();
- $it->setDefaultIncludes(['client','project','invoice']);
- $manager = new Manager();
- $resource = new \League\Fractal\Resource\Collection($tasks, $it, null);
- $resources = $manager->createData($resource)->toArray();
- foreach($resources['data'] as $key => $resource) {
+ return collect($tasks)->map(function ($task) use ($nested){
- $resources['data'][$key]['client'] = $resource['client']['data'] ?? [];
- $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? [];
- $resources['data'][$key]['project'] = $resource['project']['data'] ?? [];
- $resources['data'][$key]['invoice'] = $resource['invoice'] ?? [];
-
- }
+ return [
+ 'number' => (string) $task->number ?: '',
+ 'description' => (string) $task->description ?: '',
+ 'duration' => $task->duration ?: 0,
+ 'rate' => Number::formatMoney($task->rate ?? 0, $task->client ?? $task->company),
+ 'created_at' => $this->translateDate($task->created_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()),
+ 'updated_at' => $this->translateDate($task->updated_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()),
+ 'date' => $task->calculated_start_date ? $this->translateDate($task->calculated_start_date , $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()) : '',
+ // 'invoice_id' => $this->encodePrimaryKey($task->invoice_id) ?: '',
+ 'project' => ($task->project && !$nested) ? $this->transformProject($task->project, true) : [],
+ 'time_log' => $task->processLogs(),
+ 'custom_value1' => $task->custom_value1 ?: '',
+ 'custom_value2' => $task->custom_value2 ?: '',
+ 'custom_value3' => $task->custom_value3 ?: '',
+ 'custom_value4' => $task->custom_value4 ?: '',
+ 'status' => $task->status ? $task->status->name : '',
+ 'client' => $task->client ? [
+ 'name' => $task->client->present()->name(),
+ 'balance' => $task->client->balance,
+ 'payment_balance' => $task->client->payment_balance,
+ 'credit_balance' => $task->client->credit_balance,
+ ] : [],
+ ];
- return $resources['data'];
+ })->toArray();
}
+ /**
+ * @todo refactor
+ *
+ * @param array | \Illuminate\Support\Collection $projects
+ * @return array
+ */
public function processProjects($projects): array
{
- $it = new ProjectTransformer();
- $it->setDefaultIncludes(['client','tasks']);
- $manager = new Manager();
- $manager->setSerializer(new ArraySerializer());
- $resource = new \League\Fractal\Resource\Collection($projects, $it, Project::class);
- $i = $manager->createData($resource)->toArray();
- return $i[Project::class];
+ return
+ collect($projects)->map(function ($project){
+
+ return $this->transformProject($project);
+
+ })->toArray();
}
- public function processPurchaseOrders($purchase_orders): array
+ private function transformProject(Project $project, bool $nested = false): array
{
- $it = new PurchaseOrderTransformer();
- $it->setDefaultIncludes(['vendor','expense']);
- $manager = new Manager();
- $manager->setSerializer(new ArraySerializer());
- $resource = new \League\Fractal\Resource\Collection($purchase_orders, $it, PurchaseOrder::class);
- $i = $manager->createData($resource)->toArray();
- return $i[PurchaseOrder::class];
+ return [
+ 'name' => $project->name ?: '',
+ 'number' => $project->number ?: '',
+ 'created_at' => $this->translateDate($project->created_at, $project->client->date_format(), $project->client->locale()),
+ 'updated_at' => $this->translateDate($project->updated_at, $project->client->date_format(), $project->client->locale()),
+ 'task_rate' => Number::formatMoney($project->task_rate ?? 0, $project->client),
+ 'due_date' => $project->due_date ? $this->translateDate($project->due_date, $project->client->date_format(), $project->client->locale()) : '',
+ 'private_notes' => (string) $project->private_notes ?: '',
+ 'public_notes' => (string) $project->public_notes ?: '',
+ 'budgeted_hours' => (float) $project->budgeted_hours,
+ 'custom_value1' => (string) $project->custom_value1 ?: '',
+ 'custom_value2' => (string) $project->custom_value2 ?: '',
+ 'custom_value3' => (string) $project->custom_value3 ?: '',
+ 'custom_value4' => (string) $project->custom_value4 ?: '',
+ 'color' => (string) $project->color ?: '',
+ 'current_hours' => (int) $project->current_hours ?: 0,
+ 'tasks' => ($project->tasks && !$nested) ? $this->processTasks($project->tasks, true) : [],
+ 'client' => $project->client ? [
+ 'name' => $project->client->present()->name(),
+ 'balance' => $project->client->balance,
+ 'payment_balance' => $project->client->payment_balance,
+ 'credit_balance' => $project->client->credit_balance,
+ ] : [],
+
+ ];
}
+ /**
+ *
+ * @param array | \Illuminate\Support\Collection $purchase_orders
+ * @return array
+ */
+ public function processPurchaseOrders($purchase_orders): array
+ {
+
+ return collect($purchase_orders)->map(function ($purchase_order){
+
+ return [
+ 'vendor' => $purchase_order->vendor ? [
+ 'name' => $purchase_order->vendor->present()->name(),
+ ] : [],
+ 'amount' => (float)$purchase_order->amount,
+ 'balance' => (float)$purchase_order->balance,
+ 'client' => $purchase_order->client ? [
+ 'name' => $purchase_order->client->present()->name(),
+ 'balance' => $purchase_order->client->balance,
+ 'payment_balance' => $purchase_order->client->payment_balance,
+ 'credit_balance' => $purchase_order->client->credit_balance,
+ ] : [],
+ 'status_id' => (string)($purchase_order->status_id ?: 1),
+ 'status' => PurchaseOrder::stringStatus($purchase_order->status_id ?? 1),
+ 'is_deleted' => (bool)$purchase_order->is_deleted,
+ 'number' => $purchase_order->number ?: '',
+ 'discount' => (float)$purchase_order->discount,
+ 'po_number' => $purchase_order->po_number ?: '',
+ 'date' => $purchase_order->date ? $this->translateDate($purchase_order->date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'last_sent_date' => $purchase_order->last_sent_date ? $this->translateDate($purchase_order->last_sent_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'next_send_date' => $purchase_order->next_send_date ? $this->translateDate($purchase_order->next_send_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'reminder1_sent' => $purchase_order->reminder1_sent ? $this->translateDate($purchase_order->reminder1_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'reminder2_sent' => $purchase_order->reminder2_sent ? $this->translateDate($purchase_order->reminder2_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'reminder3_sent' => $purchase_order->reminder3_sent ? $this->translateDate($purchase_order->reminder3_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'reminder_last_sent' => $purchase_order->reminder_last_sent ? $this->translateDate($purchase_order->reminder_last_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'due_date' => $purchase_order->due_date ? $this->translateDate($purchase_order->due_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '',
+ 'terms' => $purchase_order->terms ?: '',
+ 'public_notes' => $purchase_order->public_notes ?: '',
+ 'private_notes' => $purchase_order->private_notes ?: '',
+ 'uses_inclusive_taxes' => (bool)$purchase_order->uses_inclusive_taxes,
+ 'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '',
+ 'tax_rate1' => (float)$purchase_order->tax_rate1,
+ 'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '',
+ 'tax_rate2' => (float)$purchase_order->tax_rate2,
+ 'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '',
+ 'tax_rate3' => (float)$purchase_order->tax_rate3,
+ 'total_taxes' => (float)$purchase_order->total_taxes,
+ 'is_amount_discount' => (bool)($purchase_order->is_amount_discount ?: false),
+ 'footer' => $purchase_order->footer ?: '',
+ 'partial' => (float)($purchase_order->partial ?: 0.0),
+ 'partial_due_date' => $purchase_order->partial_due_date ? $this->translateDate($purchase_order->partial_due_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()): '',
+ 'custom_value1' => (string)$purchase_order->custom_value1 ?: '',
+ 'custom_value2' => (string)$purchase_order->custom_value2 ?: '',
+ 'custom_value3' => (string)$purchase_order->custom_value3 ?: '',
+ 'custom_value4' => (string)$purchase_order->custom_value4 ?: '',
+ 'has_tasks' => (bool)$purchase_order->has_tasks,
+ 'has_expenses' => (bool)$purchase_order->has_expenses,
+ 'custom_surcharge1' => (float)$purchase_order->custom_surcharge1,
+ 'custom_surcharge2' => (float)$purchase_order->custom_surcharge2,
+ 'custom_surcharge3' => (float)$purchase_order->custom_surcharge3,
+ 'custom_surcharge4' => (float)$purchase_order->custom_surcharge4,
+ 'custom_surcharge_tax1' => (bool)$purchase_order->custom_surcharge_tax1,
+ 'custom_surcharge_tax2' => (bool)$purchase_order->custom_surcharge_tax2,
+ 'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3,
+ 'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4,
+ 'line_items' => $purchase_order->line_items ? $this->padLineItems($purchase_order->line_items, $purchase_order->vendor): (array)[],
+ 'exchange_rate' => (float)$purchase_order->exchange_rate,
+ 'currency_id' => $purchase_order->currency_id ? (string) $purchase_order->currency_id : '',
+ ];
+
+ })->toArray();
+
+ }
+
+ /**
+ * Set Company
+ *
+ * @param Company $company
+ * @return self
+ */
public function setCompany(Company $company): self
{
$this->company = $company;
-
+
return $this;
}
+ /**
+ * Get Company
+ *
+ * @return Company
+ */
public function getCompany(): Company
{
return $this->company;
}
+ /**
+ * Setter that allows external variables to override the
+ * resolved ones from this class
+ *
+ * @param mixed $variables
+ * @return self
+ */
public function overrideVariables($variables): self
{
$this->variables = $variables;
+
+ return $this;
+ }
+
+ /**
+ * Parses and finds any field stacks to inject into the DOM Document
+ *
+ * @return self
+ */
+ public function parseGlobalStacks(): self
+ {
+ $stacks = [
+ 'entity-details',
+ 'client-details',
+ 'vendor-details',
+ 'company-details',
+ 'company-address',
+ 'shipping-details',
+ ];
+
+ collect($stacks)->filter(function ($stack) {
+ return $this->document->getElementById($stack) ?? false;
+ })
+ ->map(function ($stack){
+ $node = $this->document->getElementById($stack);
+ return ['stack' => $stack, 'labels' => $node->getAttribute('labels')];
+ })
+ ->each(function ($stack) {
+ $this->parseStack($stack);
+ });
+
+ return $this;
+
+ }
+
+ /**
+ * Injects field stacks into Template
+ *
+ * @param array $stack
+ * @return self
+ */
+ private function parseStack(array $stack): self
+ {
+
+ match($stack['stack']) {
+ 'entity-details' => $this->entityDetails(),
+ 'client-details' => $this->clientDetails($stack['labels'] == 'true'),
+ 'vendor-details' => $this->vendorDetails($stack['labels'] == 'true'),
+ 'company-details' => $this->companyDetails($stack['labels'] == 'true'),
+ 'company-address' => $this->companyAddress($stack['labels'] == 'true'),
+ 'shipping-details' => $this->shippingDetails($stack['labels'] == 'true'),
+ };
+
+ $this->save();
+
+ return $this;
+ }
+
+ /**
+ * Inject the Company Details into the DOM Document
+ *
+ * @param bool $include_labels
+ * @return self
+ */
+ private function companyDetails(bool $include_labels): self
+ {
+ $var_set = $this->getVarSet();
+
+ $company_details =
+ collect($this->company->settings->pdf_variables->company_details)
+ ->filter(function ($variable) use ($var_set) {
+ return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]);
+ })
+ ->when(!$include_labels, function ($collection) {
+ return $collection->map(function ($variable) {
+ return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
+ });
+ })->toArray();
+
+ // nlog($company_details);
+
+ $company_details = $include_labels ? $this->labelledFieldStack($company_details, 'company_details-') : $company_details;
+
+ // nlog($company_details);
+
+ $this->updateElementProperties('company-details', $company_details);
+
+ return $this;
+ }
+
+ private function companyAddress(bool $include_labels = false): self
+ {
+
+ $var_set = $this->getVarSet();
+
+ $company_address =
+ collect($this->company->settings->pdf_variables->company_address)
+ ->filter(function ($variable) use ($var_set) {
+ return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]);
+ })
+ ->when(!$include_labels, function ($collection) {
+ return $collection->map(function ($variable) {
+ return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
+ });
+ })->toArray();
+
+ $company_address = $include_labels ? $this->labelledFieldStack($company_address, 'company_address-') : $company_address;
+
+ $this->updateElementProperties('company-address', $company_address);
+
+ return $this;
+ }
+
+ /**
+ * Injects the Shipping Details into the DOM Document
+ *
+ * @param bool $include_labels
+ * @return self
+ */
+ private function shippingDetails(bool $include_labels = false): self
+ {
+ if(!$this->entity->client) {
+ return $this;
+ }
+
+ $this->client = $this->entity->client;
+
+ $shipping_address = [
+ ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
+ ['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']],
+ ['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']],
+ ['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']],
+ ['element' => 'p', 'show_empty' => false, 'elements' => [
+ ['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']],
+ ['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']],
+ ['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']],
+ ]],
+ ['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
+ ];
+
+ $shipping_address =
+ collect($shipping_address)->filter(function ($address) {
+ return isset($address['content']) && !empty($address['content']);
+ })->toArray();
+
+ $this->updateElementProperties('shipping-details', $shipping_address);
+
+ return $this;
+ }
+
+ /**
+ * Injects the Client Details into the DOM Document
+ *
+ * @param bool $include_labels
+ * @return self
+ */
+ private function clientDetails(bool $include_labels = false): self
+ {
+ $var_set = $this->getVarSet();
+
+ $client_details =
+ collect($this->company->settings->pdf_variables->client_details)
+ ->filter(function ($variable) use ($var_set) {
+ return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]);
+ })
+ ->when(!$include_labels, function ($collection) {
+ return $collection->map(function ($variable) {
+ return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
+ });
+ })->toArray();
+
+ $client_details = $include_labels ? $this->labelledFieldStack($client_details, 'client_details-') : $client_details;
+
+ $this->updateElementProperties('client-details', $client_details);
+
+ return $this;
+ }
+
+ /**
+ * Resolves the entity.
+ *
+ * Only required for resolving the entity-details stack
+ *
+ * @return string
+ */
+ private function resolveEntity(): string
+ {
+ $entity_string = '';
+
+ match($this->entity) {
+ ($this->entity instanceof Invoice) => $entity_string = 'invoice',
+ ($this->entity instanceof Quote) => $entity_string = 'quote',
+ ($this->entity instanceof Credit) => $entity_string = 'credit',
+ ($this->entity instanceof RecurringInvoice) => $entity_string = 'invoice',
+ ($this->entity instanceof PurchaseOrder) => $entity_string = 'purchase_order',
+ default => $entity_string = 'invoice',
+ };
+
+ return $entity_string;
+
+ }
+
+ /**
+ * Returns the variable array by first key, if it exists
+ *
+ * @return array
+ */
+ private function getVarSet(): array
+ {
+ return array_key_exists(array_key_first($this->variables), $this->variables) ? $this->variables[array_key_first($this->variables)] : $this->variables;
+ }
+
+ /**
+ * Injects the entity details to the DOM document
+ *
+ * @return self
+ */
+ private function entityDetails(): self
+ {
+ $entity_string = $this->resolveEntity();
+ $entity_string_prop = "{$entity_string}_details";
+ $var_set = $this->getVarSet();
+
+ $entity_details =
+ collect($this->company->settings->pdf_variables->{$entity_string_prop})
+ ->filter(function ($variable) use ($var_set) {
+ return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]);
+ })->toArray();
+
+ $this->updateElementProperties("entity-details", $this->labelledFieldStack($entity_details, 'entity_details-'));
+
+ return $this;
+ }
+
+ /**
+ * Generates the field stacks with labels
+ *
+ * @param array $variables
+ * @return array
+ */
+ private function labelledFieldStack(array $variables, string $data_ref): array
+ {
+
+ $elements = [];
+
+ foreach ($variables as $variable) {
+ $_variable = explode('.', $variable)[1];
+ $_customs = ['custom1', 'custom2', 'custom3', 'custom4'];
+
+ $var = str_replace("custom", "custom_value", $_variable);
+
+ $hidden_prop = ($data_ref == 'entity_details-') ? $this->entityVariableCheck($variable) : false;
+
+ if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) {
+ $elements[] = ['element' => 'tr', 'elements' => [
+ ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => $data_ref . substr($variable, 1) . '_label']],
+ ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => $data_ref . substr($variable, 1)]],
+ ]];
+ } else {
+ $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $hidden_prop], 'elements' => [
+ ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => $data_ref . substr($variable, 1) . '_label']],
+ ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => $data_ref . substr($variable, 1)]],
+ ]];
+ }
+ }
+
+ return $elements;
+
+ }
+
+ /**
+ * Inject Vendor Details into DOM Document
+ *
+ * @param bool $include_labels
+ * @return self
+ */
+ private function vendorDetails(bool $include_labels = false): self
+ {
+
+ $var_set = $this->getVarSet();
+
+ $vendor_details =
+ collect($this->company->settings->pdf_variables->vendor_details)
+ ->filter(function ($variable) use ($var_set) {
+ return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]);
+ })->when(!$include_labels, function ($collection) {
+ return $collection->map(function ($variable) {
+ return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]];
+ });
+ })->toArray();
+
+ $vendor_details = $include_labels ? $this->labelledFieldStack($vendor_details, 'vendor_details-') : $vendor_details;
+
+ $this->updateElementProperties('vendor-details', $vendor_details);
+
+ return $this;
+ }
+
+
+ /**
+ * Performs a variable check to ensure
+ * the variable exists
+ *
+ * @param string $variable
+ * @return bool
+ *
+ */
+ public function entityVariableCheck(string $variable): bool
+ {
+ // When it comes to invoice balance, we'll always show it.
+ if ($variable == '$invoice.total') {
+ return false;
+ }
+
+ // Some variables don't map 1:1 to table columns. This gives us support for such cases.
+ $aliases = [
+ '$quote.balance_due' => 'partial',
+ ];
+
+ try {
+ $_variable = explode('.', $variable)[1];
+ } catch (\Exception $e) {
+ throw new \Exception('Company settings seems to be broken. Missing $this->service->config->entity.variable type.');
+ }
+
+ if (\in_array($variable, \array_keys($aliases))) {
+ $_variable = $aliases[$variable];
+ }
+
+ if (is_null($this->entity->{$_variable}) || empty($this->entity->{$_variable})) {
+ return true;
+ }
+
+ return false;
+ }
+
+ ////////////////////////////////////////
+ // Dom Traversal
+ ///////////////////////////////////////
+
+ public function updateElementProperties(string $element_id, array $elements): self
+ {
+ $node = $this->document->getElementById($element_id);
+
+ $this->createElementContent($node, $elements);
+
+ return $this;
+ }
+
+ public function updateElementProperty($element, string $attribute, ?string $value)
+ {
+
+ if ($attribute == 'hidden' && ($value == false || $value == 'false')) {
+ return $element;
+ }
+
+ $element->setAttribute($attribute, $value);
+
+ if ($element->getAttribute($attribute) === $value) {
+ return $element;
+ }
+
+ return $element;
+
+ }
+
+ public function createElementContent($element, $children): self
+ {
+
+ foreach ($children as $child) {
+ $contains_html = false;
+
+ //06-11-2023 for some reason this parses content as HTML
+ // if ($child['element'] !== 'script') {
+ // if ($this->company->markdown_enabled && array_key_exists('content', $child)) {
+ // $child['content'] = str_replace('
', "\r", $child['content']);
+ // $child['content'] = $this->commonmark->convert($child['content'] ?? '');
+ // }
+ // }
+
+ if (isset($child['content'])) {
+ if (isset($child['is_empty']) && $child['is_empty'] === true) {
+ continue;
+ }
+
+ $contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0;
+ }
+
+ if ($contains_html) {
+ // If the element contains the HTML, we gonna display it as is. Backend is going to
+ // encode it for us, preventing any errors on the processing stage.
+ // Later, we decode this using Javascript so it looks like it's normal HTML being injected.
+ // To get all elements that need frontend decoding, we use 'data-state' property.
+
+ $_child = $this->document->createElement($child['element'], '');
+ $_child->setAttribute('data-state', 'encoded-html');
+ $_child->nodeValue = htmlspecialchars($child['content']);
+ } else {
+ // .. in case string doesn't contain any HTML, we'll just return
+ // raw $content.
+
+ $_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : '');
+ }
+
+ $element->appendChild($_child);
+
+ if (isset($child['properties'])) {
+ foreach ($child['properties'] as $property => $value) {
+ $this->updateElementProperty($_child, $property, $value);
+ }
+ }
+
+ if (isset($child['elements'])) {
+ $this->createElementContent($_child, $child['elements']);
+ }
+
+ }
return $this;
}
+<<<<<<< HEAD
+=======
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+>>>>>>> support_for_custom_statement_designs
}
diff --git a/composer.lock b/composer.lock
index 630eb189ccfd..d58e3cc15502 100644
--- a/composer.lock
+++ b/composer.lock
@@ -485,16 +485,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.285.3",
+ "version": "3.285.4",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "afa1e722f1b2c95644f375dc1a19072e4daf67be"
+ "reference": "c462af819d81cba49939949032b20799f5ef0fff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/afa1e722f1b2c95644f375dc1a19072e4daf67be",
- "reference": "afa1e722f1b2c95644f375dc1a19072e4daf67be",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c462af819d81cba49939949032b20799f5ef0fff",
+ "reference": "c462af819d81cba49939949032b20799f5ef0fff",
"shasum": ""
},
"require": {
@@ -574,9 +574,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.285.3"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.285.4"
},
- "time": "2023-11-09T19:07:19+00:00"
+ "time": "2023-11-10T19:25:49+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -790,16 +790,16 @@
},
{
"name": "checkout/checkout-sdk-php",
- "version": "3.0.17",
+ "version": "3.0.18",
"source": {
"type": "git",
"url": "https://github.com/checkout/checkout-sdk-php.git",
- "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27"
+ "reference": "9e606ac8ad5371cfb571050e7ea2c0c05b2b3070"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27",
- "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27",
+ "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/9e606ac8ad5371cfb571050e7ea2c0c05b2b3070",
+ "reference": "9e606ac8ad5371cfb571050e7ea2c0c05b2b3070",
"shasum": ""
},
"require": {
@@ -852,9 +852,9 @@
],
"support": {
"issues": "https://github.com/checkout/checkout-sdk-php/issues",
- "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.17"
+ "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.18"
},
- "time": "2023-10-20T22:35:30+00:00"
+ "time": "2023-11-10T09:12:20+00:00"
},
{
"name": "cleverit/ubl_invoice",
@@ -2487,16 +2487,16 @@
},
{
"name": "google/apiclient-services",
- "version": "v0.323.0",
+ "version": "v0.324.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
- "reference": "d5497d30ddfafe7592102ca48bedaf222a4ca7a6"
+ "reference": "585cc823c3d59788e4a0829d5b7e41c76950d801"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/d5497d30ddfafe7592102ca48bedaf222a4ca7a6",
- "reference": "d5497d30ddfafe7592102ca48bedaf222a4ca7a6",
+ "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/585cc823c3d59788e4a0829d5b7e41c76950d801",
+ "reference": "585cc823c3d59788e4a0829d5b7e41c76950d801",
"shasum": ""
},
"require": {
@@ -2525,9 +2525,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
- "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.323.0"
+ "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.324.0"
},
- "time": "2023-11-06T01:08:38+00:00"
+ "time": "2023-11-13T01:06:14+00:00"
},
{
"name": "google/auth",
@@ -2589,24 +2589,24 @@
},
{
"name": "graham-campbell/result-type",
- "version": "v1.1.1",
+ "version": "v1.1.2",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
- "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831"
+ "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
- "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
+ "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
- "phpoption/phpoption": "^1.9.1"
+ "phpoption/phpoption": "^1.9.2"
},
"require-dev": {
- "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
},
"type": "library",
"autoload": {
@@ -2635,7 +2635,7 @@
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
- "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1"
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
},
"funding": [
{
@@ -2647,7 +2647,7 @@
"type": "tidelift"
}
],
- "time": "2023-02-25T20:23:15+00:00"
+ "time": "2023-11-12T22:16:48+00:00"
},
{
"name": "graylog2/gelf-php",
@@ -7832,16 +7832,16 @@
},
{
"name": "phpoption/phpoption",
- "version": "1.9.1",
+ "version": "1.9.2",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
- "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e"
+ "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e",
- "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
+ "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
"shasum": ""
},
"require": {
@@ -7849,7 +7849,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
- "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
},
"type": "library",
"extra": {
@@ -7891,7 +7891,7 @@
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
- "source": "https://github.com/schmittjoh/php-option/tree/1.9.1"
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
},
"funding": [
{
@@ -7903,7 +7903,7 @@
"type": "tidelift"
}
],
- "time": "2023-02-25T19:38:58+00:00"
+ "time": "2023-11-12T21:59:55+00:00"
},
{
"name": "phpseclib/phpseclib",
@@ -10288,16 +10288,16 @@
},
{
"name": "symfony/console",
- "version": "v6.3.4",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6"
+ "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6",
- "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0d14a9f6d04d4ac38a8cea1171f4554e325dae92",
+ "reference": "0d14a9f6d04d4ac38a8cea1171f4554e325dae92",
"shasum": ""
},
"require": {
@@ -10358,7 +10358,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.3.4"
+ "source": "https://github.com/symfony/console/tree/v6.3.8"
},
"funding": [
{
@@ -10374,7 +10374,7 @@
"type": "tidelift"
}
],
- "time": "2023-08-16T10:10:12+00:00"
+ "time": "2023-10-31T08:09:35+00:00"
},
{
"name": "symfony/css-selector",
@@ -10867,16 +10867,16 @@
},
{
"name": "symfony/http-client",
- "version": "v6.3.7",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d"
+ "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d",
- "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/0314e2d49939a9831929d6fc81c01c6df137fd0a",
+ "reference": "0314e2d49939a9831929d6fc81c01c6df137fd0a",
"shasum": ""
},
"require": {
@@ -10939,7 +10939,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.3.7"
+ "source": "https://github.com/symfony/http-client/tree/v6.3.8"
},
"funding": [
{
@@ -10955,7 +10955,7 @@
"type": "tidelift"
}
],
- "time": "2023-10-29T12:41:36+00:00"
+ "time": "2023-11-06T18:31:59+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -11037,16 +11037,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v6.3.7",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e"
+ "reference": "ce332676de1912c4389222987193c3ef38033df6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
- "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce332676de1912c4389222987193c3ef38033df6",
+ "reference": "ce332676de1912c4389222987193c3ef38033df6",
"shasum": ""
},
"require": {
@@ -11094,7 +11094,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.3.7"
+ "source": "https://github.com/symfony/http-foundation/tree/v6.3.8"
},
"funding": [
{
@@ -11110,20 +11110,20 @@
"type": "tidelift"
}
],
- "time": "2023-10-28T23:55:27+00:00"
+ "time": "2023-11-07T10:17:15+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v6.3.7",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6d4098095f93279d9536a0e9124439560cc764d0"
+ "reference": "929202375ccf44a309c34aeca8305408442ebcc1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6d4098095f93279d9536a0e9124439560cc764d0",
- "reference": "6d4098095f93279d9536a0e9124439560cc764d0",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/929202375ccf44a309c34aeca8305408442ebcc1",
+ "reference": "929202375ccf44a309c34aeca8305408442ebcc1",
"shasum": ""
},
"require": {
@@ -11207,7 +11207,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v6.3.7"
+ "source": "https://github.com/symfony/http-kernel/tree/v6.3.8"
},
"funding": [
{
@@ -11223,7 +11223,7 @@
"type": "tidelift"
}
],
- "time": "2023-10-29T14:31:45+00:00"
+ "time": "2023-11-10T13:47:32+00:00"
},
{
"name": "symfony/intl",
@@ -12819,16 +12819,16 @@
},
{
"name": "symfony/string",
- "version": "v6.3.5",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339"
+ "reference": "13880a87790c76ef994c91e87efb96134522577a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339",
- "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339",
+ "url": "https://api.github.com/repos/symfony/string/zipball/13880a87790c76ef994c91e87efb96134522577a",
+ "reference": "13880a87790c76ef994c91e87efb96134522577a",
"shasum": ""
},
"require": {
@@ -12885,7 +12885,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.3.5"
+ "source": "https://github.com/symfony/string/tree/v6.3.8"
},
"funding": [
{
@@ -12901,7 +12901,7 @@
"type": "tidelift"
}
],
- "time": "2023-09-18T10:38:32+00:00"
+ "time": "2023-11-09T08:28:21+00:00"
},
{
"name": "symfony/translation",
@@ -13078,16 +13078,16 @@
},
{
"name": "symfony/uid",
- "version": "v6.3.0",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384"
+ "reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384",
- "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/819fa5ac210fb7ddda4752b91a82f50be7493dd9",
+ "reference": "819fa5ac210fb7ddda4752b91a82f50be7493dd9",
"shasum": ""
},
"require": {
@@ -13132,7 +13132,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v6.3.0"
+ "source": "https://github.com/symfony/uid/tree/v6.3.8"
},
"funding": [
{
@@ -13148,20 +13148,20 @@
"type": "tidelift"
}
],
- "time": "2023-04-08T07:25:02+00:00"
+ "time": "2023-10-31T08:07:48+00:00"
},
{
"name": "symfony/validator",
- "version": "v6.3.7",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/validator.git",
- "reference": "9cc736663fa5839b9710ac2c303bb0b951014fc1"
+ "reference": "f75b40e088d095db1e788b81605a76f4563cb80e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/validator/zipball/9cc736663fa5839b9710ac2c303bb0b951014fc1",
- "reference": "9cc736663fa5839b9710ac2c303bb0b951014fc1",
+ "url": "https://api.github.com/repos/symfony/validator/zipball/f75b40e088d095db1e788b81605a76f4563cb80e",
+ "reference": "f75b40e088d095db1e788b81605a76f4563cb80e",
"shasum": ""
},
"require": {
@@ -13228,7 +13228,7 @@
"description": "Provides tools to validate values",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/validator/tree/v6.3.7"
+ "source": "https://github.com/symfony/validator/tree/v6.3.8"
},
"funding": [
{
@@ -13244,20 +13244,20 @@
"type": "tidelift"
}
],
- "time": "2023-10-28T23:11:45+00:00"
+ "time": "2023-11-07T10:17:15+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v6.3.6",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97"
+ "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97",
- "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/81acabba9046550e89634876ca64bfcd3c06aa0a",
+ "reference": "81acabba9046550e89634876ca64bfcd3c06aa0a",
"shasum": ""
},
"require": {
@@ -13312,7 +13312,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v6.3.6"
+ "source": "https://github.com/symfony/var-dumper/tree/v6.3.8"
},
"funding": [
{
@@ -13328,20 +13328,20 @@
"type": "tidelift"
}
],
- "time": "2023-10-12T18:45:56+00:00"
+ "time": "2023-11-08T10:42:36+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.3.7",
+ "version": "v6.3.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8"
+ "reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/9758b6c69d179936435d0ffb577c3708d57e38a8",
- "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/3493af8a8dad7fa91c77fa473ba23ecd95334a92",
+ "reference": "3493af8a8dad7fa91c77fa473ba23ecd95334a92",
"shasum": ""
},
"require": {
@@ -13384,7 +13384,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.3.7"
+ "source": "https://github.com/symfony/yaml/tree/v6.3.8"
},
"funding": [
{
@@ -13400,7 +13400,7 @@
"type": "tidelift"
}
],
- "time": "2023-10-28T23:31:00+00:00"
+ "time": "2023-11-06T10:58:05+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -13703,31 +13703,31 @@
},
{
"name": "vlucas/phpdotenv",
- "version": "v5.5.0",
+ "version": "v5.6.0",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
- "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7"
+ "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
- "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+ "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
"shasum": ""
},
"require": {
"ext-pcre": "*",
- "graham-campbell/result-type": "^1.0.2",
- "php": "^7.1.3 || ^8.0",
- "phpoption/phpoption": "^1.8",
- "symfony/polyfill-ctype": "^1.23",
- "symfony/polyfill-mbstring": "^1.23.1",
- "symfony/polyfill-php80": "^1.23.1"
+ "graham-campbell/result-type": "^1.1.2",
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.2",
+ "symfony/polyfill-ctype": "^1.24",
+ "symfony/polyfill-mbstring": "^1.24",
+ "symfony/polyfill-php80": "^1.24"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.4.1",
+ "bamarni/composer-bin-plugin": "^1.8.2",
"ext-filter": "*",
- "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25"
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
},
"suggest": {
"ext-filter": "Required to use the boolean validator."
@@ -13739,7 +13739,7 @@
"forward-command": true
},
"branch-alias": {
- "dev-master": "5.5-dev"
+ "dev-master": "5.6-dev"
}
},
"autoload": {
@@ -13771,7 +13771,7 @@
],
"support": {
"issues": "https://github.com/vlucas/phpdotenv/issues",
- "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0"
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
},
"funding": [
{
@@ -13783,7 +13783,7 @@
"type": "tidelift"
}
],
- "time": "2022-10-16T01:01:54+00:00"
+ "time": "2023-11-12T22:43:29+00:00"
},
{
"name": "voku/portable-ascii",
diff --git a/tests/Feature/EInvoice/FacturaeTest.php b/tests/Feature/EInvoice/FacturaeTest.php
index 0770768da14f..2d4053126341 100644
--- a/tests/Feature/EInvoice/FacturaeTest.php
+++ b/tests/Feature/EInvoice/FacturaeTest.php
@@ -47,7 +47,7 @@ class FacturaeTest extends TestCase
$this->assertNotNull($f->run());
- nlog($f->run());
+ // nlog($f->run());
// $this->assertTrue($this->validateInvoiceXML($path));
}
diff --git a/tests/Feature/TaskApiTest.php b/tests/Feature/TaskApiTest.php
index e93640852cb9..f9ce62a62f34 100644
--- a/tests/Feature/TaskApiTest.php
+++ b/tests/Feature/TaskApiTest.php
@@ -104,6 +104,90 @@ class TaskApiTest extends TestCase
}
}
+ public function testEmptyTimeLogArray()
+ {
+
+ $data = [
+ 'client_id' => $this->client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'description' => 'Test Task',
+ 'time_log' => null,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->postJson("/api/v1/tasks", $data);
+
+ $response->assertStatus(200);
+
+ $data = [
+ 'client_id' => $this->client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'description' => 'Test Task',
+ 'time_log' => '',
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->postJson("/api/v1/tasks", $data);
+
+ $response->assertStatus(200);
+
+ $data = [
+ 'client_id' => $this->client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'description' => 'Test Task',
+ 'time_log' => '[]',
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->postJson("/api/v1/tasks", $data);
+
+ $response->assertStatus(200);
+
+ $data = [
+ 'client_id' => $this->client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'description' => 'Test Task',
+ 'time_log' => '{}',
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->postJson("/api/v1/tasks", $data);
+
+ $response->assertStatus(200);
+ }
+
+ public function testFaultyTimeLogArray()
+ {
+
+ $data = [
+ 'client_id' => $this->client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'description' => 'Test Task',
+ 'time_log' => 'ABBA is the best band in the world',
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->postJson("/api/v1/tasks", $data);
+
+ $response->assertStatus(422);
+
+ }
+
public function testTaskClientRateSet()
{
$settings = ClientSettings::defaults();
@@ -282,6 +366,45 @@ class TaskApiTest extends TestCase
$response->assertStatus(200);
+ $task->time_log = 'A very strange place';
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray());
+
+ $response->assertStatus(422);
+
+ $task->time_log = null;
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray());
+
+ $response->assertStatus(200);
+
+ $task->time_log = '';
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray());
+
+ $response->assertStatus(200);
+
+
+ $task->time_log = '{}';
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray());
+
+ $response->assertStatus(200);
+
+
+
}
public function testStoppingTaskWithDescription()
diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php
index df95599ba37a..d91ad2e5f56e 100644
--- a/tests/Feature/Template/TemplateTest.php
+++ b/tests/Feature/Template/TemplateTest.php
@@ -18,6 +18,7 @@ use App\Models\Credit;
use App\Models\Design;
use App\Models\Invoice;
use App\Models\Payment;
+use App\Models\Project;
use App\Utils\HtmlEngine;
use Tests\MockAccountData;
use App\Utils\Traits\MakesDates;
@@ -165,6 +166,8 @@ class TemplateTest extends TestCase
';
+ private string $stack = '