diff --git a/.gitignore b/.gitignore index a860fb777bdf..24709e9d3276 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ yarn-error.log local_version.txt .env .phpunit.result.cache +_ide_helper.php /resources/assets/bower /public/logo @@ -34,3 +35,4 @@ nbproject public/test.pdf public/storage/test.pdf /Modules +_ide_helper_models.php \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index 035b3d682934..1051ae0f1e41 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.74 \ No newline at end of file +5.5.75 \ No newline at end of file diff --git a/_ide_helper.php b/_ide_helper.php index 26c323563f29..c5562d34111d 100644 --- a/_ide_helper.php +++ b/_ide_helper.php @@ -4193,7 +4193,7 @@ */ public static function lock($name, $seconds = 0, $owner = null) { - /** @var \Illuminate\Cache\FileStore $instance */ + /** @var \Illuminate\Cache\RedisStore $instance */ return $instance->lock($name, $seconds, $owner); } /** @@ -4206,7 +4206,7 @@ */ public static function restoreLock($name, $owner) { - /** @var \Illuminate\Cache\FileStore $instance */ + /** @var \Illuminate\Cache\RedisStore $instance */ return $instance->restoreLock($name, $owner); } /** @@ -4217,30 +4217,65 @@ */ public static function flush() { - /** @var \Illuminate\Cache\FileStore $instance */ + /** @var \Illuminate\Cache\RedisStore $instance */ return $instance->flush(); } /** - * Get the Filesystem instance. + * Get the Redis connection instance. * - * @return \Illuminate\Filesystem\Filesystem + * @return \Illuminate\Redis\Connections\Connection * @static */ - public static function getFilesystem() + public static function connection() { - /** @var \Illuminate\Cache\FileStore $instance */ - return $instance->getFilesystem(); + /** @var \Illuminate\Cache\RedisStore $instance */ + return $instance->connection(); } /** - * Get the working directory of the cache. + * Get the Redis connection instance that should be used to manage locks. * - * @return string + * @return \Illuminate\Redis\Connections\Connection * @static */ - public static function getDirectory() + public static function lockConnection() { - /** @var \Illuminate\Cache\FileStore $instance */ - return $instance->getDirectory(); + /** @var \Illuminate\Cache\RedisStore $instance */ + return $instance->lockConnection(); + } + /** + * Specify the name of the connection that should be used to store data. + * + * @param string $connection + * @return void + * @static + */ + public static function setConnection($connection) + { + /** @var \Illuminate\Cache\RedisStore $instance */ + $instance->setConnection($connection); + } + /** + * Specify the name of the connection that should be used to manage locks. + * + * @param string $connection + * @return \Illuminate\Cache\RedisStore + * @static + */ + public static function setLockConnection($connection) + { + /** @var \Illuminate\Cache\RedisStore $instance */ + return $instance->setLockConnection($connection); + } + /** + * Get the Redis database instance. + * + * @return \Illuminate\Contracts\Redis\Factory + * @static + */ + public static function getRedis() + { + /** @var \Illuminate\Cache\RedisStore $instance */ + return $instance->getRedis(); } /** * Get the cache key prefix. @@ -4250,8 +4285,20 @@ */ public static function getPrefix() { - /** @var \Illuminate\Cache\FileStore $instance */ + /** @var \Illuminate\Cache\RedisStore $instance */ return $instance->getPrefix(); + } + /** + * Set the cache key prefix. + * + * @param string $prefix + * @return void + * @static + */ + public static function setPrefix($prefix) + { + /** @var \Illuminate\Cache\RedisStore $instance */ + $instance->setPrefix($prefix); } } @@ -9854,45 +9901,44 @@ return $instance->setConnectionName($name); } /** - * Release a reserved job back onto the queue after (n) seconds. + * Migrate the delayed jobs that are ready to the regular queue. * - * @param string $queue - * @param \Illuminate\Queue\Jobs\DatabaseJobRecord $job - * @param int $delay - * @return mixed + * @param string $from + * @param string $to + * @param int $limit + * @return array * @static */ - public static function release($queue, $job, $delay) + public static function migrateExpiredJobs($from, $to) { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ - return $instance->release($queue, $job, $delay); + /** @var \Illuminate\Queue\RedisQueue $instance */ + return $instance->migrateExpiredJobs($from, $to); } /** * Delete a reserved job from the queue. * * @param string $queue - * @param string $id + * @param \Illuminate\Queue\Jobs\RedisJob $job * @return void - * @throws \Throwable * @static */ - public static function deleteReserved($queue, $id) + public static function deleteReserved($queue, $job) { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ - $instance->deleteReserved($queue, $id); + /** @var \Illuminate\Queue\RedisQueue $instance */ + $instance->deleteReserved($queue, $job); } /** * Delete a reserved job from the reserved queue and release it. * * @param string $queue - * @param \Illuminate\Queue\Jobs\DatabaseJob $job + * @param \Illuminate\Queue\Jobs\RedisJob $job * @param int $delay * @return void * @static */ public static function deleteAndRelease($queue, $job, $delay) { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ $instance->deleteAndRelease($queue, $job, $delay); } /** @@ -9904,7 +9950,7 @@ */ public static function clear($queue) { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ return $instance->clear($queue); } /** @@ -9916,19 +9962,30 @@ */ public static function getQueue($queue) { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ return $instance->getQueue($queue); } /** - * Get the underlying database instance. + * Get the connection for the queue. * - * @return \Illuminate\Database\Connection + * @return \Illuminate\Redis\Connections\Connection * @static */ - public static function getDatabase() + public static function getConnection() { - /** @var \Illuminate\Queue\DatabaseQueue $instance */ - return $instance->getDatabase(); + /** @var \Illuminate\Queue\RedisQueue $instance */ + return $instance->getConnection(); + } + /** + * Get the underlying Redis instance. + * + * @return \Illuminate\Contracts\Redis\Factory + * @static + */ + public static function getRedis() + { + /** @var \Illuminate\Queue\RedisQueue $instance */ + return $instance->getRedis(); } /** * Get the backoff for an object-based queue handler. @@ -9939,7 +9996,7 @@ */ public static function getJobBackoff($job) { //Method inherited from \Illuminate\Queue\Queue - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ return $instance->getJobBackoff($job); } /** @@ -9951,7 +10008,7 @@ */ public static function getJobExpiration($job) { //Method inherited from \Illuminate\Queue\Queue - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ return $instance->getJobExpiration($job); } /** @@ -9963,7 +10020,7 @@ */ public static function createPayloadUsing($callback) { //Method inherited from \Illuminate\Queue\Queue - \Illuminate\Queue\DatabaseQueue::createPayloadUsing($callback); + \Illuminate\Queue\RedisQueue::createPayloadUsing($callback); } /** * Get the container instance being used by the connection. @@ -9973,7 +10030,7 @@ */ public static function getContainer() { //Method inherited from \Illuminate\Queue\Queue - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ return $instance->getContainer(); } /** @@ -9985,7 +10042,7 @@ */ public static function setContainer($container) { //Method inherited from \Illuminate\Queue\Queue - /** @var \Illuminate\Queue\DatabaseQueue $instance */ + /** @var \Illuminate\Queue\RedisQueue $instance */ $instance->setContainer($container); } @@ -17797,25 +17854,6 @@ /** * * - * @method static void createSubscription(array|string $channels, \Closure $callback, string $method = 'subscribe') - * @method static \Illuminate\Redis\Limiters\ConcurrencyLimiterBuilder funnel(string $name) - * @method static \Illuminate\Redis\Limiters\DurationLimiterBuilder throttle(string $name) - * @method static mixed client() - * @method static void subscribe(array|string $channels, \Closure $callback) - * @method static void psubscribe(array|string $channels, \Closure $callback) - * @method static mixed command(string $method, array $parameters = []) - * @method static void listen(\Closure $callback) - * @method static string|null getName() - * @method static \Illuminate\Redis\Connections\Connection setName(string $name) - * @method static \Illuminate\Contracts\Events\Dispatcher getEventDispatcher() - * @method static void setEventDispatcher(\Illuminate\Contracts\Events\Dispatcher $events) - * @method static void unsetEventDispatcher() - * @method static void macro(string $name, object|callable $macro) - * @method static void mixin(object $mixin, bool $replace = true) - * @method static bool hasMacro(string $name) - * @method static void flushMacros() - * @method static mixed macroCall(string $method, array $parameters) - * @see \Illuminate\Redis\RedisManager */ class Redis { /** diff --git a/app/Factory/ClientContactFactory.php b/app/Factory/ClientContactFactory.php index 20e4d69e96f4..9d6216452485 100644 --- a/app/Factory/ClientContactFactory.php +++ b/app/Factory/ClientContactFactory.php @@ -22,7 +22,7 @@ class ClientContactFactory $client_contact->first_name = ''; $client_contact->user_id = $user_id; $client_contact->company_id = $company_id; - $client_contact->contact_key = Str::random(40); + $client_contact->contact_key = Str::random(32); $client_contact->id = 0; $client_contact->send_email = true; diff --git a/app/Factory/VendorContactFactory.php b/app/Factory/VendorContactFactory.php index 05031eda3bbf..499377b1ac64 100644 --- a/app/Factory/VendorContactFactory.php +++ b/app/Factory/VendorContactFactory.php @@ -22,7 +22,7 @@ class VendorContactFactory $vendor_contact->first_name = ''; $vendor_contact->user_id = $user_id; $vendor_contact->company_id = $company_id; - $vendor_contact->contact_key = Str::random(40); + $vendor_contact->contact_key = Str::random(32); $vendor_contact->id = 0; return $vendor_contact; diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php index cb095e4aa3cc..27636dbcb74e 100644 --- a/app/Filters/PaymentFilters.php +++ b/app/Filters/PaymentFilters.php @@ -100,7 +100,6 @@ class PaymentFilters extends QueryFilters if (count($payment_filters) >0) { $query->whereIn('status_id', $payment_filters); } - }); return $this->builder; diff --git a/app/Filters/SchedulerFilters.php b/app/Filters/SchedulerFilters.php new file mode 100644 index 000000000000..5721c1502035 --- /dev/null +++ b/app/Filters/SchedulerFilters.php @@ -0,0 +1,65 @@ +builder; + } + + return $this->builder->where(function ($query) use ($filter) { + $query->where('name', 'like', '%'.$filter.'%'); + }); + } + + /** + * Sorts the list based on $sort. + * + * @param string sort formatted as column|asc + * @return Builder + */ + public function sort(string $sort = ''): Builder + { + $sort_col = explode('|', $sort); + + if (!is_array($sort_col) || count($sort_col) != 2) { + return $this->builder; + } + + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + } + + /** + * Filters the query by the users company ID. + * + * @return Builder + */ + public function entityFilter(): Builder + { + return $this->builder->company(); + } +} diff --git a/app/Http/Controllers/ClientPortal/ContactHashLoginController.php b/app/Http/Controllers/ClientPortal/ContactHashLoginController.php index 5f5fc4fb92a6..1f8579ba6f9d 100644 --- a/app/Http/Controllers/ClientPortal/ContactHashLoginController.php +++ b/app/Http/Controllers/ClientPortal/ContactHashLoginController.php @@ -50,9 +50,9 @@ class ContactHashLoginController extends Controller public function errorPage() { return render('generic.error', [ - 'title' => session()->get('title'), - 'notification' => session()->get('notification'), - 'account' => auth()->guard('contact')?->user()?->user?->account, + 'title' => session()->get('title'), + 'notification' => session()->get('notification'), + 'account' => auth()->guard('contact')?->user()?->user?->account, 'company' => auth()->guard('contact')?->user()?->user?->company ]); } diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index 3307eb7d2eb7..61a08aea9c3e 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -12,21 +12,24 @@ namespace App\Http\Controllers\ClientPortal; -use App\Factory\PaymentFactory; -use App\Http\Controllers\Controller; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; -use App\Models\CompanyGateway; use App\Models\Invoice; use App\Models\Payment; +use Illuminate\View\View; +use App\Models\GatewayType; use App\Models\PaymentHash; +use App\Models\PaymentType; +use Illuminate\Http\Request; +use App\Models\CompanyGateway; +use App\Factory\PaymentFactory; +use App\Utils\Traits\MakesHash; +use App\Utils\Traits\MakesDates; +use App\Http\Controllers\Controller; +use Illuminate\Http\RedirectResponse; +use Illuminate\Contracts\View\Factory; +use App\PaymentDrivers\Stripe\BankTransfer; use App\Services\ClientPortal\InstantPayment; use App\Services\Subscription\SubscriptionService; -use App\Utils\Traits\MakesDates; -use App\Utils\Traits\MakesHash; -use Illuminate\Contracts\View\Factory; -use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; -use Illuminate\View\View; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; /** * Class PaymentController. @@ -56,9 +59,35 @@ class PaymentController extends Controller public function show(Request $request, Payment $payment) { $payment->load('invoices'); + $bank_details = false; + $payment_intent = false; + $data = false; + $gateway = false; + if($payment->gateway_type_id == GatewayType::DIRECT_DEBIT && $payment->type_id == PaymentType::DIRECT_DEBIT){ + + if (method_exists($payment->company_gateway->driver($payment->client), 'getPaymentIntent')) { + $stripe = $payment->company_gateway->driver($payment->client); + $payment_intent = $stripe->getPaymentIntent($payment->transaction_reference); + + $bt = new BankTransfer($stripe); + + match($payment->currency->code){ + 'MXN' => $data = $bt->formatDataforMx($payment_intent), + 'EUR' => $data = $bt->formatDataforEur($payment_intent), + 'JPY' => $data = $bt->formatDataforJp($payment_intent), + 'GBP' => $data = $bt->formatDataforUk($payment_intent), + }; + + $gateway = $stripe; + } + } + + return $this->render('payments.show', [ 'payment' => $payment, + 'bank_details' => $payment_intent ? $data : false, + 'currency' => strtolower($payment->currency->code), ]); } diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 19627cb4fe64..137e0d085abb 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -32,10 +32,12 @@ use App\Repositories\CreditRepository; use App\Repositories\InvoiceRepository; use App\Repositories\QuoteRepository; use App\Repositories\RecurringInvoiceRepository; +use App\Services\Pdf\PdfMock; use App\Services\PdfMaker\Design; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker; +use App\Services\Preview\StubBuilder; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Ninja; @@ -177,162 +179,13 @@ class PreviewController extends BaseController public function design(DesignPreviewRequest $request) { - if (Ninja::isHosted() && in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); } - $company = auth()->user()->company(); + $pdf = (new PdfMock($request->all(), auth()->user()->company()))->build()->getPdf(); - MultiDB::setDb($company->db); - - if ($request->input('entity') == 'quote') { - $repo = new QuoteRepository(); - $entity_obj = QuoteFactory::create($company->id, auth()->user()->id); - $class = Quote::class; - } elseif ($request->input('entity') == 'credit') { - $repo = new CreditRepository(); - $entity_obj = CreditFactory::create($company->id, auth()->user()->id); - $class = Credit::class; - } elseif ($request->input('entity') == 'recurring_invoice') { - $repo = new RecurringInvoiceRepository(); - $entity_obj = RecurringInvoiceFactory::create($company->id, auth()->user()->id); - $class = RecurringInvoice::class; - } else { //assume it is either an invoice or a null object - $repo = new InvoiceRepository(); - $entity_obj = InvoiceFactory::create($company->id, auth()->user()->id); - $class = Invoice::class; - } - - try { - DB::connection(config('database.default'))->beginTransaction(); - - if ($request->has('entity_id')) { - $entity_obj = $class::on(config('database.default')) - ->with('client.company') - ->where('id', $this->decodePrimaryKey($request->input('entity_id'))) - ->where('company_id', $company->id) - ->withTrashed() - ->first(); - } - - if ($request->has('client_id')) { - $client = Client::withTrashed()->find($this->decodePrimaryKey($request->client_id)); - if ($request->settings_type == 'client') { - $client->settings = $request->settings; - $client->save(); - } - } - - if ($request->has('group_id')) { - $group = GroupSetting::withTrashed()->find($this->decodePrimaryKey($request->group_id)); - if ($request->settings_type == 'group') { - $group->settings = $request->settings; - $group->save(); - } - } - - if ($request->settings_type == 'company') { - $company->settings = $request->settings; - $company->save(); - } - - if ($request->has('footer') && !$request->filled('footer') && $request->input('entity') == 'recurring_invoice') { - $request->merge(['footer' => $company->settings->invoice_footer]); - } - - if ($request->has('terms') && !$request->filled('terms') && $request->input('entity') == 'recurring_invoice') { - $request->merge(['terms' => $company->settings->invoice_terms]); - } - - $entity_obj = $repo->save($request->all(), $entity_obj); - - if (! $request->has('entity_id')) { - $entity_obj->service()->fillDefaults()->save(); - } - - App::forgetInstance('translator'); - $t = app('translator'); - App::setLocale($entity_obj->client->locale()); - $t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings())); - - $html = new HtmlEngine($entity_obj->invitations()->first()); - - $design = \App\Models\Design::find($entity_obj->design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = \App\Models\Design::find(2); - } - - 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' => $entity_obj->client, - 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), - ], - 'process_markdown' => $entity_obj->client->company->markdown_enabled, - ]; - - $maker = new PdfMaker($state); - - $maker - ->design($template) - ->build(); - - DB::connection(config('database.default'))->rollBack(); - - if (request()->query('html') == 'true') { - nlog($maker->getCompiledHTML()); - return $maker->getCompiledHTML(); - } - } catch(\Exception $e) { - nlog($e->getMessage()); - DB::connection(config('database.default'))->rollBack(); - - return; - } - - //if phantom js...... inject here.. - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); - } - - 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, auth()->user()->company()); - - - $numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company()); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - - return $pdf; - } - - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - - $response = Response::make($file_path, 200); + $response = Response::make($pdf, 200); $response->header('Content-Type', 'application/pdf'); return $response; @@ -340,10 +193,11 @@ class PreviewController extends BaseController public function live(PreviewInvoiceRequest $request) { - if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co', 'staging.invoicing.co'])) { + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); } + $company = auth()->user()->company(); MultiDB::setDb($company->db); diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 91fc65b19585..e89f75c238f5 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -208,9 +208,7 @@ class SetupController extends Controller public function checkPdf(Request $request) { try { - return response(['url' => ''], 200); - } catch (Exception $e) { nlog($e->getMessage()); diff --git a/app/Http/Controllers/TaskSchedulerController.php b/app/Http/Controllers/TaskSchedulerController.php index e16abab562be..370d45cf1feb 100644 --- a/app/Http/Controllers/TaskSchedulerController.php +++ b/app/Http/Controllers/TaskSchedulerController.php @@ -12,6 +12,7 @@ namespace App\Http\Controllers; use App\Factory\SchedulerFactory; +use App\Filters\SchedulerFilters; use App\Http\Requests\TaskScheduler\DestroySchedulerRequest; use App\Http\Requests\TaskScheduler\CreateSchedulerRequest; use App\Http\Requests\TaskScheduler\ShowSchedulerRequest; @@ -58,9 +59,9 @@ class TaskSchedulerController extends BaseController * ), * ) */ - public function index() + public function index(SchedulerFilters $filters) { - $schedulers = Scheduler::where('company_id', auth()->user()->company()->id); + $schedulers = Scheduler::filter($filters); return $this->listResponse($schedulers); } diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index 80bc02e5d5df..fcf88380d5e0 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -91,7 +91,7 @@ class InvitationController extends Controller $file_name = $invitation->purchase_order->numberFormatter().'.pdf'; - $file = (new CreatePurchaseOrderPdf($invitation))->rawPdf(); + $file = (new CreatePurchaseOrderPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; diff --git a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php index b8dfa9f09b7f..b92c22c55ad5 100644 --- a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php +++ b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php @@ -29,8 +29,15 @@ class UploadBankIntegrationRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php index 910704e08fb6..e3b62225019e 100644 --- a/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php +++ b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php @@ -29,8 +29,15 @@ class UploadBankTransactionRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 91c03091659f..ffe65adb9612 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -38,14 +38,15 @@ class StoreClientRequest extends Request public function rules() { - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } if (isset($this->number)) { diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 57a2e0874fb0..6a51aec64bf0 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -38,14 +38,15 @@ class UpdateClientRequest extends Request { /* Ensure we have a client name, and that all emails are unique*/ - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000'; diff --git a/app/Http/Requests/Client/UploadClientRequest.php b/app/Http/Requests/Client/UploadClientRequest.php index 3be3c67204a4..1093b3f1ebcd 100644 --- a/app/Http/Requests/Client/UploadClientRequest.php +++ b/app/Http/Requests/Client/UploadClientRequest.php @@ -29,8 +29,15 @@ class UploadClientRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Company/UploadCompanyRequest.php b/app/Http/Requests/Company/UploadCompanyRequest.php index f56037c0034f..999101417dd0 100644 --- a/app/Http/Requests/Company/UploadCompanyRequest.php +++ b/app/Http/Requests/Company/UploadCompanyRequest.php @@ -29,8 +29,15 @@ class UploadCompanyRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index b47f6efa58fc..589cfe24527c 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -43,14 +43,15 @@ class StoreCreditRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; diff --git a/app/Http/Requests/Credit/UpdateCreditRequest.php b/app/Http/Requests/Credit/UpdateCreditRequest.php index 71d02a674dcd..d208cf223f51 100644 --- a/app/Http/Requests/Credit/UpdateCreditRequest.php +++ b/app/Http/Requests/Credit/UpdateCreditRequest.php @@ -41,15 +41,16 @@ class UpdateCreditRequest extends Request public function rules() { $rules = []; + + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); - - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } if ($this->number) { diff --git a/app/Http/Requests/Credit/UploadCreditRequest.php b/app/Http/Requests/Credit/UploadCreditRequest.php index 8af1851982b7..06721355bbbd 100644 --- a/app/Http/Requests/Credit/UploadCreditRequest.php +++ b/app/Http/Requests/Credit/UploadCreditRequest.php @@ -29,8 +29,15 @@ class UploadCreditRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Expense/UploadExpenseRequest.php b/app/Http/Requests/Expense/UploadExpenseRequest.php index 9451e7896d21..b04cde728549 100644 --- a/app/Http/Requests/Expense/UploadExpenseRequest.php +++ b/app/Http/Requests/Expense/UploadExpenseRequest.php @@ -29,8 +29,15 @@ class UploadExpenseRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php index 05abef1bb632..c3c1415a6c90 100644 --- a/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php @@ -29,10 +29,17 @@ class UploadGroupSettingRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; - } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } } diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index 446f1380e578..5f21287c3839 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -37,24 +37,15 @@ class StoreInvoiceRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - - if ($this->input('file') && is_array($this->input('file'))) { - $documents = count($this->input('file')); - - foreach (range(0, $documents) as $index) { - $rules['file.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('file')) { - $rules['file'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index 0fbcf8cb3402..0a4f5d31e149 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -39,14 +39,15 @@ class UpdateInvoiceRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['id'] = new LockedInvoiceRule($this->invoice); diff --git a/app/Http/Requests/Invoice/UploadInvoiceRequest.php b/app/Http/Requests/Invoice/UploadInvoiceRequest.php index 8613eeef6204..70157d05aac1 100644 --- a/app/Http/Requests/Invoice/UploadInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UploadInvoiceRequest.php @@ -29,14 +29,22 @@ class UploadInvoiceRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; - } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - if ($this->input('file')) { - $rules['file'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; } + + public function prepareForValidation() + { + + } } diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 908c3fd65ad1..f31e1cf8d3ff 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -113,16 +113,17 @@ class StorePaymentRequest extends Request ]; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } - + return $rules; } } diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 1664d76d9a60..8f24eb3f6518 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -38,23 +38,23 @@ class UpdatePaymentRequest extends Request $rules = [ 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule($this->all())], 'invoices.*.invoice_id' => 'distinct', - 'documents' => 'mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx', ]; if ($this->number) { $rules['number'] = Rule::unique('payments')->where('company_id', auth()->user()->company()->id)->ignore($this->payment->id); } - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } - + return $rules; } diff --git a/app/Http/Requests/Payment/UploadPaymentRequest.php b/app/Http/Requests/Payment/UploadPaymentRequest.php index 4be1947ba2a6..ad3874895f2e 100644 --- a/app/Http/Requests/Payment/UploadPaymentRequest.php +++ b/app/Http/Requests/Payment/UploadPaymentRequest.php @@ -29,8 +29,15 @@ class UploadPaymentRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Preview/DesignPreviewRequest.php b/app/Http/Requests/Preview/DesignPreviewRequest.php index 3d9a6cb973df..21d47405e2d1 100644 --- a/app/Http/Requests/Preview/DesignPreviewRequest.php +++ b/app/Http/Requests/Preview/DesignPreviewRequest.php @@ -42,8 +42,7 @@ class DesignPreviewRequest extends Request public function rules() { $rules = [ - 'entity' => 'bail|sometimes|string', - 'entity_id' => 'bail|sometimes|string', + 'entity_type' => 'bail|required|in:invoice,quote,credit,purchase_order', 'settings_type' => 'bail|required|in:company,group,client', 'settings' => 'sometimes', 'group_id' => 'sometimes', diff --git a/app/Http/Requests/Product/StoreProductRequest.php b/app/Http/Requests/Product/StoreProductRequest.php index a12249964e44..a512dc6e1750 100644 --- a/app/Http/Requests/Product/StoreProductRequest.php +++ b/app/Http/Requests/Product/StoreProductRequest.php @@ -28,14 +28,15 @@ class StoreProductRequest extends Request public function rules() { - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['cost'] = 'sometimes|numeric'; diff --git a/app/Http/Requests/Product/UpdateProductRequest.php b/app/Http/Requests/Product/UpdateProductRequest.php index c69758b4b842..f824cc954ae0 100644 --- a/app/Http/Requests/Product/UpdateProductRequest.php +++ b/app/Http/Requests/Product/UpdateProductRequest.php @@ -31,16 +31,17 @@ class UpdateProductRequest extends Request public function rules() { - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } - + $rules['cost'] = 'numeric'; $rules['price'] = 'numeric'; $rules['quantity'] = 'numeric'; diff --git a/app/Http/Requests/Product/UploadProductRequest.php b/app/Http/Requests/Product/UploadProductRequest.php index 8a12376f7ab6..d08a20c0f571 100644 --- a/app/Http/Requests/Product/UploadProductRequest.php +++ b/app/Http/Requests/Product/UploadProductRequest.php @@ -28,9 +28,15 @@ class UploadProductRequest extends Request public function rules() { $rules = []; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/Project/CreateProjectRequest.php b/app/Http/Requests/Project/CreateProjectRequest.php index 3d3b29506c9c..e9f9f193f750 100644 --- a/app/Http/Requests/Project/CreateProjectRequest.php +++ b/app/Http/Requests/Project/CreateProjectRequest.php @@ -11,6 +11,7 @@ namespace App\Http\Requests\Project; +use App\Models\Project; use App\Http\Requests\Request; class CreateProjectRequest extends Request @@ -22,6 +23,7 @@ class CreateProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + return auth()->user()->can('create', Project::class); + } } diff --git a/app/Http/Requests/Project/DestroyProjectRequest.php b/app/Http/Requests/Project/DestroyProjectRequest.php index 4c50902015cd..368f779f8a1b 100644 --- a/app/Http/Requests/Project/DestroyProjectRequest.php +++ b/app/Http/Requests/Project/DestroyProjectRequest.php @@ -22,6 +22,6 @@ class DestroyProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + return auth()->user()->can('edit', $this->project); } } diff --git a/app/Http/Requests/Project/EditProjectRequest.php b/app/Http/Requests/Project/EditProjectRequest.php index 04bf95304d3e..5c785163a2a3 100644 --- a/app/Http/Requests/Project/EditProjectRequest.php +++ b/app/Http/Requests/Project/EditProjectRequest.php @@ -22,7 +22,7 @@ class EditProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + return auth()->user()->can('edit', $this->project); } /** diff --git a/app/Http/Requests/Project/ShowProjectRequest.php b/app/Http/Requests/Project/ShowProjectRequest.php index 140c7fb1919f..6e6b4ba98a84 100644 --- a/app/Http/Requests/Project/ShowProjectRequest.php +++ b/app/Http/Requests/Project/ShowProjectRequest.php @@ -22,7 +22,9 @@ class ShowProjectRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + // return auth()->user()->isAdmin(); + return auth()->user()->can('view', $this->project); + } /** diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index df39200d8d3d..3e2b7ee6a132 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -42,6 +42,17 @@ class StoreProjectRequest extends Request $rules['number'] = Rule::unique('projects')->where('company_id', auth()->user()->company()->id); } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $this->globalRules($rules); } diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index 50595104c824..9d938ce8d046 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -37,6 +37,17 @@ class UpdateProjectRequest extends Request $rules['number'] = Rule::unique('projects')->where('company_id', auth()->user()->company()->id)->ignore($this->project->id); } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $this->globalRules($rules); } diff --git a/app/Http/Requests/Project/UploadProjectRequest.php b/app/Http/Requests/Project/UploadProjectRequest.php index cccf73f4bad3..bf37beab7d29 100644 --- a/app/Http/Requests/Project/UploadProjectRequest.php +++ b/app/Http/Requests/Project/UploadProjectRequest.php @@ -29,8 +29,15 @@ class UploadProjectRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php index 21c82dda16c0..3071a870a6ec 100644 --- a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -47,6 +47,17 @@ class StorePurchaseOrderRequest extends Request $rules['is_amount_discount'] = ['boolean']; $rules['line_items'] = 'array'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php index d84a443d74b5..3ee030773a09 100644 --- a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -50,6 +50,17 @@ class UpdatePurchaseOrderRequest extends Request $rules['discount'] = 'sometimes|numeric'; $rules['is_amount_discount'] = ['boolean']; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } diff --git a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php index a534153f3076..3fdce5fac4e0 100644 --- a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php @@ -29,10 +29,17 @@ class UploadPurchaseOrderRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; - } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } } diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index 2722d64aa22f..18c132367891 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -39,16 +39,17 @@ class StoreQuoteRequest extends Request $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } - + $rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)]; $rules['discount'] = 'sometimes|numeric'; diff --git a/app/Http/Requests/Quote/UpdateQuoteRequest.php b/app/Http/Requests/Quote/UpdateQuoteRequest.php index d3b786fde20e..5d75ef768a7a 100644 --- a/app/Http/Requests/Quote/UpdateQuoteRequest.php +++ b/app/Http/Requests/Quote/UpdateQuoteRequest.php @@ -37,16 +37,18 @@ class UpdateQuoteRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } + if ($this->number) { $rules['number'] = Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->quote->id); } diff --git a/app/Http/Requests/Quote/UploadQuoteRequest.php b/app/Http/Requests/Quote/UploadQuoteRequest.php index ee7a125df8c2..1dce9e19de03 100644 --- a/app/Http/Requests/Quote/UploadQuoteRequest.php +++ b/app/Http/Requests/Quote/UploadQuoteRequest.php @@ -28,9 +28,16 @@ class UploadQuoteRequest extends Request public function rules() { $rules = []; + + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php index 978989b3730f..d2752a446e57 100644 --- a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php @@ -48,6 +48,17 @@ class StoreRecurringExpenseRequest extends Request $rules['tax_amount2'] = 'numeric'; $rules['tax_amount3'] = 'numeric'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $this->globalRules($rules); } diff --git a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php index acbb167aff32..271a46bfd25b 100644 --- a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php @@ -48,6 +48,17 @@ class UpdateRecurringExpenseRequest extends Request $rules['tax_amount3'] = 'numeric'; $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $this->globalRules($rules); } diff --git a/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php index 516a0495a940..d086142bff41 100644 --- a/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php @@ -29,8 +29,15 @@ class UploadRecurringExpenseRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index 6b92361e9566..9cfab48bbf5f 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -38,14 +38,15 @@ class StoreRecurringInvoiceRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index bbaeed17f9f6..2ad0c1d52558 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -38,14 +38,15 @@ class UpdateRecurringInvoiceRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } if ($this->number) { diff --git a/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php index b975ad85ed5a..a6125e8c7bcb 100644 --- a/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php @@ -29,10 +29,17 @@ class UploadRecurringInvoiceRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; - } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } } diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php index 588ccaa80e9a..ec3225dfd0f1 100644 --- a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -36,15 +36,16 @@ class StoreRecurringQuoteRequest extends Request public function rules() { $rules = []; + + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); - - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; diff --git a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php index 9c58ad24a20e..957173a241fc 100644 --- a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php @@ -37,16 +37,17 @@ class UpdateRecurringQuoteRequest extends Request { $rules = []; - if ($this->input('documents') && is_array($this->input('documents'))) { - $documents = count($this->input('documents')); + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; - foreach (range(0, $documents) as $index) { - $rules['documents.'.$index] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; - } - } elseif ($this->input('documents')) { - $rules['documents'] = 'file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } - + if ($this->number) { $rules['number'] = Rule::unique('recurring_quotes')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_quote->id); } diff --git a/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php index ed6b6824ff2f..6ec54bf6ae1b 100644 --- a/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php @@ -29,10 +29,17 @@ class UploadRecurringQuoteRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; - } + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 281490e37ed3..5553feddf077 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -20,6 +20,7 @@ class Request extends FormRequest use MakesHash; use RuntimeFormRequest; + protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; /** * Get the validation rules that apply to the request. * diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index b5d160316e08..a4fe31f950c5 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -58,6 +58,17 @@ class StoreTaskRequest extends Request $fail('Please correct overlapping values'); } }]; + + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } return $this->globalRules($rules); diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 8917e44643d4..69194e7beee0 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -30,7 +30,6 @@ class UpdateTaskRequest extends Request */ public function authorize() : bool { - nlog("oioi"); //prevent locked tasks from updating if ($this->task->invoice_id && $this->task->company->invoice_task_lock) { return false; @@ -67,6 +66,17 @@ class UpdateTaskRequest extends Request } }]; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $this->globalRules($rules); } diff --git a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php index dc537d1a37ab..4c5038813644 100644 --- a/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/UpdateSchedulerRequest.php @@ -11,8 +11,8 @@ namespace App\Http\Requests\TaskScheduler; use App\Http\Requests\Request; -use Illuminate\Validation\Rule; use App\Http\ValidationRules\Scheduler\ValidClientIds; +use Illuminate\Validation\Rule; class UpdateSchedulerRequest extends Request { diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index dc67adcbffa4..e2ee08adf7cc 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -47,6 +47,16 @@ class StoreVendorRequest extends Request $rules['currency_id'] = 'bail|required|exists:currencies,id'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } return $rules; } diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index 8beda8c22eef..46ac272f643d 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -44,6 +44,17 @@ class UpdateVendorRequest extends Request $rules['contacts.*.email'] = 'nullable|distinct'; $rules['currency_id'] = 'bail|sometimes|exists:currencies,id'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; + } + return $rules; } diff --git a/app/Http/Requests/Vendor/UploadVendorRequest.php b/app/Http/Requests/Vendor/UploadVendorRequest.php index ca89b547cbba..fb08ca962608 100644 --- a/app/Http/Requests/Vendor/UploadVendorRequest.php +++ b/app/Http/Requests/Vendor/UploadVendorRequest.php @@ -29,8 +29,15 @@ class UploadVendorRequest extends Request { $rules = []; - if ($this->input('documents')) { - $rules['documents'] = 'file|mimes:csv,png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:2000000'; + if($this->file('documents') && is_array($this->file('documents'))) + $rules['documents.*'] = $this->file_validation; + elseif($this->file('documents')) + $rules['documents'] = $this->file_validation; + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->file_validation; + } elseif ($this->file('file')) { + $rules['file'] = $this->file_validation; } return $rules; diff --git a/app/Http/ValidationRules/Company/ValidCompanyQuantity.php b/app/Http/ValidationRules/Company/ValidCompanyQuantity.php index b9166698e8c5..75796f03eb97 100644 --- a/app/Http/ValidationRules/Company/ValidCompanyQuantity.php +++ b/app/Http/ValidationRules/Company/ValidCompanyQuantity.php @@ -26,8 +26,9 @@ class ValidCompanyQuantity implements Rule */ public function passes($attribute, $value) { - if(config('ninja.testvars.travis')) + if (config('ninja.testvars.travis')) { return true; + } if (Ninja::isSelfHost()) { return auth()->user()->company()->account->companies->count() < 10; diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index b6dc286b23df..da3e02d04da8 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -611,8 +611,9 @@ class NinjaMailerJob implements ShouldQueue public function failed($exception = null) { - if($exception) + if ($exception) { nlog($exception->getMessage()); + } config(['queue.failed.driver' => null]); } diff --git a/app/Jobs/Util/UnlinkFile.php b/app/Jobs/Util/UnlinkFile.php index 82757879f794..722a489c98c3 100644 --- a/app/Jobs/Util/UnlinkFile.php +++ b/app/Jobs/Util/UnlinkFile.php @@ -22,14 +22,8 @@ class UnlinkFile implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $file_path; - - protected $disk; - - public function __construct(string $disk, ?string $file_path) + public function __construct(protected string $disk = '', protected ?string $file_path = '') { - $this->file_path = $file_path; - $this->disk = $disk; } /** @@ -40,7 +34,12 @@ class UnlinkFile implements ShouldQueue public function handle() { /* Do not delete files if we are on the sync queue*/ - if (config('queue.default') == 'sync' || ! $this->file_path) { + if (config('queue.default') == 'sync') { + return; + } + + + if (!$this->file_path) { return; } diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php index 9db966cffc5d..5f581f88b3f8 100644 --- a/app/Listeners/Payment/PaymentNotification.php +++ b/app/Listeners/Payment/PaymentNotification.php @@ -77,7 +77,6 @@ class PaymentNotification implements ShouldQueue (new NinjaMailerJob($nmo))->handle(); $nmo = null; - } } diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 39c697a8c899..db9e5e9d7f1a 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -11,16 +11,16 @@ namespace App\Models; -use Illuminate\Support\Str; -use Illuminate\Support\Carbon; -use App\Utils\Traits\MakesHash; +use App\DataMapper\ClientSettings; use App\Jobs\Util\WebhookHandler; use App\Models\Traits\Excludable; -use App\DataMapper\ClientSettings; -use Illuminate\Database\Eloquent\Model; +use App\Utils\Traits\MakesHash; use App\Utils\Traits\UserSessionAttributes; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; +use Illuminate\Support\Carbon; +use Illuminate\Support\Str; /** * Class BaseModel diff --git a/app/Models/Client.php b/app/Models/Client.php index 173bd945310f..288e68c374da 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -327,21 +327,6 @@ class Client extends BaseModel implements HasLocalePreference return $this->service()->updateBalance($amount); } - /** - * Adjusts client "balances" when a client - * makes a payment that goes on file, but does - * not effect the client.balance record. - * - * @param float $amount Adjustment amount - * @return Client - */ - // public function processUnappliedPayment($amount) :Client - // { - // return $this->service()->updatePaidToDate($amount) - // ->adjustCreditBalance($amount) - // ->save(); - // } - /** * Returns the entire filtered set * of settings which have been merged from diff --git a/app/Models/Scheduler.php b/app/Models/Scheduler.php index 2e34d9ab2e06..028b4fb6dbd4 100644 --- a/app/Models/Scheduler.php +++ b/app/Models/Scheduler.php @@ -32,7 +32,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; class Scheduler extends BaseModel { use SoftDeletes; - + use Filterable; + protected $fillable = [ 'name', 'frequency_id', diff --git a/app/Models/StaticModel.php b/app/Models/StaticModel.php index c2a29fd67ddb..b2c6ccf58506 100644 --- a/app/Models/StaticModel.php +++ b/app/Models/StaticModel.php @@ -11,8 +11,8 @@ namespace App\Models; -use App\Utils\Traits\MakesHash; use App\Models\Traits\Excludable; +use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; diff --git a/app/Models/User.php b/app/Models/User.php index f2211b547158..e952e7f5a9b3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -486,15 +486,15 @@ class User extends Authenticatable implements MustVerifyEmail /** * Used when we need to filter permissions carefully. - * + * * For instance, users that have view_client permissions should not - * see the client balance, however if they also have + * see the client balance, however if they also have * view_invoice or view_all etc, then they should be able to see the balance. - * + * * First we pass over the excluded permissions and return false if we find a match. - * + * * If those permissions are not hit, then we can iterate through the matched_permissions and search for a hit. - * + * * Note, returning FALSE here means the user does NOT have the permission we want to exclude * * @param array $matched_permission @@ -513,7 +513,7 @@ class User extends Authenticatable implements MustVerifyEmail } } - foreach($matched_permission as $permission) { + foreach ($matched_permission as $permission) { if ($this->hasExactPermission($permission)) { return true; } diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 20582ad23ab0..3c5d7ddc095c 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -169,6 +169,11 @@ class Vendor extends BaseModel return ''; } + public function getMergedSettings() :object + { + return $this->company->settings; + } + public function purchase_order_filepath($invitation) { $contact_key = $invitation->contact->contact_key; diff --git a/app/PaymentDrivers/Stripe/BankTransfer.php b/app/PaymentDrivers/Stripe/BankTransfer.php index 29b83c6c9aed..7e110e9f8844 100644 --- a/app/PaymentDrivers/Stripe/BankTransfer.php +++ b/app/PaymentDrivers/Stripe/BankTransfer.php @@ -12,16 +12,17 @@ namespace App\PaymentDrivers\Stripe; -use App\Models\Payment; -use App\Models\SystemLog; -use Stripe\PaymentIntent; -use App\Models\GatewayType; -use App\Models\PaymentType; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; use App\Exceptions\PaymentFailed; -use App\PaymentDrivers\StripePaymentDriver; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Util\SystemLogger; +use App\Models\GatewayType; +use App\Models\Payment; +use App\Models\PaymentType; +use App\Models\SystemLog; +use App\PaymentDrivers\StripePaymentDriver; +use App\Utils\Number; +use App\Utils\Traits\MakesHash; +use Stripe\PaymentIntent; class BankTransfer { @@ -68,7 +69,7 @@ class BankTransfer $data['return_url'] = $this->buildReturnUrl(); $data['gateway'] = $this->stripe; $data['client_secret'] = $intent ? $intent->client_secret : false; - + return render('gateways.stripe.bank_transfer.pay', $data); } @@ -79,14 +80,12 @@ class BankTransfer */ private function resolveBankType() { - - return match($this->stripe->client->currency()->code){ + return match ($this->stripe->client->currency()->code) { 'GBP' => ['type' => 'gb_bank_transfer'], 'EUR' => ['type' => 'eu_bank_transfer', 'eu_bank_transfer' => ['country' => $this->stripe->client->country->iso_3166_2]], 'JPY' => ['type' => 'jp_bank_transfer'], 'MXN' => ['type' =>'mx_bank_transfer'], }; - } /** @@ -103,59 +102,145 @@ class BankTransfer ]); } - + + /** + * paymentResponse + * + * @param mixed $request + * @return void + */ public function paymentResponse(PaymentResponseRequest $request) { - $this->stripe->init(); $this->stripe->setPaymentHash($request->getPaymentHash()); $this->stripe->client = $this->stripe->payment_hash->fee_invoice->client; - if($request->payment_intent){ - + if ($request->payment_intent) { $pi = \Stripe\PaymentIntent::retrieve( $request->payment_intent, $this->stripe->stripe_connect_auth ); if (in_array($pi->status, ['succeeded', 'processing'])) { - return $this->processSuccesfulRedirect($pi); + $payment = $this->processSuccesfulRedirect($pi); + redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); } /* Create a pending payment */ - if($pi->status == 'requires_action') { + if ($pi->status == 'requires_action' && $pi->next_action->type == 'display_bank_transfer_instructions') { + match ($pi->next_action->display_bank_transfer_instructions->currency) { + 'mxn' => $data['bank_details'] = $this->formatDataforMx($pi), + 'gbp' => $data['bank_details'] = $this->formatDataforUk($pi), + 'eur' => $data['bank_details'] = $this->formatDataforEur($pi), + 'jpy' => $data['bank_details'] = $this->formatDataforJp($pi), + }; + + $payment = $this->processSuccesfulRedirect($pi); - $data = [ - 'payment_method' => $pi->payment_method, - 'payment_type' => PaymentType::DIRECT_DEBIT, - 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), - 'transaction_reference' => $pi->id, - 'gateway_type_id' => GatewayType::DIRECT_DEBIT, + return render('gateways.stripe.bank_transfer.bank_details_container', $data); + } + + return $this->processUnsuccesfulRedirect(); + } + } + + /** + * formatDataForUk + * + * @param PaymentIntent $pi + * @return array + */ + public function formatDataForUk(PaymentIntent $pi): array + { + return [ + 'amount' => Number::formatMoney($this->stripe->convertFromStripeAmount($pi->next_action->display_bank_transfer_instructions->amount_remaining, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), $this->stripe->client), + 'account_holder_name' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->sort_code->account_holder_name, + 'account_number' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->sort_code->account_number, + 'sort_code' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->sort_code->sort_code, + 'reference' => $pi->next_action->display_bank_transfer_instructions->reference, + 'description' => $pi->description, + 'gateway' => $this->stripe->company_gateway, + 'currency' => $pi->next_action->display_bank_transfer_instructions->currency, ]; + } + + /** + * formatDataforMx + * + * @param PaymentIntent $pi + * @return array + */ + public function formatDataforMx(PaymentIntent $pi): array + { + return [ + 'amount' => Number::formatMoney($this->stripe->convertFromStripeAmount($pi->next_action->display_bank_transfer_instructions->amount_remaining, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), $this->stripe->client), + 'account_holder_name' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->spei->bank_name, + 'account_number' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->spei->bank_code, + 'sort_code' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->spei->clabe, + 'reference' => $pi->next_action->display_bank_transfer_instructions->reference, + 'description' => $pi->description, + 'gateway' => $this->stripe->company_gateway, + 'currency' => $pi->next_action->display_bank_transfer_instructions->currency, - $payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING); - - SystemLogger::dispatch( - ['response' => $this->stripe->payment_hash->data, 'data' => $data], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_GATEWAY_SUCCESS, - SystemLog::TYPE_STRIPE, - $this->stripe->client, - $this->stripe->client->company, - ); - - return redirect($pi->next_action->display_bank_transfer_instructions->hosted_instructions_url); - - } - return $this->processUnsuccesfulRedirect(); - - } - + ]; } - public function processSuccesfulRedirect($payment_intent) + + /** + * formatDataforEur + * + * @param mixed $pi + * @return array + */ + public function formatDataforEur(PaymentIntent $pi): array + { + return [ + 'amount' => Number::formatMoney($this->stripe->convertFromStripeAmount($pi->next_action->display_bank_transfer_instructions->amount_remaining, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), $this->stripe->client), + 'account_holder_name' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->iban->account_holder_name, + 'account_number' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->iban->iban, + 'sort_code' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->iban->bic, + 'reference' => $pi->next_action->display_bank_transfer_instructions->reference, + 'description' => $pi->description, + 'gateway' => $this->stripe->company_gateway, + 'currency' => $pi->next_action->display_bank_transfer_instructions->currency, + + ]; + } + + /** + * + * @param PaymentIntent $pi + * @return array + */ + public function formatDataforJp(PaymentIntent $pi): array + { + return [ + 'amount' => Number::formatMoney($this->stripe->convertFromStripeAmount($pi->next_action->display_bank_transfer_instructions->amount_remaining, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), $this->stripe->client), + 'account_holder_name' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->account_holder_name, + 'account_number' => $pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->account_number, + 'account_type' =>$pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->account_type, + 'bank_code' =>$pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->bank_code, + 'bank_name' =>$pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->bank_name, + 'branch_code' =>$pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->branch_code, + 'branch_name' =>$pi->next_action->display_bank_transfer_instructions->financial_addresses[0]->zengin->branch_name, + 'reference' => $pi->next_action->display_bank_transfer_instructions->reference, + 'description' => $pi->description, + 'gateway' => $this->stripe->company_gateway, + 'currency' => $pi->next_action->display_bank_transfer_instructions->currency, + + ]; + } + + + /** + * processSuccesfulRedirect + * + * @param PaymentIntent $payment_intent + * @return Payment + */ + public function processSuccesfulRedirect(PaymentIntent $payment_intent): Payment { $this->stripe->init(); @@ -168,7 +253,7 @@ class BankTransfer ]; - $payment = $this->stripe->createPayment($data, $payment_intent->status == 'processing' ? Payment::STATUS_PENDING : Payment::STATUS_COMPLETED); + $payment = $this->stripe->createPayment($data, $payment_intent->status == 'succeeded' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING); SystemLogger::dispatch( ['response' => $this->stripe->payment_hash->data, 'data' => $data], @@ -179,9 +264,14 @@ class BankTransfer $this->stripe->client->company, ); - return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]); + return $payment; } - + + /** + * processUnsuccesfulRedirect + * + * @return void + */ public function processUnsuccesfulRedirect() { $server_response = $this->stripe->payment_hash->data; @@ -204,6 +294,4 @@ class BankTransfer throw new PaymentFailed('Failed to process the payment.', 500); } - - -} \ No newline at end of file +} diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php index 70f01f81cbba..49e994c2bd61 100644 --- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php +++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php @@ -76,23 +76,20 @@ class UpdatePaymentMethods $this->importBankAccounts($customer, $client); $this->importPMBankAccounts($customer, $client); - } - /* ACH may also be nested inside Payment Methods.*/ + /* ACH may also be nested inside Payment Methods.*/ public function importPMBankAccounts($customer, $client) { $bank_methods = \Stripe\PaymentMethod::all( [ 'customer' => $customer->id, 'type' => 'us_bank_account', - ], + ], $this->stripe->stripe_connect_auth ); - foreach($bank_methods->data as $method) - { - + foreach ($bank_methods->data as $method) { $token_exists = ClientGatewayToken::where([ 'gateway_customer_reference' => $customer->id, 'token' => $method->id, @@ -126,9 +123,7 @@ class UpdatePaymentMethods } $this->stripe->storeGatewayToken($data, $additional_data); - } - } public function importBankAccounts($customer, $client) diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index f714c7f095e1..9f331d5bfee2 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -260,7 +260,12 @@ class StripePaymentDriver extends BaseDriver if ( $this->client && isset($this->client->country) - && in_array($this->client->country->iso_3166_2, ['FR', 'IE', 'NL', 'GB', 'DE', 'ES', 'JP', 'MX']) + && ( + (in_array($this->client->country->iso_3166_2, ['FR', 'IE', 'NL', 'DE', 'ES']) && $this->client->currency()->code == 'EUR') || + ($this->client->country->iso_3166_2 == 'JP' && $this->client->currency()->code == 'JPY') || + ($this->client->country->iso_3166_2 == 'MX' && $this->client->currency()->code == 'MXN') || + ($this->client->country->iso_3166_2 == 'GB' && $this->client->currency()->code == 'GBP') + ) ) { $types[] = GatewayType::DIRECT_DEBIT; } @@ -433,6 +438,17 @@ class StripePaymentDriver extends BaseDriver return PaymentIntent::create($data, array_merge($meta, ['idempotency_key' => uniqid("st", true)])); } + public function getPaymentIntent($payment_intent_id): ?PaymentIntent + { + $this->init(); + + return PaymentIntent::retrieve( + $payment_intent_id, + $this->stripe_connect_auth + ); + + } + /** * Returns a setup intent that allows the user * to enter card details without initiating a transaction. diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php new file mode 100644 index 000000000000..c57fef889f33 --- /dev/null +++ b/app/Services/Pdf/PdfBuilder.php @@ -0,0 +1,1601 @@ +service = $service; + + $this->commonmark = new CommonMarkConverter([ + 'allow_unsafe_links' => false, + ]); + } + + /** + * Builds the template sections + * + * @return self + * + */ + public function build(): self + { + $this->getTemplate() + ->buildSections() + ->getEmptyElements() + ->updateElementProperties() + ->updateVariables(); + + return $this; + } + + /** + * Final method to get compiled HTML. + * + * @param bool $final @deprecated // is it? i still see it being called elsewhere + * @return string + */ + public function getCompiledHTML($final = false) + { + $html = $this->document->saveHTML(); + + return str_replace('%24', '$', $html); + } + + /** + * Generate the template + * + * @return self + * + */ + private function getTemplate() :self + { + $document = new DOMDocument(); + + $document->validateOnParse = true; + + @$document->loadHTML(mb_convert_encoding($this->service->designer->template, 'HTML-ENTITIES', 'UTF-8')); + + $this->document = $document; + + // $this->xpath = new DOMXPath($document); + + return $this; + } + + /** + * Generates product entity sections + * + * @return self + * + */ + private function getProductSections(): self + { + $this->genericSectionBuilder() + ->getClientDetails() + ->getProductAndTaskTables() + ->getProductEntityDetails() + ->getProductTotals(); + + return $this; + } + + private function mergeSections(array $section) :self + { + $this->sections = array_merge($this->sections, $section); + + return $this; + } + + /** + * Generates delivery note sections + * + * @return self + * + */ + private function getDeliveryNoteSections(): self + { + $this->genericSectionBuilder() + ->getProductTotals(); + + $this->mergeSections([ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDeliveryDetails(), + ], + 'delivery-note-table' => [ + 'id' => 'delivery-note-table', + 'elements' => $this->deliveryNoteTable(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->deliveryNoteDetails(), + ], + ]); + + return $this; + } + + /** + * Generates statement sections + * + * @return self + * + */ + private function getStatementSections(): self + { + $this->genericSectionBuilder(); + + $this->mergeSections([ + 'statement-invoice-table' => [ + 'id' => 'statement-invoice-table', + 'elements' => $this->statementInvoiceTable(), + ], + 'statement-invoice-table-totals' => [ + 'id' => 'statement-invoice-table-totals', + 'elements' => $this->statementInvoiceTableTotals(), + ], + 'statement-payment-table' => [ + 'id' => 'statement-payment-table', + 'elements' => $this->statementPaymentTable(), + ], + 'statement-payment-table-totals' => [ + 'id' => 'statement-payment-table-totals', + 'elements' => $this->statementPaymentTableTotals(), + ], + 'statement-aging-table' => [ + 'id' => 'statement-aging-table', + 'elements' => $this->statementAgingTable(), + ], + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->statementTableTotals(), + ], + ]); + + return $this; + } + + /** + * Parent method for building invoice table totals + * for statements. + * + * @return array + */ + public function statementInvoiceTableTotals(): array + { + $outstanding = $this->service->options['invoices']->sum('balance'); + + return [ + ['element' => 'p', 'content' => '$outstanding_label: ' . $this->service->config->formatMoney($outstanding)], + ]; + } + + + /** + * Parent method for building payments table within statement. + * + * @return array + */ + public function statementPaymentTable(): array + { + if (is_null($this->service->options['payments'])) { + return []; + } + + if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + return []; + } + + $tbody = []; + + //24-03-2022 show payments per invoice + foreach ($this->service->options['invoices'] as $invoice) { + foreach ($invoice->payments as $payment) { + if ($payment->is_deleted) { + continue; + } + + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->amount) ?: ' ']; + + $tbody[] = $element; + + $this->payment_amount_total += $payment->pivot->amount; + } + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + } + + /** + * Generates the statement payments table + * + * @return array + * + */ + public function statementPaymentTableTotals(): array + { + if (is_null($this->service->options['payments']) || !$this->service->options['payments']->first()) { + return []; + } + + if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + return []; + } + + $payment = $this->service->options['payments']->first(); + + return [ + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->type ? $payment->type->name : ctrans('texts.manual_entry'))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], + ]; + } + + /** + * Generates the statement aging table + * + * @return array + * + */ + public function statementAgingTable(): array + { + if (\array_key_exists('show_aging_table', $this->service->options) && $this->service->options['show_aging_table'] === false) { + return []; + } + + $elements = [ + ['element' => 'thead', 'elements' => []], + ['element' => 'tbody', 'elements' => [ + ['element' => 'tr', 'elements' => []], + ]], + ]; + + foreach ($this->service->options['aging'] as $column => $value) { + $elements[0]['elements'][] = ['element' => 'th', 'content' => $column]; + $elements[1]['elements'][] = ['element' => 'td', 'content' => $value]; + } + + return $elements; + } + + + /** + * Generates the purchase order sections + * + * @return self + * + */ + private function getPurchaseOrderSections(): self + { + $this->genericSectionBuilder() + ->getProductAndTaskTables() + ->getProductTotals(); + + $this->mergeSections([ + 'vendor-details' => [ + 'id' => 'vendor-details', + 'elements' => $this->vendorDetails(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->purchaseOrderDetails(), + ], + ]); + + return $this; + } + + /** + * Generates the generic section which apply + * across all design templates + * + * @return self + * + */ + private function genericSectionBuilder(): self + { + $this->mergeSections([ + 'company-details' => [ + 'id' => 'company-details', + 'elements' => $this->companyDetails(), + ], + 'company-address' => [ + 'id' => 'company-address', + 'elements' => $this->companyAddress(), + ], + 'footer-elements' => [ + 'id' => 'footer', + 'elements' => [ + $this->sharedFooterElements(), + ], + ], + ]); + + return $this; + } + + /** + * Generates the invoices table for statements + * + * @return array + * + */ + public function statementInvoiceTable(): array + { + $tbody = []; + + foreach ($this->service->options['invoices'] as $invoice) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->balance) ?: ' ']; + + $tbody[] = $element; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_invoice')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + } + + + /** + * Generate the structure of table body. () + * + * @param string $type "$product" or "$task" + * @return array + * + */ + public function buildTableBody(string $type): array + { + $elements = []; + + $items = $this->transformLineItems($this->service->config->entity->line_items, $type); + + $this->processNewLines($items); + + if (count($items) == 0) { + return []; + } + + if ($type == PdfService::DELIVERY_NOTE) { + $product_customs = [false, false, false, false]; + + foreach ($items as $row) { + for ($i = 0; $i < count($product_customs); $i++) { + if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) { + $product_customs[$i] = true; + } + } + } + + foreach ($items as $row) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key'], 'properties' => ['data-ref' => 'delivery_note_table.product_key-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes'], 'properties' => ['data-ref' => 'delivery_note_table.notes-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity'], 'properties' => ['data-ref' => 'delivery_note_table.quantity-td']]; + + for ($i = 0; $i < count($product_customs); $i++) { + if ($product_customs[$i]) { + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note' . ($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product' . ($i + 1) . '-td']]; + } + } + + $elements[] = $element; + } + + return $elements; + } + + foreach ($items as $row) { + $element = ['element' => 'tr', 'elements' => []]; + + if ( + array_key_exists($type, $this->service->options) && + !empty($this->service->options[$type]) && + !is_null($this->service->options[$type]) + ) { + $document = new DOMDocument(); + $document->loadHTML($this->service->options[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + + $td = $document->getElementsByTagName('tr')->item(0); + + if ($td) { + foreach ($td->childNodes as $child) { + if ($child->nodeType !== 1) { + continue; + } + + if ($child->tagName !== 'td') { + continue; + } + + $element['elements'][] = ['element' => 'td', 'content' => strtr($child->nodeValue, $row)]; + } + } + } else { + $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + + foreach ($this->service->config->pdf_variables["{$_type}_columns"] as $key => $cell) { + // We want to keep aliases like these: + // $task.cost => $task.rate + // $task.quantity => $task.hours + + if ($cell == '$task.rate') { + $element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']]; + } elseif ($cell == '$product.discount' && !$this->service->company->enable_product_discount) { + $element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']]; + } elseif ($cell == '$product.quantity' && !$this->service->company->enable_product_quantity) { + $element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']]; + } elseif ($cell == '$task.hours') { + $element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']]; + } elseif ($cell == '$product.tax_rate1') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax1-td']]; + } elseif ($cell == '$product.tax_rate2') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']]; + } elseif ($cell == '$product.tax_rate3') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']]; + } elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; + } else { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; + } + } + } + + $elements[] = $element; + } + + $document = null; + + return $elements; + } + + /** + * Formats the line items for display. + * + * @param mixed $items + * @param string $table_type + * @param mixed|null $custom_fields + * + * @return array + */ + public function transformLineItems($items, $table_type = '$product') :array + { + $data = []; + + $locale_info = localeconv(); + + // $this->service->config->entity_currency = $this->service->config->currency; + + foreach ($items as $key => $item) { + if ($table_type == '$product' && $item->type_id != 1) { + if ($item->type_id != 4 && $item->type_id != 6 && $item->type_id != 5) { + continue; + } + } + + if ($table_type == '$task' && $item->type_id != 2) { + // if ($item->type_id != 4 && $item->type_id != 5) { + continue; + // } + } + + $helpers = new Helpers(); + $_table_type = ltrim($table_type, '$'); // From $product -> product. + + $data[$key][$table_type.'.product_key'] = is_null(optional($item)->product_key) ? $item->item : $item->product_key; + $data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item; + $data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service; + + $currentDateTime = null; + if (isset($this->service->config->entity->next_send_date)) { + $currentDateTime = Carbon::parse($this->service->config->entity->next_send_date); + } + + $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); + $data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); + + $data[$key][$table_type.".{$_table_type}1"] = strlen($item->custom_value1) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}2"] = strlen($item->custom_value2) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}3"] = strlen($item->custom_value3) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->service->config->currency_entity) : ''; + + if ($item->quantity > 0 || $item->cost > 0) { + $data[$key][$table_type.'.quantity'] = $this->service->config->formatMoney($item->quantity); + + $data[$key][$table_type.'.unit_cost'] = $this->service->config->formatMoney($item->cost); + + $data[$key][$table_type.'.cost'] = $this->service->config->formatMoney($item->cost); + + $data[$key][$table_type.'.line_total'] = $this->service->config->formatMoney($item->line_total); + } else { + $data[$key][$table_type.'.quantity'] = ''; + + $data[$key][$table_type.'.unit_cost'] = ''; + + $data[$key][$table_type.'.cost'] = ''; + + $data[$key][$table_type.'.line_total'] = ''; + } + + if (property_exists($item, 'gross_line_total')) { + $data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' : $this->service->config->formatMoney($item->gross_line_total); + } else { + $data[$key][$table_type.'.gross_line_total'] = ''; + } + + if (property_exists($item, 'tax_amount')) { + $data[$key][$table_type.'.tax_amount'] = ($item->tax_amount == 0) ? '' : $this->service->config->formatMoney($item->tax_amount); + } else { + $data[$key][$table_type.'.tax_amount'] = ''; + } + + if (isset($item->discount) && $item->discount > 0) { + if ($item->is_amount_discount) { + $data[$key][$table_type.'.discount'] = $this->service->config->formatMoney($item->discount); + } else { + $data[$key][$table_type.'.discount'] = floatval($item->discount).'%'; + } + } else { + $data[$key][$table_type.'.discount'] = ''; + } + + // Previously we used to check for tax_rate value, + // but that's no longer necessary. + + if (isset($item->tax_rate1)) { + $data[$key][$table_type.'.tax_rate1'] = floatval($item->tax_rate1).'%'; + $data[$key][$table_type.'.tax1'] = &$data[$key][$table_type.'.tax_rate1']; + } + + if (isset($item->tax_rate2)) { + $data[$key][$table_type.'.tax_rate2'] = floatval($item->tax_rate2).'%'; + $data[$key][$table_type.'.tax2'] = &$data[$key][$table_type.'.tax_rate2']; + } + + if (isset($item->tax_rate3)) { + $data[$key][$table_type.'.tax_rate3'] = floatval($item->tax_rate3).'%'; + $data[$key][$table_type.'.tax3'] = &$data[$key][$table_type.'.tax_rate3']; + } + + $data[$key]['task_id'] = property_exists($item, 'task_id') ? $item->task_id : ''; + } + + //nlog(microtime(true) - $start); + + return $data; + } + + /** + * Generate the structure of table headers. () + * + * @param string $type "product" or "task" + * @return array + * + */ + public function buildTableHeader(string $type): array + { + $this->processTaxColumns($type); + + $elements = []; + + // Some of column can be aliased. This is simple workaround for these. + $aliases = [ + '$product.product_key' => '$product.item', + '$task.product_key' => '$task.service', + '$task.rate' => '$task.cost', + ]; + + foreach ($this->service->config->pdf_variables["{$type}_columns"] as $column) { + if (array_key_exists($column, $aliases)) { + $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; + } elseif ($column == '$product.discount' && !$this->service->company->enable_product_discount) { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; + } elseif ($column == '$product.quantity' && !$this->service->company->enable_product_quantity) { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; + } elseif ($column == '$product.tax_rate1') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; + } elseif ($column == '$product.tax_rate2') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; + } elseif ($column == '$product.tax_rate3') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; + } else { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->service->config->settings->hide_empty_columns_on_pdf]]; + } + } + + return $elements; + } + + /** + * This method will help us decide either we show + * one "tax rate" column in the table or 3 custom tax rates. + * + * Logic below will help us calculate that & inject the result in the + * global state of the $context (design state). + * + * @param string $type "product" or "task" + * @return void + */ + public function processTaxColumns(string $type): void + { + if ($type == 'product') { + $type_id = 1; + } + + if ($type == 'task') { + $type_id = 2; + } + + // At the moment we pass "task" or "product" as type. + // However, "pdf_variables" contains "$task.tax" or "$product.tax" <-- Notice the dollar sign. + // This sprintf() will help us convert "task" or "product" into "$task" or "$product" without + // evaluating the variable. + + if (in_array(sprintf('%s%s.tax', '$', $type), (array) $this->service->config->pdf_variables["{$type}_columns"])) { + $line_items = collect($this->service->config->entity->line_items)->filter(function ($item) use ($type_id) { + return $item->type_id = $type_id; + }); + + $tax1 = $line_items->where('tax_name1', '<>', '')->where('type_id', $type_id)->count(); + $tax2 = $line_items->where('tax_name2', '<>', '')->where('type_id', $type_id)->count(); + $tax3 = $line_items->where('tax_name3', '<>', '')->where('type_id', $type_id)->count(); + + $taxes = []; + + if ($tax1 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate1', '$', $type)); + } + + if ($tax2 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate2', '$', $type)); + } + + if ($tax3 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate3', '$', $type)); + } + + $key = array_search(sprintf('%s%s.tax', '$', $type), $this->service->config->pdf_variables["{$type}_columns"], true); + + if ($key !== false) { + array_splice($this->service->config->pdf_variables["{$type}_columns"], $key, 1, $taxes); + } + } + } + + /** + * Generates the javascript block for + * hiding elements which need to be hidden + * + * @return array + * + */ + public function sharedFooterElements(): array + { + // We want to show headers for statements, no exceptions. + $statements = " + document.querySelectorAll('#statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => { + t.hidden = false; + }); + "; + + $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; + + // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, + // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. + + $html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);'; + + return ['element' => 'div', 'elements' => [ + ['element' => 'script', 'content' => $statements], + ['element' => 'script', 'content' => $javascript], + ['element' => 'script', 'content' => $html_decode], + ]]; + } + + /** + * Generates the totals table for + * the product type entities + * + * @return self + * + */ + private function getProductTotals(): self + { + $this->mergeSections([ + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->getTableTotals(), + ], + ]); + + return $this; + } + + /** + * Generates the entity details for + * Credits + * Quotes + * Invoices + * + * @return self + * + */ + private function getProductEntityDetails(): self + { + if ($this->service->config->entity_string == 'invoice') { + $this->mergeSections([ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->invoiceDetails(), + ], + ]); + } elseif ($this->service->config->entity_string == 'quote') { + $this->mergeSections([ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->quoteDetails(), + ], + ]); + } elseif ($this->service->config->entity_string == 'credit') { + $this->mergeSections([ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->creditDetails(), + ], + ]); + } + + return $this; + } + + /** + * Parent entry point when building sections of the design content + * + * @return self + * + */ + private function buildSections() :self + { + return match ($this->service->document_type) { + PdfService::PRODUCT => $this->getProductSections(), + PdfService::DELIVERY_NOTE => $this->getDeliveryNoteSections(), + PdfService::STATEMENT => $this->getStatementSections(), + PdfService::PURCHASE_ORDER => $this->getPurchaseOrderSections(), + }; + } + + /** + * Generates the table totals for statements + * + * @return array + * + */ + private function statementTableTotals(): array + { + return [ + ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ + ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: block; align-items: flex-start; page-break-inside: avoid; visible !important;'], 'elements' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ]], + ]], + ]; + } + + /** + * Performs a variable check to ensure + * the variable exists + * + * @param string $variables + * @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->service->config->entity->{$_variable})) { + return true; + } + + if (empty($this->service->config->entity->{$_variable})) { + return true; + } + + return false; + } + + //First pass done, need a second pass to abstract this content completely. + /** + * Builds the table totals for all entities, we'll want to split this + * + * @return array + * + */ + public function getTableTotals() :array + { + //need to see where we don't pass all these particular variables. try and refactor thisout + $_variables = array_key_exists('variables', $this->service->options) + ? $this->service->options['variables'] + : ['values' => ['$entity.public_notes' => $this->service->config->entity->public_notes, '$entity.terms' => $this->service->config->entity->terms, '$entity_footer' => $this->service->config->entity->footer], 'labels' => []]; + + $variables = $this->service->config->pdf_variables['total_columns']; + + $elements = [ + ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ + ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], + ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [ + ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], + ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], + ]], + ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']], + ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ]], + ]], + ['element' => 'div', 'properties' => ['class' => 'totals-table-right-side', 'dir' => '$dir'], 'elements' => []], + ]; + + + if ($this->service->document_type == PdfService::DELIVERY_NOTE) { + return $elements; + } + + if ($this->service->config->entity instanceof Quote) { + // We don't want to show Balanace due on the quotes. + if (in_array('$outstanding', $variables)) { + $variables = \array_diff($variables, ['$outstanding']); + } + + if ($this->service->config->entity->partial > 0) { + $variables[] = '$partial_due'; + } + } + + if ($this->service->config->entity instanceof Credit) { + // We don't want to show Balanace due on the quotes. + if (in_array('$paid_to_date', $variables)) { + $variables = \array_diff($variables, ['$paid_to_date']); + } + } + + foreach (['discount'] as $property) { + $variable = sprintf('%s%s', '$', $property); + + if ( + !is_null($this->service->config->entity->{$property}) && + !empty($this->service->config->entity->{$property}) && + $this->service->config->entity->{$property} != 0 + ) { + continue; + } + + $variables = array_filter($variables, function ($m) use ($variable) { + return $m != $variable; + }); + } + + foreach ($variables as $variable) { + if ($variable == '$total_taxes') { + $taxes = $this->service->config->entity->total_tax_map; + + if (!$taxes) { + continue; + } + + foreach ($taxes as $i => $tax) { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']], + ['element' => 'span', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], + ]]; + } + } elseif ($variable == '$line_taxes') { + $taxes = $this->service->config->entity->tax_map; + + if (!$taxes) { + continue; + } + + foreach ($taxes as $i => $tax) { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']], + ['element' => 'span', 'content', 'content' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], + ]]; + } + } elseif (Str::startsWith($variable, '$custom_surcharge')) { + $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 + + $visible = intval($this->service->config->entity->{$_variable}) != 0; + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } elseif (Str::startsWith($variable, '$custom')) { + $field = explode('_', $variable); + $visible = is_object($this->service->company->custom_fields) && property_exists($this->service->company->custom_fields, $field[1]) && !empty($this->service->company->custom_fields->{$field[1]}); + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } else { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } + } + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => '',], + ['element' => 'span', 'content' => ''], + ]]; + + return $elements; + } + + /** + * Generates the product and task tables + * + * @return self + * + */ + public function getProductAndTaskTables(): self + { + $this->mergeSections([ + 'product-table' => [ + 'id' => 'product-table', + 'elements' => $this->productTable(), + ], + 'task-table' => [ + 'id' => 'task-table', + 'elements' => $this->taskTable(), + ], + ]); + + return $this; + } + + /** + * Generates the client details + * + * @return self + * + */ + public function getClientDetails(): self + { + $this->mergeSections([ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], + 'shipping-details' => [ + 'id' => 'shipping-details', + 'elements' => $this->shippingDetails(), + ], + ]); + + return $this; + } + + /** + * Generates the product table + * + * @return array + */ + public function productTable(): array + { + $product_items = collect($this->service->config->entity->line_items)->filter(function ($item) { + return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5; + }); + + if (count($product_items) == 0) { + return []; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], + ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], + ]; + } + + /** + * Generates the task table + * + * @return array + */ + public function taskTable(): array + { + $task_items = collect($this->service->config->entity->line_items)->filter(function ($item) { + return $item->type_id == 2; + }); + + if (count($task_items) == 0) { + return []; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('task')], + ['element' => 'tbody', 'elements' => $this->buildTableBody('$task')], + ]; + } + + + /** + * Generates the statement details + * + * @return array + * + */ + public function statementDetails(): array + { + $s_date = $this->translateDate(now(), $this->service->config->date_format, $this->service->config->locale); + + return [ + ['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ""], + ['element' => 'th', 'properties' => [], 'content' => "

".ctrans('texts.statement')."

"], + ]], + ['element' => 'tr', 'properties' => [], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ctrans('texts.statement_date')], + ['element' => 'th', 'properties' => [], 'content' => $s_date ?? ''], + ]], + ['element' => 'tr', 'properties' => [], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'], + ['element' => 'th', 'properties' => [], 'content' => $this->service->config->formatMoney($this->service->options['invoices']->sum('balance'))], + ]], + ]; + } + + /** + * Generates the invoice details + * + * @return array + * + */ + public function invoiceDetails(): array + { + $variables = $this->service->config->pdf_variables['invoice_details']; + + return $this->genericDetailsBuilder($variables); + } + + /** + * Generates the quote details + * + * @return array + * + */ + public function quoteDetails(): array + { + $variables = $this->service->config->pdf_variables['quote_details']; + + if ($this->service->config->entity->partial > 0) { + $variables[] = '$quote.balance_due'; + } + + return $this->genericDetailsBuilder($variables); + } + + + /** + * Generates the credit note details + * + * @return array + * + */ + public function creditDetails(): array + { + $variables = $this->service->config->pdf_variables['credit_details']; + + return $this->genericDetailsBuilder($variables); + } + + /** + * Generates the purchase order details + * + * @return array + */ + public function purchaseOrderDetails(): array + { + $variables = $this->service->config->pdf_variables['purchase_order_details']; + + return $this->genericDetailsBuilder($variables); + } + + /** + * Generates the deliveyr note details + * + * @return array + * + */ + public function deliveryNoteDetails(): array + { + $variables = $this->service->config->pdf_variables['invoice_details']; + + $variables = array_filter($variables, function ($m) { + return !in_array($m, ['$invoice.balance_due', '$invoice.total']); + }); + + return $this->genericDetailsBuilder($variables); + } + + /** + * Generates the custom values for the + * entity. + * + * @param array + * @return array + */ + public function genericDetailsBuilder(array $variables): array + { + $elements = []; + + + foreach ($variables as $variable) { + $_variable = explode('.', $variable)[1]; + $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; + + $var = str_replace("custom", "custom_value", $_variable); + + if (in_array($_variable, $_customs) && !empty($this->service->config->entity->{$var})) { + $elements[] = ['element' => 'tr', 'elements' => [ + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } else { + $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [ + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } + } + + return $elements; + } + + + /** + * Generates the client delivery + * details array + * + * @return array + * + */ + public function clientDeliveryDetails(): array + { + $elements = []; + + if (!$this->service->config->client) { + return $elements; + } + + $elements = [ + ['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], + ['element' => 'p', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']], + ['element' => 'p', 'show_empty' => false, 'elements' => [ + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], + ]], + ['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], + ]; + + if (!is_null($this->service->config->contact)) { + $elements[] = ['element' => 'p', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; + } + + return $elements; + } + + /** + * Generates the client details section + * + * @return array + */ + public function clientDetails(): array + { + $elements = []; + + if (!$this->service->config->client) { + return $elements; + } + + $variables = $this->service->config->pdf_variables['client_details']; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + } + + return $elements; + } + + public function shippingDetails(): array + { + $elements = []; + + if (!$this->service->config->client) { + return $elements; + } + + $elements = [ + ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], + ['element' => 'p', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], + ['element' => 'p', 'show_empty' => false, 'elements' => [ + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']], + ]], + ['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], + ]; + + return $elements; + } + + + /** + * Generates the delivery note table + * + * @return array + */ + public function deliveryNoteTable(): array + { + /* Static array of delivery note columns*/ + $thead = [ + ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']], + ['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']], + ['element' => 'th', 'content' => '$product.quantity_label', 'properties' => ['data-ref' => 'delivery_note-product.quantity_label']], + ]; + + $items = $this->transformLineItems($this->service->config->entity->line_items, $this->service->document_type); + + $this->processNewLines($items); + + $product_customs = [false, false, false, false]; + + foreach ($items as $row) { + for ($i = 0; $i < count($product_customs); $i++) { + if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) { + $product_customs[$i] = true; + } + } + } + + for ($i = 0; $i < count($product_customs); $i++) { + if ($product_customs[$i]) { + array_push($thead, ['element' => 'th', 'content' => '$product.product' . ($i + 1) . '_label', 'properties' => ['data-ref' => 'delivery_note-product.product' . ($i + 1) . '_label']]); + } + } + + return [ + ['element' => 'thead', 'elements' => $thead], + ['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)], + ]; + } + + /** + * Passes an array of items by reference + * and performs a nl2br + * + * @param array + * @return void + * + */ + public function processNewLines(array &$items): void + { + foreach ($items as $key => $item) { + foreach ($item as $variable => $value) { + $item[$variable] = str_replace("\n", '
', $value); + } + + $items[$key] = $item; + } + } + + /** + * Generates an arary of the company details + * + * @return array + * + */ + public function companyDetails(): array + { + $variables = $this->service->config->pdf_variables['company_details']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + } + + return $elements; + } + + /** + * + * Generates an array of the company address + * + * @return array + * + */ + public function companyAddress(): array + { + $variables = $this->service->config->pdf_variables['company_address']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + } + + return $elements; + } + + /** + * + * Generates an array of vendor details + * + * @return array + * + */ + public function vendorDetails(): array + { + $elements = []; + + $variables = $this->service->config->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; + } + + + //////////////////////////////////////// + // Dom Traversal + /////////////////////////////////////// + + + public function getSectionNode(string $selector) + { + return $this->document->getElementById($selector); + } + + public function updateElementProperties() :self + { + foreach ($this->sections as $element) { + if (isset($element['tag'])) { + $node = $this->document->getElementsByTagName($element['tag'])->item(0); + } elseif (! is_null($this->document->getElementById($element['id']))) { + $node = $this->document->getElementById($element['id']); + } else { + continue; + } + + if (isset($element['properties'])) { + foreach ($element['properties'] as $property => $value) { + $this->updateElementProperty($node, $property, $value); + } + } + + if (isset($element['elements'])) { + $this->createElementContent($node, $element['elements']); + } + } + + return $this; + } + + public function updateElementProperty($element, string $attribute, ?string $value) + { + // We have exception for "hidden" property. + // hidden="true" or hidden="false" will both hide the element, + // that's why we have to create an exception here for this rule. + + 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; + + if ($child['element'] !== 'script') { + if ($this->service->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; + } + + public function updateVariables() + { + $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); + + $html = strtr($html, $this->service->html_variables['values']); + + @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + + $this->document->saveHTML(); + + return $this; + } + + public function updateVariable(string $element, string $variable, string $value) + { + $element = $this->document->getElementById($element); + + $original = $element->nodeValue; + + $element->nodeValue = ''; + + $replaced = strtr($original, [$variable => $value]); + + $element->appendChild( + $this->document->createTextNode($replaced) + ); + + return $element; + } + + public function getEmptyElements() :self + { + foreach ($this->sections as $element) { + if (isset($element['elements'])) { + $this->getEmptyChildrens($element['elements'], $this->service->html_variables); + } + } + + return $this; + } + + public function getEmptyChildrens(array $children) + { + foreach ($children as $key => $child) { + if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { + $value = strtr($child['content'], $this->service->html_variables['values']); + if ($value === '' || $value === ' ' || $value === ' ') { + $child['is_empty'] = true; + } + } + + if (isset($child['elements'])) { + $this->getEmptyChildrens($child['elements']); + } + } + + return $this; + } +} diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php new file mode 100644 index 000000000000..11576763410f --- /dev/null +++ b/app/Services/Pdf/PdfConfiguration.php @@ -0,0 +1,348 @@ +setEntityType() + ->setDateFormat() + ->setPdfVariables() + ->setDesign() + ->setCurrencyForPdf() + ->setLocale(); + + return $this; + } + + /** + * setLocale + * + * @return self + */ + private function setLocale(): self + { + App::forgetInstance('translator'); + + $t = app('translator'); + + App::setLocale($this->settings_object->locale()); + + $t->replace(Ninja::transformTranslations($this->settings)); + + $this->locale = $this->settings_object->locale(); + + return $this; + } + + /** + * setCurrency + * + * @return self + */ + private function setCurrencyForPdf(): self + { + $this->currency = $this->client ? $this->client->currency() : $this->vendor->currency(); + + $this->currency_entity = $this->client ? $this->client : $this->vendor; + + return $this; + } + + /** + * setPdfVariables + * + * @return self + */ + public function setPdfVariables() :self + { + $default = (array) CompanySettings::getEntityVariableDefaults(); + + // $variables = (array)$this->service->company->settings->pdf_variables; + $variables = (array)$this->settings->pdf_variables; + + foreach ($default as $property => $value) { + if (array_key_exists($property, $variables)) { + continue; + } + + $variables[$property] = $value; + } + + $this->pdf_variables = $variables; + + return $this; + } + + /** + * setEntityType + * + * @return self + */ + private function setEntityType(): self + { + $entity_design_id = ''; + + if ($this->service->invitation instanceof InvoiceInvitation) { + $this->entity = $this->service->invitation->invoice; + $this->entity_string = 'invoice'; + $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; + $this->path = $this->client->invoice_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + $this->country = $this->client->country; + } elseif ($this->service->invitation instanceof QuoteInvitation) { + $this->entity = $this->service->invitation->quote; + $this->entity_string = 'quote'; + $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; + $this->path = $this->client->quote_filepath($this->service->invitation); + $this->entity_design_id = 'quote_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + $this->country = $this->client->country; + } elseif ($this->service->invitation instanceof CreditInvitation) { + $this->entity = $this->service->invitation->credit; + $this->entity_string = 'credit'; + $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; + $this->path = $this->client->credit_filepath($this->service->invitation); + $this->entity_design_id = 'credit_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + $this->country = $this->client->country; + } elseif ($this->service->invitation instanceof RecurringInvoiceInvitation) { + $this->entity = $this->service->invitation->recurring_invoice; + $this->entity_string = 'recurring_invoice'; + $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; + $this->path = $this->client->recurring_invoice_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + $this->country = $this->client->country; + } elseif ($this->service->invitation instanceof PurchaseOrderInvitation) { + $this->entity = $this->service->invitation->purchase_order; + $this->entity_string = 'purchase_order'; + $this->vendor = $this->entity->vendor; + $this->vendor_contact = $this->service->invitation->contact; + $this->path = $this->vendor->purchase_order_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->entity_design_id = 'purchase_order_design_id'; + $this->settings = $this->vendor->company->settings; + $this->settings_object = $this->vendor; + $this->client = null; + $this->country = $this->vendor->country ?: $this->vendor->company->country(); + } else { + throw new \Exception('Unable to resolve entity', 500); + } + + $this->setTaxMap($this->entity->calc()->getTaxMap()); + $this->setTotalTaxMap($this->entity->calc()->getTotalTaxMap()); + + $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; + + return $this; + } + + public function setTaxMap($map): self + { + $this->tax_map = $map; + + return $this; + } + + public function setTotalTaxMap($map): self + { + $this->total_tax_map = $map; + + return $this; + } + + public function setCurrency(Currency $currency): self + { + $this->currency = $currency; + + return $this; + } + + public function setCountry(Country $country): self + { + $this->country = $country; + + return $this; + } + + /** + * setDesign + * + * @return self + */ + private function setDesign(): self + { + $design_id = $this->entity->design_id ? : $this->decodePrimaryKey($this->settings_object->getSetting($this->entity_design_id)); + + $this->design = Design::find($design_id ?: 2); + + return $this; + } + + /** + * formatMoney + * + * @param float $value + * @return string + */ + public function formatMoney($value): string + { + $value = floatval($value); + + $thousand = $this->currency->thousand_separator; + $decimal = $this->currency->decimal_separator; + $precision = $this->currency->precision; + $code = $this->currency->code; + $swapSymbol = $this->currency->swap_currency_symbol; + + if (isset($this->country->thousand_separator) && strlen($this->country->thousand_separator) >= 1) { + $thousand = $this->country->thousand_separator; + } + + if (isset($this->country->decimal_separator) && strlen($this->country->decimal_separator) >= 1) { + $decimal = $this->country->decimal_separator; + } + + if (isset($this->country->swap_currency_symbol) && strlen($this->country->swap_currency_symbol) >= 1) { + $swapSymbol = $this->country->swap_currency_symbol; + } + + $value = number_format($value, $precision, $decimal, $thousand); + $symbol = $this->currency->symbol; + + if ($this->settings->show_currency_code === true && $this->currency->code == 'CHF') { + return "{$code} {$value}"; + } elseif ($this->settings->show_currency_code === true) { + return "{$value} {$code}"; + } elseif ($swapSymbol) { + return "{$value} ".trim($symbol); + } elseif ($this->settings->show_currency_code === false) { + return "{$symbol}{$value}"; + } else { + $value = floatval($value); + $thousand = $this->currency->thousand_separator; + $decimal = $this->currency->decimal_separator; + $precision = $this->currency->precision; + + return number_format($value, $precision, $decimal, $thousand); + } + } + + /** + * date_format + * + * @return self + */ + public function setDateFormat(): self + { + $date_formats = Cache::get('date_formats'); + + if (! $date_formats) { + $this->buildCache(true); + } + + $this->date_format = $date_formats->filter(function ($item) { + return $item->id == $this->settings->date_format_id; + })->first()->format; + + return $this; + } +} diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php new file mode 100644 index 000000000000..5db92b32a390 --- /dev/null +++ b/app/Services/Pdf/PdfDesigner.php @@ -0,0 +1,73 @@ +service->config->design->is_custom) { + $this->template = $this->composeFromPartials(json_decode(json_encode($this->service->config->design->design), true)); + } else { + $this->template = file_get_contents(config('ninja.designs.base_path') . strtolower($this->service->config->design->name) . '.html'); + } + + return $this; + } + + /** + * If the user has implemented a custom design, then we need to rebuild the design at this point + */ + + /** + * Returns the custom HTML design as + * a string + * + * @param array + * @return string + * + */ + private function composeFromPartials(array $partials) :string + { + $html = ''; + + $html .= $partials['includes']; + $html .= $partials['header']; + $html .= $partials['body']; + $html .= $partials['footer']; + + return $html; + } +} diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php new file mode 100644 index 000000000000..34a6fe52d5a3 --- /dev/null +++ b/app/Services/Pdf/PdfMock.php @@ -0,0 +1,758 @@ +mock->invitation); + + $pdf_config = (new PdfConfiguration($pdf_service)); + $pdf_config->entity = $this->mock; + $pdf_config->entity_string = $this->request['entity_type']; + $pdf_config->setTaxMap($this->mock->tax_map); + $pdf_config->setTotalTaxMap($this->mock->total_tax_map); + $pdf_config->client = $this->mock->client; + $pdf_config->settings_object = $this->mock->client; + $pdf_config->settings = $this->getMergedSettings(); + $this->settings = $pdf_config->settings; + $pdf_config->entity_design_id = $pdf_config->settings->{"{$pdf_config->entity_string}_design_id"}; + $pdf_config->setPdfVariables(); + $pdf_config->setCurrency(Currency::find($this->settings->currency_id)); + $pdf_config->setCountry(Country::find($this->settings->country_id)); + $pdf_config->design = Design::find($this->decodePrimaryKey($pdf_config->entity_design_id)); + $pdf_config->currency_entity = $this->mock->client; + + $pdf_service->config = $pdf_config; + + $pdf_designer = (new PdfDesigner($pdf_service))->build(); + $pdf_service->designer = $pdf_designer; + + $pdf_service->html_variables = $this->getStubVariables(); + + $pdf_builder = (new PdfBuilder($pdf_service))->build(); + $pdf_service->builder = $pdf_builder; + + $html = $pdf_service->getHtml(); + + return $pdf_service->resolvePdfEngine($html); + } + + public function build(): self + { + $this->mock = $this->initEntity(); + + return $this; + } + + public function initEntity(): mixed + { + match ($this->request['entity_type']) { + 'invoice' => $entity = Invoice::factory()->make(), + 'quote' => $entity = Quote::factory()->make(), + 'credit' => $entity = Credit::factory()->make(), + 'purchase_order' => $entity = PurchaseOrder::factory()->make(), + default => $entity = Invoice::factory()->make() + }; + + if ($this->request['entity_type'] == PurchaseOrder::class) { + $entity->vendor = Vendor::factory()->make(); + } else { + $entity->client = Client::factory()->make(); + } + + $entity->tax_map = $this->getTaxMap(); + $entity->total_tax_map = $this->getTotalTaxMap(); + $entity->invitation = InvoiceInvitation::factory()->make(); + $entity->invitation->company = $this->company; + + return $entity; + } + + public function getMergedSettings() :object + { + match ($this->request['settings_type']) { + 'group' => $settings = ClientSettings::buildClientSettings($this->company->settings, $this->request['settings']), + 'client' => $settings = ClientSettings::buildClientSettings($this->company->settings, $this->request['settings']), + 'company' => $settings = (object)$this->request['settings'], + default => $settings = $this->company->settings, + }; + + return $settings; + } + + + private function getTaxMap() + { + return collect([['name' => 'GST', 'total' => 10]]); + } + + private function getTotalTaxMap() + { + return [['name' => 'GST', 'total' => 10]]; + } + + public function getStubVariables() + { + return ['values' => + [ + + '$client.shipping_postal_code' => '46420', + '$client.billing_postal_code' => '11243', + '$company.city_state_postal' => 'Beveley Hills, CA, 90210', + '$company.postal_city_state' => 'CA', + '$company.postal_city' => '90210, CA', + '$product.gross_line_total' => '100', + '$client.postal_city_state' => '11243 Aufderharchester, North Carolina', + '$client.postal_city' => '11243 Aufderharchester, North Carolina', + '$client.shipping_address1' => '453', + '$client.shipping_address2' => '66327 Waters Trail', + '$client.city_state_postal' => 'Aufderharchester, North Carolina 11243', + '$client.shipping_address' => '453
66327 Waters Trail
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.billing_address2' => '63993 Aiyana View', + '$client.billing_address1' => '8447', + '$client.shipping_country' => 'USA', + '$invoiceninja.whitelabel' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', + '$client.billing_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.billing_country' => 'USA', + '$task.gross_line_total' => '100', + '$contact.portal_button' => 'View client portal', + '$client.shipping_state' => 'Delaware', + '$invoice.public_notes' => 'These are some public notes for your document', + '$client.shipping_city' => 'Kesslerport', + '$client.billing_state' => 'North Carolina', + '$product.description' => 'A Product Description', + '$product.product_key' => 'A Product Key', + '$entity.public_notes' => 'Entity Public notes', + '$invoice.balance_due' => '$0.00', + '$client.public_notes' => ' ', + '$company.postal_code' => $this->settings->postal_code, + '$client.billing_city' => 'Aufderharchester', + '$secondary_font_name' => $this->settings->primary_font, + '$product.line_total' => '', + '$product.tax_amount' => '', + '$company.vat_number' => $this->settings->vat_number, + '$invoice.invoice_no' => '0029', + '$quote.quote_number' => '0029', + '$client.postal_code' => '11243', + '$contact.first_name' => 'Benedict', + '$secondary_font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', + '$contact.signature' => '', + '$company_logo_size' => $this->settings->company_logo_size ?: '65%', + '$product.tax_name1' => '', + '$product.tax_name2' => '', + '$product.tax_name3' => '', + '$product.unit_cost' => '', + '$quote.valid_until' => '2023-10-24', + '$custom_surcharge1' => '$0.00', + '$custom_surcharge2' => '$0.00', + '$custom_surcharge3' => '$0.00', + '$custom_surcharge4' => '$0.00', + '$quote.balance_due' => '$0.00', + '$company.id_number' => $this->settings->id_number, + '$invoice.po_number' => 'PO12345', + '$invoice_total_raw' => 0.0, + '$postal_city_state' => '11243 Aufderharchester, North Carolina', + '$client.vat_number' => '975977515', + '$city_state_postal' => 'Aufderharchester, North Carolina 11243', + '$contact.full_name' => 'Benedict Eichmann', + '$contact.last_name' => 'Eichmann', + '$company.country_2' => 'US', + '$product.product1' => '', + '$product.product2' => '', + '$product.product3' => '', + '$product.product4' => '', + '$statement_amount' => '', + '$task.description' => '', + '$product.discount' => '', + '$entity_issued_to' => 'Bob JOnes', + '$assigned_to_user' => '', + '$product.quantity' => '', + '$total_tax_labels' => '', + '$total_tax_values' => '', + '$invoice.discount' => '$0.00', + '$invoice.subtotal' => '$0.00', + '$company.address2' => $this->settings->address2, + '$partial_due_date' => ' ', + '$invoice.due_date' => '2023-10-24', + '$client.id_number' => 'CLI-2023-1234', + '$credit.po_number' => 'PO12345', + '$company.address1' => $this->settings->address1, + '$credit.credit_no' => '0029', + '$invoice.datetime' => '25/Feb/2023 1:10 am', + '$contact.custom1' => null, + '$contact.custom2' => null, + '$contact.custom3' => null, + '$contact.custom4' => null, + '$task.line_total' => '', + '$line_tax_labels' => '', + '$line_tax_values' => '', + '$secondary_color' => $this->settings->secondary_color, + '$invoice.balance' => '$0.00', + '$invoice.custom1' => 'custom value', + '$invoice.custom2' => 'custom value', + '$invoice.custom3' => 'custom value', + '$invoice.custom4' => 'custom value', + '$company.custom1' => 'custom value', + '$company.custom2' => 'custom value', + '$company.custom3' => 'custom value', + '$company.custom4' => 'custom value', + '$quote.po_number' => 'PO12345', + '$company.website' => $this->settings->website, + '$balance_due_raw' => '0.00', + '$entity.datetime' => '25/Feb/2023 1:10 am', + '$credit.datetime' => '25/Feb/2023 1:10 am', + '$client.address2' => '63993 Aiyana View', + '$client.address1' => '8447', + '$user.first_name' => 'Derrick Monahan DDS', + '$created_by_user' => 'Derrick Monahan DDS Erna Wunsch', + '$client.currency' => 'USD', + '$company.country' => 'United States', + '$company.address' => 'United States
', + '$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg', + '$task.tax_name1' => '', + '$task.tax_name2' => '', + '$task.tax_name3' => '', + '$client.balance' => '$0.00', + '$client_balance' => '$0.00', + '$credit.balance' => '$0.00', + '$credit_balance' => '$0.00', + '$gross_subtotal' => '$0.00', + '$invoice.amount' => '$0.00', + '$client.custom1' => 'custom value', + '$client.custom2' => 'custom value', + '$client.custom3' => 'custom value', + '$client.custom4' => 'custom value', + '$emailSignature' => 'A email signature.', + '$invoice.number' => '0029', + '$quote.quote_no' => '0029', + '$quote.datetime' => '25/Feb/2023 1:10 am', + '$client_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$payment_button' => 'Pay Now', + '$payment_qrcode' => ' + + +', + '$client.country' => 'Afghanistan', + '$user.last_name' => 'Erna Wunsch', + '$client.website' => 'http://www.parisian.org/', + '$dir_text_align' => 'left', + '$entity_images' => '', + '$task.discount' => '', + '$contact.email' => 'bob@gmail.com', + '$primary_color' => $this->settings->primary_color, + '$credit_amount' => '$0.00', + '$invoice.total' => '$0.00', + '$invoice.taxes' => '$0.00', + '$quote.custom1' => 'custom value', + '$quote.custom2' => 'custom value', + '$quote.custom3' => 'custom value', + '$quote.custom4' => 'custom value', + '$company.email' => $this->settings->email, + '$client.number' => '12345', + '$company.phone' => $this->settings->phone, + '$company.state' => $this->settings->state, + '$credit.number' => '0029', + '$entity_number' => '0029', + '$credit_number' => '0029', + '$global_margin' => '6.35mm', + '$contact.phone' => '681-480-9828', + '$portal_button' => 'View client portal', + '$paymentButton' => 'Pay Now', + '$entity_footer' => 'Default invoice footer', + '$client.lang_2' => 'en', + '$product.date' => '', + '$client.email' => 'client@gmail.com', + '$product.item' => '', + '$public_notes' => 'These are very public notes', + '$task.service' => '', + '$credit.total' => '$0.00', + '$net_subtotal' => '$0.00', + '$paid_to_date' => '$0.00', + '$quote.amount' => '$0.00', + '$company.city' => $this->settings->city, + '$payment.date' => '2022-10-10', + '$client.phone' => '555-123-3212', + '$number_short' => '0029', + '$quote.number' => '0029', + '$invoice.date' => '25/Feb/2023', + '$company.name' => $this->settings->name, + '$portalButton' => 'View client portal', + '$contact.name' => 'Benedict Eichmann', + '$entity.terms' => 'Default company invoice terms', + '$client.state' => 'North Carolina', + '$company.logo' => $this->settings->company_logo, + '$company_logo' => $this->settings->company_logo, + '$payment_link' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$status_logo' => '', + '$description' => '', + '$product.tax' => '', + '$valid_until' => '', + '$your_entity' => '', + '$shipping' => '', + '$balance_due' => '$0.00', + '$outstanding' => '$0.00', + '$partial_due' => '$0.00', + '$quote.total' => '$0.00', + '$payment_due' => ' ', + '$credit.date' => '25/Feb/2023', + '$invoiceDate' => '25/Feb/2023', + '$view_button' => 'View Invoice', + '$client.city' => 'Aufderharchester', + '$spc_qr_code' => 'SPC +0200 +1 + +K +434343 + + + + +CH + + + + + + + +0.000000 +USD + + + + + + + +NON + +0029 +EPD +', + '$client_name' => 'A Client Called Bob', + '$client.name' => 'A Client Called Bob', + '$paymentLink' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$payment_url' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$page_layout' => $this->settings->page_layout, + '$task.task1' => '', + '$task.task2' => '', + '$task.task3' => '', + '$task.task4' => '', + '$task.hours' => '', + '$amount_due' => '$0.00', + '$amount_raw' => '0.00', + '$invoice_no' => '0029', + '$quote.date' => '25/Feb/2023', + '$vat_number' => '975977515', + '$viewButton' => 'View Invoice', + '$portal_url' => 'http://ninja.test:8000/client/', + '$task.date' => '', + '$task.rate' => '', + '$task.cost' => '', + '$statement' => '', + '$user_iban' => ' ', + '$signature' => ' ', + '$id_number' => 'ID Number', + '$credit_no' => '0029', + '$font_size' => $this->settings->font_size, + '$view_link' => 'View Invoice', + '$page_size' => $this->settings->page_size, + '$country_2' => 'AF', + '$firstName' => 'Benedict', + '$user.name' => 'Derrick Monahan DDS Erna Wunsch', + '$font_name' => 'Roboto', + '$auto_bill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$payments' => '', + '$task.tax' => '', + '$discount' => '$0.00', + '$subtotal' => '$0.00', + '$company1' => 'custom value', + '$company2' => 'custom value', + '$company3' => 'custom value', + '$company4' => 'custom value', + '$due_date' => '2022-01-01', + '$poNumber' => 'PO-123456', + '$quote_no' => '0029', + '$address2' => '63993 Aiyana View', + '$address1' => '8447', + '$viewLink' => 'View Invoice', + '$autoBill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$view_url' => 'http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', + '$details' => '', + '$balance' => '$0.00', + '$partial' => '$0.00', + '$client1' => 'custom value', + '$client2' => 'custom value', + '$client3' => 'custom value', + '$client4' => 'custom value', + '$dueDate' => '2022-01-01', + '$invoice' => '0029', + '$account' => '434343', + '$country' => 'United States', + '$contact' => 'Benedict Eichmann', + '$app_url' => 'http://ninja.test:8000', + '$website' => 'http://www.parisian.org/', + '$entity' => '', + '$thanks' => 'Thanks!', + '$amount' => '$0.00', + '$method' => ' ', + '$number' => '0029', + '$footer' => 'Default invoice footer', + '$client' => 'The Client Name', + '$email' => 'email@invoiceninja.net', + '$notes' => '', + '_rate1' => '', + '_rate2' => '', + '_rate3' => '', + '$taxes' => '$0.00', + '$total' => '$0.00', + '$phone' => ' ', + '$terms' => 'Default company invoice terms', + '$from' => 'Bob Jones', + '$item' => '', + '$date' => '25/Feb/2023', + '$tax' => '', + '$dir' => 'ltr', + '$to' => 'Jimmy Giggles', + '$show_paid_stamp' => $this->settings->show_paid_stamp ? 'flex' : 'none', + '$status_logo' => '
' . ctrans('texts.paid') .'
', + '$show_shipping_address' => $this->settings->show_shipping_address ? 'flex' : 'none', + '$show_shipping_address_block' => $this->settings->show_shipping_address ? 'block' : 'none', + '$show_shipping_address_visibility' => $this->settings->show_shipping_address ? 'visible' : 'hidden', + ], + 'labels' => + [ + '$client.shipping_postal_code_label' => 'Shipping Postal Code', + '$client.billing_postal_code_label' => 'Postal Code', + '$company.city_state_postal_label' => 'City/State/Postal', + '$company.postal_city_state_label' => 'Postal/City/State', + '$company.postal_city_label' => 'Postal/City', + '$product.gross_line_total_label' => 'Gross line total', + '$client.postal_city_state_label' => 'Postal/City/State', + '$client.postal_city_label' => 'Postal/City', + '$client.shipping_address1_label' => 'Shipping Street', + '$client.shipping_address2_label' => 'Shipping Apt/Suite', + '$client.city_state_postal_label' => 'City/State/Postal', + '$client.shipping_address_label' => 'Shipping Address', + '$client.billing_address2_label' => 'Apt/Suite', + '$client.billing_address1_label' => 'Street', + '$client.shipping_country_label' => 'Shipping Country', + '$invoiceninja.whitelabel_label' => '', + '$client.billing_address_label' => 'Address', + '$client.billing_country_label' => 'Country', + '$task.gross_line_total_label' => 'Gross line total', + '$contact.portal_button_label' => 'view_client_portal', + '$client.shipping_state_label' => 'Shipping State/Province', + '$invoice.public_notes_label' => 'Public Notes', + '$client.shipping_city_label' => 'Shipping City', + '$client.billing_state_label' => 'State/Province', + '$product.description_label' => 'Description', + '$product.product_key_label' => 'Product', + '$entity.public_notes_label' => 'Public Notes', + '$invoice.balance_due_label' => 'Balance Due', + '$client.public_notes_label' => 'Notes', + '$company.postal_code_label' => 'Postal Code', + '$client.billing_city_label' => 'City', + '$secondary_font_name_label' => '', + '$product.line_total_label' => 'Line Total', + '$product.tax_amount_label' => 'Tax', + '$company.vat_number_label' => 'VAT Number', + '$invoice.invoice_no_label' => 'Invoice Number', + '$quote.quote_number_label' => 'Quote Number', + '$client.postal_code_label' => 'Postal Code', + '$contact.first_name_label' => 'First Name', + '$secondary_font_url_label' => '', + '$contact.signature_label' => '', + '$company_logo_size_label' => '', + '$product.tax_name1_label' => 'Tax', + '$product.tax_name2_label' => 'Tax', + '$product.tax_name3_label' => 'Tax', + '$product.unit_cost_label' => 'Unit Cost', + '$quote.valid_until_label' => 'Valid Until', + '$custom_surcharge1_label' => '', + '$custom_surcharge2_label' => '', + '$custom_surcharge3_label' => '', + '$custom_surcharge4_label' => '', + '$quote.balance_due_label' => 'Balance Due', + '$company.id_number_label' => 'ID Number', + '$invoice.po_number_label' => 'PO Number', + '$invoice_total_raw_label' => 'Invoice Total', + '$postal_city_state_label' => 'Postal/City/State', + '$client.vat_number_label' => 'VAT Number', + '$city_state_postal_label' => 'City/State/Postal', + '$contact.full_name_label' => 'Name', + '$contact.last_name_label' => 'Last Name', + '$company.country_2_label' => 'Country', + '$product.product1_label' => '', + '$product.product2_label' => '', + '$product.product3_label' => '', + '$product.product4_label' => '', + '$statement_amount_label' => 'Amount', + '$task.description_label' => 'Description', + '$product.discount_label' => 'Discount', + '$entity_issued_to_label' => 'Invoice issued to', + '$assigned_to_user_label' => 'Name', + '$product.quantity_label' => 'Quantity', + '$total_tax_labels_label' => 'Taxes', + '$total_tax_values_label' => 'Taxes', + '$invoice.discount_label' => 'Discount', + '$invoice.subtotal_label' => 'Subtotal', + '$company.address2_label' => 'Apt/Suite', + '$partial_due_date_label' => 'Due Date', + '$invoice.due_date_label' => 'Due Date', + '$client.id_number_label' => 'ID Number', + '$credit.po_number_label' => 'PO Number', + '$company.address1_label' => 'Street', + '$credit.credit_no_label' => 'Invoice Number', + '$invoice.datetime_label' => 'Date', + '$contact.custom1_label' => '', + '$contact.custom2_label' => '', + '$contact.custom3_label' => '', + '$contact.custom4_label' => '', + '$task.line_total_label' => 'Line Total', + '$line_tax_labels_label' => 'Taxes', + '$line_tax_values_label' => 'Taxes', + '$secondary_color_label' => '', + '$invoice.balance_label' => 'Balance', + '$invoice.custom1_label' => '', + '$invoice.custom2_label' => '', + '$invoice.custom3_label' => '', + '$invoice.custom4_label' => '', + '$company.custom1_label' => '', + '$company.custom2_label' => '', + '$company.custom3_label' => '', + '$company.custom4_label' => '', + '$quote.po_number_label' => 'PO Number', + '$company.website_label' => 'Website', + '$balance_due_raw_label' => 'Balance Due', + '$entity.datetime_label' => 'Date', + '$credit.datetime_label' => 'Date', + '$client.address2_label' => 'Apt/Suite', + '$client.address1_label' => 'Street', + '$user.first_name_label' => 'First Name', + '$created_by_user_label' => 'Name', + '$client.currency_label' => '', + '$company.country_label' => 'Country', + '$company.address_label' => 'Address', + '$tech_hero_image_label' => '', + '$task.tax_name1_label' => 'Tax', + '$task.tax_name2_label' => 'Tax', + '$task.tax_name3_label' => 'Tax', + '$client.balance_label' => 'Account balance', + '$client_balance_label' => 'Account balance', + '$credit.balance_label' => 'Balance', + '$credit_balance_label' => 'Credit Balance', + '$gross_subtotal_label' => 'Subtotal', + '$invoice.amount_label' => 'Total', + '$client.custom1_label' => '', + '$client.custom2_label' => '', + '$client.custom3_label' => '', + '$client.custom4_label' => '', + '$emailSignature_label' => '', + '$invoice.number_label' => 'Invoice Number', + '$quote.quote_no_label' => 'Quote Number', + '$quote.datetime_label' => 'Date', + '$client_address_label' => 'Address', + '$client.address_label' => 'Address', + '$payment_button_label' => 'Pay Now', + '$payment_qrcode_label' => 'Pay Now', + '$client.country_label' => 'Country', + '$user.last_name_label' => 'Last Name', + '$client.website_label' => 'Website', + '$dir_text_align_label' => '', + '$entity_images_label' => '', + '$task.discount_label' => 'Discount', + '$contact.email_label' => 'Email', + '$primary_color_label' => '', + '$credit_amount_label' => 'Credit Amount', + '$invoice.total_label' => 'Invoice Total', + '$invoice.taxes_label' => 'Taxes', + '$quote.custom1_label' => '', + '$quote.custom2_label' => '', + '$quote.custom3_label' => '', + '$quote.custom4_label' => '', + '$company.email_label' => 'Email', + '$client.number_label' => 'Number', + '$company.phone_label' => 'Phone', + '$company.state_label' => 'State/Province', + '$credit.number_label' => 'Credit Number', + '$entity_number_label' => 'Invoice Number', + '$credit_number_label' => 'Invoice Number', + '$global_margin_label' => '', + '$contact.phone_label' => 'Phone', + '$portal_button_label' => 'view_client_portal', + '$paymentButton_label' => 'Pay Now', + '$entity_footer_label' => '', + '$client.lang_2_label' => '', + '$product.date_label' => 'Date', + '$client.email_label' => 'Email', + '$product.item_label' => 'Item', + '$public_notes_label' => 'Public Notes', + '$task.service_label' => 'Service', + '$credit.total_label' => 'Credit Total', + '$net_subtotal_label' => 'Net', + '$paid_to_date_label' => 'Paid to Date', + '$quote.amount_label' => 'Quote Total', + '$company.city_label' => 'City', + '$payment.date_label' => 'Payment Date', + '$client.phone_label' => 'Phone', + '$number_short_label' => 'Invoice #', + '$quote.number_label' => 'Quote Number', + '$invoice.date_label' => 'Invoice Date', + '$company.name_label' => 'Company Name', + '$portalButton_label' => 'view_client_portal', + '$contact.name_label' => 'Contact Name', + '$entity.terms_label' => 'Invoice Terms', + '$client.state_label' => 'State/Province', + '$company.logo_label' => 'Logo', + '$company_logo_label' => 'Logo', + '$payment_link_label' => 'Pay Now', + '$status_logo_label' => '', + '$description_label' => 'Description', + '$product.tax_label' => 'Tax', + '$valid_until_label' => 'Valid Until', + '$your_entity_label' => 'Your Invoice', + '$shipping_label' => 'Shipping', + '$balance_due_label' => 'Balance Due', + '$outstanding_label' => 'Balance Due', + '$partial_due_label' => 'Partial Due', + '$quote.total_label' => 'Total', + '$payment_due_label' => 'Payment due', + '$credit.date_label' => 'Credit Date', + '$invoiceDate_label' => 'Invoice Date', + '$view_button_label' => 'View Invoice', + '$client.city_label' => 'City', + '$spc_qr_code_label' => '', + '$client_name_label' => 'Client Name', + '$client.name_label' => 'Client Name', + '$paymentLink_label' => 'Pay Now', + '$payment_url_label' => 'Pay Now', + '$page_layout_label' => '', + '$task.task1_label' => '', + '$task.task2_label' => '', + '$task.task3_label' => '', + '$task.task4_label' => '', + '$task.hours_label' => 'Hours', + '$amount_due_label' => 'Amount due', + '$amount_raw_label' => 'Amount', + '$invoice_no_label' => 'Invoice Number', + '$quote.date_label' => 'Quote Date', + '$vat_number_label' => 'VAT Number', + '$viewButton_label' => 'View Invoice', + '$portal_url_label' => '', + '$task.date_label' => 'Date', + '$task.rate_label' => 'Rate', + '$task.cost_label' => 'Rate', + '$statement_label' => 'Statement', + '$user_iban_label' => '', + '$signature_label' => '', + '$id_number_label' => 'ID Number', + '$credit_no_label' => 'Invoice Number', + '$font_size_label' => '', + '$view_link_label' => 'View Invoice', + '$page_size_label' => '', + '$country_2_label' => 'Country', + '$firstName_label' => 'First Name', + '$user.name_label' => 'Name', + '$font_name_label' => '', + '$auto_bill_label' => '', + '$payments_label' => 'Payments', + '$task.tax_label' => 'Tax', + '$discount_label' => 'Discount', + '$subtotal_label' => 'Subtotal', + '$company1_label' => '', + '$company2_label' => '', + '$company3_label' => '', + '$company4_label' => '', + '$due_date_label' => 'Due Date', + '$poNumber_label' => 'PO Number', + '$quote_no_label' => 'Quote Number', + '$address2_label' => 'Apt/Suite', + '$address1_label' => 'Street', + '$viewLink_label' => 'View Invoice', + '$autoBill_label' => '', + '$view_url_label' => 'View Invoice', + '$font_url_label' => '', + '$details_label' => 'Details', + '$balance_label' => 'Balance', + '$partial_label' => 'Partial Due', + '$client1_label' => '', + '$client2_label' => '', + '$client3_label' => '', + '$client4_label' => '', + '$dueDate_label' => 'Due Date', + '$invoice_label' => 'Invoice Number', + '$account_label' => 'Company Name', + '$country_label' => 'Country', + '$contact_label' => 'Name', + '$app_url_label' => '', + '$website_label' => 'Website', + '$entity_label' => 'Invoice', + '$thanks_label' => 'Thanks', + '$amount_label' => 'Total', + '$method_label' => 'Method', + '$number_label' => 'Invoice Number', + '$footer_label' => '', + '$client_label' => 'Client Name', + '$email_label' => 'Email', + '$notes_label' => 'Public Notes', + '_rate1_label' => 'Tax', + '_rate2_label' => 'Tax', + '_rate3_label' => 'Tax', + '$taxes_label' => 'Taxes', + '$total_label' => 'Total', + '$phone_label' => 'Phone', + '$terms_label' => 'Invoice Terms', + '$from_label' => 'From', + '$item_label' => 'Item', + '$date_label' => 'Invoice Date', + '$tax_label' => 'Tax', + '$dir_label' => '', + '$to_label' => 'To', + '$show_paid_stamp_label' => '', + '$status_logo_label' => '', + '$show_shipping_address_label' => '', + '$show_shipping_address_block_label' => '', + '$show_shipping_address_visibility_label' => '', + ], +]; + } +} diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php new file mode 100644 index 000000000000..5f202749067c --- /dev/null +++ b/app/Services/Pdf/PdfService.php @@ -0,0 +1,145 @@ +invitation = $invitation; + + $this->company = $invitation->company; + + $this->document_type = $document_type; + + $this->options = $options; + } + + /** + * Resolves the PDF generation type and + * attempts to generate a PDF from the HTML + * string. + * + * @return mixed | Exception + * + */ + public function getPdf() + { + try { + $pdf = $this->resolvePdfEngine($this->getHtml()); + + $numbered_pdf = $this->pageNumbering($pdf, $this->company); + + if ($numbered_pdf) { + $pdf = $numbered_pdf; + } + } catch (\Exception $e) { + nlog(print_r($e->getMessage(), 1)); + throw new \Exception($e->getMessage(), $e->getCode()); + } + + return $pdf; + } + + /** + * Renders the dom document to HTML + * + * @return string + * + */ + public function getHtml(): string + { + $html = $this->builder->getCompiledHTML(); + + if (config('ninja.log_pdf_html')) { + info($html); + } + + return $html; + } + + /** + * Initialize all the services to build the PDF + * + * @return self + */ + public function init(): self + { + $this->config = (new PdfConfiguration($this))->init(); + + + $this->html_variables = $this->config->client ? + (new HtmlEngine($this->invitation))->generateLabelsAndValues() : + (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues(); + + $this->designer = (new PdfDesigner($this))->build(); + + $this->builder = (new PdfBuilder($this))->build(); + + return $this; + } + + /** + * resolvePdfEngine + * + * @return mixed + */ + public function resolvePdfEngine(string $html): mixed + { + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $pdf = (new Phantom)->convertHtmlToPdf($html); + } elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($html); + } else { + $pdf = $this->makePdf(null, null, $html); + } + + return $pdf; + } +} diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 0232b6876698..4487a381e6af 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -121,6 +121,10 @@ class Design extends BaseDesign 'id' => 'client-details', 'elements' => $this->clientDetails(), ], + 'shipping-details' => [ + 'id' => 'shipping-details', + 'elements' => $this->shippingDetails(), + ], 'vendor-details' => [ 'id' => 'vendor-details', 'elements' => $this->vendorDetails(), @@ -171,10 +175,6 @@ class Design extends BaseDesign $this->sharedFooterElements(), ], ], - // 'swiss-qr' => [ - // 'id' => 'swiss-qr', - // 'elements' => $this->swissQrCodeElement(), - // ] ]; } @@ -237,6 +237,34 @@ class Design extends BaseDesign return $elements; } + public function shippingDetails(): array + { + $elements = []; + + if (!$this->client) { + return $elements; + } + + $elements = [ + ['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], + ]; + + // if (!is_null($this->context['contact'])) { + // $elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; + // } + + return $elements; + } + public function clientDetails(): array { $elements = []; @@ -275,6 +303,72 @@ class Design extends BaseDesign return $elements; } + public function entityDetailsx(): array + { + if ($this->type === 'statement') { + $s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); + + return [ + ['element' => 'p', 'content' => "

".ctrans('texts.statement')."

", 'properties' => ['data-ref' => 'statement-label']], + ['element' => 'p', 'content' => ctrans('texts.statement_date'), 'properties' => ['data-ref' => 'statement-label'],'elements' => + ['element' => 'span', 'content' => "{$s_date} "] + ], + ['element' => 'p', 'content' => '$balance_due_label', 'properties' => ['data-ref' => 'statement-label'],'elements' => + ['element' => 'span', 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)] + ], + ]; + } + + $variables = $this->context['pdf_variables']['invoice_details']; + + if ($this->entity instanceof Quote) { + $variables = $this->context['pdf_variables']['quote_details']; + + if ($this->entity->partial > 0) { + $variables[] = '$quote.balance_due'; + } + } + + if ($this->entity instanceof Credit) { + $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. + if ($this->type == self::DELIVERY_NOTE) { + $variables = array_filter($variables, function ($m) { + return !in_array($m, ['$invoice.balance_due', '$invoice.total']); + }); + } + + foreach ($variables as $variable) { + $_variable = explode('.', $variable)[1]; + $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; + + /* 2/7/2022 don't show custom values if they are empty */ + $var = str_replace("custom", "custom_value", $_variable); + + if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { + $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($_variable)};"],'elements' => [ + ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } else { + $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($variable)};"], 'elements' => [ + ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } + } + + return $elements; + } + public function entityDetails(): array { if ($this->type === 'statement') { @@ -348,6 +442,7 @@ class Design extends BaseDesign return $elements; } + public function deliveryNoteTable(): array { if ($this->type !== self::DELIVERY_NOTE) { diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index b6393f563527..bef121554684 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -246,6 +246,7 @@ trait DesignHelpers ]]; } + public function entityVariableCheck(string $variable): bool { // Extract $invoice.date => date @@ -282,6 +283,43 @@ trait DesignHelpers return false; } + public function entityVariableCheckx(string $variable): string + { + // Extract $invoice.date => date + // so we can append date as $entity->date and not $entity->$invoice.date; + + // When it comes to invoice balance, we'll always show it. + if ($variable == '$invoice.total') { + return 'visible'; + } + + // 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) { + nlog("Company settings seems to be broken. Could not resolve {$variable} type."); + return 'collapse'; + } + + if (\in_array($variable, \array_keys($aliases))) { + $_variable = $aliases[$variable]; + } + + if (is_null($this->entity->{$_variable})) { + return 'collapse'; + } + + if (empty($this->entity->{$_variable})) { + return 'collapse'; + } + + return 'visible'; + } + public function composeFromPartials(array $partials) { $html = ''; diff --git a/app/Services/PdfMaker/PdfMakerUtilities.php b/app/Services/PdfMaker/PdfMakerUtilities.php index 6670360c0691..95be78b0b1e1 100644 --- a/app/Services/PdfMaker/PdfMakerUtilities.php +++ b/app/Services/PdfMaker/PdfMakerUtilities.php @@ -178,7 +178,7 @@ trait PdfMakerUtilities foreach ($children as $key => &$child) { if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { $value = strtr($child['content'], $variables['values']); - if ($value === '' || $value === ' ') { + if ($value === '' || $value === ' ' || $value === ' '){ $child['is_empty'] = true; } } diff --git a/app/Services/Preview/StubBuilder.php b/app/Services/Preview/StubBuilder.php new file mode 100644 index 000000000000..c17d3de593ba --- /dev/null +++ b/app/Services/Preview/StubBuilder.php @@ -0,0 +1,309 @@ +entity_type = $entity_type; + + return $this; + } + + public function build(): self + { + try { + DB::connection(config('database.default'))->transaction(function () { + $this->createRecipient() + ->initializeSettings() + ->createEntity() + ->linkRelations() + ->buildHtml(); + }); + } catch (\Throwable $throwable) { + nlog("DB ERROR " . $throwable->getMessage()); + + if (DB::connection(config('database.default'))->transactionLevel() > 0) { + DB::connection(config('database.default'))->rollBack(); + } + } catch(\Exception $e) { + nlog($e->getMessage()); + + if (DB::connection(config('database.default'))->transactionLevel() > 0) { + DB::connection(config('database.default'))->rollBack(); + } + } + + return $this; + } + + public function getPdf(): mixed + { + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + return (new Phantom)->convertHtmlToPdf($this->html); + } + + if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + $pdf = (new NinjaPdf())->build($this->html); + + $numbered_pdf = $this->pageNumbering($pdf, $this->company); + + if ($numbered_pdf) { + $pdf = $numbered_pdf; + } + + return $pdf; + } + + return (new PreviewPdf($this->html, $this->company))->handle(); + } + + private function initializeSettings(): self + { + $this->dynamic_settings_type = 'company'; + + match ($this->dynamic_settings_type) { + 'company' => $this->setCompanySettings(), + 'client' => $this->setClientSettings(), + 'group' => $this->setGroupSettings(), + }; + + + return $this; + } + + private function setCompanySettings(): self + { + $this->company->settings = $this->settings; + $this->company->save(); + + return $this; + } + + private function setClientSettings(): self + { + $this->recipient->settings = $this->settings; + $this->recipient->save(); + + return $this; + } + + private function setGroupSettings(): self + { + $g = GroupSettingFactory::create($this->company->id, $this->user->id); + $g->name = Str::random(10); + $g->settings = $this->settings; + $g->save(); + + $this->recipient->group_settings_id = $g->id; + $this->recipient->save(); + + return $this; + } + + public function setSettings($settings): self + { + $this->settings = $settings; + + return $this; + } + + public function setSettingsType($type): self + { + $this->dynamic_settings_type = $type; + + return $this; + } + + private function buildHtml(): self + { + $html = new HtmlEngine($this->invitation); + + $design_string = "{$this->entity_type}_design_id"; + + $design = DesignModel::withTrashed()->find($this->decodePrimaryKey($html->settings->{$design_string})); + + $template = new PdfMakerDesign(strtolower($design->name)); + + $state = [ + 'template' => $template->elements([ + 'client' => $this->recipient, + 'entity' => $this->entity, + 'pdf_variables' => (array) $html->settings->pdf_variables, + '$product' => $design->design->product, + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $this->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $this->html = $maker->design($template) + ->build() + ->getCompiledHTML(); + + return $this; + } + + private function linkRelations(): self + { + $this->entity->setRelation('invitations', $this->invitation); + $this->entity->setRelation($this->recipient_string, $this->recipient); + $this->entity->setRelation('company', $this->company); + $this->entity->load("{$this->recipient_string}.company"); + + return $this; + } + + private function createRecipient(): self + { + match ($this->entity_type) { + 'invoice' => $this->createClient(), + 'quote' => $this->createClient(), + 'credit' => $this->createClient(), + 'purchase_order' => $this->createVendor(), + }; + + return $this; + } + + private function createClient(): self + { + $this->recipient = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + ]); + + $this->contact = ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->recipient->id, + ]); + + $this->recipient_string = 'client'; + + return $this; + } + + private function createVendor(): self + { + $this->recipient = Vendor::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->user->company()->id, + ]); + + $this->contact = VendorContact::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'vendor_id' => $this->recipient->id, + ]); + + $this->recipient_string = 'vendor'; + + return $this; + } + + + private function createEntity(): self + { + match ($this->entity_type) { + 'invoice' => $this->createInvoice(), + 'quote' => $this->createQuote(), + 'credit' => $this->createCredit(), + 'purchase_order' => $this->createPurchaseOrder(), + }; + + return $this; + } + + private function createInvoice() + { + $this->entity = Invoice::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->recipient->id, + 'terms' => $this->company->settings->invoice_terms, + 'footer' => $this->company->settings->invoice_footer, + 'status_id' => Invoice::STATUS_PAID, + ]); + + $this->invitation = InvoiceInvitation::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'invoice_id' => $this->entity->id, + 'client_contact_id' => $this->contact->id, + ]); + } + + private function createQuote() + { + $this->entity->save(); + } + + private function createCredit() + { + $this->entity->save(); + } + + private function createPurchaseOrder() + { + $this->entity->save(); + } +} diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index 5bef865ce085..c3c9ad8a10ba 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -105,7 +105,7 @@ class Helpers * Process reserved keywords on PDF. * * @param string $value - * @param Client|Company $entity + * @param Client|Company|Vendor $entity * @param null|Carbon $currentDateTime * @return null|string */ diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 11ec8649f24b..cc4129a536d0 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -116,6 +116,10 @@ class HtmlEngine $data['$global_margin'] = ['value' => '6.35mm', 'label' => '']; $data['$company_logo_size'] = ['value' => $this->resolveCompanyLogoSize(), 'label' => '']; + $data['$show_shipping_address'] = ['value' => $this->settings?->show_shipping_address ? 'flex' : 'none', 'label' => '']; + $data['$show_shipping_address_block'] = ['value' => $this->settings?->show_shipping_address ? 'block' : 'none', 'label' => '']; + $data['$show_shipping_address_visibility'] = ['value' => $this->settings?->show_shipping_address ? 'visible' : 'hidden', 'label' => '']; + $data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => '']; $data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; @@ -124,21 +128,21 @@ class HtmlEngine $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->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.date')]; $data['$status_logo'] = ['value' => '', 'label' => '']; $data['$invoice.date'] = &$data['$date']; $data['$invoiceDate'] = &$data['$date']; - $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; - $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + $data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->client->date_format(), $this->client->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->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')]; + $data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')]; $data['$invoice.due_date'] = &$data['$due_date']; - $data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; - $data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')]; + $data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; + $data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')]; $data['$poNumber'] = &$data['$invoice.po_number']; $data['$po_number'] = &$data['$invoice.po_number']; $data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.date')]; @@ -148,13 +152,13 @@ class HtmlEngine $data['$payment_button'] = ['value' => $this->buildViewButton($this->invitation->getPaymentLink(), ctrans('texts.pay_now')), 'label' => ctrans('texts.pay_now')]; $data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')]; $data['$payment_qrcode'] = ['value' => $this->invitation->getPaymentQrCode(), 'label' => ctrans('texts.pay_now')]; - $data['$exchange_rate'] = ['value' => $this->entity->exchange_rate ?: ' ', 'label' => ctrans('texts.exchange_rate')]; + $data['$exchange_rate'] = ['value' => $this->entity->exchange_rate ?: ' ', 'label' => ctrans('texts.exchange_rate')]; if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { $data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')]; - $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; - $data['$invoice'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; - $data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')]; + $data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; + $data['$invoice'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')]; + $data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')]; $data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?: ''), $this->client) ?: '', 'label' => ctrans('texts.invoice_terms')]; $data['$terms'] = &$data['$entity.terms']; $data['$view_link'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_invoice')), 'label' => ctrans('texts.view_invoice')]; @@ -163,12 +167,12 @@ class HtmlEngine $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->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')]; - $data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; - $data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; - $data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; - $data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; + $data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; + $data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; + $data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; + $data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; $data['$custom1'] = &$data['$invoice.custom1']; $data['$custom2'] = &$data['$invoice.custom2']; @@ -190,10 +194,10 @@ class HtmlEngine $data['$invoice.project'] = &$data['$project.name']; } - if ($this->entity->status_id == 4) { - $data['$status_logo'] = ['value' => '
' . ctrans('texts.paid') .'
', 'label' => '']; - } + $data['$status_logo'] = ['value' => '
' . ctrans('texts.paid') .'
', 'label' => '']; + $data['$show_paid_stamp'] = ['value' => $this->entity->status_id == 4 && $this->settings?->show_paid_stamp ? 'flex' : 'none', 'label' => '']; + if ($this->entity->vendor) { $data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')]; } @@ -219,12 +223,12 @@ class HtmlEngine $data['$view_button'] = &$data['$view_link']; $data['$approveButton'] = ['value' => $this->buildViewButton($this->invitation->getLink(), ctrans('texts.view_quote')), 'label' => ctrans('texts.approve')]; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')]; - $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')]; - $data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; - $data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; - $data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; - $data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; + $data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; + $data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; + $data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; + $data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; $data['$custom1'] = &$data['$quote.custom1']; $data['$custom2'] = &$data['$quote.custom2']; @@ -263,12 +267,12 @@ class HtmlEngine $data['$viewLink'] = &$data['$view_link']; $data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; // $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')]; - $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')]; + $data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')]; - $data['$credit.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; - $data['$credit.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; - $data['$credit.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; - $data['$credit.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; + $data['$credit.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; + $data['$credit.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; + $data['$credit.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; + $data['$credit.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; $data['$custom1'] = &$data['$credit.custom1']; $data['$custom2'] = &$data['$credit.custom2']; @@ -289,32 +293,32 @@ class HtmlEngine $data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>'']; $data['$entity_number'] = &$data['$number']; - $data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ($this->entity->is_amount_discount) ? ctrans('texts.discount') : ctrans('texts.discount').' '.$this->entity->discount.'%']; + $data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ($this->entity->is_amount_discount) ? ctrans('texts.discount') : ctrans('texts.discount').' '.$this->entity->discount.'%']; $data['$discount'] = &$data['$invoice.discount']; - $data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')]; - $data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')]; + $data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')]; + $data['$gross_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getGrossSubTotal(), $this->client) ?: ' ', '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->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; + $data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes - $this->entity_calc->getTotalDiscount()), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; } else { - $data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; + $data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal() - $this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')]; } $data['$invoice.subtotal'] = &$data['$subtotal']; /* Do not change the order of these */ if ($this->entity->partial > 0) { - $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', '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->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')]; + $data['$due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->client->date_format(), $this->client->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->client) ?: ' ', 'label' => ctrans('texts.balance_due')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->client) ?: ' ', '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->client) ?: ' ', 'label' => ctrans('texts.balance_due')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', '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')]; } @@ -325,13 +329,13 @@ class HtmlEngine if ($this->entity_string == 'credit') { - $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; $data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.credit_balance')]; $data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')]; } if ($this->entity_string == 'credit' && $this->entity->status_id == 1) { - $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->amount, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; $data['$balance_due_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.credit_balance')]; $data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')]; } @@ -339,27 +343,27 @@ class HtmlEngine /* Do not change the order of these */ $data['$outstanding'] = &$data['$balance_due']; - $data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')]; + $data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')]; $data['$partial'] = &$data['$partial_due']; - $data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.total')]; + $data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.total')]; $data['$amount'] = &$data['$total']; $data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')]; $data['$quote.total'] = &$data['$total']; - $data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.invoice_total')]; + $data['$invoice.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.invoice_total')]; $data['$invoice_total_raw'] = ['value' => $this->entity_calc->getTotal(), 'label' => ctrans('texts.invoice_total')]; $data['$invoice.amount'] = &$data['$total']; - $data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.quote_total')]; - $data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_total')]; - $data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')]; + $data['$quote.amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.quote_total')]; + $data['$credit.total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_total')]; + $data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')]; $data['$credit.total'] = &$data['$credit.total']; $data['$credit.po_number'] = &$data['$invoice.po_number']; $data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')]; - $data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')]; + $data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')]; $data['$credit.balance'] = &$data['$balance']; $data['$invoice.balance'] = &$data['$balance']; - $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; + $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; $data['$invoice.taxes'] = &$data['$taxes']; $data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')]; @@ -368,7 +372,7 @@ class HtmlEngine $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['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')]; + $data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')]; $data['$invoice.public_notes'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->public_notes ?: ''), $this->client) ?: '', 'label' => ctrans('texts.public_notes')]; @@ -379,16 +383,16 @@ class HtmlEngine $data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")]; $data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")]; - $data['$quote.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')]; - $data['$quote.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')]; + $data['$quote.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')]; + $data['$quote.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')]; $data['$quote.po_number'] = &$data['$invoice.po_number']; $data['$quote.quote_number'] = &$data['$quote.number']; $data['$quote_no'] = &$data['$quote.number']; $data['$quote.quote_no'] = &$data['$quote.number']; $data['$quote.valid_until'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.valid_until')]; $data['$valid_until'] = &$data['$quote.valid_until']; - $data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')]; - $data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')]; + $data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; $data['$credit_number'] = &$data['$number']; @@ -397,21 +401,21 @@ class HtmlEngine $data['$invoice_no'] = &$data['$number']; $data['$invoice.invoice_no'] = &$data['$number']; - $data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->client->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')]; - $data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->client->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')]; - $data['$client3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client3', $this->client->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client3')]; - $data['$client4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client4', $this->client->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client4')]; + $data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->client->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')]; + $data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->client->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')]; + $data['$client3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client3', $this->client->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client3')]; + $data['$client4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client4', $this->client->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client4')]; $data['$client.custom1'] = &$data['$client1']; $data['$client.custom2'] = &$data['$client2']; $data['$client.custom3'] = &$data['$client3']; $data['$client.custom4'] = &$data['$client4']; - $data['$address1'] = ['value' => $this->client->address1 ?: ' ', 'label' => ctrans('texts.address1')]; - $data['$address2'] = ['value' => $this->client->address2 ?: ' ', 'label' => ctrans('texts.address2')]; - $data['$id_number'] = ['value' => $this->client->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; - $data['$client.number'] = ['value' => $this->client->number ?: ' ', 'label' => ctrans('texts.number')]; - $data['$vat_number'] = ['value' => $this->client->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; - $data['$website'] = ['value' => $this->client->present()->website() ?: ' ', 'label' => ctrans('texts.website')]; - $data['$phone'] = ['value' => $this->client->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')]; + $data['$address1'] = ['value' => $this->client->address1 ?: ' ', 'label' => ctrans('texts.address1')]; + $data['$address2'] = ['value' => $this->client->address2 ?: ' ', 'label' => ctrans('texts.address2')]; + $data['$id_number'] = ['value' => $this->client->id_number ?: ' ', 'label' => ctrans('texts.id_number')]; + $data['$client.number'] = ['value' => $this->client->number ?: ' ', 'label' => ctrans('texts.number')]; + $data['$vat_number'] = ['value' => $this->client->vat_number ?: ' ', 'label' => ctrans('texts.vat_number')]; + $data['$website'] = ['value' => $this->client->present()->website() ?: ' ', 'label' => ctrans('texts.website')]; + $data['$phone'] = ['value' => $this->client->present()->phone() ?: ' ', 'label' => ctrans('texts.phone')]; $data['$country'] = ['value' => isset($this->client->country->name) ? ctrans('texts.country_' . $this->client->country->name) : '', 'label' => ctrans('texts.country')]; $data['$country_2'] = ['value' => isset($this->client->country) ? $this->client->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')]; @@ -420,27 +424,27 @@ class HtmlEngine $data['$email'] = ['value' => '', 'label' => ctrans('texts.email')]; } - $data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: ' ', 'label' => ctrans('texts.client_name')]; + $data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: ' ', 'label' => ctrans('texts.client_name')]; $data['$client.name'] = &$data['$client_name']; $data['$client'] = &$data['$client_name']; $data['$client.address1'] = &$data['$address1']; $data['$client.address2'] = &$data['$address2']; - $data['$client_address'] = ['value' => $this->client->present()->address() ?: ' ', 'label' => ctrans('texts.address')]; + $data['$client_address'] = ['value' => $this->client->present()->address() ?: ' ', 'label' => ctrans('texts.address')]; $data['$client.address'] = &$data['$client_address']; - $data['$client.postal_code'] = ['value' => $this->client->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')]; - $data['$client.public_notes'] = ['value' => $this->client->public_notes ?: ' ', 'label' => ctrans('texts.notes')]; - $data['$client.city'] = ['value' => $this->client->city ?: ' ', 'label' => ctrans('texts.city')]; - $data['$client.state'] = ['value' => $this->client->state ?: ' ', 'label' => ctrans('texts.state')]; + $data['$client.postal_code'] = ['value' => $this->client->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')]; + $data['$client.public_notes'] = ['value' => $this->client->public_notes ?: ' ', 'label' => ctrans('texts.notes')]; + $data['$client.city'] = ['value' => $this->client->city ?: ' ', 'label' => ctrans('texts.city')]; + $data['$client.state'] = ['value' => $this->client->state ?: ' ', 'label' => ctrans('texts.state')]; $data['$client.id_number'] = &$data['$id_number']; $data['$client.vat_number'] = &$data['$vat_number']; $data['$client.website'] = &$data['$website']; $data['$client.phone'] = &$data['$phone']; - $data['$city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; $data['$client.city_state_postal'] = &$data['$city_state_postal']; - $data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; $data['$client.postal_city_state'] = &$data['$postal_city_state']; - $data['$postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, null, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; + $data['$postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->city, null, $this->client->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; $data['$client.postal_city'] = &$data['$postal_city']; $data['$client.country'] = &$data['$country']; $data['$client.email'] = &$data['$email']; @@ -453,12 +457,12 @@ class HtmlEngine $data['$client.billing_postal_code'] = &$data['$client.postal_code']; $data['$client.billing_country'] = &$data['$client.country']; - $data['$client.shipping_address'] = ['value' => $this->client->present()->shipping_address() ?: ' ', 'label' => ctrans('texts.shipping_address')]; - $data['$client.shipping_address1'] = ['value' => $this->client->shipping_address1 ?: ' ', 'label' => ctrans('texts.shipping_address1')]; - $data['$client.shipping_address2'] = ['value' => $this->client->shipping_address2 ?: ' ', 'label' => ctrans('texts.shipping_address2')]; - $data['$client.shipping_city'] = ['value' => $this->client->shipping_city ?: ' ', 'label' => ctrans('texts.shipping_city')]; - $data['$client.shipping_state'] = ['value' => $this->client->shipping_state ?: ' ', 'label' => ctrans('texts.shipping_state')]; - $data['$client.shipping_postal_code'] = ['value' => $this->client->shipping_postal_code ?: ' ', 'label' => ctrans('texts.shipping_postal_code')]; + $data['$client.shipping_address'] = ['value' => $this->client->present()->shipping_address() ?: ' ', 'label' => ctrans('texts.shipping_address')]; + $data['$client.shipping_address1'] = ['value' => $this->client->shipping_address1 ?: ' ', 'label' => ctrans('texts.shipping_address1')]; + $data['$client.shipping_address2'] = ['value' => $this->client->shipping_address2 ?: ' ', 'label' => ctrans('texts.shipping_address2')]; + $data['$client.shipping_city'] = ['value' => $this->client->shipping_city ?: ' ', 'label' => ctrans('texts.shipping_city')]; + $data['$client.shipping_state'] = ['value' => $this->client->shipping_state ?: ' ', 'label' => ctrans('texts.shipping_state')]; + $data['$client.shipping_postal_code'] = ['value' => $this->client->shipping_postal_code ?: ' ', 'label' => ctrans('texts.shipping_postal_code')]; $data['$client.shipping_country'] = ['value' => isset($this->client->shipping_country->name) ? ctrans('texts.country_' . $this->client->shipping_country->name) : '', 'label' => ctrans('texts.shipping_country')]; $data['$client.currency'] = ['value' => $this->client->currency()->code, 'label' => '']; @@ -485,54 +489,54 @@ class HtmlEngine $data['$contact.portal_button'] = &$data['$portal_button']; $data['$portalButton'] = &$data['$portal_button']; - $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['$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.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; + $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.postal_city'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, null, $this->settings->postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; $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.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['$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['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => '']; $data['$emailSignature'] = &$data['$signature']; $data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => '']; $logo = $this->company->present()->logo_base64($this->settings); - $data['$company.logo'] = ['value' => $logo ?: ' ', 'label' => ctrans('texts.logo')]; + $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->client) ?: ' ', '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->client) ?: ' ', '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->client) ?: ' ', '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->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')]; + $data['$company1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', '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->client) ?: ' ', '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->client) ?: ' ', '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->client) ?: ' ', '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->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')]; - $data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')]; - $data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')]; - $data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')]; + $data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')]; + $data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')]; + $data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')]; + $data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->client) ?: ' ', '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')]; @@ -584,13 +588,15 @@ class HtmlEngine $data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')]; $data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; $data['$to'] = ['value' => '', 'label' => ctrans('texts.to')]; + $data['$shipping'] = ['value' => '', 'label' => ctrans('texts.ship_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_size'] = ['value' => $this->settings->font_size . 'px !important;', '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' => '']; @@ -623,8 +629,8 @@ class HtmlEngine $data['$dir'] = ['value' => in_array(optional($this->client->language())->locale, ['ar', 'he']) ? 'rtl' : 'ltr', 'label' => '']; $data['$dir_text_align'] = ['value' => in_array(optional($this->client->language())->locale, ['ar', 'he']) ? 'right' : 'left', 'label' => '']; - $data['$payment.date'] = ['value' => ' ', 'label' => ctrans('texts.payment_date')]; - $data['$method'] = ['value' => ' ', 'label' => ctrans('texts.method')]; + $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')]; @@ -745,7 +751,7 @@ class HtmlEngine return ctrans('texts.country_' . $country->name); } - return ' '; + return ' '; } @@ -760,7 +766,7 @@ class HtmlEngine // return ctrans('texts.country_' . $country->iso_3166_2); // } - return ' '; + return ' '; } /** * Due to the way we are compiling the blade template we diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index be4fce4fa454..55ff27f8d7b4 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -32,6 +32,7 @@ class SystemHealth 'mbstring', 'xml', 'bcmath', + 'iconv', ]; private static $php_version = 8.1; diff --git a/config/ninja.php b/config/ninja.php index e35ce9d2f44f..acc893f628c4 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.74', - 'app_tag' => '5.5.74', + 'app_version' => '5.5.75', + 'app_tag' => '5.5.75', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/database/factories/ClientFactory.php b/database/factories/ClientFactory.php index 5535cca440fa..3b0463502f76 100644 --- a/database/factories/ClientFactory.php +++ b/database/factories/ClientFactory.php @@ -30,11 +30,11 @@ class ClientFactory extends Factory 'balance' => 0, 'paid_to_date' => 0, 'vat_number' => $this->faker->numberBetween(123456789, 987654321), - 'id_number' => '', - 'custom_value1' => '', - 'custom_value2' => '', - 'custom_value3' => '', - 'custom_value4' => '', + 'id_number' => $this->faker->iban(), + 'custom_value1' => $this->faker->dateTime(), + 'custom_value2' => $this->faker->colorName(), + 'custom_value3' => $this->faker->word(), + 'custom_value4' => $this->faker->email(), 'address1' => $this->faker->buildingNumber(), 'address2' => $this->faker->streetAddress(), 'city' => $this->faker->city(), diff --git a/database/factories/InvoiceFactory.php b/database/factories/InvoiceFactory.php index 2236a0f104d2..0724a776b324 100644 --- a/database/factories/InvoiceFactory.php +++ b/database/factories/InvoiceFactory.php @@ -35,10 +35,10 @@ class InvoiceFactory extends Factory 'tax_rate2' => 17.5, //'tax_name3' => 'THIRDTAX', //'tax_rate3' => 5, - // 'custom_value1' => $this->faker->date(), - //'custom_value2' => rand(0, 1) ? 'yes' : 'no', - // 'custom_value3' => $this->faker->numberBetween(1,4), - // 'custom_value4' => $this->faker->numberBetween(1,4), + 'custom_value1' => $this->faker->date(), + 'custom_value2' => rand(0, 1) ? 'yes' : 'no', + 'custom_value3' => $this->faker->numberBetween(1,4), + 'custom_value4' => $this->faker->numberBetween(1,4), 'is_deleted' => false, 'po_number' => $this->faker->text(10), 'date' => $this->faker->date(), diff --git a/database/factories/VendorContactFactory.php b/database/factories/VendorContactFactory.php index 03b8556e8b74..9f26356f2213 100644 --- a/database/factories/VendorContactFactory.php +++ b/database/factories/VendorContactFactory.php @@ -26,7 +26,12 @@ class VendorContactFactory extends Factory 'first_name' => $this->faker->firstName(), 'last_name' => $this->faker->lastName(), 'phone' => $this->faker->phoneNumber(), + 'email_verified_at' => now(), 'email' => $this->faker->unique()->safeEmail(), + 'send_email' => true, + 'password' => bcrypt('password'), + 'remember_token' => \Illuminate\Support\Str::random(10), + 'contact_key' => \Illuminate\Support\Str::random(32), ]; } } diff --git a/lang/en/texts.php b/lang/en/texts.php index fad5214c85cc..a3b1d7f30dd0 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2189,7 +2189,6 @@ $LANG = array( 'logo_warning_too_large' => 'The image file is too large.', 'logo_warning_fileinfo' => 'Warning: To support gifs the fileinfo PHP extension needs to be enabled.', 'logo_warning_invalid' => 'There was a problem reading the image file, please try a different format.', - 'error_refresh_page' => 'An error occurred, please refresh the page and try again.', 'data' => 'Data', 'imported_settings' => 'Successfully imported settings', @@ -5002,6 +5001,13 @@ $LANG = array( 'gateway_payment_text_no_invoice' => 'Payment with no invoice for amount :amount for client :client', 'click_to_variables' => 'Client here to see all variables.', 'ship_to' => 'Ship to', + 'stripe_direct_debit_details' => 'Please transfer into the nominated bank account above.', + 'branch_name' => 'Branch Name', + 'branch_code' => 'Branch Code', + 'bank_name' => 'Bank Name', + 'bank_code' => 'Bank Code', + 'bic' => 'BIC', + ); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index 01d435cc1df8..e0c7021aad01 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -4,7 +4,7 @@ :root { --primary-color: $primary_color; --secondary-color: $secondary_color; - --line-height: 1.6; + --line-height: 1.4; } body { @@ -58,13 +58,13 @@ #header, #header-spacer { height: 160px; - padding: 3rem; + padding: 2rem; margin-bottom: 1rem; } .company-logo { - height: 100%; - max-width: 100%; - /* max-width: $company_logo_size;*/ +/* height: 100%;*/ +/* max-width: 100%;*/ + max-width: $company_logo_size; object-fit: contain; object-position: left center; } @@ -82,10 +82,17 @@ } #client-details { - margin: 2rem; + padding-right:1rem; display: flex; flex-direction: column; - line-height: var(--line-height); + line-height: var(--line-height) !important; + padding-left: 2rem; + } + + #shipping-details { + display: $show_shipping_address; + flex-direction: column; + line-height: var(--line-height) !important; } #client-details > :first-child { @@ -94,9 +101,13 @@ .client-entity-wrapper { display: grid; - grid-template-columns: 1.5fr 1fr; + grid-template-columns: 2fr 1fr; padding-left: 1rem; } + + .client-wrapper-left-side { + display: flex; + } .entity-details-wrapper { background-color: var(--primary-color); @@ -112,7 +123,7 @@ #entity-details > tr, #entity-details th { font-weight: normal; - padding-bottom: 0.5rem; + line-height: var(--line-height) !important; } [data-ref="table"] { @@ -325,6 +336,7 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; } .project-header { @@ -393,14 +405,21 @@
-

