mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-12 22:22:32 +01:00
Merge pull request #7598 from turbo124/v5-develop
Fixes for Purchase Orders
This commit is contained in:
commit
0b717aceac
@ -25,6 +25,9 @@ class CompanySettings extends BaseSettings
|
||||
/*Invoice*/
|
||||
public $auto_archive_invoice = false; // @implemented
|
||||
|
||||
public $qr_iban = ''; //@implemented
|
||||
public $besr_id = ''; //@implemented
|
||||
|
||||
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
|
||||
|
||||
public $enable_client_portal_tasks = false; //@ben to implement
|
||||
@ -289,6 +292,8 @@ 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',
|
||||
'require_purchase_order_signature' => 'bool',
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
|
||||
class UserFactory
|
||||
|
146
app/Helpers/SwissQr/SwissQrGenerator.php
Normal file
146
app/Helpers/SwissQr/SwissQrGenerator.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Helpers\SwissQr;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Invoice;
|
||||
use Sprain\SwissQrBill as QrBill;
|
||||
|
||||
/**
|
||||
* SwissQrGenerator.
|
||||
*/
|
||||
class SwissQrGenerator
|
||||
{
|
||||
|
||||
protected Company $company;
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
// 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(
|
||||
$this->client->present()->name(),
|
||||
$this->client->address1 ?: '',
|
||||
$this->client->address2 ?: '',
|
||||
$this->client->postal_code ?: '',
|
||||
$this->client->city ?: '',
|
||||
'CH'
|
||||
));
|
||||
|
||||
// Add payment amount information
|
||||
// What amount is to be paid?
|
||||
$qrBill->setPaymentAmountInformation(
|
||||
QrBill\DataGroup\Element\PaymentAmountInformation::create(
|
||||
'CHF',
|
||||
$this->calcDueAmount()
|
||||
));
|
||||
|
||||
// Add payment reference
|
||||
// This is what you will need to identify incoming payments.
|
||||
$referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate(
|
||||
$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(
|
||||
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(
|
||||
$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 $key => $violation) {
|
||||
}
|
||||
|
||||
// return $e->getMessage();
|
||||
}
|
||||
|
||||
$output = new QrBill\PaymentPart\Output\HtmlOutput\HtmlOutput($qrBill, 'en');
|
||||
|
||||
$html = $output
|
||||
->setPrintable(false)
|
||||
->getPaymentPart();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
470
app/Http/Controllers/PreviewPurchaseOrderController.php
Normal file
470
app/Http/Controllers/PreviewPurchaseOrderController.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DataMapper\Analytics\LivePreview;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\PurchaseOrderFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewPurchaseOrderRequest;
|
||||
use App\Jobs\Util\PreviewPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Repositories\CreditRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\PurchaseOrderRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
class PreviewPurchaseOrderController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
use MakesInvoiceHtml;
|
||||
use PageNumbering;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template filled with entity variables.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/preview/purchase_order",
|
||||
* operationId="getPreviewPurchaseOrder",
|
||||
* tags={"preview"},
|
||||
* summary="Returns a pdf preview for purchase order",
|
||||
* description="Returns a pdf preview for purchase order.",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The pdf response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
if (request()->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;
|
||||
}
|
||||
}
|
@ -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 = PurchaseOrderInvitation::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))->rawPdf();
|
||||
|
||||
// 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);
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
62
app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php
Normal file
62
app/Http/Requests/Preview/PreviewPurchaseOrderRequest.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Preview;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PreviewPurchaseOrderRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
53
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
53
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
@ -65,6 +65,10 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
|
||||
public $vendor;
|
||||
|
||||
private string $path = '';
|
||||
|
||||
private string $file_path = '';
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -88,6 +92,32 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$pdf = $this->rawPdf();
|
||||
|
||||
if ($pdf) {
|
||||
|
||||
try{
|
||||
|
||||
if(!Storage::disk($this->disk)->exists($this->path))
|
||||
Storage::disk($this->disk)->makeDirectory($this->path, 0775);
|
||||
|
||||
Storage::disk($this->disk)->put($this->file_path, $pdf, 'public');
|
||||
|
||||
}
|
||||
catch(\Exception $e)
|
||||
{
|
||||
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $this->file_path;
|
||||
}
|
||||
|
||||
public function rawPdf()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
@ -109,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');
|
||||
|
||||
@ -191,25 +221,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)
|
||||
|
@ -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;
|
||||
/**
|
||||
@ -373,10 +373,15 @@ class Account extends BaseModel
|
||||
|
||||
public function getDailyEmailLimit()
|
||||
{
|
||||
if($this->is_flagged)
|
||||
return 0;
|
||||
|
||||
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;
|
||||
|
@ -126,6 +126,26 @@ class CompanyPresenter extends EntityPresenter
|
||||
}
|
||||
}
|
||||
|
||||
public function address1()
|
||||
{
|
||||
return $this->entity->settings->address1;
|
||||
}
|
||||
|
||||
public function address2()
|
||||
{
|
||||
return $this->entity->settings->address2;
|
||||
}
|
||||
|
||||
public function qr_iban()
|
||||
{
|
||||
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;
|
||||
|
@ -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()
|
||||
|
@ -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})) {
|
||||
|
@ -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'] = ;
|
||||
|
@ -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)) {
|
||||
|
@ -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",
|
||||
|
1042
composer.lock
generated
1042
composer.lock
generated
File diff suppressed because it is too large
Load Diff
51
database/factories/PurchaseOrderFactory.php
Normal file
51
database/factories/PurchaseOrderFactory.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class PurchaseOrderFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = PurchaseOrder::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'status_id' => 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),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddFlagToAccountsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->boolean('is_flagged')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
@ -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,4 +42,7 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Route::fallback('BaseController@notFoundVendor');
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user