From 8bd3f54bb09da9ace0884226e8625839f39187f7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jun 2022 10:38:48 +1000 Subject: [PATCH 01/13] Minor fixes for company imports --- app/Jobs/Company/CompanyImport.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 3a8529262d43..1d411abf5a4e 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -1423,21 +1423,21 @@ class CompanyImport implements ShouldQueue $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); - $new_obj->number = $this->getNextInvoiceNumber($client = Client::find($obj_array['client_id']),$new_obj); + $new_obj->number = $this->getNextInvoiceNumber($client = Client::withTrashed()->find($obj_array['client_id']),$new_obj); } elseif($class == 'App\Models\Payment' && is_null($obj->{$match_key})){ $new_obj = new Payment(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); - $new_obj->number = $this->getNextPaymentNumber($client = Client::find($obj_array['client_id']), $new_obj); + $new_obj->number = $this->getNextPaymentNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj); } elseif($class == 'App\Models\Quote' && is_null($obj->{$match_key})){ $new_obj = new Quote(); $new_obj->company_id = $this->company->id; $new_obj->fill($obj_array); $new_obj->save(['timestamps' => false]); - $new_obj->number = $this->getNextQuoteNumber($client = Client::find($obj_array['client_id']), $new_obj); + $new_obj->number = $this->getNextQuoteNumber($client = Client::withTrashed()->find($obj_array['client_id']), $new_obj); } elseif($class == 'App\Models\ClientContact'){ $new_obj = new ClientContact(); From 776f3428ba501baa9dd261dbf6f6b70c4bcb70e6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jun 2022 11:37:40 +1000 Subject: [PATCH 02/13] Purchase order decorators --- .../PreviewPurchaseOrderController.php | 470 ++++++++++++++++++ .../VendorPortal/InvitationController.php | 56 +-- .../Preview/PreviewPurchaseOrderRequest.php | 62 +++ app/Models/Account.php | 5 +- routes/api.php | 3 + routes/vendor.php | 3 + tests/Feature/PreviewTest.php | 25 + 7 files changed, 587 insertions(+), 37 deletions(-) create mode 100644 app/Http/Controllers/PreviewPurchaseOrderController.php create mode 100644 app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php diff --git a/app/Http/Controllers/PreviewPurchaseOrderController.php b/app/Http/Controllers/PreviewPurchaseOrderController.php new file mode 100644 index 000000000000..f8b287570715 --- /dev/null +++ b/app/Http/Controllers/PreviewPurchaseOrderController.php @@ -0,0 +1,470 @@ +has('entity') && + request()->has('entity_id') && + ! empty(request()->input('entity')) && + ! empty(request()->input('entity_id')) && + request()->has('body')) { + + $design_object = json_decode(json_encode(request()->input('design'))); + + if (! is_object($design_object)) { + return response()->json(['message' => ctrans('texts.invalid_design_object')], 400); + } + + $entity_obj = PurchaseOrder::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); + + if (! $entity_obj) { + return $this->blankEntity(); + } + + App::forgetInstance('translator'); + $t = app('translator'); + App::setLocale($entity_obj->company->locale()); + $t->replace(Ninja::transformTranslations($entity_obj->company->settings)); + + $html = new VendorHtmlEngine($entity_obj->invitations()->first()); + + $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; + + $design_class = new $design_namespace(); + + $state = [ + 'template' => $design_class->elements([ + 'client' => null, + 'vendor' => $entity_obj->vendor, + 'entity' => $entity_obj, + 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + 'variables' => $html->generateLabelsAndValues(), + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $entity_obj->company->markdown_enabled, + ]; + + $design = new Design(request()->design['name']); + $maker = new PdfMaker($state); + + $maker + ->design($design) + ->build(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + //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()); + + if($numbered_pdf) + $pdf = $numbered_pdf; + + return $pdf; + + } + + //else + $file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company()); + + return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); + } + + return $this->blankEntity(); + } + + public function live(PreviewPurchaseOrderRequest $request) + { + $company = auth()->user()->company(); + + MultiDB::setDb($company->db); + + $repo = new PurchaseOrderRepository(); + $entity_obj = PurchaseOrderFactory::create($company->id, auth()->user()->id); + $class = PurchaseOrder::class; + + try { + + DB::connection(config('database.default'))->beginTransaction(); + + if($request->has('entity_id')){ + + $entity_obj = $class::on(config('database.default')) + ->with('vendor.company') + ->where('id', $this->decodePrimaryKey($request->input('entity_id'))) + ->where('company_id', $company->id) + ->withTrashed() + ->first(); + + } + + $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->company->locale()); + $t->replace(Ninja::transformTranslations($entity_obj->company->settings)); + + $html = new VendorHtmlEngine($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' => null, + 'vendor' => $entity_obj->vendor, + 'entity' => $entity_obj, + 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + 'variables' => $html->generateLabelsAndValues(), + '$product' => $design->design->product, + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $entity_obj->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($template) + ->build(); + + DB::connection(config('database.default'))->rollBack(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + + } + catch(\Exception $e){ + + 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()); + + if($numbered_pdf) + $pdf = $numbered_pdf; + + return $pdf; + } + + $file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), $company); + + + if(Ninja::isHosted()) + { + LightLogs::create(new LivePreview()) + ->increment() + ->queue(); + } + + + $response = Response::make($file_path, 200); + $response->header('Content-Type', 'application/pdf'); + + return $response; + + } + + private function blankEntity() + { + App::forgetInstance('translator'); + $t = app('translator'); + $t->replace(Ninja::transformTranslations(auth()->user()->company()->settings)); + + $invitation = PurchaseOrderInvitation::where('company_id', auth()->user()->company()->id)->orderBy('id', 'desc')->first(); + + /* If we don't have a valid invitation in the system - create a mock using transactions */ + if(!$invitation) + return $this->mockEntity(); + + $design_object = json_decode(json_encode(request()->input('design'))); + + if (! is_object($design_object)) { + return response()->json(['message' => 'Invalid custom design object'], 400); + } + + $html = new VendorHtmlEngine($invitation); + + $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); + + $state = [ + 'template' => $design->elements([ + 'client' => null, + 'vendor' => $invitation->purchase_order->vendor, + 'entity' => $invitation->purchase_order, + 'pdf_variables' => (array) $invitation->company->settings->pdf_variables, + 'products' => request()->design['design']['product'], + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $invitation->company->markdown_enabled, + ]; + + + $maker = new PdfMaker($state); + + $maker + ->design($design) + ->build(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + 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()); + + if($numbered_pdf) + $pdf = $numbered_pdf; + + return $pdf; + } + + $file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company()); + + $response = Response::make($file_path, 200); + $response->header('Content-Type', 'application/pdf'); + + return $response; + + } + + private function mockEntity() + { + + DB::connection(auth()->user()->company()->db)->beginTransaction(); + + $vendor = Vendor::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + ]); + + $contact = VendorContact::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'vendor_id' => $vendor->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + $purchase_order = PurchaseOrder::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'vendor_id' => $vendor->id, + 'terms' => 'Sample Terms', + 'footer' => 'Sample Footer', + 'public_notes' => 'Sample Public Notes', + ]); + + $invitation = PurchaseOrderInvitation::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'purchase_order_id' => $purchase_order->id, + 'vendor_contact_id' => $contact->id, + ]); + + $purchase_order->setRelation('invitations', $invitation); + $purchase_order->setRelation('vendor', $vendor); + $purchase_order->setRelation('company', auth()->user()->company()); + $purchase_order->load('vendor.company'); + + $design_object = json_decode(json_encode(request()->input('design'))); + + if (! is_object($design_object)) { + return response()->json(['message' => 'Invalid custom design object'], 400); + } + + $html = new VendorHtmlEngine($purchase_order->invitations()->first()); + + $design = new Design(Design::CUSTOM, ['custom_partials' => request()->design['design']]); + + $state = [ + 'template' => $design->elements([ + 'client' => null, + 'vendor' => $purchase_order->vendor, + 'entity' => $purchase_order, + 'pdf_variables' => (array) $purchase_order->company->settings->pdf_variables, + 'products' => request()->design['design']['product'], + ]), + 'variables' => $html->generateLabelsAndValues(), + 'process_markdown' => $purchase_order->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($design) + ->build(); + + DB::connection(auth()->user()->company()->db)->rollBack(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + 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()); + + if($numbered_pdf) + $pdf = $numbered_pdf; + + return $pdf; + } + + $file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company()); + + $response = Response::make($file_path, 200); + $response->header('Content-Type', 'application/pdf'); + + return $response; + } +} diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index 9f5f6ac93bb8..77824f43e798 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -17,11 +17,13 @@ use App\Events\Misc\InvitationWasViewed; use App\Events\Quote\QuoteWasViewed; use App\Http\Controllers\Controller; use App\Jobs\Entity\CreateRawPdf; +use App\Jobs\Vendor\CreatePurchaseOrderPdf; use App\Models\Client; use App\Models\ClientContact; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; use App\Models\Payment; +use App\Models\PurchaseOrder; use App\Models\PurchaseOrderInvitation; use App\Models\QuoteInvitation; use App\Services\ClientPortal\InstantPayment; @@ -95,50 +97,32 @@ class InvitationController extends Controller } + public function download(string $invitation_key) + { + $invitation = PurchaseOrder::withTrashed() + ->where('key', $invitation_key) + ->with('contact.vendor') + ->firstOrFail(); + if(!$invitation) + return response()->json(["message" => "no record found"], 400); - // public function routerForDownload(string $entity, string $invitation_key) - // { + $file_name = $invitation->purchase_order->numberFormatter().'.pdf'; - // set_time_limit(45); + // $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db); - // if(Ninja::isHosted()) - // return $this->returnRawPdf($entity, $invitation_key); + $file = (new CreatePurchaseOrderPdf($invitation))->handle(); - // return redirect('client/'.$entity.'/'.$invitation_key.'/download_pdf'); - // } + $headers = ['Content-Type' => 'application/pdf']; - // private function returnRawPdf(string $entity, string $invitation_key) - // { + if(request()->input('inline') == 'true') + $headers = array_merge($headers, ['Content-Disposition' => 'inline']); - // if(!in_array($entity, ['invoice', 'credit', 'quote', 'recurring_invoice'])) - // return response()->json(['message' => 'Invalid resource request']); + return response()->streamDownload(function () use($file) { + echo $file; + }, $file_name, $headers); + } - // $key = $entity.'_id'; - - // $entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation'; - - // $invitation = $entity_obj::where('key', $invitation_key) - // ->with('contact.client') - // ->firstOrFail(); - - // if(!$invitation) - // return response()->json(["message" => "no record found"], 400); - - // $file_name = $invitation->purchase_order->numberFormatter().'.pdf'; - - // $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db); - - // $headers = ['Content-Type' => 'application/pdf']; - - // if(request()->input('inline') == 'true') - // $headers = array_merge($headers, ['Content-Disposition' => 'inline']); - - // return response()->streamDownload(function () use($file) { - // echo $file; - // }, $file_name, $headers); - - // } diff --git a/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php new file mode 100644 index 000000000000..92b892690cd3 --- /dev/null +++ b/app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php @@ -0,0 +1,62 @@ +user()->can('create', PurchaseOrder::class); + } + + public function rules() + { + $rules = []; + + $rules['number'] = ['nullable']; + + return $rules; + } + + protected function prepareForValidation() + { + $input = $this->all(); + + $input = $this->decodePrimaryKeys($input); + + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = 0; + $input['balance'] = 0; + $input['number'] = ctrans('texts.live_preview') . " #". rand(0,1000); + + $this->replace($input); + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index d41ec4e95267..9cde2bf4a055 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -33,7 +33,7 @@ class Account extends BaseModel use PresentableTrait; use MakesHash; - private $free_plan_email_quota = 250; + private $free_plan_email_quota = 100; private $paid_plan_email_quota = 500; /** @@ -377,6 +377,9 @@ class Account extends BaseModel if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) return 20; + if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2 && !$this->payment_id) + return 20; + if($this->isPaid()){ $limit = $this->paid_plan_email_quota; $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 100; diff --git a/routes/api.php b/routes/api.php index 38aa0208f46e..b605901d8f73 100644 --- a/routes/api.php +++ b/routes/api.php @@ -129,6 +129,9 @@ Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale Route::post('preview', 'PreviewController@show')->name('preview.show'); Route::post('live_preview', 'PreviewController@live')->name('preview.live'); + Route::post('preview/purchase_order', 'PreviewPurchaseOrderController@show')->name('preview_purchase_order.show'); + Route::post('live_preview/purchase_order', 'PreviewPurchaseOrderController@live')->name('preview_purchase_order.live'); + Route::resource('products', 'ProductController'); // name = (products. index / create / show / update / destroy / edit Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk'); Route::put('products/{product}/upload', 'ProductController@upload'); diff --git a/routes/vendor.php b/routes/vendor.php index 28986e8ad0ac..86559e6fc926 100644 --- a/routes/vendor.php +++ b/routes/vendor.php @@ -40,4 +40,7 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr }); +Route::get('purchase_order/{invitation_key}/download', 'VendorPortal\InvitationController@download'); + + Route::fallback('BaseController@notFoundVendor'); \ No newline at end of file diff --git a/tests/Feature/PreviewTest.php b/tests/Feature/PreviewTest.php index 4c7279ced804..57d86e9bd817 100644 --- a/tests/Feature/PreviewTest.php +++ b/tests/Feature/PreviewTest.php @@ -47,6 +47,31 @@ class PreviewTest extends TestCase $response->assertStatus(200); } + public function testPurchaseOrderPreviewRoute() + { + $data = $this->getData(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/preview/purchase_order', $data); + + $response->assertStatus(200); + } + + public function testPurchaseOrderPreviewHtmlResponse() + { + $data = $this->getData(); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/preview/purchase_order?html=true', $data); + + $response->assertStatus(200); + } + + public function testPreviewHtmlResponse() { $data = $this->getData(); From e1b1114722df8fbda4c3cbaaf392c9d8da494623 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jun 2022 11:42:17 +1000 Subject: [PATCH 03/13] Purchase order decorators --- routes/vendor.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/vendor.php b/routes/vendor.php index 86559e6fc926..f807deb1548d 100644 --- a/routes/vendor.php +++ b/routes/vendor.php @@ -20,6 +20,8 @@ Route::get('vendors', [VendorContactLoginController::class, 'catch'])->name('ven Route::group(['middleware' => ['invite_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () { /*Invitation catches*/ Route::get('purchase_order/{invitation_key}', [InvitationController::class, 'purchaseOrder']); + Route::get('purchase_order/{invitation_key}/download', [InvitationController::class, 'download']); + // Route::get('purchase_order/{invitation_key}/download_pdf', 'PurchaseOrderController@downloadPdf')->name('recurring_invoice.download_invitation_key'); // Route::get('purchase_order/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload'); @@ -40,7 +42,7 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr }); -Route::get('purchase_order/{invitation_key}/download', 'VendorPortal\InvitationController@download'); + Route::fallback('BaseController@notFoundVendor'); \ No newline at end of file From 36a72906793b474ddb43c79679b7a933c7c61b27 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jun 2022 11:44:05 +1000 Subject: [PATCH 04/13] Purchase order decorators --- app/Http/Controllers/VendorPortal/InvitationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index 77824f43e798..bcc37db58dcf 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -99,7 +99,7 @@ class InvitationController extends Controller public function download(string $invitation_key) { - $invitation = PurchaseOrder::withTrashed() + $invitation = PurchaseOrderInvitation::withTrashed() ->where('key', $invitation_key) ->with('contact.vendor') ->firstOrFail(); From 73bb2c96db7a3cf3b0d81a7093a92483f62809a1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 29 Jun 2022 11:47:16 +1000 Subject: [PATCH 05/13] Purchase order decorators --- .../VendorPortal/InvitationController.php | 2 +- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 45 +++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index bcc37db58dcf..9c20dab32620 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -111,7 +111,7 @@ class InvitationController extends Controller // $file = CreateRawPdf::dispatchNow($invitation, $invitation->company->db); - $file = (new CreatePurchaseOrderPdf($invitation))->handle(); + $file = (new CreatePurchaseOrderPdf($invitation))->rawPdf(); $headers = ['Content-Type' => 'application/pdf']; diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index fde78e220368..f1433484db6a 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -88,6 +88,32 @@ class CreatePurchaseOrderPdf implements ShouldQueue } public function handle() + { + + $pdf = $this->rawPdf(); + + if ($pdf) { + + try{ + + if(!Storage::disk($this->disk)->exists($path)) + Storage::disk($this->disk)->makeDirectory($path, 0775); + + Storage::disk($this->disk)->put($file_path, $pdf, 'public'); + + } + catch(\Exception $e) + { + + throw new FilePermissionsFailure($e->getMessage()); + + } + } + + return $file_path; + } + + public function rawPdf() { MultiDB::setDb($this->company->db); @@ -191,25 +217,8 @@ class CreatePurchaseOrderPdf implements ShouldQueue info($maker->getCompiledHTML()); } - if ($pdf) { + return $pdf; - try{ - - if(!Storage::disk($this->disk)->exists($path)) - Storage::disk($this->disk)->makeDirectory($path, 0775); - - Storage::disk($this->disk)->put($file_path, $pdf, 'public'); - - } - catch(\Exception $e) - { - - throw new FilePermissionsFailure($e->getMessage()); - - } - } - - return $file_path; } public function failed($e) From 127e9f723f5814ce3ccde13619fd05a9557982bb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 10:11:55 +1000 Subject: [PATCH 06/13] Add flagging abilities to accounts table --- app/Factory/UserFactory.php | 1 + app/Jobs/Mail/NinjaMailerJob.php | 6 +++- app/Models/Account.php | 2 ++ ...6_30_000126_add_flag_to_accounts_table.php | 30 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2022_06_30_000126_add_flag_to_accounts_table.php diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php index baba2fff2588..da56a183875e 100644 --- a/app/Factory/UserFactory.php +++ b/app/Factory/UserFactory.php @@ -11,6 +11,7 @@ namespace App\Factory; +use App\Models\CompanyUser; use App\Models\User; class UserFactory diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index fe5576fbc53b..6e7296e96c52 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -308,8 +308,9 @@ class NinjaMailerJob implements ShouldQueue private function preFlightChecksFail() { + /* If we are migrating data we don't want to fire any emails */ - if ($this->nmo->company->is_disabled && !$this->override) + if($this->nmo->company->is_disabled && !$this->override) return true; /* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */ @@ -324,6 +325,9 @@ class NinjaMailerJob implements ShouldQueue if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded()) return true; + if(Ninja::isHosted() && $this->company->account && $this->nmo->company->account->is_flagged) + return true; + /* Ensure the user has a valid email address */ if(!str_contains($this->nmo->to_user->email, "@")) return true; diff --git a/app/Models/Account.php b/app/Models/Account.php index d41ec4e95267..a9ff4020ca07 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -373,6 +373,8 @@ class Account extends BaseModel public function getDailyEmailLimit() { + if($this->is_flagged) + return 0; if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) return 20; diff --git a/database/migrations/2022_06_30_000126_add_flag_to_accounts_table.php b/database/migrations/2022_06_30_000126_add_flag_to_accounts_table.php new file mode 100644 index 000000000000..7118a7d7f564 --- /dev/null +++ b/database/migrations/2022_06_30_000126_add_flag_to_accounts_table.php @@ -0,0 +1,30 @@ +boolean('is_flagged')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} From 725aca8796e3000d1faf31d06415fea85d41fcb9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 13:32:44 +1000 Subject: [PATCH 07/13] Fixes for paths --- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 16 ++++++++++------ app/Models/Account.php | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index f1433484db6a..d11f62e64f0e 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -65,6 +65,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue public $vendor; + private string $path = ''; + + private string $file_path = ''; + /** * Create a new job instance. * @@ -96,10 +100,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue try{ - if(!Storage::disk($this->disk)->exists($path)) - Storage::disk($this->disk)->makeDirectory($path, 0775); + if(!Storage::disk($this->disk)->exists($this->path)) + Storage::disk($this->disk)->makeDirectory($this->path, 0775); - Storage::disk($this->disk)->put($file_path, $pdf, 'public'); + Storage::disk($this->disk)->put($this->file_path, $pdf, 'public'); } catch(\Exception $e) @@ -110,7 +114,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue } } - return $file_path; + return $this->file_path; } public function rawPdf() @@ -135,10 +139,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue $entity_design_id = ''; - $path = $this->vendor->purchase_order_filepath($this->invitation); + $this->path = $this->vendor->purchase_order_filepath($this->invitation); $entity_design_id = 'purchase_order_design_id'; - $file_path = $path.$this->entity->numberFormatter().'.pdf'; + $this->file_path = $this->path.$this->entity->numberFormatter().'.pdf'; $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn'); diff --git a/app/Models/Account.php b/app/Models/Account.php index 9cde2bf4a055..92fba26d8327 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -373,6 +373,8 @@ class Account extends BaseModel public function getDailyEmailLimit() { + if(Carbon::createFromTimestamp($this->created_at)->diffInDays() == 0) + return 7; if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) return 20; From c59ad58200656425f86006ee79eeb1af48c6a32f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 13:34:15 +1000 Subject: [PATCH 08/13] minor fixes --- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 10 +- app/Models/Presenters/CompanyPresenter.php | 15 + composer.json | 1 + composer.lock | 1042 +++++++++++++++++--- 4 files changed, 927 insertions(+), 141 deletions(-) diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index f1433484db6a..e4a360079246 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -96,10 +96,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue try{ - if(!Storage::disk($this->disk)->exists($path)) - Storage::disk($this->disk)->makeDirectory($path, 0775); + if(!Storage::disk($this->disk)->exists($this->path)) + Storage::disk($this->disk)->makeDirectory($this->path, 0775); - Storage::disk($this->disk)->put($file_path, $pdf, 'public'); + Storage::disk($this->disk)->put($this->file_path, $pdf, 'public'); } catch(\Exception $e) @@ -135,10 +135,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue $entity_design_id = ''; - $path = $this->vendor->purchase_order_filepath($this->invitation); + $this->path = $this->vendor->purchase_order_filepath($this->invitation); $entity_design_id = 'purchase_order_design_id'; - $file_path = $path.$this->entity->numberFormatter().'.pdf'; + $this->file_path = $this->path.$this->entity->numberFormatter().'.pdf'; $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn'); diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php index 911b6370e5b5..4f871a552231 100644 --- a/app/Models/Presenters/CompanyPresenter.php +++ b/app/Models/Presenters/CompanyPresenter.php @@ -126,6 +126,21 @@ class CompanyPresenter extends EntityPresenter } } + public function address1() + { + return $this->settings->address1; + } + + public function address2() + { + return $this->settings->address2; + } + + public function qr_iban() + { + $qr_iban = Helpers::formatCustomFieldValue($this->custom_fields, 'qr_iban') + } + public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban) { $settings = $this->entity->settings; diff --git a/composer.json b/composer.json index c67ba67be29d..afdfd8039dc6 100644 --- a/composer.json +++ b/composer.json @@ -85,6 +85,7 @@ "setasign/fpdi": "^2.3", "socialiteproviders/apple": "^5.2", "socialiteproviders/microsoft": "^4.1", + "sprain/swiss-qr-bill": "^3.2", "square/square": "13.0.0.20210721", "stripe/stripe-php": "^7.50", "symfony/http-client": "^5.2", diff --git a/composer.lock b/composer.lock index b0444b4bcc65..fff1d3ee162a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df84a1903809a8e781d937e679821e74", + "content-hash": "b2410c363539f962178bbe1f238fde01", "packages": [ { "name": "afosto/yaac", @@ -434,16 +434,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.228.1", + "version": "3.229.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "53b7f43945b19bb0700c75d4c5f130055096e817" + "reference": "5e01f0809682060dc5018bf5f267f40c32f4944d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/53b7f43945b19bb0700c75d4c5f130055096e817", - "reference": "53b7f43945b19bb0700c75d4c5f130055096e817", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5e01f0809682060dc5018bf5f267f40c32f4944d", + "reference": "5e01f0809682060dc5018bf5f267f40c32f4944d", "shasum": "" }, "require": { @@ -451,7 +451,7 @@ "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", - "guzzlehttp/guzzle": "^6.5.7 || ^7.4.4", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", "guzzlehttp/promises": "^1.4.0", "guzzlehttp/psr7": "^1.8.5 || ^2.3", "mtdowling/jmespath.php": "^2.6", @@ -519,9 +519,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.228.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.229.0" }, - "time": "2022-06-22T18:16:48+00:00" + "time": "2022-06-29T18:15:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1845,6 +1845,81 @@ ], "time": "2020-12-29T14:50:06+00:00" }, + { + "name": "endroid/qr-code", + "version": "3.9.7", + "source": { + "type": "git", + "url": "https://github.com/endroid/qr-code.git", + "reference": "94563d7b3105288e6ac53a67ae720e3669fac1f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/94563d7b3105288e6ac53a67ae720e3669fac1f6", + "reference": "94563d7b3105288e6ac53a67ae720e3669fac1f6", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "khanamiryan/qrcode-detector-decoder": "^1.0.5", + "myclabs/php-enum": "^1.5", + "php": "^7.3||^8.0", + "symfony/options-resolver": "^3.4||^4.4||^5.0", + "symfony/property-access": "^3.4||^4.4||^5.0" + }, + "require-dev": { + "endroid/quality": "^1.5.2", + "setasign/fpdf": "^1.8" + }, + "suggest": { + "ext-gd": "Required for generating PNG images", + "roave/security-advisories": "Avoids installation of package versions with vulnerabilities", + "setasign/fpdf": "Required to use the FPDF writer.", + "symfony/security-checker": "Checks your composer.lock for vulnerabilities" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Endroid\\QrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen van den Enden", + "email": "info@endroid.nl" + } + ], + "description": "Endroid QR Code", + "homepage": "https://github.com/endroid/qr-code", + "keywords": [ + "bundle", + "code", + "endroid", + "php", + "qr", + "qrcode" + ], + "support": { + "issues": "https://github.com/endroid/qr-code/issues", + "source": "https://github.com/endroid/qr-code/tree/3.9.7" + }, + "funding": [ + { + "url": "https://github.com/endroid", + "type": "github" + } + ], + "time": "2021-04-20T19:10:54+00:00" + }, { "name": "eway/eway-rapid-php", "version": "1.4.0", @@ -2169,16 +2244,16 @@ }, { "name": "gocardless/gocardless-pro", - "version": "4.16.0", + "version": "4.17.0", "source": { "type": "git", "url": "https://github.com/gocardless/gocardless-pro-php.git", - "reference": "31116c4a47b7815cbe25e411a1e0382e42006691" + "reference": "59ccdcbfbbf1a18b55c749ed121137dce6d6f3ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/gocardless/gocardless-pro-php/zipball/31116c4a47b7815cbe25e411a1e0382e42006691", - "reference": "31116c4a47b7815cbe25e411a1e0382e42006691", + "url": "https://api.github.com/repos/gocardless/gocardless-pro-php/zipball/59ccdcbfbbf1a18b55c749ed121137dce6d6f3ae", + "reference": "59ccdcbfbbf1a18b55c749ed121137dce6d6f3ae", "shasum": "" }, "require": { @@ -2218,9 +2293,9 @@ ], "support": { "issues": "https://github.com/gocardless/gocardless-pro-php/issues", - "source": "https://github.com/gocardless/gocardless-pro-php/tree/v4.16.0" + "source": "https://github.com/gocardless/gocardless-pro-php/tree/v4.17.0" }, - "time": "2022-04-25T14:24:52+00:00" + "time": "2022-06-29T12:55:58+00:00" }, { "name": "google/apiclient", @@ -2294,16 +2369,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.254.0", + "version": "v0.255.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "e1b16659df899f3bdf5c798358c256a9cc01efeb" + "reference": "2b895ceb08eb106f65e975221e5d2e971cf7470e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/e1b16659df899f3bdf5c798358c256a9cc01efeb", - "reference": "e1b16659df899f3bdf5c798358c256a9cc01efeb", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/2b895ceb08eb106f65e975221e5d2e971cf7470e", + "reference": "2b895ceb08eb106f65e975221e5d2e971cf7470e", "shasum": "" }, "require": { @@ -2332,9 +2407,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.254.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.255.0" }, - "time": "2022-06-19T01:16:11+00:00" + "time": "2022-06-27T01:30:11+00:00" }, { "name": "google/auth", @@ -3448,6 +3523,110 @@ }, "time": "2021-10-08T21:21:46+00:00" }, + { + "name": "khanamiryan/qrcode-detector-decoder", + "version": "1.0.5.2", + "source": { + "type": "git", + "url": "https://github.com/khanamiryan/php-qrcode-detector-decoder.git", + "reference": "04fdd58d86a387065f707dc6d3cc304c719910c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/khanamiryan/php-qrcode-detector-decoder/zipball/04fdd58d86a387065f707dc6d3cc304c719910c1", + "reference": "04fdd58d86a387065f707dc6d3cc304c719910c1", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 | ^7.5 | ^8.0 | ^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Common/customFunctions.php" + ], + "psr-4": { + "Zxing\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "Apache-2.0" + ], + "authors": [ + { + "name": "Ashot Khanamiryan", + "email": "a.khanamiryan@gmail.com", + "homepage": "https://github.com/khanamiryan", + "role": "Developer" + } + ], + "description": "QR code decoder / reader", + "homepage": "https://github.com/khanamiryan/php-qrcode-detector-decoder/", + "keywords": [ + "barcode", + "qr", + "zxing" + ], + "support": { + "issues": "https://github.com/khanamiryan/php-qrcode-detector-decoder/issues", + "source": "https://github.com/khanamiryan/php-qrcode-detector-decoder/tree/1.0.5.2" + }, + "time": "2021-07-13T18:46:38+00:00" + }, + { + "name": "kmukku/php-iso11649", + "version": "1.6", + "source": { + "type": "git", + "url": "https://github.com/kmukku/php-iso11649.git", + "reference": "723863147a8ff1c292c337e9459402f4a35c3b1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kmukku/php-iso11649/zipball/723863147a8ff1c292c337e9459402f4a35c3b1e", + "reference": "723863147a8ff1c292c337e9459402f4a35c3b1e", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "kmukku\\phpIso11649\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Keijo Mukku", + "email": "keijo.mukku@gmail.com" + } + ], + "description": "ISO 11649 creditor reference library for php", + "homepage": "https://github.com/kmukku/php-iso11649", + "keywords": [ + "Banking", + "ISO 11649", + "RF creditor reference", + "finance" + ], + "support": { + "issues": "https://github.com/kmukku/php-iso11649/issues", + "source": "https://github.com/kmukku/php-iso11649/tree/master" + }, + "time": "2020-04-21T13:01:17+00:00" + }, { "name": "laracasts/presenter", "version": "0.2.5", @@ -3500,16 +3679,16 @@ }, { "name": "laravel/framework", - "version": "v8.83.17", + "version": "v8.83.18", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2cf142cd5100b02da248acad3988bdaba5635e16" + "reference": "db8188e9cc8359a5c6706fa9d9f55aad7f235077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2cf142cd5100b02da248acad3988bdaba5635e16", - "reference": "2cf142cd5100b02da248acad3988bdaba5635e16", + "url": "https://api.github.com/repos/laravel/framework/zipball/db8188e9cc8359a5c6706fa9d9f55aad7f235077", + "reference": "db8188e9cc8359a5c6706fa9d9f55aad7f235077", "shasum": "" }, "require": { @@ -3669,7 +3848,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-06-21T14:38:31+00:00" + "time": "2022-06-28T14:30:38+00:00" }, { "name": "laravel/serializable-closure", @@ -4857,16 +5036,16 @@ }, { "name": "livewire/livewire", - "version": "v2.10.5", + "version": "v2.10.6", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "9ea6237760f627b3b6a05d15137880780ac843b5" + "reference": "020ad095cf1239138b097d22b584e2701ec3edfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/9ea6237760f627b3b6a05d15137880780ac843b5", - "reference": "9ea6237760f627b3b6a05d15137880780ac843b5", + "url": "https://api.github.com/repos/livewire/livewire/zipball/020ad095cf1239138b097d22b584e2701ec3edfb", + "reference": "020ad095cf1239138b097d22b584e2701ec3edfb", "shasum": "" }, "require": { @@ -4918,7 +5097,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.10.5" + "source": "https://github.com/livewire/livewire/tree/v2.10.6" }, "funding": [ { @@ -4926,7 +5105,7 @@ "type": "github" } ], - "time": "2022-04-07T21:38:12+00:00" + "time": "2022-06-19T02:54:20+00:00" }, { "name": "microsoft/microsoft-graph", @@ -5322,6 +5501,66 @@ }, "time": "2021-06-14T00:11:39+00:00" }, + { + "name": "myclabs/php-enum", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "b942d263c641ddb5190929ff840c68f78713e937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", + "reference": "b942d263c641ddb5190929ff840c68f78713e937", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-07-05T08:18:36+00:00" + }, { "name": "nelexa/zip", "version": "4.0.2", @@ -5397,16 +5636,16 @@ }, { "name": "nesbot/carbon", - "version": "2.58.0", + "version": "2.59.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055" + "reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055", - "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a9000603ea337c8df16cc41f8b6be95a65f4d0f5", + "reference": "a9000603ea337c8df16cc41f8b6be95a65f4d0f5", "shasum": "" }, "require": { @@ -5421,11 +5660,12 @@ "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.54 || ^1.0", - "phpunit/php-file-iterator": "^2.0.5", - "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", "squizlabs/php_codesniffer": "^3.4" }, "bin": [ @@ -5482,15 +5722,19 @@ }, "funding": [ { - "url": "https://opencollective.com/Carbon", - "type": "open_collective" + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", "type": "tidelift" } ], - "time": "2022-04-25T19:31:17+00:00" + "time": "2022-06-29T21:43:55+00:00" }, { "name": "nette/schema", @@ -7698,16 +7942,16 @@ }, { "name": "razorpay/razorpay", - "version": "v2.8.3", + "version": "v2.8.4", "source": { "type": "git", "url": "https://github.com/razorpay/razorpay-php.git", - "reference": "1ae60f9142f63cb01e6f9b843dd0a3573976fd40" + "reference": "3f2edc150f6ca035d15ab81382f7f76417de91f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/1ae60f9142f63cb01e6f9b843dd0a3573976fd40", - "reference": "1ae60f9142f63cb01e6f9b843dd0a3573976fd40", + "url": "https://api.github.com/repos/razorpay/razorpay-php/zipball/3f2edc150f6ca035d15ab81382f7f76417de91f6", + "reference": "3f2edc150f6ca035d15ab81382f7f76417de91f6", "shasum": "" }, "require": { @@ -7759,7 +8003,7 @@ "issues": "https://github.com/Razorpay/razorpay-php/issues", "source": "https://github.com/Razorpay/razorpay-php" }, - "time": "2022-04-29T11:11:00+00:00" + "time": "2022-06-28T11:46:08+00:00" }, { "name": "rmccue/requests", @@ -8026,16 +8270,16 @@ }, { "name": "sentry/sentry", - "version": "3.6.0", + "version": "3.6.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "6d1a6ee29c558be373bfe08d454a3c116c02dd0d" + "reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/6d1a6ee29c558be373bfe08d454a3c116c02dd0d", - "reference": "6d1a6ee29c558be373bfe08d454a3c116c02dd0d", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/5b8f2934b0b20bb01da11c76985ceb5bd6c6af91", + "reference": "5b8f2934b0b20bb01da11c76985ceb5bd6c6af91", "shasum": "" }, "require": { @@ -8115,7 +8359,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/3.6.0" + "source": "https://github.com/getsentry/sentry-php/tree/3.6.1" }, "funding": [ { @@ -8127,20 +8371,20 @@ "type": "custom" } ], - "time": "2022-06-09T20:33:39+00:00" + "time": "2022-06-27T07:58:00+00:00" }, { "name": "sentry/sentry-laravel", - "version": "2.12.0", + "version": "2.12.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "35b8807019e4ca300e4530c2b784517475296fca" + "reference": "bf7b4e6d43f0cf0c320041bb7d3a2a28c7edca57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/35b8807019e4ca300e4530c2b784517475296fca", - "reference": "35b8807019e4ca300e4530c2b784517475296fca", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/bf7b4e6d43f0cf0c320041bb7d3a2a28c7edca57", + "reference": "bf7b4e6d43f0cf0c320041bb7d3a2a28c7edca57", "shasum": "" }, "require": { @@ -8206,7 +8450,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/2.12.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/2.12.1" }, "funding": [ { @@ -8218,7 +8462,7 @@ "type": "custom" } ], - "time": "2022-04-05T10:05:19+00:00" + "time": "2022-06-27T11:34:22+00:00" }, { "name": "setasign/fpdf", @@ -8524,6 +8768,68 @@ }, "time": "2021-03-16T01:16:00+00:00" }, + { + "name": "sprain/swiss-qr-bill", + "version": "v3.2", + "source": { + "type": "git", + "url": "https://github.com/sprain/php-swiss-qr-bill.git", + "reference": "c76cd7e4010a3e7aa4f6426c6d6d6710ed7d6262" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sprain/php-swiss-qr-bill/zipball/c76cd7e4010a3e7aa4f6426c6d6d6710ed7d6262", + "reference": "c76cd7e4010a3e7aa4f6426c6d6d6710ed7d6262", + "shasum": "" + }, + "require": { + "endroid/qr-code": "^3.9.7", + "khanamiryan/qrcode-detector-decoder": "^1.0.3", + "kmukku/php-iso11649": "^1.5", + "php": "^7.4|^8.0", + "symfony/intl": "^3.4.47|^4.4|^5.0", + "symfony/polyfill-intl-icu": "^1.23", + "symfony/validator": "^3.4.47|^4.4|^5.0" + }, + "require-dev": { + "dg/bypass-finals": "^1.3", + "dms/phpunit-arraysubset-asserts": "^0.1|^0.2", + "fpdf/fpdf": "^1.82", + "friendsofphp/php-cs-fixer": "^2.19", + "phpstan/phpstan": "^0.12.53", + "phpunit/phpunit": "^8.0|^9.0", + "symfony/css-selector": "^4.2", + "symfony/var-dumper": "^5.1", + "tecnickcom/tcpdf": "^6.3.2" + }, + "suggest": { + "fpdf/fpdf": "Needed to create pdfs with FpdfOutput", + "tecnickcom/tcpdf": "Needed to create pdfs with TcPdfOutput" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sprain\\SwissQrBill\\": "src", + "Sprain\\Tests\\SwissQrBill\\": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library to create Swiss QR bills", + "support": { + "issues": "https://github.com/sprain/php-swiss-qr-bill/issues", + "source": "https://github.com/sprain/php-swiss-qr-bill/tree/v3.2" + }, + "funding": [ + { + "url": "https://github.com/sprain", + "type": "github" + } + ], + "time": "2021-10-22T11:31:39+00:00" + }, { "name": "square/square", "version": "13.0.0.20210721", @@ -8719,16 +9025,16 @@ }, { "name": "symfony/console", - "version": "v5.4.9", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", - "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", + "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000", "shasum": "" }, "require": { @@ -8798,7 +9104,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.9" + "source": "https://github.com/symfony/console/tree/v5.4.10" }, "funding": [ { @@ -8814,7 +9120,7 @@ "type": "tidelift" } ], - "time": "2022-05-18T06:17:34+00:00" + "time": "2022-06-26T13:00:04+00:00" }, { "name": "symfony/css-selector", @@ -8884,7 +9190,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -8931,7 +9237,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" }, "funding": [ { @@ -9107,7 +9413,7 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -9166,7 +9472,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" }, "funding": [ { @@ -9400,16 +9706,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "1a4f708e4e87f335d1b1be6148060739152f0bd5" + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1a4f708e4e87f335d1b1be6148060739152f0bd5", - "reference": "1a4f708e4e87f335d1b1be6148060739152f0bd5", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", "shasum": "" }, "require": { @@ -9458,7 +9764,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" }, "funding": [ { @@ -9474,20 +9780,20 @@ "type": "tidelift" } ], - "time": "2022-03-13T20:07:29+00:00" + "time": "2022-04-12T15:48:08+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.9", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" + "reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", - "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e7793b7906f72a8cc51054fbca9dcff7a8af1c1e", + "reference": "e7793b7906f72a8cc51054fbca9dcff7a8af1c1e", "shasum": "" }, "require": { @@ -9531,7 +9837,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.9" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.10" }, "funding": [ { @@ -9547,20 +9853,20 @@ "type": "tidelift" } ], - "time": "2022-05-17T15:07:29+00:00" + "time": "2022-06-19T13:13:40+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.9", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8" + "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/34b121ad3dc761f35fe1346d2f15618f8cbf77f8", - "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/255ae3b0a488d78fbb34da23d3e0c059874b5948", + "reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948", "shasum": "" }, "require": { @@ -9643,7 +9949,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.9" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.10" }, "funding": [ { @@ -9659,20 +9965,108 @@ "type": "tidelift" } ], - "time": "2022-05-27T07:09:08+00:00" + "time": "2022-06-26T16:57:59+00:00" }, { - "name": "symfony/mime", - "version": "v5.4.9", + "name": "symfony/intl", + "version": "v5.4.10", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" + "url": "https://github.com/symfony/intl.git", + "reference": "e62efe352693f0cfd5ea3878fc06760582eecc4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", - "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", + "url": "https://api.github.com/repos/symfony/intl/zipball/e62efe352693f0cfd5ea3878fc06760582eecc4c", + "reference": "e62efe352693f0cfd5ea3878fc06760582eecc4c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/filesystem": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Intl\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Eriksen Costa", + "email": "eriksen.costa@infranology.com.br" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "homepage": "https://symfony.com", + "keywords": [ + "i18n", + "icu", + "internationalization", + "intl", + "l10n", + "localization" + ], + "support": { + "source": "https://github.com/symfony/intl/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-26T13:00:04+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "02265e1e5111c3cd7480387af25e82378b7ab9cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/02265e1e5111c3cd7480387af25e82378b7ab9cc", + "reference": "02265e1e5111c3cd7480387af25e82378b7ab9cc", "shasum": "" }, "require": { @@ -9726,7 +10120,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.9" + "source": "https://github.com/symfony/mime/tree/v5.4.10" }, "funding": [ { @@ -9742,7 +10136,7 @@ "type": "tidelift" } ], - "time": "2022-05-21T10:24:18+00:00" + "time": "2022-06-09T12:22:40+00:00" }, { "name": "symfony/options-resolver", @@ -10059,6 +10453,93 @@ ], "time": "2022-05-24T11:49:31+00:00" }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/e407643d610e5f2c8a4b14189150f68934bf5e48", + "reference": "e407643d610e5f2c8a4b14189150f68934bf5e48", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.26.0", @@ -10774,6 +11255,178 @@ ], "time": "2022-04-08T05:07:18+00:00" }, + { + "name": "symfony/property-access", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "fe501d498d6ec7e9efe928c90fabedf629116495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/fe501d498d6ec7e9efe928c90fabedf629116495", + "reference": "fe501d498d6ec7e9efe928c90fabedf629116495", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/property-info": "^5.2|^6.0" + }, + "require-dev": { + "symfony/cache": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/cache-implementation": "To cache access methods." + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-12T15:48:08+00:00" + }, + { + "name": "symfony/property-info", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "924406e19365953870517eb7f63ac3f7bfb71875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/924406e19365953870517eb7f63ac3f7bfb71875", + "reference": "924406e19365953870517eb7f63ac3f7bfb71875", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" + }, + "suggest": { + "phpdocumentor/reflection-docblock": "To use the PHPDoc", + "psr/cache-implementation": "To cache results", + "symfony/doctrine-bridge": "To use Doctrine metadata", + "symfony/serializer": "To use Serializer metadata" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-31T05:14:08+00:00" + }, { "name": "symfony/psr-http-message-bridge", "version": "v2.1.2", @@ -10954,16 +11607,16 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", - "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { @@ -11017,7 +11670,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" }, "funding": [ { @@ -11033,20 +11686,20 @@ "type": "tidelift" } ], - "time": "2022-03-13T20:07:29+00:00" + "time": "2022-05-30T19:17:29+00:00" }, { "name": "symfony/string", - "version": "v5.4.9", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99" + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", - "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99", + "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097", + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097", "shasum": "" }, "require": { @@ -11103,7 +11756,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.9" + "source": "https://github.com/symfony/string/tree/v5.4.10" }, "funding": [ { @@ -11119,7 +11772,7 @@ "type": "tidelift" } ], - "time": "2022-04-19T10:40:37+00:00" + "time": "2022-06-26T15:57:47+00:00" }, { "name": "symfony/translation", @@ -11220,16 +11873,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v2.5.1", + "version": "v2.5.2", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "1211df0afa701e45a04253110e959d4af4ef0f07" + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1211df0afa701e45a04253110e959d4af4ef0f07", - "reference": "1211df0afa701e45a04253110e959d4af4ef0f07", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", "shasum": "" }, "require": { @@ -11278,7 +11931,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" }, "funding": [ { @@ -11294,7 +11947,120 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/validator", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/validator.git", + "reference": "303490582fee6ed46fa3bd9701ef0ff741ada648" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/validator/zipball/303490582fee6ed46fa3bd9701ef0ff741ada648", + "reference": "303490582fee6ed46fa3bd9701ef0ff741ada648", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/translation-contracts": "^1.1|^2|^3" + }, + "conflict": { + "doctrine/annotations": "<1.13", + "doctrine/cache": "<1.11", + "doctrine/lexer": "<1.1", + "phpunit/phpunit": "<5.4.3", + "symfony/dependency-injection": "<4.4", + "symfony/expression-language": "<5.1", + "symfony/http-kernel": "<4.4", + "symfony/intl": "<4.4", + "symfony/property-info": "<5.3", + "symfony/translation": "<4.4", + "symfony/yaml": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "doctrine/cache": "^1.11|^2.0", + "egulias/email-validator": "^2.1.10|^3", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^5.1|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/property-access": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.3|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "egulias/email-validator": "Strict (RFC compliant) email validation", + "psr/cache-implementation": "For using the mapping cache.", + "symfony/config": "", + "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints", + "symfony/http-foundation": "", + "symfony/intl": "", + "symfony/property-access": "For accessing properties within comparison constraints", + "symfony/property-info": "To automatically add NotNull and Type constraints", + "symfony/translation": "For translating validation errors.", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Validator\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to validate values", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/validator/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-09T12:24:18+00:00" }, { "name": "symfony/var-dumper", @@ -12157,16 +12923,16 @@ }, { "name": "brianium/paratest", - "version": "v6.4.4", + "version": "v6.5.1", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "589cdb23728b2a19872945580b95d8aa2c6619da" + "reference": "41fc4cc01422dae2d6bf6a0ce39756f57ac7d8a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/589cdb23728b2a19872945580b95d8aa2c6619da", - "reference": "589cdb23728b2a19872945580b95d8aa2c6619da", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/41fc4cc01422dae2d6bf6a0ce39756f57ac7d8a9", + "reference": "41fc4cc01422dae2d6bf6a0ce39756f57ac7d8a9", "shasum": "" }, "require": { @@ -12174,26 +12940,30 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", + "jean85/pretty-package-versions": "^2.0.5", "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.11", + "phpunit/php-code-coverage": "^9.2.15", "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.5.14", - "sebastian/environment": "^5.1.3", - "symfony/console": "^5.4.0 || ^6.0.0", - "symfony/process": "^5.4.0 || ^6.0.0" + "phpunit/phpunit": "^9.5.21", + "sebastian/environment": "^5.1.4", + "symfony/console": "^5.4.9 || ^6.1.1", + "symfony/process": "^5.4.8 || ^6.1.0" }, "require-dev": { "doctrine/coding-standard": "^9.0.0", + "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.26.5", + "infection/infection": "^0.26.12", "malukenho/mcbumpface": "^1.1.5", - "squizlabs/php_codesniffer": "^3.6.2", - "symfony/filesystem": "^v5.4.0 || ^6.0.0", - "vimeo/psalm": "^4.20.0" + "squizlabs/php_codesniffer": "^3.7.1", + "symfony/filesystem": "^5.4.9 || ^6.1.0", + "vimeo/psalm": "^4.23.0" }, "bin": [ - "bin/paratest" + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" ], "type": "library", "autoload": { @@ -12229,7 +12999,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.4.4" + "source": "https://github.com/paratestphp/paratest/tree/v6.5.1" }, "funding": [ { @@ -12241,7 +13011,7 @@ "type": "paypal" } ], - "time": "2022-03-28T07:55:11+00:00" + "time": "2022-06-24T16:02:27+00:00" }, { "name": "composer/pcre", @@ -15570,16 +16340,16 @@ }, { "name": "symfony/yaml", - "version": "v5.4.3", + "version": "v5.4.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e80f87d2c9495966768310fc531b487ce64237a2" + "reference": "04e42926429d9e8b39c174387ab990bf7817f7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e80f87d2c9495966768310fc531b487ce64237a2", - "reference": "e80f87d2c9495966768310fc531b487ce64237a2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/04e42926429d9e8b39c174387ab990bf7817f7a2", + "reference": "04e42926429d9e8b39c174387ab990bf7817f7a2", "shasum": "" }, "require": { @@ -15625,7 +16395,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.3" + "source": "https://github.com/symfony/yaml/tree/v5.4.10" }, "funding": [ { @@ -15641,7 +16411,7 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:32:32+00:00" + "time": "2022-06-20T11:50:59+00:00" }, { "name": "theseer/tokenizer", @@ -15784,5 +16554,5 @@ "platform-dev": { "php": "^7.4|^8.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } From 27f1e753a3f711066ddcbae3ac22c1416c9ef183 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 14:29:18 +1000 Subject: [PATCH 09/13] Working on Swiss QR Codes --- app/DataMapper/CompanySettings.php | 3 +++ app/Models/Presenters/CompanyPresenter.php | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 3ee028fe98f7..837855712de0 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -25,6 +25,8 @@ class CompanySettings extends BaseSettings /*Invoice*/ public $auto_archive_invoice = false; // @implemented + public $qr_iban = ''; + public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented public $enable_client_portal_tasks = false; //@ben to implement @@ -289,6 +291,7 @@ class CompanySettings extends BaseSettings public $auto_archive_invoice_cancelled = false; public static $casts = [ + 'qr_iban' => 'string', 'email_subject_purchase_order' => 'string', 'email_template_purchase_order' => 'string', 'require_purchase_order_signature' => 'bool', diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php index 4f871a552231..600436d973ce 100644 --- a/app/Models/Presenters/CompanyPresenter.php +++ b/app/Models/Presenters/CompanyPresenter.php @@ -128,17 +128,17 @@ class CompanyPresenter extends EntityPresenter public function address1() { - return $this->settings->address1; + return $this->entity->settings->address1; } public function address2() { - return $this->settings->address2; + return $this->entity->settings->address2; } public function qr_iban() { - $qr_iban = Helpers::formatCustomFieldValue($this->custom_fields, 'qr_iban') + return $this->entity->getSetting('qr_iban'); } public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban) From da66fa627153a47732046a68cf772d0771203bfe Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 14:37:08 +1000 Subject: [PATCH 10/13] Qr Swiss --- app/Helpers/SwissQr/SwissQrGenerator.php | 133 +++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 app/Helpers/SwissQr/SwissQrGenerator.php diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php new file mode 100644 index 000000000000..bf484fda8b01 --- /dev/null +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -0,0 +1,133 @@ +company = $company; + + $this->invoice = $invoice; + } + + public function run() + { + + // This is an example how to create a typical qr bill: + // - with reference number + // - with known debtor + // - with specified amount + // - with human-readable additional information + // - using your QR-IBAN + // + // Likely the most common use-case in the business world. + + // Create a new instance of QrBill, containing default headers with fixed values + $qrBill = \QrBill\QrBill::create(); + + + // Add creditor information + // Who will receive the payment and to which bank account? + $qrBill->setCreditor( + QrBill\DataGroup\Element\CombinedAddress::create( + $this->company->present()->name(), + $this->company->present()->address1(), + $this->company->present()->getCompanyCityState(), + 'CH' + )); + + $qrBill->setCreditorInformation( + QrBill\DataGroup\Element\CreditorInformation::create( + $this->company->present()->qr_iban() // This is a special QR-IBAN. Classic IBANs will not be valid here. + )); + + // Add debtor information + // Who has to pay the invoice? This part is optional. + // + // Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress. + // They are interchangeable for creditor as well as debtor. + $qrBill->setUltimateDebtor( + QrBill\DataGroup\Element\StructuredAddress::createWithStreet( + 'Pia-Maria Rutschmann-Schnyder', + 'Grosse Marktgasse', + '28', + '9400', + 'Rorschach', + 'CH' + )); + + // Add payment amount information + // What amount is to be paid? + $qrBill->setPaymentAmountInformation( + QrBill\DataGroup\Element\PaymentAmountInformation::create( + 'CHF', + 2500.25 + )); + + // Add payment reference + // This is what you will need to identify incoming payments. + $referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate( + '210000', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL. + '313947143000901' // A number to match the payment with your internal data, e.g. an invoice number + ); + + $qrBill->setPaymentReference( + QrBill\DataGroup\Element\PaymentReference::create( + QrBill\DataGroup\Element\PaymentReference::TYPE_QR, + $referenceNumber + )); + + // Optionally, add some human-readable information about what the bill is for. + $qrBill->setAdditionalInformation( + QrBill\DataGroup\Element\AdditionalInformation::create( + 'Invoice 123456, Gardening work' + ) + ); + + // Now get the QR code image and save it as a file. + try { + $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.png'); + $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.svg'); + } catch (Exception $e) { + foreach($qrBill->getViolations() as $violation) { + print $violation->getMessage()."\n"; + } + exit; + } + + $output = new QrBill\PaymentPart\Output\HtmlOutput\HtmlOutput($qrBill, 'en'); + + $html = $output + ->setPrintable(false) + ->getPaymentPart(); + + // Next: Output full payment parts, depending on the format you want to use: + // + // - FpdfOutput/fpdf-example.php + // - HtmlOutput/html-example.php + // - TcPdfOutput/tcpdf-example.php + } + +} \ No newline at end of file From f0c7f4588c4a6cf285531ba6765118a40787bc3e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 16:09:06 +1000 Subject: [PATCH 11/13] Swiss QR Codes --- app/DataMapper/CompanySettings.php | 4 +- app/Helpers/SwissQr/SwissQrGenerator.php | 59 +++++++++++++--------- app/Models/Presenters/CompanyPresenter.php | 5 ++ composer.lock | 2 +- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 837855712de0..241751974daf 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -25,7 +25,8 @@ class CompanySettings extends BaseSettings /*Invoice*/ public $auto_archive_invoice = false; // @implemented - public $qr_iban = ''; + public $qr_iban = ''; //@implemented + public $besr_id = ''; //@implemented public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented @@ -291,6 +292,7 @@ class CompanySettings extends BaseSettings public $auto_archive_invoice_cancelled = false; public static $casts = [ + 'besr_id' => 'string', 'qr_iban' => 'string', 'email_subject_purchase_order' => 'string', 'email_template_purchase_order' => 'string', diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index bf484fda8b01..e0e6766211e0 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -11,6 +11,7 @@ namespace App\Helpers\SwissQr; +use App\Models\Client; use App\Models\Company; use App\Models\Invoice; use Sprain\SwissQrBill as QrBill; @@ -25,11 +26,26 @@ class SwissQrGenerator protected Invoice $invoice; + protected Client $client; + public function __construct(Invoice $invoice, Company $company) { $this->company = $company; $this->invoice = $invoice; + + $this->client = $invoice->client; + } + + private function calcDueAmount() + { + if($this->invoice->partial > 0) + return $this->invoice->partial; + + if($this->invoice->status_id == Invoice::STATUS_DRAFT) + return $this->invoice->amount; + + return $this->invoice->balance; } public function run() @@ -45,7 +61,7 @@ class SwissQrGenerator // Likely the most common use-case in the business world. // Create a new instance of QrBill, containing default headers with fixed values - $qrBill = \QrBill\QrBill::create(); + $qrBill = QrBill\QrBill::create(); // Add creditor information @@ -60,21 +76,21 @@ class SwissQrGenerator $qrBill->setCreditorInformation( QrBill\DataGroup\Element\CreditorInformation::create( - $this->company->present()->qr_iban() // This is a special QR-IBAN. Classic IBANs will not be valid here. + $this->company->present()->qr_iban() ?: '' // This is a special QR-IBAN. Classic IBANs will not be valid here. )); // Add debtor information // Who has to pay the invoice? This part is optional. // - // Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress. + // Notice how you can use two different styles of addresses: CombinedAddress or StructuredAddress // They are interchangeable for creditor as well as debtor. $qrBill->setUltimateDebtor( QrBill\DataGroup\Element\StructuredAddress::createWithStreet( - 'Pia-Maria Rutschmann-Schnyder', - 'Grosse Marktgasse', - '28', - '9400', - 'Rorschach', + $this->client->present()->name(), + $this->client->address1 ?: '', + $this->client->address2 ?: '', + $this->client->postal_code ?: '', + $this->client->city ?: '', 'CH' )); @@ -83,14 +99,14 @@ class SwissQrGenerator $qrBill->setPaymentAmountInformation( QrBill\DataGroup\Element\PaymentAmountInformation::create( 'CHF', - 2500.25 + $this->calcDueAmount() )); // Add payment reference // This is what you will need to identify incoming payments. $referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate( - '210000', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL. - '313947143000901' // A number to match the payment with your internal data, e.g. an invoice number + $this->company->present()->besr_id() ?: '', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL. + $this->invoice->number // A number to match the payment with your internal data, e.g. an invoice number ); $qrBill->setPaymentReference( @@ -102,19 +118,20 @@ class SwissQrGenerator // Optionally, add some human-readable information about what the bill is for. $qrBill->setAdditionalInformation( QrBill\DataGroup\Element\AdditionalInformation::create( - 'Invoice 123456, Gardening work' + $this->invoice->public_notes ?: '' ) ); + // Now get the QR code image and save it as a file. try { - $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.png'); - $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.svg'); - } catch (Exception $e) { - foreach($qrBill->getViolations() as $violation) { - print $violation->getMessage()."\n"; + // $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.png'); + // $qrBill->getQrCode()->writeFile(__DIR__ . '/qr.svg'); + } catch (\Exception $e) { + foreach($qrBill->getViolations() as $key => $violation) { } - exit; + + // return $e->getMessage(); } $output = new QrBill\PaymentPart\Output\HtmlOutput\HtmlOutput($qrBill, 'en'); @@ -123,11 +140,7 @@ class SwissQrGenerator ->setPrintable(false) ->getPaymentPart(); - // Next: Output full payment parts, depending on the format you want to use: - // - // - FpdfOutput/fpdf-example.php - // - HtmlOutput/html-example.php - // - TcPdfOutput/tcpdf-example.php + return $html; } } \ No newline at end of file diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php index 600436d973ce..8106844991d0 100644 --- a/app/Models/Presenters/CompanyPresenter.php +++ b/app/Models/Presenters/CompanyPresenter.php @@ -141,6 +141,11 @@ class CompanyPresenter extends EntityPresenter return $this->entity->getSetting('qr_iban'); } + public function besr_id() + { + return $this->entity->getSetting('besr_id'); + } + public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban) { $settings = $this->entity->settings; diff --git a/composer.lock b/composer.lock index fff1d3ee162a..c544b3794afb 100644 --- a/composer.lock +++ b/composer.lock @@ -16554,5 +16554,5 @@ "platform-dev": { "php": "^7.4|^8.0" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.3.0" } From 59e397837454749c37c7841de15ec2cc96f1ba02 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 16:40:31 +1000 Subject: [PATCH 12/13] Templates for Purchase Orders --- app/Utils/TemplateEngine.php | 92 ++++++++++++++++++--- app/Utils/Traits/MakesTemplateData.php | 30 +++++++ database/factories/PurchaseOrderFactory.php | 51 ++++++++++++ 3 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 database/factories/PurchaseOrderFactory.php diff --git a/app/Utils/TemplateEngine.php b/app/Utils/TemplateEngine.php index 4a2e2ca0a582..9f13195f808e 100644 --- a/app/Utils/TemplateEngine.php +++ b/app/Utils/TemplateEngine.php @@ -16,8 +16,12 @@ use App\Models\Client; use App\Models\ClientContact; use App\Models\Invoice; use App\Models\InvoiceInvitation; +use App\Models\PurchaseOrder; +use App\Models\PurchaseOrderInvitation; use App\Models\Quote; use App\Models\QuoteInvitation; +use App\Models\Vendor; +use App\Models\VendorContact; use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; @@ -26,6 +30,7 @@ use App\Utils\Traits\MakesTemplateData; use DB; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Lang; +use Illuminate\Support\Str; use League\CommonMark\CommonMarkConverter; use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles; @@ -88,7 +93,7 @@ class TemplateEngine private function setEntity() { if (strlen($this->entity) > 1 && strlen($this->entity_id) > 1) { - $class = 'App\Models\\'.ucfirst($this->entity); + $class = 'App\Models\\'.ucfirst(Str::camel($this->entity)); $this->entity_obj = $class::withTrashed()->where('id', $this->decodePrimaryKey($this->entity_id))->company()->first(); } else { $this->mockEntity(); @@ -99,7 +104,11 @@ class TemplateEngine private function setSettingsObject() { - if ($this->entity_obj) { + if($this->entity == 'purchase_order'){ + $this->settings_entity = auth()->user()->company(); + $this->settings = $this->settings_entity->settings; + } + elseif ($this->entity_obj) { $this->settings_entity = $this->entity_obj->client; $this->settings = $this->settings_entity->getMergedSettings(); } else { @@ -143,7 +152,10 @@ class TemplateEngine $this->raw_body = $this->body; $this->raw_subject = $this->subject; - if ($this->entity_obj) { + if($this->entity == 'purchase_order'){ + $this->fakerValues(); + } + elseif ($this->entity_obj) { $this->entityValues($this->entity_obj->client->primary_contact()->first()); } else { $this->fakerValues(); @@ -198,7 +210,17 @@ class TemplateEngine $data['footer'] = ''; $data['logo'] = auth()->user()->company()->present()->logo(); - $data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client)); + if($this->entity_obj->client) + $data = array_merge($data, Helpers::sharedEmailVariables($this->entity_obj->client)); + else{ + + $data['signature'] = $this->settings->email_signature; + $data['settings'] = $this->settings; + $data['whitelabel'] = $this->entity_obj ? $this->entity_obj->company->account->isPaid() : true; + $data['company'] = $this->entity_obj ? $this->entity_obj->company : ''; + $data['settings'] = $this->settings; + } + if ($email_style == 'custom') { $wrapper = $this->settings_entity->getSetting('email_style_custom'); @@ -243,6 +265,8 @@ class TemplateEngine { DB::connection(config('database.default'))->beginTransaction(); + $vendor = false; + $client = Client::factory()->create([ 'user_id' => auth()->user()->id, 'company_id' => auth()->user()->company()->id, @@ -289,12 +313,60 @@ class TemplateEngine ]); } - $this->entity_obj->setRelation('invitations', $invitation); - $this->entity_obj->setRelation('client', $client); - $this->entity_obj->setRelation('company', auth()->user()->company()); - $this->entity_obj->load('client'); - $client->setRelation('company', auth()->user()->company()); - $client->load('company'); + + + if($this->entity == 'purchase_order') + { + + $vendor = Vendor::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + ]); + + $contact = VendorContact::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'vendor_id' => $vendor->id, + 'is_primary' => 1, + 'send_email' => true, + ]); + + + $this->entity_obj = PurchaseOrder::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'vendor_id' => $vendor->id, + ]); + + $invitation = PurchaseOrderInvitation::factory()->create([ + 'user_id' => auth()->user()->id, + 'company_id' => auth()->user()->company()->id, + 'purchase_order_id' => $this->entity_obj->id, + 'vendor_contact_id' => $contact->id, + ]); + + } + + if($vendor) + { + + $this->entity_obj->setRelation('invitations', $invitation); + $this->entity_obj->setRelation('vendor', $vendor); + $this->entity_obj->setRelation('company', auth()->user()->company()); + $this->entity_obj->load('vendor'); + $vendor->setRelation('company', auth()->user()->company()); + $vendor->load('company'); + + } + else + { + $this->entity_obj->setRelation('invitations', $invitation); + $this->entity_obj->setRelation('client', $client); + $this->entity_obj->setRelation('company', auth()->user()->company()); + $this->entity_obj->load('client'); + $client->setRelation('company', auth()->user()->company()); + $client->load('company'); + } } private function tearDown() diff --git a/app/Utils/Traits/MakesTemplateData.php b/app/Utils/Traits/MakesTemplateData.php index 2b17cbea61d5..fa6b93282197 100644 --- a/app/Utils/Traits/MakesTemplateData.php +++ b/app/Utils/Traits/MakesTemplateData.php @@ -200,6 +200,36 @@ trait MakesTemplateData $data['$task.tax_name3'] = ['value' => 'CA Sales Tax', 'label' => ctrans('texts.tax')]; $data['$task.line_total'] = ['value' => '$100.00', 'label' => ctrans('texts.line_total')]; + $data['$vendor_name'] = &$data['$client_name']; + $data['$vendor.name'] = &$data['$client_name']; + $data['$vendor'] = &$data['$client_name']; + + $data['$vendor.address1'] = &$data['$address1']; + $data['$vendor.address2'] = &$data['$address2']; + $data['$vendor_address'] = ['value' => '5 Kalamazoo Way\n Jimbuckeroo\n USA 90210', 'label' => ctrans('texts.address')]; + $data['$vendor.address'] = &$data['$vendor_address']; + $data['$vendor.postal_code'] = ['value' => '90210', 'label' => ctrans('texts.postal_code')]; + $data['$vendor.public_notes'] = $data['$invoice.public_notes']; + $data['$vendor.city'] = &$data['$company.city']; + $data['$vendor.state'] = &$data['$company.state']; + $data['$vendor.id_number'] = &$data['$id_number']; + $data['$vendor.vat_number'] = &$data['$vat_number']; + $data['$vendor.website'] = &$data['$website']; + $data['$vendor.phone'] = &$data['$phone']; + $data['$vendor.city_state_postal'] = &$data['$city_state_postal']; + $data['$vendor.postal_city_state'] = &$data['$postal_city_state']; + $data['$vendor.country'] = &$data['$country']; + $data['$vendor.email'] = &$data['$email']; + + $data['$vendor.billing_address1'] = &$data['$vendor.address1']; + $data['$vendor.billing_address2'] = &$data['$vendor.address2']; + $data['$vendor.billing_city'] = &$data['$vendor.city']; + $data['$vendor.billing_state'] = &$data['$vendor.state']; + $data['$vendor.billing_postal_code'] = &$data['$vendor.postal_code']; + $data['$vendor.billing_country'] = &$data['$vendor.country']; + + + //$data['$paid_to_date'] = ; // $data['$your_invoice'] = ; // $data['$quote'] = ; diff --git a/database/factories/PurchaseOrderFactory.php b/database/factories/PurchaseOrderFactory.php new file mode 100644 index 000000000000..df91de22f5ba --- /dev/null +++ b/database/factories/PurchaseOrderFactory.php @@ -0,0 +1,51 @@ + Invoice::STATUS_SENT, + 'number' => $this->faker->ean13(), + 'discount' => $this->faker->numberBetween(1, 10), + 'is_amount_discount' => (bool) random_int(0, 1), + 'tax_name1' => 'GST', + 'tax_rate1' => 10, + 'tax_name2' => 'VAT', + 'tax_rate2' => 17.5, + 'is_deleted' => false, + 'po_number' => $this->faker->text(10), + 'date' => $this->faker->date(), + 'due_date' => $this->faker->date(), + 'line_items' => InvoiceItemFactory::generate(5), + 'terms' => $this->faker->text(500), + ]; + } +} From 0894752c80e240112364f205008cf5f8a6104e58 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 30 Jun 2022 17:51:39 +1000 Subject: [PATCH 13/13] Fixes for tests --- app/Utils/Traits/CompanySettingsSaver.php | 6 ++++++ app/Utils/Traits/SettingsSaver.php | 2 +- tests/Feature/CompanySettingsTest.php | 20 +++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/Utils/Traits/CompanySettingsSaver.php b/app/Utils/Traits/CompanySettingsSaver.php index 67f628ae1908..5dd2bad44033 100644 --- a/app/Utils/Traits/CompanySettingsSaver.php +++ b/app/Utils/Traits/CompanySettingsSaver.php @@ -120,6 +120,9 @@ trait CompanySettingsSaver elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') { $value = 'integer'; + if($key == 'besr_id') + $value = 'string'; + if (! property_exists($settings, $key)) { continue; } elseif (! $this->checkAttribute($value, $settings->{$key})) { @@ -187,6 +190,9 @@ trait CompanySettingsSaver if($key == 'gmail_sending_user_id') $value = 'string'; + if($key == 'besr_id') + $value = 'string'; + if (! property_exists($settings, $key)) { continue; } elseif ($this->checkAttribute($value, $settings->{$key})) { diff --git a/app/Utils/Traits/SettingsSaver.php b/app/Utils/Traits/SettingsSaver.php index cb6af5ebb3aa..e507a90677cf 100644 --- a/app/Utils/Traits/SettingsSaver.php +++ b/app/Utils/Traits/SettingsSaver.php @@ -55,7 +55,7 @@ trait SettingsSaver elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && strlen($settings->{$key}) >= 1)) { $value = 'integer'; - if($key == 'gmail_sending_user_id') + if($key == 'gmail_sending_user_id' || $key == 'besr_id') $value = 'string'; if (! property_exists($settings, $key)) { diff --git a/tests/Feature/CompanySettingsTest.php b/tests/Feature/CompanySettingsTest.php index 80c753bb72db..9a546d046117 100644 --- a/tests/Feature/CompanySettingsTest.php +++ b/tests/Feature/CompanySettingsTest.php @@ -56,7 +56,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); } @@ -78,11 +78,13 @@ class CompanySettingsTest extends TestCase $this->company->saveSettings($settings, $this->company); + $response = false; + try { $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); } catch (ValidationException $e) { $message = json_decode($e->validator->getMessageBag(), 1); nlog($message); @@ -109,7 +111,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); $response->assertStatus(200); @@ -135,7 +137,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); $response->assertStatus(200); @@ -162,7 +164,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->put('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); + ])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $this->company->toArray()); $response->assertStatus(200); @@ -185,7 +187,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->post('/api/v1/companies?include=company', $this->company->toArray()); + ])->postJson('/api/v1/companies?include=company', $this->company->toArray()); $arr = $response->json(); $response->assertStatus(200); @@ -203,7 +205,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->post('/api/v1/companies?include=company', $this->company->toArray()); + ])->postJson('/api/v1/companies?include=company', $this->company->toArray()); $arr = $response->json(); $response->assertStatus(200); @@ -221,7 +223,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->post('/api/v1/companies?include=company', $this->company->toArray()); + ])->postJson('/api/v1/companies?include=company', $this->company->toArray()); $arr = $response->json(); $response->assertStatus(200); @@ -239,7 +241,7 @@ class CompanySettingsTest extends TestCase $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-Token' => $this->token, - ])->post('/api/v1/companies?include=company', $this->company->toArray()); + ])->postJson('/api/v1/companies?include=company', $this->company->toArray()); $arr = $response->json(); $response->assertStatus(200);