$entity_label

-
-
+
+

$entity_label

+
+
+
+
+

&

+
+
+

$entity_label

-
+
@@ -419,7 +438,7 @@
-
+
$status_logo
diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index 89263c580a47..a291ee4ff1b6 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -25,7 +25,6 @@ font-size: $font_size !important; } - @page { margin-left: $global_margin; margin-right: $global_margin; @@ -43,11 +42,12 @@ display: grid; grid-template-columns: 1.8fr 1fr 1fr; grid-gap: 20px; + margin-bottom: 2rem; } .company-logo { - max-width: 65%; - /* max-width: $company_logo_size;*/ +/* max-width: 65%;*/ + max-width: $company_logo_size; } .header-container > span { @@ -69,7 +69,7 @@ } .entity-issued-to { - margin-top: 2rem; + /* margin-top: 2rem; */ font-weight: bold; } @@ -77,17 +77,26 @@ display: flex; justify-content: space-between; gap: 20px; + margin-bottom: 2rem; } #client-details { display: flex; flex-direction: column; - margin-top: 1rem; line-height: var(--line-height); + vertical-align: top; + margin-left: 1rem; } - #client-details > p:nth-child(1) { + #client-details > p:nth-child(2) { color: var(--primary-color); + font-size: 120%; + } + + #shipping-details { + display: $show_shipping_address; + flex-direction: column; + line-height: var(--line-height); } #entity-details { @@ -112,7 +121,7 @@ [data-ref="table"] { margin-top: 0.5rem; - margin-bottom: 50px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -174,14 +183,13 @@ } #table-totals { - margin-top: 0.5rem; + margin-top: 0rem; display: grid; grid-template-columns: 2fr 1fr; gap: 80px; - padding-left: 1rem; padding-top: 0.5rem; padding-bottom: 0.8rem; - margin-right: .5rem; + padding-left: 0.7rem; page-break-inside:auto; overflow: visible !important; } @@ -205,7 +213,7 @@ #table-totals>.totals-table-right-side>*> :nth-child(2) { text-align: right; - padding-right: 7px; + padding-right: 17px; } #table-totals @@ -230,7 +238,7 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem + padding-left: 0.8rem } #footer { @@ -311,6 +319,7 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; } .project-header { @@ -378,10 +387,10 @@
-

