diff --git a/app/Filters/QueryFilters.php b/app/Filters/QueryFilters.php index 83fb8a36fdc6..87bfb3288bee 100644 --- a/app/Filters/QueryFilters.php +++ b/app/Filters/QueryFilters.php @@ -169,7 +169,7 @@ abstract class QueryFilters public function clientFilter() { if (auth()->guard('contact')->user()) { - return $this->builder->whereClientId(auth()->guard('contact')->user()->client->id); + return $this->builder->where('client_id', auth()->guard('contact')->user()->client->id); } } @@ -179,6 +179,15 @@ abstract class QueryFilters $created_at = date('Y-m-d H:i:s', $value); + if(is_string($created_at)){ + + $created_at = strtotime(str_replace("/","-",$created_at)); + + if(!$created_at) + return $this->builder; + + } + return $this->builder->where('created_at', '>=', $created_at); } diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index 6d134adf90da..b3eb796bd409 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -112,7 +112,7 @@ class SwissQrGenerator } else { - $invoice_number = $this->invoice->number; + $invoice_number = iconv("UTF-8", "ASCII", $this->invoice->number); } if(strlen($this->company->present()->besr_id()) > 1) @@ -141,7 +141,7 @@ class SwissQrGenerator // Optionally, add some human-readable information about what the bill is for. $qrBill->setAdditionalInformation( QrBill\DataGroup\Element\AdditionalInformation::create( - $this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice_number]) + $this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number]) ) ); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 55a1d03aad8e..30104c8c9fd3 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -33,6 +33,7 @@ use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; use App\Repositories\CreditRepository; +use App\Services\PdfMaker\PdfMerge; use App\Transformers\CreditTransformer; use App\Utils\Ninja; use App\Utils\TempFile; @@ -534,6 +535,20 @@ class CreditController extends BaseController return response()->json(['message' => ctrans('texts.sent_message')], 200); } + if($action == 'merge' && auth()->user()->can('view', $credits->first())){ + + $paths = $credits->map(function ($credit){ + return $credit->service()->getCreditPdf($credit->invitations->first()); + }); + + $merge = (new PdfMerge($paths->toArray()))->run(); + + return response()->streamDownload(function () use ($merge) { + echo ($merge); + }, 'print.pdf', ['Content-Type' => 'application/pdf']); + + } + $credits->each(function ($credit, $key) use ($action) { if (auth()->user()->can('edit', $credit)) { $this->performAction($credit, $action, true); diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 8050c433af1b..8e61ae7fdbf9 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -40,6 +40,7 @@ use App\Models\Invoice; use App\Models\Quote; use App\Models\TransactionEvent; use App\Repositories\InvoiceRepository; +use App\Services\PdfMaker\PdfMerge; use App\Transformers\InvoiceTransformer; use App\Transformers\QuoteTransformer; use App\Utils\Ninja; @@ -588,6 +589,20 @@ class InvoiceController extends BaseController } + if($action == 'merge' && auth()->user()->can('view', $invoices->first())){ + + $paths = $invoices->map(function ($invoice){ + return $invoice->service()->getInvoicePdf(); + }); + + $merge = (new PdfMerge($paths->toArray()))->run(); + + return response()->streamDownload(function () use ($merge) { + echo ($merge); + }, 'print.pdf', ['Content-Type' => 'application/pdf']); + + } + /* * Send the other actions to the switch */ diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 83d8c9cd55cb..e417027f1b31 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -32,6 +32,7 @@ use App\Models\Client; use App\Models\Expense; use App\Models\PurchaseOrder; use App\Repositories\PurchaseOrderRepository; +use App\Services\PdfMaker\PdfMerge; use App\Transformers\ExpenseTransformer; use App\Transformers\PurchaseOrderTransformer; use App\Utils\Ninja; @@ -515,6 +516,20 @@ class PurchaseOrderController extends BaseController return response()->json(['message' => ctrans('texts.sent_message')], 200); } + if($action == 'merge' && auth()->user()->can('view', $purchase_orders->first())){ + + $paths = $purchase_orders->map(function ($purchase_order){ + return $purchase_order->service()->getPurchaseOrderPdf(); + }); + + $merge = (new PdfMerge($paths->toArray()))->run(); + + return response()->streamDownload(function () use ($merge) { + echo ($merge); + }, 'print.pdf', ['Content-Type' => 'application/pdf']); + + } + /* * Send the other actions to the switch */ diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index a42c9c09c8e3..fc8f6319329c 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -35,6 +35,7 @@ use App\Models\Invoice; use App\Models\Project; use App\Models\Quote; use App\Repositories\QuoteRepository; +use App\Services\PdfMaker\PdfMerge; use App\Transformers\InvoiceTransformer; use App\Transformers\ProjectTransformer; use App\Transformers\QuoteTransformer; @@ -561,6 +562,20 @@ class QuoteController extends BaseController return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); } + if($action == 'merge' && auth()->user()->can('view', $quotes->first())){ + + $paths = $quotes->map(function ($quote){ + return $quote->service()->getQuotePdf(); + }); + + $merge = (new PdfMerge($paths->toArray()))->run(); + + return response()->streamDownload(function () use ($merge) { + echo ($merge); + }, 'print.pdf', ['Content-Type' => 'application/pdf']); + + } + if($action == 'convert_to_project') { diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 403ef964a37e..b5ed69206c61 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -99,7 +99,7 @@ class ReminderJob implements ShouldQueue (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { - EmailEntity::dispatchSync($invitation, $invitation->company, $reminder_template); + EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); }); diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php index 8e383090ad6f..951fe587a3e1 100644 --- a/app/Mail/Engine/PaymentEmailEngine.php +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -251,17 +251,17 @@ class PaymentEmailEngine extends BaseEmailEngine private function formatInvoiceField($field) { - $invoice = ''; + $invoicex = ''; foreach ($this->payment->invoices as $invoice) { $invoice_field = $invoice->{$field}; - $invoice .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}"; + $invoicex .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}"; } - return $invoice; + return $invoicex; } diff --git a/app/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/PaymentDrivers/PayPalExpressPaymentDriver.php index 86cad72f4aca..c69f680dec2b 100644 --- a/app/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -95,7 +95,7 @@ class PayPalExpressPaymentDriver extends BaseDriver return $response->redirect(); } - $this->sendFailureMail($response->getMessage() ?: ''); + // $this->sendFailureMail($response->getMessage() ?: ''); $message = [ 'server_response' => $response->getMessage(), diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index 3c927e544baa..eb6bc14558e3 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -137,6 +137,9 @@ class Charge return false; } + if($response?->status != 'succeeded') + $this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400)); + if ($cgt->gateway_type_id == GatewayType::SEPA) { $payment_method_type = PaymentType::SEPA; $status = Payment::STATUS_PENDING; diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 1595b422eda5..127387abbfff 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -304,8 +304,9 @@ class BaseRepository if (! $model->design_id) $model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id')); - //links tasks and expenses back to the invoice. - $model->service()->linkEntities()->save(); + //links tasks and expenses back to the invoice, but only if we are not in the middle of a transaction. + if (\DB::transactionLevel() == 0) + $model->service()->linkEntities()->save(); if($this->new_model) event('eloquent.created: App\Models\Invoice', $model); diff --git a/app/Services/PdfMaker/PdfMerge.php b/app/Services/PdfMaker/PdfMerge.php new file mode 100644 index 000000000000..e4b2f50137b5 --- /dev/null +++ b/app/Services/PdfMaker/PdfMerge.php @@ -0,0 +1,42 @@ +file_paths as $file) { + $pageCount = $pdf->setSourceFile(StreamReader::createByString(Storage::get($file))); + for ($i = 0; $i < $pageCount; $i++) { + $tpl = $pdf->importPage($i + 1, '/MediaBox'); + $pdf->addPage(); + $pdf->useTemplate($tpl); + } + } + + return $pdf->Output('S'); + + } + +} diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 4806a89d1d35..f38e8ca9de42 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -143,6 +143,7 @@ class HtmlEngine $data['$credit.datetime'] = &$data['$entity.datetime']; $data['$payment_button'] = ['value' => ''.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')]; if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php index 7e2e6fae1455..8b6481bd1ff0 100644 --- a/app/Utils/Traits/Inviteable.php +++ b/app/Utils/Traits/Inviteable.php @@ -13,6 +13,11 @@ namespace App\Utils\Traits; use App\Utils\Ninja; use Illuminate\Support\Str; +use BaconQrCode\Renderer\ImageRenderer; +use BaconQrCode\Renderer\Image\SvgImageBackEnd; +use BaconQrCode\Renderer\RendererStyle\RendererStyle; +use BaconQrCode\Writer; + /** * Class Inviteable. @@ -54,6 +59,22 @@ trait Inviteable return $domain.'/client/pay/'.$this->key; } + public function getPaymentQrCode() + { + + $renderer = new ImageRenderer( + new RendererStyle(200), + new SvgImageBackEnd() + ); + $writer = new Writer($renderer); + + $qr = $writer->writeString($this->getPaymentLink()); + + return " + {$qr}"; + + } + public function getUnsubscribeLink() { if (Ninja::isHosted()) {