$entity_issued_to_label:

-
+

$entity_issued_to_label:

+
@@ -395,7 +404,7 @@
-
+
$status_logo
diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index 3797915b09cd..26f30e016587 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -39,22 +39,40 @@ .header-wrapper { display: grid; - grid-template-columns: 1fr 0.5fr; - line-height: var(--line-height); + margin-top: 2rem; + gap: 20px; + grid-template-columns: 2fr 1fr 1fr; + grid-template-areas: "a b c"; + grid-auto-columns: minmax(0, 5fr); + grid-auto-flow: column; + justify-content: left; } .header-wrapper2 { display: grid; - grid-template-columns: 1fr 0.5fr; margin-top: 2rem; - min-width: 100%; + gap: 20px; + grid-template-columns: 2fr 2fr auto; + grid-template-areas: "a b c"; + grid-auto-columns: minmax(0, 5fr); + grid-auto-flow: column; + justify-content: left; } .company-logo { - max-width: 65%; - /* max-width: $company_logo_size;*/ +/* max-width: 65%;*/ + max-width: $company_logo_size; } + .logo-container { + display: flex; + flex-direction: column; + } + + .company-container { + display: flex; + flex-direction: column; + } .client-and-entity-wrapper { display: flex; padding: 1rem; @@ -68,18 +86,23 @@ line-height: var(--line-height); } + .header-wrapper #company-details { + display: flex; + flex-direction: column; + line-height: var(--line-height); + } + .header-wrapper #entity-details { - margin-top: 0.5rem; + padding-right: 0.5rem; text-align: left; + line-height: var(--line-height); width: 100%; } .header-wrapper #entity-details > tr, .header-wrapper #entity-details th { font-weight: normal; - padding-left: 0.9rem; - padding-top: 0.3rem; - padding-bottom: 0.3rem; + white-space: nowrap; } .header-wrapper @@ -91,12 +114,29 @@ background-color: #e6e6e6; } + #entity-details { + text-align: left; + width: 100%; + } + + #entity-details th { + font-weight:normal; + line-height: 1.5rem; + padding-right: 2rem; + } + #client-details { display: flex; flex-direction: column; line-height: var(--line-height); } + #shipping-details { + visibility: $show_shipping_address_visibility; + flex-direction: column; + line-height: var(--line-height); + } + [data-ref="table"] { margin-top: 2rem; min-width: 100%; @@ -171,17 +211,6 @@ padding-right: 0px; } - - #entity-details { - text-align: left; - width: 100%; - } - - #entity-details th { - font-weight:normal; - line-height: 1.5rem; - } - #table-totals > * [data-element='total-table-balance-due-label'], @@ -270,6 +299,10 @@ [data-ref="total_table-public_notes"] { font-weight: normal; } [data-ref="total_table-terms"] { font-weight: normal; } + /* [data-ref="shipping_address-label"] { + display: none; + } */ + .stamp { transform: rotate(12deg); color: #555; @@ -298,6 +331,7 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; } .project-header { @@ -355,24 +389,22 @@
-
-
- -
-
-
-
-
-
-
-
-
- -
-

$entity_label

-
-
-
+
+
+ +
+
+
+
+
+
+
+
+
+

$entity_label

+
+
+
@@ -383,7 +415,7 @@
-
+
$status_logo
diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index f20942b0cb69..0c197fe05efe 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -55,9 +55,12 @@ gap: 20px; } + .company-logo-container { + display: inline-block; + } + .company-logo { - max-width: 65%; - /* max-width: $company_logo_size;*/ + max-width: $company_logo_size; } #company-details { @@ -86,35 +89,45 @@ } .client-and-entity-wrapper { - display: flex; +/* display: flex;*/ padding: 1rem; + display: grid; + grid-template-columns: 1fr 1fr 1fr; border-top: 1px solid #d8d8d8; border-bottom: 1px solid #d8d8d8; } #entity-details { + display:flex; text-align: left; margin-right: 20px; + line-height: var(--line-height) !important; } #entity-details > tr, #entity-details th { font-weight: normal; padding-right: 15px; - padding-top: 2.5px; - padding-bottom: 2.5px; + line-height: var(--line-height) !important; } #client-details { display: flex; flex-direction: column; line-height: var(--line-height); + padding-right:30px; } #client-details > :first-child { font-weight: bold; } + #shipping-details { + display: $show_shipping_address; + flex-direction: column; + line-height: var(--line-height); + } + [data-ref="table"] { margin-top: 1rem; margin-bottom: 5px; @@ -166,8 +179,9 @@ margin-top: 0rem; display: grid; grid-template-columns: 2fr 1fr; - padding-top: .5rem; + padding-top: 0rem; padding-right: 1rem; + padding-left: 1rem; gap: 80px; page-break-inside:avoid; overflow: visible !important; @@ -210,11 +224,12 @@ #table-totals > * > :last-child { text-align: right; - padding-right: 1rem; + padding-right: 0.5rem; } #footer { margin-top: 10px; + margin-left: 1rem; } /** Markdown-specific styles. **/ @@ -225,12 +240,6 @@ margin-bottom: 0; } - .company-logo-container { - display: flex; - flex-direction: column; - justify-content: flex-end; - } - [data-ref="statement-totals"] { margin-top: 1rem; text-align: right; @@ -277,6 +286,8 @@ z-index:200 !important; position: fixed; text-align: center; + float:right; + } .is-paid { @@ -290,6 +301,7 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; } .project-header { @@ -359,10 +371,11 @@

$entity_label

-
+
+
@@ -373,7 +386,7 @@
-
+
$status_logo
@@ -402,7 +415,7 @@ $entity_images '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', - 'client-details','vendor-details', 'swiss-qr' + 'client-details','vendor-details', 'swiss-qr','shipping-details' ]; tables.forEach((tableIdentifier) => { @@ -415,4 +428,4 @@ $entity_images }); - + \ No newline at end of file diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index 880c31ed7433..ff267027f181 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -40,23 +40,43 @@ .header-wrapper { display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-rows:0.5fr; + grid-template-columns: auto auto auto auto; + grid-template-areas: "a b c d e"; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + justify-content:left; gap: 20px; line-height: var(--line-height); } .company-logo { - max-width: 65%; - /* max-width: $company_logo_size;*/ + max-width: $company_logo_size; + float:right; } - #entity-details p { margin-top: 5px; } + ,logo-wrapper { + grid-area: e; + align-content: right; + border:1px solid #000; + } + + #entity-details { + width: 100%; + white-space: nowrap; + margin-right: 3rem; + } + + #entity-details p { + margin-top: 5px; + } .header-wrapper #client-details, .header-wrapper #company-details, .header-wrapper #company-address { display: flex; flex-direction: column; + line-height: var(--line-height) !important; } [data-ref="company_details-company.name"] { @@ -70,11 +90,12 @@ .header-wrapper .company-info-wrapper > * { margin-bottom: 1rem; + grid-row-end: 4; } .entity-label-wrapper { display: grid; - grid-template-columns: 2fr 1fr; + grid-template-columns: 3fr 1fr; margin-top: 1rem; } @@ -95,13 +116,19 @@ text-align: left; } + #shipping-details { + display: $show_shipping_address; + flex-direction: column; + line-height: var(--line-height) !important; + } + .entity-label-wrapper #entity-details > tr, .entity-label-wrapper #entity-details th { font-weight: normal; } [data-ref="table"] { - margin-bottom: 50px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -182,7 +209,7 @@ } #table-totals > * > * { - padding-left: 1rem; + padding-left: 0.5rem; } #table-totals > * > :last-child { @@ -191,7 +218,8 @@ } [data-ref="total_table-footer"] { - padding-left: 1rem + padding-left: 0.5rem; + padding-right:0.8rem; } #footer { @@ -263,6 +291,8 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; + } .project-header { @@ -325,22 +355,28 @@
+
+
+
+

$entity_label  #$entity_number

-
+
+
+
@@ -351,7 +387,7 @@
-
+
$status_logo
diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 3eab4fe810f1..312719b34123 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -35,8 +35,8 @@ } .company-logo { - max-width: 55%; - /* max-width: $company_logo_size;*/ +/* max-width: 55%;*/ + max-width: $company_logo_size; margin-left: auto; margin-right: auto; display: block; @@ -55,31 +55,53 @@ display: flex; margin-top: 1rem; gap: 20px; - margin-left: 10px; - line-height: var(--line-height); + margin-left: 0px; + line-height: var(--line-height) !important; } - #entity-details p { margin-right: 20px; margin-top: 5px; } - + #entity-details p { + margin-right: 0px; + margin-top: 0px; + white-space: nowrap; + line-height: var(--line-height) !important; + } + .client-entity-wrapper .wrapper-info-text { display: block; font-size: 1.5rem; font-weight: normal; } + + .client-entity-wrapper .shipping-info-text { + display: block; + font-size: 1.5rem; + font-weight: normal; + display: $show_shipping_address; + } .client-entity-wrapper .wrapper-left-side { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: auto auto ; + grid-template-areas: "a b c d"; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + justify-content:left; } - .text-with-client { margin-right: 15px; } + .text-with-client { margin-right: 1px; } .client-entity-wrapper .wrapper-left-side #client-details, .client-entity-wrapper .wrapper-left-side #company-details, .client-entity-wrapper .wrapper-left-side #company-address { display: flex; flex-direction: column; - margin-top: 0.5rem; + margin-right: 5px; + } + + .client-entity-wrapper .wrapper-left-side #shipping-details { + display: $show_shipping_address; + flex-direction: column; + margin-right: 5px; } .client-entity-wrapper .wrapper-left-side .company-info { @@ -89,18 +111,21 @@ .client-entity-wrapper #entity-details { text-align: left; - margin-top: 0.5rem; min-width: 100%; + line-height: var(--line-height) !important; } .client-entity-wrapper #entity-details > tr, .client-entity-wrapper #entity-details th { font-weight: normal; + padding-right:8px; + line-height: var(--line-height) !important; } [data-ref="table"] { margin-top: 3rem; - margin-bottom: 50px; + margin-bottom: 5 + px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -126,6 +151,7 @@ [data-ref="table"] > thead > tr > th:last-child { text-align: right; + padding-right: 1rem; } [data-ref="table"] > tbody > tr > td { @@ -140,6 +166,11 @@ [data-ref="table"] > tbody > tr > td:last-child { text-align: right; + padding-right: 1rem; + } + + [data-ref="shipping_address-label"] { + display: none; } #table-totals { @@ -267,6 +298,8 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; + } .project-header { @@ -329,23 +362,27 @@
-
+

$to_label

-
+
+

$shipping_label

+
+
+

$from_label

-
-
-

$details_label

-
-
+
+

$details_label

+
+
+
@@ -356,7 +393,7 @@
-
+
$status_logo
diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 20579f679dab..7d3ae9964ebf 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -36,7 +36,7 @@ .header-wrapper { display: grid; - grid-template-columns: 1.2fr 1.8fr; + grid-template-columns: 0.5fr 1.5fr; gap: 20px; line-height: var(--line-height); } @@ -67,10 +67,17 @@ .header-wrapper .header-right-side-wrapper { display: grid; - grid-template-columns: 1fr 1fr; + /* grid-template-columns: 1fr 1fr; */ gap: 10px; border-left: 1px solid #303030; padding-left: 1rem; + + grid-template-columns: auto auto auto; + grid-template-areas: "a b c"; + grid-auto-columns: minmax(0, 1fr); + grid-auto-flow: column; + justify-content:left; + } .header-wrapper .header-right-side-wrapper #client-details { @@ -79,13 +86,26 @@ margin-top: 0.8rem; } + .header-wrapper .header-right-side-wrapper #shipping-details { + display: $show_shipping_address; + flex-direction: column; + margin-top: 0.8rem; + } + + .shipping-text-label { + font-size: 1.1rem; + color: var(--primary-color); + text-transform: uppercase; + font-weight: bold; + display: $show_shipping_address; + } + .header-wrapper { margin-left: auto; } .company-logo { - max-width: 65%; - /* max-width: $company_logo_size;*/ + max-width: $company_logo_size; } .entity-label { @@ -96,6 +116,14 @@ .entity-details-wrapper > * { margin-right: 1.5rem; + direction: $dir; + } + + .entity-details-wrapper { + display: flex; + flex-wrap: wrap; + direction: $dir; + justify-content: space-between; } .entity-details-wrapper .entity-property-label { @@ -110,9 +138,13 @@ font-weight: bold; } + [data-ref="shipping_address-label"] { + display: none; + } + [data-ref="table"] { margin-top: 1rem; - margin-bottom: 50px; + margin-bottom: 5px; min-width: 100%; table-layout: fixed; overflow-wrap: break-word; @@ -139,6 +171,12 @@ [data-ref="table"] > thead > tr > th:last-child { text-align: right; + padding-right: 1rem; + } + + [data-ref="table"] > thead > tr > th:nth-last-child(2) { + text-align: right; + padding-right: 1rem; } [data-ref="table"] > tbody > tr > td { @@ -155,7 +193,7 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - margin-right: .75rem; + margin-right: 1rem; gap: 80px; page-break-inside:auto; overflow: visible !important; @@ -211,16 +249,6 @@ margin-bottom: 0; } - .entity-details-wrapper > * { - direction: $dir; - } - - .entity-details-wrapper { - display: flex; - flex-wrap: wrap; - direction: $dir; - } - [data-ref="product_table-product.unit_cost-td"] { text-align: right; } [data-ref="totals_table-outstanding"] { color: var(--primary-color); } @@ -284,6 +312,7 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; } .project-header { @@ -354,6 +383,10 @@
+
+

$shipping_label:

+
+
-
+
$status_logo
diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index a1d5f93f50f8..011f307f872a 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -64,6 +64,7 @@ .company-name { text-align: left; + margin-left: 1rem; } #header .company-name { @@ -73,28 +74,39 @@ #entity-details { text-align: left; color: #fff4e9 !important; + line-height: 1.6; } #entity-details > tr, #entity-details th { font-weight: normal; - padding-bottom: 0.5rem; } .logo-client-wrapper { - margin: 0 2rem 0rem; + margin-left: 2rem; display: grid; - grid-template-columns: 1.5fr 1fr; + grid-template-columns: 1fr auto auto; + margin-top: 1rem; + margin-bottom: 1rem; } .company-logo { - max-width: 55%; - /* max-width: $company_logo_size;*/ + max-width: $company_logo_size; } #client-details { display: flex; flex-direction: column; + margin-left: 10px; + margin-right: 10px; + + } + + #shipping-details { + display: $show_shipping_address; + flex-direction: column; + margin-left: 10px; + margin-right: 10px; } #client-details > * { @@ -138,7 +150,7 @@ [data-ref="table"] > tbody > tr > td { border-bottom: 1px solid var(--secondary-color); - padding: 1rem; + padding: 0.8rem; } [data-ref="table"] > tbody > tr > td:first-child { @@ -154,6 +166,7 @@ width: 100%; position: fixed; bottom: 0; + padding-left: 1rem; } @@ -202,7 +215,7 @@ display: grid; grid-template-columns: 2fr 1fr; padding-top: 0.5rem; - padding-left: 3rem; + padding-left: 2.5rem; padding-right: 3rem; gap: 80px; } @@ -311,6 +324,8 @@ opacity: 0.2; z-index:200 !important; position: fixed; + display: $show_paid_stamp; + } .project-header { @@ -358,7 +373,7 @@
@@ -376,6 +391,7 @@
+
@@ -393,7 +409,7 @@
-
+
$status_logo
@@ -411,7 +427,7 @@ $entity_images