mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
commit
5f7de014b0
@ -54,4 +54,4 @@ PHANTOMJS_SECRET=secret
|
||||
UPDATE_SECRET=secret
|
||||
|
||||
COMPOSER_AUTH='{"github-oauth": {"github.com": "${{ secrets.GITHUB_TOKEN }}"}}'
|
||||
SENTRY_LARAVEL_DSN=https://cc7e8e2c678041689e53e409b7dba236@sentry.invoicing.co/5
|
||||
SENTRY_LARAVEL_DSN=https://32f01ea994744fa08a0f688769cef78a@sentry.invoicing.co/9
|
@ -1 +1 @@
|
||||
5.3.16
|
||||
5.3.17
|
@ -29,6 +29,7 @@ use App\Models\Expense;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
@ -532,7 +533,9 @@ class CreateTestData extends Command
|
||||
$invoice->save();
|
||||
$invoice->service()->createInvitations()->markSent();
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$this->invoice_repo->markSent($invoice);
|
||||
}
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$invoice = $invoice->service()->markPaid()->save();
|
||||
@ -545,6 +548,9 @@ class CreateTestData extends Command
|
||||
'documentable_id' => $invoice->id
|
||||
]);
|
||||
|
||||
RecurringInvoice::factory()->create(['user_id' => $invoice->user->id, 'company_id' => $invoice->company->id, 'client_id' => $invoice->client_id]);
|
||||
|
||||
|
||||
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
|
||||
}
|
||||
|
||||
|
@ -693,13 +693,13 @@ class CompanySettings extends BaseSettings
|
||||
'$invoice.date',
|
||||
'$due_date',
|
||||
'$total',
|
||||
'$outstanding',
|
||||
'$balance',
|
||||
],
|
||||
'statement_payment_columns' => [
|
||||
'$invoice.number',
|
||||
'$payment.date',
|
||||
'$method',
|
||||
'$outstanding',
|
||||
'$statement_amount',
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -123,7 +123,7 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailInvoiceTemplate()
|
||||
{
|
||||
$invoice_message = '<p>'.self::transformText('invoice_message').'</p><div class="center">$view_link</div>';
|
||||
$invoice_message = '<p>'.self::transformText('invoice_message').'</p><div class="center">$view_button</div>';
|
||||
|
||||
return $invoice_message;
|
||||
}
|
||||
@ -135,7 +135,7 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailQuoteTemplate()
|
||||
{
|
||||
$quote_message = '<p>'.self::transformText('quote_message').'</p><div class="center">$view_link</div>';
|
||||
$quote_message = '<p>'.self::transformText('quote_message').'</p><div class="center">$view_button</div>';
|
||||
|
||||
return $quote_message;
|
||||
}
|
||||
@ -147,21 +147,21 @@ class EmailTemplateDefaults
|
||||
|
||||
public static function emailPaymentTemplate()
|
||||
{
|
||||
$payment_message = '<p>'.self::transformText('payment_message').'</p><div class="center">$view_link</div>';
|
||||
$payment_message = '<p>'.self::transformText('payment_message').'</p><div class="center">$view_button</div>';
|
||||
|
||||
return $payment_message;
|
||||
}
|
||||
|
||||
public static function emailCreditTemplate()
|
||||
{
|
||||
$credit_message = '<p>'.self::transformText('credit_message').'</p><div class="center">$view_link</div>';
|
||||
$credit_message = '<p>'.self::transformText('credit_message').'</p><div class="center">$view_button</div>';
|
||||
|
||||
return $credit_message;
|
||||
}
|
||||
|
||||
public static function emailPaymentPartialTemplate()
|
||||
{
|
||||
$payment_message = '<p>'.self::transformText('payment_message').'</p><div class="center">$view_link</div>';
|
||||
$payment_message = '<p>'.self::transformText('payment_message').'</p><div class="center">$view_button</div>';
|
||||
|
||||
return $payment_message;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['id']);
|
||||
unset($quote_array['invitations']);
|
||||
unset($quote_array['terms']);
|
||||
unset($quote_array['public_notes']);
|
||||
// unset($quote_array['public_notes']);
|
||||
unset($quote_array['footer']);
|
||||
unset($quote_array['design_id']);
|
||||
|
||||
|
@ -14,6 +14,7 @@ namespace App\Factory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Helpers;
|
||||
|
||||
class RecurringInvoiceToInvoiceFactory
|
||||
{
|
||||
@ -24,14 +25,15 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->discount = $recurring_invoice->discount;
|
||||
$invoice->is_amount_discount = $recurring_invoice->is_amount_discount;
|
||||
$invoice->po_number = $recurring_invoice->po_number;
|
||||
$invoice->footer = $recurring_invoice->footer;
|
||||
$invoice->terms = $recurring_invoice->terms;
|
||||
$invoice->public_notes = $recurring_invoice->public_notes;
|
||||
$invoice->footer = self::tranformObject($recurring_invoice->footer, $client);
|
||||
$invoice->terms = self::tranformObject($recurring_invoice->terms, $client);
|
||||
$invoice->public_notes = self::tranformObject($recurring_invoice->public_notes, $client);
|
||||
$invoice->private_notes = $recurring_invoice->private_notes;
|
||||
//$invoice->date = now()->format($client->date_format());
|
||||
//$invoice->due_date = $recurring_invoice->calculateDueDate(now());
|
||||
$invoice->is_deleted = $recurring_invoice->is_deleted;
|
||||
$invoice->line_items = $recurring_invoice->line_items;
|
||||
// $invoice->line_items = $recurring_invoice->line_items;
|
||||
$invoice->line_items = self::transformItems($recurring_invoice, $client);
|
||||
$invoice->tax_name1 = $recurring_invoice->tax_name1;
|
||||
$invoice->tax_rate1 = $recurring_invoice->tax_rate1;
|
||||
$invoice->tax_name2 = $recurring_invoice->tax_name2;
|
||||
@ -45,6 +47,7 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->custom_value3 = $recurring_invoice->custom_value3;
|
||||
$invoice->custom_value4 = $recurring_invoice->custom_value4;
|
||||
$invoice->amount = $recurring_invoice->amount;
|
||||
$invoice->uses_inclusive_taxes = $recurring_invoice->uses_inclusive_taxes;
|
||||
// $invoice->balance = $recurring_invoice->balance;
|
||||
$invoice->user_id = $recurring_invoice->user_id;
|
||||
$invoice->assigned_user_id = $recurring_invoice->assigned_user_id;
|
||||
@ -57,4 +60,25 @@ class RecurringInvoiceToInvoiceFactory
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
private static function transformItems($recurring_invoice, $client)
|
||||
{
|
||||
|
||||
$line_items = $recurring_invoice->line_items;
|
||||
|
||||
foreach($line_items as $key => $item){
|
||||
|
||||
if(property_exists($line_items[$key], 'notes'))
|
||||
$line_items[$key]->notes = Helpers::processReservedKeywords($item->notes, $client);
|
||||
|
||||
}
|
||||
|
||||
return $line_items;
|
||||
|
||||
}
|
||||
|
||||
private static function tranformObject($object, $client)
|
||||
{
|
||||
return Helpers::processReservedKeywords($object, $client);
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ class PaymentController extends Controller
|
||||
$invoice_totals = $payable_invoices->sum('amount');
|
||||
$first_invoice = $invoices->first();
|
||||
$credit_totals = $first_invoice->client->getSetting('use_credits_payment') == 'always' ? $first_invoice->client->service()->getCreditBalance() : 0;
|
||||
$starting_invoice_amount = $first_invoice->amount;
|
||||
$starting_invoice_amount = $first_invoice->balance;
|
||||
|
||||
if ($gateway) {
|
||||
$first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save();
|
||||
@ -234,7 +234,7 @@ class PaymentController extends Controller
|
||||
* by adding it as a line item, and then subtract
|
||||
* the starting and finishing amounts of the invoice.
|
||||
*/
|
||||
$fee_totals = $first_invoice->amount - $starting_invoice_amount;
|
||||
$fee_totals = $first_invoice->balance - $starting_invoice_amount;
|
||||
|
||||
if ($gateway) {
|
||||
$tokens = auth()->user()->client->gateway_tokens()
|
||||
|
59
app/Http/Controllers/ClientPortal/StatementController.php
Normal file
59
app/Http/Controllers/ClientPortal/StatementController.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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 App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Statements\ShowStatementRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class StatementController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the statement in the client portal.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return render('statement.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the raw stream of the PDF.
|
||||
*
|
||||
* @param ShowStatementRequest $request
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|JsonResponse|\Illuminate\Http\Response|StreamedResponse
|
||||
*/
|
||||
public function raw(ShowStatementRequest $request)
|
||||
{
|
||||
$pdf = $request->client()->service()->statement(
|
||||
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table'])
|
||||
);
|
||||
|
||||
if ($pdf && $request->query('download')) {
|
||||
return response()->streamDownload(function () use ($pdf) {
|
||||
echo $pdf;
|
||||
}, 'statement.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
if ($pdf) {
|
||||
return response($pdf, 200)->withHeaders([
|
||||
'Content-Type' => 'application/pdf',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Something went wrong. Please check logs.']);
|
||||
}
|
||||
}
|
@ -110,7 +110,9 @@ class ClientStatementController extends BaseController
|
||||
|
||||
public function statement(CreateStatementRequest $request)
|
||||
{
|
||||
$pdf = $this->createStatement($request);
|
||||
$pdf = $request->client()->service()->statement(
|
||||
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table'])
|
||||
);
|
||||
|
||||
if ($pdf) {
|
||||
return response()->streamDownload(function () use ($pdf) {
|
||||
@ -120,88 +122,4 @@ class ClientStatementController extends BaseController
|
||||
|
||||
return response()->json(['message' => 'Something went wrong. Please check logs.']);
|
||||
}
|
||||
|
||||
protected function createStatement(CreateStatementRequest $request): ?string
|
||||
{
|
||||
$invitation = false;
|
||||
|
||||
if ($request->getInvoices()->count() >= 1) {
|
||||
$this->entity = $request->getInvoices()->first();
|
||||
$invitation = $this->entity->invitations->first();
|
||||
}
|
||||
else if ($request->getPayments()->count() >= 1) {
|
||||
$this->entity = $request->getPayments()->first()->invoices->first()->invitations->first();
|
||||
$invitation = $this->entity->invitations->first();
|
||||
}
|
||||
|
||||
$entity_design_id = 1;
|
||||
|
||||
$entity_design_id = $this->entity->design_id
|
||||
? $this->entity->design_id
|
||||
: $this->decodePrimaryKey($this->entity->client->getSetting('invoice_design_id'));
|
||||
|
||||
|
||||
$design = Design::find($entity_design_id);
|
||||
|
||||
if (!$design) {
|
||||
$design = Design::find($entity_design_id);
|
||||
}
|
||||
|
||||
$html = new HtmlEngine($invitation);
|
||||
|
||||
$options = [
|
||||
'start_date' => $request->start_date,
|
||||
'end_date' => $request->end_date,
|
||||
'show_payments_table' => $request->show_payments_table,
|
||||
'show_aging_table' => $request->show_aging_table,
|
||||
];
|
||||
|
||||
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), $options);
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $this->entity->client,
|
||||
'entity' => $this->entity,
|
||||
'pdf_variables' => (array)$this->entity->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
'invoices' => $request->getInvoices(),
|
||||
'payments' => $request->getPayments(),
|
||||
'aging' => $request->getAging(),
|
||||
], \App\Services\PdfMaker\Design::STATEMENT),
|
||||
'variables' => $variables,
|
||||
'options' => [],
|
||||
'process_markdown' => $this->entity->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
$pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
else if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
} else {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
}
|
||||
|
@ -589,14 +589,20 @@ class CreditController extends BaseController
|
||||
public function downloadPdf($invitation_key)
|
||||
{
|
||||
$invitation = $this->credit_repository->getInvitationByKey($invitation_key);
|
||||
// $contact = $invitation->contact;
|
||||
|
||||
$credit = $invitation->credit;
|
||||
|
||||
$file = $credit->service()->getCreditPdf($invitation);
|
||||
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}, basename($file), $headers);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -115,8 +115,8 @@ class EmailController extends BaseController
|
||||
{
|
||||
$entity = $request->input('entity');
|
||||
$entity_obj = $entity::find($request->input('entity_id'));
|
||||
$subject = $request->input('subject');
|
||||
$body = $request->input('body');
|
||||
$subject = $request->has('subject') ? $request->input('subject') : '';
|
||||
$body = $request->has('body') ? $request->input('body') : '';
|
||||
$entity_string = strtolower(class_basename($entity_obj));
|
||||
$template = str_replace("email_template_", "", $request->input('template'));
|
||||
|
||||
|
@ -804,9 +804,15 @@ class InvoiceController extends BaseController
|
||||
|
||||
$file = $invoice->service()->getInvoicePdf($contact);
|
||||
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}, basename($file), $headers);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,7 +212,7 @@ class PreviewController extends BaseController
|
||||
}
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
$entity_obj->load('client');
|
||||
|
||||
App::forgetInstance('translator');
|
||||
|
@ -740,11 +740,16 @@ class QuoteController extends BaseController
|
||||
|
||||
$file = $quote->service()->getQuotePdf($contact);
|
||||
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
if(request()->input('inline') == 'true')
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
|
||||
return response()->streamDownload(function () use($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}, basename($file), $headers);
|
||||
|
||||
|
||||
// return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,6 +29,7 @@ class SubdomainController extends BaseController
|
||||
'preview',
|
||||
'invoiceninja',
|
||||
'cname',
|
||||
'sandbox',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\ClientPortal\Statements;
|
||||
|
||||
use App\Models\Client;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ShowStatementRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'show_payments_table' => $this->has('show_payments_table') ? \boolval($this->show_payments_table) : false,
|
||||
'show_aging_table' => $this->has('show_aging_table') ? \boolval($this->show_aging_table) : false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function client(): Client
|
||||
{
|
||||
return auth('contact')->user()->client;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ class DefaultCompanyRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin()
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
|
@ -39,8 +39,8 @@ class SendEmailRequest extends Request
|
||||
'template' => 'required',
|
||||
'entity' => 'required',
|
||||
'entity_id' => 'required',
|
||||
'subject' => 'required',
|
||||
'body' => 'required',
|
||||
// 'subject' => 'required',
|
||||
// 'body' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,7 @@ namespace App\Http\Requests\Statements;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CreateStatementRequest extends Request
|
||||
{
|
||||
@ -33,11 +29,12 @@ class CreateStatementRequest extends Request
|
||||
return [
|
||||
'start_date' => 'required|date_format:Y-m-d',
|
||||
'end_date' => 'required|date_format:Y-m-d',
|
||||
'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id,
|
||||
'client_id' => 'bail|required|exists:clients,id,company_id,' . auth()->user()->company()->id,
|
||||
'show_payments_table' => 'boolean',
|
||||
'show_aging_table' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
@ -45,125 +42,15 @@ class CreateStatementRequest extends Request
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
|
||||
$this->merge([
|
||||
'show_payments_table' => $this->has('show_payments_table') ? \boolval($this->show_payments_table) : false,
|
||||
'show_aging_table' => $this->has('show_aging_table') ? \boolval($this->show_aging_table) : false,
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* The collection of invoices for the statement.
|
||||
*
|
||||
* @return Invoice[]|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getInvoices()
|
||||
|
||||
public function client(): ?Client
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
// $input['start_date & $input['end_date are available.
|
||||
$client = Client::where('id', $input['client_id'])->first();
|
||||
|
||||
$from = Carbon::parse($input['start_date']);
|
||||
$to = Carbon::parse($input['end_date']);
|
||||
|
||||
return Invoice::where('company_id', auth()->user()->company()->id)
|
||||
->where('client_id', $client->id)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
|
||||
->whereBetween('date',[$from, $to])
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of payments for the statement.
|
||||
*
|
||||
* @return Payment[]|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getPayments()
|
||||
{
|
||||
// $input['start_date & $input['end_date are available.
|
||||
$input = $this->all();
|
||||
|
||||
$client = Client::where('id', $input['client_id'])->first();
|
||||
|
||||
$from = Carbon::parse($input['start_date']);
|
||||
$to = Carbon::parse($input['end_date']);
|
||||
|
||||
return Payment::where('company_id', auth()->user()->company()->id)
|
||||
->where('client_id', $client->id)
|
||||
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->whereBetween('date',[$from, $to])
|
||||
->get();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The array of aging data.
|
||||
*/
|
||||
public function getAging(): array
|
||||
{
|
||||
return [
|
||||
'0-30' => $this->getAgingAmount('30'),
|
||||
'30-60' => $this->getAgingAmount('60'),
|
||||
'60-90' => $this->getAgingAmount('90'),
|
||||
'90-120' => $this->getAgingAmount('120'),
|
||||
'120+' => $this->getAgingAmount('120+'),
|
||||
];
|
||||
}
|
||||
|
||||
private function getAgingAmount($range)
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$ranges = $this->calculateDateRanges($range);
|
||||
|
||||
$from = $ranges[0];
|
||||
$to = $ranges[1];
|
||||
|
||||
$client = Client::where('id', $input['client_id'])->first();
|
||||
|
||||
$amount = Invoice::where('company_id', auth()->user()->company()->id)
|
||||
->where('client_id', $client->id)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereBetween('date',[$from, $to])
|
||||
->sum('balance');
|
||||
|
||||
return Number::formatMoney($amount, $client);
|
||||
}
|
||||
|
||||
private function calculateDateRanges($range)
|
||||
{
|
||||
|
||||
$ranges = [];
|
||||
|
||||
switch ($range) {
|
||||
case '30':
|
||||
$ranges[0] = now();
|
||||
$ranges[1] = now()->subDays(30);
|
||||
return $ranges;
|
||||
break;
|
||||
case '60':
|
||||
$ranges[0] = now()->subDays(30);
|
||||
$ranges[1] = now()->subDays(60);
|
||||
return $ranges;
|
||||
break;
|
||||
case '90':
|
||||
$ranges[0] = now()->subDays(60);
|
||||
$ranges[1] = now()->subDays(90);
|
||||
return $ranges;
|
||||
break;
|
||||
case '120':
|
||||
$ranges[0] = now()->subDays(90);
|
||||
$ranges[1] = now()->subDays(120);
|
||||
return $ranges;
|
||||
break;
|
||||
case '120+':
|
||||
$ranges[0] = now()->subDays(120);
|
||||
$ranges[1] = now()->subYears(40);
|
||||
return $ranges;
|
||||
break;
|
||||
default:
|
||||
$ranges[0] = now()->subDays(0);
|
||||
$ranges[1] = now()->subDays(30);
|
||||
return $ranges;
|
||||
break;
|
||||
}
|
||||
|
||||
return Client::where('id', $this->client_id)->first();
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +119,8 @@ class PortalComposer
|
||||
$data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.tasks.index', 'icon' => 'clock'];
|
||||
}
|
||||
|
||||
$data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -514,7 +514,7 @@ class CompanyExport implements ShouldQueue
|
||||
$nmo->company = $company_reference;
|
||||
$nmo->settings = $this->company->settings;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
sleep(3);
|
||||
|
@ -13,6 +13,7 @@ namespace App\Jobs\Company;
|
||||
|
||||
use App\Exceptions\ImportCompanyFailed;
|
||||
use App\Exceptions\NonExistingMigrationFile;
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
@ -65,14 +66,15 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use JsonMachine\JsonDecoder\ExtJsonDecoder;
|
||||
use JsonMachine\JsonMachine;
|
||||
use ZipArchive;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
use JsonMachine\JsonMachine;
|
||||
use JsonMachine\JsonDecoder\ExtJsonDecoder;
|
||||
|
||||
class CompanyImport implements ShouldQueue
|
||||
{
|
||||
@ -208,7 +210,8 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
$this->preFlightChecks()
|
||||
->purgeCompanyData()
|
||||
->importData();
|
||||
->importData()
|
||||
->postImportCleanup();
|
||||
|
||||
$data = [
|
||||
'errors' => []
|
||||
@ -234,12 +237,32 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
private function postImportCleanup()
|
||||
{
|
||||
//ensure all clients have a contact
|
||||
|
||||
$this->company
|
||||
->clients()
|
||||
->whereDoesntHave('contacts')
|
||||
->cursor()
|
||||
->each(function ($client){
|
||||
|
||||
$new_contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$new_contact->client_id = $client->id;
|
||||
$new_contact->contact_key = Str::random(40);
|
||||
$new_contact->is_primary = true;
|
||||
$new_contact->confirmed = true;
|
||||
$new_contact->email = ' ';
|
||||
$new_contact->save();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function unzipFile()
|
||||
{
|
||||
|
||||
// if(mime_content_type(Storage::path($this->file_location)) == 'text/plain')
|
||||
// return Storage::path($this->file_location);
|
||||
|
||||
$path = TempFile::filePath(Storage::disk(config('filesystems.default'))->get($this->file_location), basename($this->file_location));
|
||||
|
||||
$zip = new ZipArchive();
|
||||
@ -1391,6 +1414,9 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
private function sendImportMail($message){
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new CompanyImportFailure($this->company, $message);
|
||||
|
@ -38,6 +38,7 @@ use App\Repositories\BaseRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\PaymentRepository;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@ -52,6 +53,7 @@ use League\Csv\Reader;
|
||||
use League\Csv\Statement;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class CSVImport implements ShouldQueue {
|
||||
|
||||
@ -132,6 +134,10 @@ class CSVImport implements ShouldQueue {
|
||||
'company' => $this->company,
|
||||
];
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new ImportCompleted($this->company, $data);
|
||||
$nmo->company = $this->company;
|
||||
|
@ -19,13 +19,14 @@ use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesInvoiceValues;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SendRecurring implements ShouldQueue
|
||||
{
|
||||
|
@ -120,7 +120,7 @@ class WebhookHandler implements ShouldQueue
|
||||
SystemLog::CATEGORY_WEBHOOK,
|
||||
SystemLog::EVENT_WEBHOOK_SUCCESS,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->company->clients->first(),
|
||||
$this->resolveClient(),
|
||||
$this->company
|
||||
);
|
||||
|
||||
@ -137,7 +137,7 @@ class WebhookHandler implements ShouldQueue
|
||||
SystemLog::CATEGORY_WEBHOOK,
|
||||
SystemLog::EVENT_WEBHOOK_RESPONSE,
|
||||
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||
$this->company->clients->first(),
|
||||
$this->resolveClient(),
|
||||
$this->company,
|
||||
);
|
||||
|
||||
@ -145,6 +145,15 @@ class WebhookHandler implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function resolveClient()
|
||||
{
|
||||
if($this->entity->client()->exists()){
|
||||
return $this->entity->client;
|
||||
}
|
||||
|
||||
return $this->company->clients->first();
|
||||
}
|
||||
|
||||
public function failed($exception)
|
||||
{
|
||||
nlog(print_r($exception->getMessage(), 1));
|
||||
|
@ -135,6 +135,7 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
{
|
||||
$data = [];
|
||||
|
||||
|
||||
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
|
||||
$data['$to'] = ['value' => '', 'label' => ctrans('texts.to')];
|
||||
$data['$number'] = ['value' => $this->payment->number ?: ' ', 'label' => ctrans('texts.payment_number')];
|
||||
@ -227,6 +228,7 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
$data['$company4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company4', $this->settings->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')];
|
||||
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->payment->getLink().'">'.ctrans('texts.view_payment').'</a>', 'label' => ctrans('texts.view_payment')];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$paymentLink'] = &$data['$view_link'];
|
||||
$data['$portalButton'] = ['value' => "<a href='{$this->payment->getPortalLink()}'>".ctrans('texts.login')."</a>", 'label' =>''];
|
||||
$data['$portal_url'] = &$data['$portalButton'];
|
||||
|
@ -90,11 +90,17 @@ class Gateway extends StaticModel
|
||||
case 7:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
|
||||
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true],
|
||||
];
|
||||
case 15:
|
||||
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
||||
break;
|
||||
case 20:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
|
||||
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe
|
||||
case 39:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
|
||||
break;
|
||||
|
@ -42,6 +42,7 @@ class PaymentType extends StaticModel
|
||||
const SEPA = 29;
|
||||
const GOCARDLESS = 30;
|
||||
const CRYPTO = 31;
|
||||
const MOLLIE_BANK_TRANSFER = 34;
|
||||
|
||||
public static function parseCardType($cardName)
|
||||
{
|
||||
|
@ -55,7 +55,10 @@ class CompanyGatewayObserver
|
||||
public function restored(CompanyGateway $company_gateway)
|
||||
{
|
||||
//When we restore the gateway, bring back the tokens!
|
||||
ClientGatewayToken::where('company_gateway_id', $company_gateway->id)->withTrashed()->get()->restore();
|
||||
ClientGatewayToken::where('company_gateway_id', $company_gateway->id)
|
||||
->withTrashed()->cursor()->each(function ($cgt){
|
||||
$cgt->restore();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ class InvoiceObserver
|
||||
public function created(Invoice $invoice)
|
||||
{
|
||||
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company->id)
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company_id)
|
||||
->where('event_id', Webhook::EVENT_CREATE_INVOICE)
|
||||
->exists();
|
||||
|
||||
@ -47,7 +47,7 @@ class InvoiceObserver
|
||||
*/
|
||||
public function updated(Invoice $invoice)
|
||||
{
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company->id)
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company_id)
|
||||
->where('event_id', Webhook::EVENT_UPDATE_INVOICE)
|
||||
->exists();
|
||||
|
||||
@ -65,7 +65,7 @@ class InvoiceObserver
|
||||
*/
|
||||
public function deleted(Invoice $invoice)
|
||||
{
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company->id)
|
||||
$subscriptions = Webhook::where('company_id', $invoice->company_id)
|
||||
->where('event_id', Webhook::EVENT_DELETE_INVOICE)
|
||||
->exists();
|
||||
|
||||
|
@ -29,6 +29,9 @@ class PaymentObserver
|
||||
->where('event_id', Webhook::EVENT_CREATE_PAYMENT)
|
||||
->exists();
|
||||
|
||||
if($payment->invoices()->exists())
|
||||
$payment->load('invoices');
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch(Webhook::EVENT_CREATE_PAYMENT, $payment, $payment->company);
|
||||
}
|
||||
|
@ -151,6 +151,11 @@ class CreditCard
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) {
|
||||
/** https://developer.paypal.com/braintree/docs/reference/request/transaction/sale/php#full-example */
|
||||
$data['options']['verificationMerchantAccountId'] = $this->braintree->company_gateway->getConfigField('merchantAccountId');
|
||||
}
|
||||
|
||||
$response = $this->braintree->gateway->paymentMethod()->create($data);
|
||||
|
||||
if ($response->success) {
|
||||
|
@ -95,7 +95,7 @@ class Token
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
$error = $response_status['message']
|
||||
$error = $response_status['message'];
|
||||
$error_code = $response->ResponseMessage;
|
||||
|
||||
$data = [
|
||||
|
212
app/PaymentDrivers/Mollie/BankTransfer.php
Normal file
212
app/PaymentDrivers/Mollie/BankTransfer.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?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 App\PaymentDrivers\Mollie;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\MolliePaymentDriver;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Illuminate\View\View;
|
||||
use Mollie\Api\Resources\Payment as ResourcesPayment;
|
||||
|
||||
class BankTransfer implements MethodInterface
|
||||
{
|
||||
protected MolliePaymentDriver $mollie;
|
||||
|
||||
public function __construct(MolliePaymentDriver $mollie)
|
||||
{
|
||||
$this->mollie = $mollie;
|
||||
|
||||
$this->mollie->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the authorization page for bank transfer.
|
||||
*
|
||||
* @param array $data
|
||||
* @return View
|
||||
*/
|
||||
public function authorizeView(array $data): View
|
||||
{
|
||||
return render('gateways.mollie.bank_transfer.authorize', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization for bank transfer.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the payment page for bank transfer.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::BANK_TRANSFER)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->create([
|
||||
'method' => 'banktransfer',
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee),
|
||||
],
|
||||
'description' => \sprintf('Invoices: %s', collect($data['invoices'])->pluck('invoice_number')),
|
||||
'redirectUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->mollie->company_gateway->id,
|
||||
'payment_hash' => $this->mollie->payment_hash->hash,
|
||||
'payment_method_id' => GatewayType::BANK_TRANSFER,
|
||||
]),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect(
|
||||
$payment->getCheckoutUrl()
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful payment.
|
||||
*
|
||||
* @param Exception $e
|
||||
* @throws PaymentFailed
|
||||
* @return void
|
||||
*/
|
||||
public function processUnsuccessfulPayment(\Exception $e): void
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->mollie->client,
|
||||
$e->getMessage(),
|
||||
$this->mollie->client->company,
|
||||
$this->mollie->payment_hash->data->amount_with_fee
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payments for the bank transfer.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
if (! \property_exists($this->mollie->payment_hash->data, 'payment_id')) {
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed('Whoops, something went wrong. Missing required [payment_id] parameter. Please contact administrator. Reference hash: ' . $this->mollie->payment_hash->hash)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$payment = $this->mollie->gateway->payments->get(
|
||||
$this->mollie->payment_hash->data->payment_id
|
||||
);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'open') {
|
||||
return $this->processOpenPayment($payment);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment(
|
||||
new PaymentFailed(ctrans('texts.status_voided'))
|
||||
);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException | \Exception $exception) {
|
||||
return $this->processUnsuccessfulPayment($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the successful payment for bank transfer.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @param string $status
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment, $status = 'paid'): RedirectResponse
|
||||
{
|
||||
$data = [
|
||||
'gateway_type_id' => GatewayType::BANK_TRANSFER,
|
||||
'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total,
|
||||
'payment_type' => PaymentType::MOLLIE_BANK_TRANSFER,
|
||||
'transaction_reference' => $payment->id,
|
||||
];
|
||||
|
||||
$payment_record = $this->mollie->createPayment(
|
||||
$data,
|
||||
$status === 'paid' ? Payment::STATUS_COMPLETED : Payment::STATUS_PENDING
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'open' payment status for bank transfer.
|
||||
*
|
||||
* @param ResourcesPayment $payment
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function processOpenPayment(\Mollie\Api\Resources\Payment $payment): RedirectResponse
|
||||
{
|
||||
return $this->processSuccessfulPayment($payment, 'open');
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Mollie\BankTransfer;
|
||||
use App\PaymentDrivers\Mollie\CreditCard;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@ -64,6 +65,7 @@ class MolliePaymentDriver extends BaseDriver
|
||||
*/
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||
GatewayType::BANK_TRANSFER => BankTransfer::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_MOLLIE;
|
||||
@ -84,6 +86,7 @@ class MolliePaymentDriver extends BaseDriver
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
@ -77,31 +77,36 @@ class Token
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)),0);
|
||||
|
||||
$header =[
|
||||
'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'timestamp' => now()->format('c'),
|
||||
'version' => 'v1',
|
||||
];
|
||||
// $header =[
|
||||
// 'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
// 'timestamp' => now()->format('c'),
|
||||
// 'version' => 'v1',
|
||||
// ];
|
||||
|
||||
nlog($header);
|
||||
// $body = [
|
||||
// 'amount' => $amount,
|
||||
// 'item_name' => 'purchase',
|
||||
// 'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
// // 'm_payment_id' => $payment_hash->hash,
|
||||
// ];
|
||||
|
||||
$body = [
|
||||
'amount' => $amount,
|
||||
'item_name' => 'purchase',
|
||||
'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'),
|
||||
'm_payment_id' => $payment_hash->hash,
|
||||
'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'),
|
||||
];
|
||||
// $header['signature'] = md5( $this->generate_parameter_string(array_merge($header, $body), false) );
|
||||
|
||||
$header['signature'] = $this->genSig(array_merge($header, $body));
|
||||
// $result = $this->send($header, $body, $cgt->token);
|
||||
$api = new \PayFast\PayFastApi(
|
||||
[
|
||||
'merchantId' => $this->payfast->company_gateway->getConfigField('merchantId'),
|
||||
'passPhrase' => $this->payfast->company_gateway->getConfigField('passPhrase'),
|
||||
'testMode' => $this->payfast->company_gateway->getConfigField('testMode')
|
||||
]
|
||||
);
|
||||
|
||||
nlog($header['signature']);
|
||||
nlog($header['timestamp']);
|
||||
nlog($this->payfast->company_gateway->getConfigField('merchantId'));
|
||||
$adhocArray = $api
|
||||
->subscriptions
|
||||
->adhoc($cgt->token, ['amount' => $amount, 'item_name' => 'purchase']);
|
||||
|
||||
$result = $this->send($header, $body, $cgt->token);
|
||||
|
||||
nlog($result);
|
||||
nlog($adhocArray);
|
||||
|
||||
// /*Refactor and push to BaseDriver*/
|
||||
// if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') {
|
||||
@ -143,6 +148,44 @@ class Token
|
||||
// }
|
||||
}
|
||||
|
||||
protected function generate_parameter_string( $api_data, $sort_data_before_merge = true, $skip_empty_values = true ) {
|
||||
|
||||
// if sorting is required the passphrase should be added in before sort.
|
||||
if ( ! empty( $this->payfast->company_gateway->getConfigField('passPhrase') ) && $sort_data_before_merge )
|
||||
$api_data['passphrase'] = $this->payfast->company_gateway->getConfigField('passPhrase');
|
||||
|
||||
if ( $sort_data_before_merge ) {
|
||||
ksort( $api_data );
|
||||
}
|
||||
|
||||
// concatenate the array key value pairs.
|
||||
$parameter_string = '';
|
||||
foreach ( $api_data as $key => $val ) {
|
||||
|
||||
if ( $skip_empty_values && empty( $val ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'signature' !== $key ) {
|
||||
$val = urlencode( $val );
|
||||
$parameter_string .= "$key=$val&";
|
||||
}
|
||||
}
|
||||
// when not sorting passphrase should be added to the end before md5
|
||||
if ( $sort_data_before_merge ) {
|
||||
$parameter_string = rtrim( $parameter_string, '&' );
|
||||
} elseif ( ! empty( $this->pass_phrase ) ) {
|
||||
$parameter_string .= 'passphrase=' . urlencode( $this->payfast->company_gateway->getConfigField('passPhrase') );
|
||||
} else {
|
||||
$parameter_string = rtrim( $parameter_string, '&' );
|
||||
}
|
||||
|
||||
nlog($parameter_string);
|
||||
|
||||
return $parameter_string;
|
||||
|
||||
}
|
||||
|
||||
private function genSig($data)
|
||||
{
|
||||
$fields = [];
|
||||
|
@ -77,7 +77,7 @@ class PayFastPaymentDriver extends BaseDriver
|
||||
]
|
||||
);
|
||||
|
||||
} catch(Exception $e) {
|
||||
} catch(\Exception $e) {
|
||||
|
||||
echo '##PAYFAST## There was an exception: '.$e->getMessage();
|
||||
|
||||
@ -161,6 +161,8 @@ class PayFastPaymentDriver extends BaseDriver
|
||||
}
|
||||
}
|
||||
|
||||
nlog(http_build_query($fields));
|
||||
|
||||
return md5(http_build_query($fields));
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class CreditCard
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => $this->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
||||
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
||||
];
|
||||
|
||||
$payment_intent_data['setup_future_usage'] = 'off_session';
|
||||
|
@ -43,7 +43,7 @@ class SEPA
|
||||
'customer' => $customer->id,
|
||||
], $this->stripe_driver->stripe_connect_auth);
|
||||
|
||||
$client_secret = $setup_intent->client_secret
|
||||
$client_secret = $setup_intent->client_secret;
|
||||
// Pass the client secret to the client
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ use App\PaymentDrivers\StripePaymentDriver;
|
||||
class SOFORT
|
||||
{
|
||||
/** @var StripePaymentDriver */
|
||||
public $stripe;
|
||||
public StripePaymentDriver $stripe;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
{
|
||||
@ -45,6 +45,17 @@ class SOFORT
|
||||
$data['customer'] = $this->stripe->findOrCreateCustomer()->id;
|
||||
$data['country'] = $this->stripe->client->country->iso_3166_2;
|
||||
|
||||
$intent = \Stripe\PaymentIntent::create([
|
||||
'amount' => $data['stripe_amount'],
|
||||
'currency' => 'eur',
|
||||
'payment_method_types' => ['sofort'],
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
||||
|
||||
]);
|
||||
|
||||
$data['pi_client_secret'] = $intent->client_secret;
|
||||
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
@ -66,23 +77,23 @@ class SOFORT
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
if ($request->redirect_status == 'succeeded') {
|
||||
return $this->processSuccessfulPayment($request->source);
|
||||
return $this->processSuccessfulPayment($request->payment_intent);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment();
|
||||
}
|
||||
|
||||
public function processSuccessfulPayment(string $source)
|
||||
public function processSuccessfulPayment(string $payment_intent)
|
||||
{
|
||||
/* @todo: https://github.com/invoiceninja/invoiceninja/pull/3789/files#r436175798 */
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$data = [
|
||||
'payment_method' => $this->stripe->payment_hash->data->source,
|
||||
'payment_method' => $payment_intent,
|
||||
'payment_type' => PaymentType::SOFORT,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => $source,
|
||||
'transaction_reference' => $payment_intent,
|
||||
'gateway_type_id' => GatewayType::SOFORT,
|
||||
];
|
||||
|
||||
|
@ -423,7 +423,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
// Allow app to catch up with webhook request.
|
||||
sleep(2);
|
||||
|
||||
if ($request->type === 'charge.succeeded') {
|
||||
if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
|
||||
|
||||
foreach ($request->data as $transaction) {
|
||||
$payment = Payment::query()
|
||||
@ -634,4 +634,14 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
return response()->json(['message' => 'success'], 200);
|
||||
}
|
||||
|
||||
public function decodeUnicodeString($string)
|
||||
{
|
||||
return html_entity_decode($string, ENT_QUOTES, 'UTF-8');
|
||||
// return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $this->decode_encoded_utf8($string));
|
||||
}
|
||||
|
||||
public function decode_encoded_utf8($string){
|
||||
return preg_replace_callback('#\\\\u([0-9a-f]{4})#ism', function($matches) { return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); }, $string);
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,16 @@ class ClientService
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the client statement.
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function statement(array $options = [])
|
||||
{
|
||||
return (new Statement($this->client, $options))->run();
|
||||
}
|
||||
|
||||
public function save() :Client
|
||||
{
|
||||
$this->client->save();
|
||||
|
353
app/Services/Client/Statement.php
Normal file
353
app/Services/Client/Statement.php
Normal file
@ -0,0 +1,353 @@
|
||||
<?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 App\Services\Client;
|
||||
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Statement
|
||||
{
|
||||
use PdfMakerTrait;
|
||||
|
||||
protected Client $client;
|
||||
|
||||
/**
|
||||
* @var Invoice|Payment|null
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
protected array $options;
|
||||
|
||||
protected bool $rollback = false;
|
||||
|
||||
public function __construct(Client $client, array $options)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function run(): ?string
|
||||
{
|
||||
$this
|
||||
->setupOptions()
|
||||
->setupEntity();
|
||||
|
||||
$html = new HtmlEngine($this->getInvitation());
|
||||
|
||||
if ($this->getDesign()->is_custom) {
|
||||
$this->options['custom_partials'] = \json_decode(\json_encode($this->getDesign()->design), true);
|
||||
|
||||
$template = new PdfMakerDesign(\App\Services\PdfMaker\Design::CUSTOM, $this->options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($this->getDesign()->name), $this->options);
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $this->entity->client,
|
||||
'entity' => $this->entity,
|
||||
'pdf_variables' => (array)$this->entity->company->settings->pdf_variables,
|
||||
'$product' => $this->getDesign()->design->product,
|
||||
'variables' => $variables,
|
||||
'invoices' => $this->getInvoices(),
|
||||
'payments' => $this->getPayments(),
|
||||
'aging' => $this->getAging(),
|
||||
], \App\Services\PdfMaker\Design::STATEMENT),
|
||||
'variables' => $variables,
|
||||
'options' => [],
|
||||
'process_markdown' => $this->entity->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
$pdf = null;
|
||||
|
||||
try {
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
$pdf = (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
} elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
} else {
|
||||
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog(print_r($e->getMessage(), 1));
|
||||
}
|
||||
|
||||
if ($this->rollback) {
|
||||
DB::rollBack();
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup correct entity instance.
|
||||
*
|
||||
* @return Statement
|
||||
*/
|
||||
protected function setupEntity(): self
|
||||
{
|
||||
if (count($this->getInvoices()) >= 1) {
|
||||
$this->entity = $this->getInvoices()->first();
|
||||
}
|
||||
|
||||
if (\is_null($this->entity)) {
|
||||
DB::beginTransaction();
|
||||
$this->rollback = true;
|
||||
|
||||
$invoice = InvoiceFactory::create($this->client->company->id, $this->client->user->id);
|
||||
$invoice->client_id = $this->client->id;
|
||||
$invoice->line_items = $this->buildLineItems();
|
||||
$invoice->save();
|
||||
|
||||
$invitation = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
|
||||
$invitation->invoice_id = $invoice->id;
|
||||
$invitation->client_contact_id = $this->client->contacts->first()->id;
|
||||
$invitation->save();
|
||||
|
||||
$this->entity = $invoice;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildLineItems($count = 1)
|
||||
{
|
||||
$line_items = [];
|
||||
|
||||
for ($x = 0; $x < $count; $x++) {
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
//$item->cost = 10;
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$item->tax_name1 = 'GST';
|
||||
$item->tax_rate1 = 10.00;
|
||||
}
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$item->tax_name1 = 'VAT';
|
||||
$item->tax_rate1 = 17.50;
|
||||
}
|
||||
|
||||
if (rand(0, 1)) {
|
||||
$item->tax_name1 = 'Sales Tax';
|
||||
$item->tax_rate1 = 5;
|
||||
}
|
||||
|
||||
$product = Product::all()->random();
|
||||
|
||||
$item->cost = (float) $product->cost;
|
||||
$item->product_key = $product->product_key;
|
||||
$item->notes = $product->notes;
|
||||
$item->custom_value1 = $product->custom_value1;
|
||||
$item->custom_value2 = $product->custom_value2;
|
||||
$item->custom_value3 = $product->custom_value3;
|
||||
$item->custom_value4 = $product->custom_value4;
|
||||
|
||||
$line_items[] = $item;
|
||||
}
|
||||
|
||||
return $line_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup & prepare options.
|
||||
*
|
||||
* @return Statement
|
||||
*/
|
||||
protected function setupOptions(): self
|
||||
{
|
||||
if (! \array_key_exists('start_date', $this->options)) {
|
||||
$this->options['start_date'] = now()->startOfYear()->format('Y-m-d');
|
||||
}
|
||||
|
||||
if (! \array_key_exists('end_date', $this->options)) {
|
||||
$this->options['end_date'] = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
if (! \array_key_exists('show_payments_table', $this->options)) {
|
||||
$this->options['show_payments_table'] = false;
|
||||
}
|
||||
|
||||
if (! \array_key_exists('show_aging_table', $this->options)) {
|
||||
$this->options['show_aging_table'] = false;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of invoices for the statement.
|
||||
*
|
||||
* @return Invoice[]|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected function getInvoices(): Collection
|
||||
{
|
||||
return Invoice::where('company_id', $this->client->company->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
|
||||
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
|
||||
->orderBy('number', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* The collection of payments for the statement.
|
||||
*
|
||||
* @return Payment[]|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected function getPayments(): Collection
|
||||
{
|
||||
return Payment::where('company_id', $this->client->company->id)
|
||||
->where('client_id', $this->client->id)
|
||||
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
|
||||
->orderBy('number', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get correct invitation ID.
|
||||
*
|
||||
* @return int|bool
|
||||
*/
|
||||
protected function getInvitation()
|
||||
{
|
||||
if ($this->entity instanceof Invoice || $this->entity instanceof Payment) {
|
||||
return $this->entity->invitations->first();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of aging data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAging(): array
|
||||
{
|
||||
return [
|
||||
'0-30' => $this->getAgingAmount('30'),
|
||||
'30-60' => $this->getAgingAmount('60'),
|
||||
'60-90' => $this->getAgingAmount('90'),
|
||||
'90-120' => $this->getAgingAmount('120'),
|
||||
'120+' => $this->getAgingAmount('120+'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate aging amount.
|
||||
*
|
||||
* @param mixed $range
|
||||
* @return string
|
||||
*/
|
||||
private function getAgingAmount($range)
|
||||
{
|
||||
$ranges = $this->calculateDateRanges($range);
|
||||
|
||||
$from = $ranges[0];
|
||||
$to = $ranges[1];
|
||||
|
||||
$client = Client::where('id', $this->client->id)->first();
|
||||
|
||||
$amount = Invoice::where('company_id', $this->client->company->id)
|
||||
->where('client_id', $client->id)
|
||||
->where('company_id', $this->client->company_id)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', 0)
|
||||
->whereBetween('date', [$to, $from])
|
||||
->sum('balance');
|
||||
|
||||
return Number::formatMoney($amount, $client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate date ranges for aging.
|
||||
*
|
||||
* @param mixed $range
|
||||
* @return array
|
||||
*/
|
||||
private function calculateDateRanges($range)
|
||||
{
|
||||
$ranges = [];
|
||||
|
||||
switch ($range) {
|
||||
case '30':
|
||||
$ranges[0] = now()->startOfDay();
|
||||
$ranges[1] = now()->startOfDay()->subDays(30);
|
||||
return $ranges;
|
||||
case '60':
|
||||
$ranges[0] = now()->startOfDay()->subDays(30);
|
||||
$ranges[1] = now()->startOfDay()->subDays(60);
|
||||
return $ranges;
|
||||
case '90':
|
||||
$ranges[0] = now()->startOfDay()->subDays(60);
|
||||
$ranges[1] = now()->startOfDay()->subDays(90);
|
||||
return $ranges;
|
||||
case '120':
|
||||
$ranges[0] = now()->startOfDay()->subDays(90);
|
||||
$ranges[1] = now()->startOfDay()->subDays(120);
|
||||
return $ranges;
|
||||
case '120+':
|
||||
$ranges[0] = now()->startOfDay()->subDays(120);
|
||||
$ranges[1] = now()->startOfDay()->subYears(40);
|
||||
return $ranges;
|
||||
default:
|
||||
$ranges[0] = now()->startOfDay()->subDays(0);
|
||||
$ranges[1] = now()->subDays(30);
|
||||
return $ranges;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get correct design for statement.
|
||||
*
|
||||
* @return \App\Models\Design
|
||||
*/
|
||||
protected function getDesign(): Design
|
||||
{
|
||||
$id = 1;
|
||||
|
||||
if (!empty($this->client->getSetting('entity_design_id'))) {
|
||||
$id = (int) $this->client->getSetting('entity_design_id');
|
||||
}
|
||||
|
||||
return Design::find($id);
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ class AutoBillInvoice extends AbstractService
|
||||
/* Determine $amount */
|
||||
if ($this->invoice->partial > 0) {
|
||||
$is_partial = true;
|
||||
$invoice_total = $this->invoice->amount;
|
||||
$invoice_total = $this->invoice->balance;
|
||||
$amount = $this->invoice->partial;
|
||||
} elseif ($this->invoice->balance > 0) {
|
||||
$amount = $this->invoice->balance;
|
||||
@ -94,14 +94,18 @@ class AutoBillInvoice extends AbstractService
|
||||
/* $gateway fee */
|
||||
$this->invoice = $this->invoice->service()->addGatewayFee($gateway_token->gateway, $gateway_token->gateway_type_id, $amount)->save();
|
||||
|
||||
//change from $this->invoice->amount to $this->invoice->balance
|
||||
if($is_partial)
|
||||
$fee = $this->invoice->amount - $invoice_total;
|
||||
$fee = $this->invoice->balance - $invoice_total;
|
||||
else
|
||||
$fee = $this->invoice->amount - $amount;
|
||||
$fee = $this->invoice->balance - $amount;
|
||||
|
||||
if($fee > $amount)
|
||||
$fee = 0;
|
||||
|
||||
/* Build payment hash */
|
||||
$payment_hash = PaymentHash::create([
|
||||
'hash' => Str::random(128),
|
||||
'hash' => Str::random(64),
|
||||
'data' => ['invoices' => [['invoice_id' => $this->invoice->hashed_id, 'amount' => $amount]]],
|
||||
'fee_total' => $fee,
|
||||
'fee_invoice_id' => $this->invoice->id,
|
||||
|
@ -28,6 +28,8 @@ class LedgerService
|
||||
{
|
||||
$balance = 0;
|
||||
|
||||
\DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$company_ledger = $this->ledger();
|
||||
|
||||
if ($company_ledger) {
|
||||
@ -44,6 +46,8 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
\DB::connection(config('database.default'))->commit();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -51,6 +55,8 @@ class LedgerService
|
||||
{
|
||||
$balance = 0;
|
||||
|
||||
\DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
/* Get the last record for the client and set the current balance*/
|
||||
$company_ledger = $this->ledger();
|
||||
|
||||
@ -68,6 +74,8 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
\DB::connection(config('database.default'))->commit();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -75,6 +83,8 @@ class LedgerService
|
||||
{
|
||||
$balance = 0;
|
||||
|
||||
\DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
$company_ledger = $this->ledger();
|
||||
|
||||
if ($company_ledger) {
|
||||
@ -91,6 +101,8 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
\DB::connection(config('database.default'))->commit();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -99,6 +111,7 @@ class LedgerService
|
||||
return CompanyLedger::whereClientId($this->entity->client_id)
|
||||
->whereCompanyId($this->entity->company_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
}
|
||||
|
||||
@ -109,3 +122,11 @@ class LedgerService
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
\DB::connection(config('database.default'))->commit();
|
||||
|
||||
|
||||
*/
|
||||
|
@ -232,7 +232,7 @@ class Design extends BaseDesign
|
||||
]],
|
||||
['element' => 'tr', 'properties' => [], 'elements' => [
|
||||
['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'],
|
||||
['element' => 'th', 'properties' => [], 'content' => '$balance_due'],
|
||||
['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->entity->client)],
|
||||
]],
|
||||
];
|
||||
}
|
||||
@ -241,6 +241,10 @@ class Design extends BaseDesign
|
||||
|
||||
if ($this->entity instanceof Quote) {
|
||||
$variables = $this->context['pdf_variables']['quote_details'];
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$variables[] = '$quote.balance_due';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->entity instanceof Credit) {
|
||||
@ -379,10 +383,10 @@ class Design extends BaseDesign
|
||||
return [];
|
||||
}
|
||||
|
||||
$outstanding = $this->invoices->sum('amount');
|
||||
$outstanding = $this->invoices->sum('balance');
|
||||
|
||||
return [
|
||||
['element' => 'p', 'content' => '$outstanding_label: $outstanding'],
|
||||
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->entity->client)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -424,7 +428,11 @@ class Design extends BaseDesign
|
||||
|
||||
public function statementPaymentTableTotals(): array
|
||||
{
|
||||
if ($this->type !== self::STATEMENT || !$this->payments->first()) {
|
||||
if (is_null($this->payments) || !$this->payments->first() || $this->type !== self::STATEMENT) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (\array_key_exists('show_payments_table', $this->options) && $this->options['show_payments_table'] === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -627,6 +635,10 @@ class Design extends BaseDesign
|
||||
if (in_array('$outstanding', $variables)) {
|
||||
$variables = \array_diff($variables, ['$outstanding']);
|
||||
}
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$variables[] = '$partial_due';
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['discount'] as $property) {
|
||||
|
@ -40,8 +40,11 @@ trait DesignHelpers
|
||||
|
||||
if (isset($this->context['invoices'])) {
|
||||
$this->invoices = $this->context['invoices'];
|
||||
|
||||
if (\count($this->invoices) >= 1) {
|
||||
$this->entity = $this->invoices->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->context['payments'])) {
|
||||
$this->payments = $this->context['payments'];
|
||||
@ -68,6 +71,8 @@ trait DesignHelpers
|
||||
|
||||
$variables[$property] = $value;
|
||||
}
|
||||
|
||||
$this->context['pdf_variables'] = $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -269,12 +274,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some variables don't map 1:1 to table columns. This gives us support for such cases.
|
||||
$aliases = [
|
||||
'$quote.balance_due' => 'partial',
|
||||
];
|
||||
|
||||
try {
|
||||
$_variable = explode('.', $variable)[1];
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Company settings seems to be broken. Missing $entity.variable type.');
|
||||
}
|
||||
|
||||
if (\in_array($variable, \array_keys($aliases))) {
|
||||
$_variable = $aliases[$variable];
|
||||
}
|
||||
|
||||
if (is_null($this->entity->{$_variable})) {
|
||||
return true;
|
||||
}
|
||||
|
@ -82,6 +82,12 @@ class PdfMaker
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final method to get compiled HTML.
|
||||
*
|
||||
* @param bool $final @deprecated
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCompiledHTML($final = false)
|
||||
{
|
||||
$html = $this->document->saveHTML();
|
||||
|
@ -97,6 +97,12 @@ class RecurringService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function fillDefaults()
|
||||
{
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->recurring_entity->save();
|
||||
|
@ -64,7 +64,8 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
|
||||
public function transform(RecurringInvoice $invoice)
|
||||
{
|
||||
return [
|
||||
|
||||
$data = [
|
||||
'id' => $this->encodePrimaryKey($invoice->id),
|
||||
'user_id' => $this->encodePrimaryKey($invoice->user_id),
|
||||
'project_id' => $this->encodePrimaryKey($invoice->project_id),
|
||||
@ -120,13 +121,19 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
'entity_type' => 'recurringInvoice',
|
||||
'frequency_id' => (string) $invoice->frequency_id,
|
||||
'remaining_cycles' => (int) $invoice->remaining_cycles,
|
||||
//'recurring_dates' => (array) $invoice->recurringDates(),
|
||||
'recurring_dates' => [],
|
||||
'auto_bill' => (string) $invoice->auto_bill,
|
||||
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
|
||||
'due_date_days' => (string) $invoice->due_date_days ?: '',
|
||||
'paid_to_date' => (float) $invoice->paid_to_date,
|
||||
'subscription_id' => (string)$this->encodePrimaryKey($invoice->subscription_id),
|
||||
|
||||
];
|
||||
|
||||
|
||||
if(request()->has('show_dates') && request()->query('show_dates') == 'true')
|
||||
$data['recurring_dates'] = (array) $invoice->recurringDates();
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ namespace App\Utils;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use stdClass;
|
||||
|
||||
class Helpers
|
||||
@ -97,4 +99,169 @@ class Helpers
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process reserved keywords on PDF.
|
||||
*
|
||||
* @param string $value
|
||||
* @param Client $client
|
||||
* @return null|string
|
||||
*/
|
||||
public static function processReservedKeywords(?string $value, Client $client): ?string
|
||||
{
|
||||
if(!$value)
|
||||
return '';
|
||||
|
||||
Carbon::setLocale($client->locale());
|
||||
|
||||
$replacements = [
|
||||
'literal' => [
|
||||
':MONTH' => Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F'),
|
||||
':YEAR' => now()->year,
|
||||
':QUARTER' => 'Q' . now()->quarter,
|
||||
':WEEK_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subDays(7)->translatedFormat($client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->translatedFormat($client->date_format())
|
||||
),
|
||||
':WEEK_AHEAD' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->addDays(7)->translatedFormat($client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(14)->translatedFormat($client->date_format())
|
||||
),
|
||||
':WEEK' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(7)->translatedFormat($client->date_format())
|
||||
),
|
||||
],
|
||||
'raw' => [
|
||||
':MONTH' => now()->month,
|
||||
':YEAR' => now()->year,
|
||||
':QUARTER' => now()->quarter,
|
||||
],
|
||||
'ranges' => [
|
||||
'MONTHYEAR' => Carbon::createFromDate(now()->year, now()->month),
|
||||
],
|
||||
'ranges_raw' => [
|
||||
'MONTH' => now()->month,
|
||||
'YEAR' => now()->year,
|
||||
],
|
||||
];
|
||||
|
||||
// First case, with ranges.
|
||||
preg_match_all('/\[(.*?)]/', $value, $ranges);
|
||||
|
||||
$matches = array_shift($ranges);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (!Str::contains($match, '|')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Str::contains($match, '|')) {
|
||||
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
|
||||
|
||||
$left = substr($parts[0], 1); // 'MONTH'
|
||||
$right = substr($parts[1], 0, -1); // MONTH+2
|
||||
|
||||
// If left side is not part of replacements, skip.
|
||||
if (!array_key_exists($left, $replacements['ranges'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
$_right = '';
|
||||
|
||||
// If right side doesn't have any calculations, replace with raw ranges keyword.
|
||||
if (!Str::contains($right, ['-', '+', '/', '*'])) {
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
// If right side contains one of math operations, calculate.
|
||||
if (Str::contains($right, ['+'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0]; // + -
|
||||
|
||||
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
|
||||
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
$replacement = sprintf('%s to %s', $_left, $_right);
|
||||
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', preg_quote($match)), $replacement, $value, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Second case with more common calculations.
|
||||
preg_match_all('/:([^:\s]+)/', $value, $common);
|
||||
|
||||
$matches = array_shift($common);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$matches = collect($replacements['literal'])->filter(function ($value, $key) use ($match) {
|
||||
return Str::startsWith($match, $key);
|
||||
});
|
||||
|
||||
if ($matches->count() === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Str::contains($match, ['-', '+', '/', '*'])) {
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', $matches->keys()->first()), $replacements['literal'][$matches->keys()->first()], $value, 1
|
||||
);
|
||||
}
|
||||
|
||||
if (Str::contains($match, ['-', '+', '/', '*'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $match, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0];
|
||||
|
||||
$_value = explode($_operation, $match); // [:MONTH, 4]
|
||||
|
||||
$raw = strtr($matches->keys()->first(), $replacements['raw']); // :MONTH => 1
|
||||
|
||||
$number = $res = preg_replace("/[^0-9]/", '', $_value[1]); // :MONTH+1. || :MONTH+2! => 1 || 2
|
||||
|
||||
$target = "/{$matches->keys()->first()}\\{$_operation}{$number}/"; // /:$KEYWORD\\$OPERATION$VALUE => /:MONTH\\+1
|
||||
|
||||
$output = (int) $raw + (int)$_value[1];
|
||||
|
||||
if ($operation == '+') {
|
||||
$output = (int) $raw + (int)$_value[1]; // 1 (:MONTH) + 4
|
||||
}
|
||||
|
||||
if ($_operation == '-') {
|
||||
$output = (int)$raw - (int)$_value[1]; // 1 (:MONTH) - 4
|
||||
}
|
||||
|
||||
if ($_operation == '/' && (int)$_value[1] != 0) {
|
||||
$output = (int)$raw / (int)$_value[1]; // 1 (:MONTH) / 4
|
||||
}
|
||||
|
||||
if ($_operation == '*') {
|
||||
$output = (int)$raw * (int)$_value[1]; // 1 (:MONTH) * 4
|
||||
}
|
||||
|
||||
if ($matches->keys()->first() == ':MONTH') {
|
||||
$output = \Carbon\Carbon::create()->month($output)->translatedFormat('F');
|
||||
}
|
||||
|
||||
$value = preg_replace(
|
||||
$target, $output, $value, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ use App\Models\InvoiceInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\transformTranslations;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
@ -96,6 +98,11 @@ class HtmlEngine
|
||||
exit;
|
||||
}
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->settings));
|
||||
|
||||
$data = [];
|
||||
$data['$global_margin'] = ['value' => '6.35mm', 'label' => ''];
|
||||
$data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
@ -127,11 +134,12 @@ class HtmlEngine
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->client) ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
$data['$view_button'] = &$data['$view_link'];
|
||||
$data['$paymentButton'] = &$data['$view_link'];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
@ -231,12 +239,15 @@ class HtmlEngine
|
||||
$data['$user.name'] = ['value' => $this->entity->user->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$user.first_name'] = ['value' => $this->entity->user->first_name, 'label' => ctrans('texts.first_name')];
|
||||
$data['$user.last_name'] = ['value' => $this->entity->user->last_name, 'label' => ctrans('texts.last_name')];
|
||||
$data['$created_by_user'] = &$data['$user.name'];
|
||||
$data['$assigned_to_user'] = ['value' => $this->entity->assigned_user ? $this->entity->assigned_user->present()->name() : '', 'label' => ctrans('texts.name')];
|
||||
|
||||
$data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
|
||||
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$invoice.public_notes'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->public_notes), $this->client) ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$notes'] = &$data['$public_notes'];
|
||||
@ -434,7 +445,7 @@ class HtmlEngine
|
||||
$data['$description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
|
||||
//$data['$entity_footer'] = ['value' => $this->client->getSetting("{$this->entity_string}_footer"), 'label' => ''];
|
||||
$data['$entity_footer'] = ['value' => $this->entity->footer, 'label' => ''];
|
||||
$data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer), $this->client), 'label' => ''];
|
||||
|
||||
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
|
||||
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
|
||||
@ -453,6 +464,9 @@ class HtmlEngine
|
||||
$data['$payment.date'] = ['value' => ' ', 'label' => ctrans('texts.payment_date')];
|
||||
$data['$method'] = ['value' => ' ', 'label' => ctrans('texts.method')];
|
||||
|
||||
$data['$statement_amount'] = ['value' => '', 'label' => ctrans('texts.amount')];
|
||||
$data['$statement'] = ['value' => '', 'label' => ctrans('texts.statement')];
|
||||
|
||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||
|
||||
|
@ -64,6 +64,9 @@ class Number
|
||||
// remove everything except numbers and dot "."
|
||||
$s = preg_replace("/[^0-9\.]/", '', $s);
|
||||
|
||||
if($s < 1)
|
||||
return (float)$s;
|
||||
|
||||
// remove all seperators from first part and keep the end
|
||||
$s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3);
|
||||
|
||||
|
@ -294,8 +294,8 @@ trait MakesInvoiceValues
|
||||
$data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item;
|
||||
$data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service;
|
||||
|
||||
$data[$key][$table_type.'.notes'] = $this->processReservedKeywords($item->notes);
|
||||
$data[$key][$table_type.'.description'] = $this->processReservedKeywords($item->notes);
|
||||
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->client);
|
||||
|
||||
/* need to test here as this is new - 18/09/2021*/
|
||||
if(!array_key_exists($table_type.'.gross_line_total', $data[$key]))
|
||||
@ -350,168 +350,6 @@ trait MakesInvoiceValues
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process reserved words like :MONTH :YEAR :QUARTER
|
||||
* as well as their operations.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string|null
|
||||
*/
|
||||
private function processReservedKeywords(string $value): ?string
|
||||
{
|
||||
Carbon::setLocale($this->client->locale());
|
||||
|
||||
$replacements = [
|
||||
'literal' => [
|
||||
':MONTH' => Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F'),
|
||||
':YEAR' => now()->year,
|
||||
':QUARTER' => 'Q' . now()->quarter,
|
||||
':WEEK_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subDays(7)->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->translatedFormat($this->client->date_format())
|
||||
),
|
||||
':WEEK_AHEAD' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->addDays(7)->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(14)->translatedFormat($this->client->date_format())
|
||||
),
|
||||
':WEEK' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(7)->translatedFormat($this->client->date_format())
|
||||
),
|
||||
],
|
||||
'raw' => [
|
||||
':MONTH' => now()->month,
|
||||
':YEAR' => now()->year,
|
||||
':QUARTER' => now()->quarter,
|
||||
],
|
||||
'ranges' => [
|
||||
'MONTHYEAR' => Carbon::createFromDate(now()->year, now()->month),
|
||||
],
|
||||
'ranges_raw' => [
|
||||
'MONTH' => now()->month,
|
||||
'YEAR' => now()->year,
|
||||
],
|
||||
];
|
||||
|
||||
// First case, with ranges.
|
||||
preg_match_all('/\[(.*?)]/', $value, $ranges);
|
||||
|
||||
$matches = array_shift($ranges);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (!Str::contains($match, '|')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Str::contains($match, '|')) {
|
||||
$parts = explode('|', $match); // [ '[MONTH', 'MONTH+2]' ]
|
||||
|
||||
$left = substr($parts[0], 1); // 'MONTH'
|
||||
$right = substr($parts[1], 0, -1); // MONTH+2
|
||||
|
||||
// If left side is not part of replacements, skip.
|
||||
if (!array_key_exists($left, $replacements['ranges'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$_left = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
$_right = '';
|
||||
|
||||
// If right side doesn't have any calculations, replace with raw ranges keyword.
|
||||
if (!Str::contains($right, ['-', '+', '/', '*'])) {
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
// If right side contains one of math operations, calculate.
|
||||
if (Str::contains($right, ['+'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $right, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0]; // + -
|
||||
|
||||
$_value = explode($_operation, $right); // [MONTHYEAR, 4]
|
||||
|
||||
$_right = Carbon::createFromDate(now()->year, now()->month)->addMonths($_value[1])->translatedFormat('F Y');
|
||||
}
|
||||
|
||||
$replacement = sprintf('%s to %s', $_left, $_right);
|
||||
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', preg_quote($match)), $replacement, $value, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Second case with more common calculations.
|
||||
preg_match_all('/:([^:\s]+)/', $value, $common);
|
||||
|
||||
$matches = array_shift($common);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$matches = collect($replacements['literal'])->filter(function ($value, $key) use ($match) {
|
||||
return Str::startsWith($match, $key);
|
||||
});
|
||||
|
||||
if ($matches->count() === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Str::contains($match, ['-', '+', '/', '*'])) {
|
||||
$value = preg_replace(
|
||||
sprintf('/%s/', $matches->keys()->first()), $replacements['literal'][$matches->keys()->first()], $value, 1
|
||||
);
|
||||
}
|
||||
|
||||
if (Str::contains($match, ['-', '+', '/', '*'])) {
|
||||
$operation = preg_match_all('/(?!^-)[+*\/-](\s?-)?/', $match, $_matches);
|
||||
|
||||
$_operation = array_shift($_matches)[0];
|
||||
|
||||
$_value = explode($_operation, $match); // [:MONTH, 4]
|
||||
|
||||
$raw = strtr($matches->keys()->first(), $replacements['raw']); // :MONTH => 1
|
||||
|
||||
$number = $res = preg_replace("/[^0-9]/", '', $_value[1]); // :MONTH+1. || :MONTH+2! => 1 || 2
|
||||
|
||||
$target = "/{$matches->keys()->first()}\\{$_operation}{$number}/"; // /:$KEYWORD\\$OPERATION$VALUE => /:MONTH\\+1
|
||||
|
||||
$output = (int) $raw + (int)$_value[1];
|
||||
|
||||
if ($operation == '+') {
|
||||
$output = (int) $raw + (int)$_value[1]; // 1 (:MONTH) + 4
|
||||
}
|
||||
|
||||
if ($_operation == '-') {
|
||||
$output = (int)$raw - (int)$_value[1]; // 1 (:MONTH) - 4
|
||||
}
|
||||
|
||||
if ($_operation == '/' && (int)$_value[1] != 0) {
|
||||
$output = (int)$raw / (int)$_value[1]; // 1 (:MONTH) / 4
|
||||
}
|
||||
|
||||
if ($_operation == '*') {
|
||||
$output = (int)$raw * (int)$_value[1]; // 1 (:MONTH) * 4
|
||||
}
|
||||
|
||||
if ($matches->keys()->first() == ':MONTH') {
|
||||
$output = \Carbon\Carbon::create()->month($output)->translatedFormat('F');
|
||||
}
|
||||
|
||||
$value = preg_replace(
|
||||
$target, $output, $value, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the way we are compiling the blade template we
|
||||
* have no ability to iterate, so in the case
|
||||
|
@ -64,7 +64,7 @@
|
||||
"league/flysystem-cached-adapter": "^1.1",
|
||||
"league/fractal": "^0.17.0",
|
||||
"league/omnipay": "^3.1",
|
||||
"livewire/livewire": "^2.4",
|
||||
"livewire/livewire": "^2.6",
|
||||
"maennchen/zipstream-php": "^1.2",
|
||||
"mollie/mollie-api-php": "^2.36",
|
||||
"nwidart/laravel-modules": "^8.0",
|
||||
|
115
composer.lock
generated
115
composer.lock
generated
@ -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": "54d84c4ecc41d25ece12b91b181e3431",
|
||||
"content-hash": "96908a391244cbc96eefbb130bd7bed9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "apimatic/jsonmapper",
|
||||
@ -323,16 +323,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.194.1",
|
||||
"version": "3.194.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "67bdee05acef9e8ad60098090996690b49babd09"
|
||||
"reference": "1f0a0cec5721b6346c968533fba9b44e462fc728"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/67bdee05acef9e8ad60098090996690b49babd09",
|
||||
"reference": "67bdee05acef9e8ad60098090996690b49babd09",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1f0a0cec5721b6346c968533fba9b44e462fc728",
|
||||
"reference": "1f0a0cec5721b6346c968533fba9b44e462fc728",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -408,9 +408,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.194.1"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.194.2"
|
||||
},
|
||||
"time": "2021-09-17T18:15:42+00:00"
|
||||
"time": "2021-09-21T18:14:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@ -2652,16 +2652,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/apiclient",
|
||||
"version": "v2.10.1",
|
||||
"version": "v2.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-api-php-client.git",
|
||||
"reference": "11871e94006ce7a419bb6124d51b6f9ace3f679b"
|
||||
"reference": "7db9eb40c8ba887e81c0fe84f2888a967396cdfb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/11871e94006ce7a419bb6124d51b6f9ace3f679b",
|
||||
"reference": "11871e94006ce7a419bb6124d51b6f9ace3f679b",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/7db9eb40c8ba887e81c0fe84f2888a967396cdfb",
|
||||
"reference": "7db9eb40c8ba887e81c0fe84f2888a967396cdfb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2669,8 +2669,8 @@
|
||||
"google/apiclient-services": "~0.200",
|
||||
"google/auth": "^1.10",
|
||||
"guzzlehttp/guzzle": "~5.3.3||~6.0||~7.0",
|
||||
"guzzlehttp/psr7": "^1.2",
|
||||
"monolog/monolog": "^1.17|^2.0",
|
||||
"guzzlehttp/psr7": "^1.7||^2.0.0",
|
||||
"monolog/monolog": "^1.17||^2.0",
|
||||
"php": "^5.6|^7.0|^8.0",
|
||||
"phpseclib/phpseclib": "~2.0||^3.0.2"
|
||||
},
|
||||
@ -2679,10 +2679,12 @@
|
||||
"composer/composer": "^1.10.22",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7",
|
||||
"phpcompatibility/php-compatibility": "^9.2",
|
||||
"phpunit/phpunit": "^5.7||^8.5.13",
|
||||
"phpspec/prophecy-phpunit": "^1.1||^2.0",
|
||||
"phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "~2.3",
|
||||
"symfony/css-selector": "~2.1",
|
||||
"symfony/dom-crawler": "~2.1"
|
||||
"symfony/dom-crawler": "~2.1",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)"
|
||||
@ -2715,22 +2717,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/google-api-php-client/issues",
|
||||
"source": "https://github.com/googleapis/google-api-php-client/tree/v2.10.1"
|
||||
"source": "https://github.com/googleapis/google-api-php-client/tree/v2.11.0"
|
||||
},
|
||||
"time": "2021-06-25T14:25:44+00:00"
|
||||
"time": "2021-09-20T21:15:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/apiclient-services",
|
||||
"version": "v0.212.0",
|
||||
"version": "v0.213.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/google-api-php-client-services.git",
|
||||
"reference": "2c4bd512502ad9cdfec8ea711ea1592c79d345e5"
|
||||
"reference": "260311821505438eb9208b068da0d849b8ea9baa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/2c4bd512502ad9cdfec8ea711ea1592c79d345e5",
|
||||
"reference": "2c4bd512502ad9cdfec8ea711ea1592c79d345e5",
|
||||
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/260311821505438eb9208b068da0d849b8ea9baa",
|
||||
"reference": "260311821505438eb9208b068da0d849b8ea9baa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2759,9 +2761,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.212.0"
|
||||
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.213.0"
|
||||
},
|
||||
"time": "2021-09-12T11:18:27+00:00"
|
||||
"time": "2021-09-19T11:18:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "google/auth",
|
||||
@ -5586,20 +5588,20 @@
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v3.2.3",
|
||||
"version": "v3.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822"
|
||||
"reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
|
||||
"reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/9cd80396ca58d7969ab44fc7afcf03624dfa526e",
|
||||
"reference": "9cd80396ca58d7969ab44fc7afcf03624dfa526e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2 <8.1"
|
||||
"php": ">=7.2 <8.2"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/di": "<3.0.6"
|
||||
@ -5665,22 +5667,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v3.2.3"
|
||||
"source": "https://github.com/nette/utils/tree/v3.2.5"
|
||||
},
|
||||
"time": "2021-08-16T21:05:00+00:00"
|
||||
"time": "2021-09-20T10:50:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.12.0",
|
||||
"version": "v4.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
|
||||
"reference": "50953a2691a922aa1769461637869a0a2faa3f53"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53",
|
||||
"reference": "50953a2691a922aa1769461637869a0a2faa3f53",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5721,9 +5723,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0"
|
||||
},
|
||||
"time": "2021-07-21T10:44:31+00:00"
|
||||
"time": "2021-09-20T12:20:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nwidart/laravel-modules",
|
||||
@ -12740,16 +12742,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.14.1",
|
||||
"version": "2.14.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filp/whoops.git",
|
||||
"reference": "15ead64e9828f0fc90932114429c4f7923570cb1"
|
||||
"reference": "89584ce67dd32307f1063cc43846674f4679feda"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/15ead64e9828f0fc90932114429c4f7923570cb1",
|
||||
"reference": "15ead64e9828f0fc90932114429c4f7923570cb1",
|
||||
"url": "https://api.github.com/repos/filp/whoops/zipball/89584ce67dd32307f1063cc43846674f4679feda",
|
||||
"reference": "89584ce67dd32307f1063cc43846674f4679feda",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -12799,7 +12801,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/filp/whoops/issues",
|
||||
"source": "https://github.com/filp/whoops/tree/2.14.1"
|
||||
"source": "https://github.com/filp/whoops/tree/2.14.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -12807,7 +12809,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-29T12:00:00+00:00"
|
||||
"time": "2021-09-19T12:00:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
@ -13290,33 +13292,32 @@
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
"version": "v5.9.0",
|
||||
"version": "v5.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nunomaduro/collision.git",
|
||||
"reference": "63456f5c3e8c4bc52bd573e5c85674d64d84fd43"
|
||||
"reference": "3004cfa49c022183395eabc6d0e5207dfe498d00"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/63456f5c3e8c4bc52bd573e5c85674d64d84fd43",
|
||||
"reference": "63456f5c3e8c4bc52bd573e5c85674d64d84fd43",
|
||||
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/3004cfa49c022183395eabc6d0e5207dfe498d00",
|
||||
"reference": "3004cfa49c022183395eabc6d0e5207dfe498d00",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"facade/ignition-contracts": "^1.0",
|
||||
"filp/whoops": "^2.7.2",
|
||||
"filp/whoops": "^2.14.3",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"symfony/console": "^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^6.1",
|
||||
"fideloper/proxy": "^4.4.1",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"fruitcake/laravel-cors": "^2.0.3",
|
||||
"laravel/framework": "^8.0 || ^9.0",
|
||||
"laravel/framework": "8.x-dev",
|
||||
"nunomaduro/larastan": "^0.6.2",
|
||||
"nunomaduro/mock-final-classes": "^1.0",
|
||||
"orchestra/testbench": "^6.0 || ^7.0",
|
||||
"orchestra/testbench": "^6.0",
|
||||
"phpstan/phpstan": "^0.12.64",
|
||||
"phpunit/phpunit": "^9.5.0"
|
||||
},
|
||||
@ -13374,7 +13375,7 @@
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-26T15:32:09+00:00"
|
||||
"time": "2021-09-20T15:06:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "openlss/lib-array2xml",
|
||||
@ -15274,16 +15275,16 @@
|
||||
},
|
||||
{
|
||||
"name": "swagger-api/swagger-ui",
|
||||
"version": "v3.52.2",
|
||||
"version": "v3.52.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swagger-api/swagger-ui.git",
|
||||
"reference": "e5611d72ff6b4affb373fa8859cc5feb6981f367"
|
||||
"reference": "aa9f2e6733327b5f042f2529db76558d9c09bed2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/e5611d72ff6b4affb373fa8859cc5feb6981f367",
|
||||
"reference": "e5611d72ff6b4affb373fa8859cc5feb6981f367",
|
||||
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/aa9f2e6733327b5f042f2529db76558d9c09bed2",
|
||||
"reference": "aa9f2e6733327b5f042f2529db76558d9c09bed2",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -15329,9 +15330,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/swagger-api/swagger-ui/issues",
|
||||
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.2"
|
||||
"source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.3"
|
||||
},
|
||||
"time": "2021-09-13T12:46:28+00:00"
|
||||
"time": "2021-09-20T12:12:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.16',
|
||||
'app_tag' => '5.3.16',
|
||||
'app_version' => '5.3.17',
|
||||
'app_tag' => '5.3.17',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -37,7 +37,7 @@ return [
|
||||
'trusted_proxies' => env('TRUSTED_PROXIES', false),
|
||||
'is_docker' => env('IS_DOCKER', false),
|
||||
'local_download' => env('LOCAL_DOWNLOAD', false),
|
||||
'sentry_dsn' => env('SENTRY_LARAVEL_DSN', 'https://9b4e15e575214354a7d666489783904a@sentry.invoicing.co/6'),
|
||||
'sentry_dsn' => env('SENTRY_LARAVEL_DSN', 'https://32f01ea994744fa08a0f688769cef78a@sentry.invoicing.co/9'),
|
||||
'environment' => env('NINJA_ENVIRONMENT', 'selfhost'), // 'hosted', 'development', 'selfhost', 'reseller'
|
||||
'preconfigured_install' => env('PRECONFIGURED_INSTALL',false),
|
||||
'update_secret' => env('UPDATE_SECRET', ''),
|
||||
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SetSquareTestModeBoolean extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//Fixes a state where the deleted_at timestamp is 000
|
||||
|
||||
if(!Ninja::isHosted())
|
||||
{
|
||||
|
||||
Invoice::withTrashed()->where('deleted_at', '0000-00-00 00:00:00.000000')->update(['deleted_at' => null]);
|
||||
Quote::withTrashed()->where('deleted_at', '0000-00-00 00:00:00.000000')->update(['deleted_at' => null]);
|
||||
Credit::withTrashed()->where('deleted_at', '0000-00-00 00:00:00.000000')->update(['deleted_at' => null]);
|
||||
|
||||
}
|
||||
|
||||
// fixes a bool cast to string back to bool
|
||||
if($gateway = Gateway::find(57))
|
||||
{
|
||||
$fields = json_decode($gateway->fields);
|
||||
$fields->testMode = false;
|
||||
|
||||
$gateway->fields = json_encode($fields);
|
||||
$gateway->save();
|
||||
}
|
||||
}
|
||||
}
|
60
database/migrations/2021_09_23_100629_add_currencies.php
Normal file
60
database/migrations/2021_09_23_100629_add_currencies.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AddCurrencies extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Model::unguard();
|
||||
|
||||
|
||||
$currencies = [
|
||||
['id' => 105, 'name' => 'Gambia Dalasi', 'code' => 'GMD', 'symbol' => 'D', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 106, 'name' => 'Paraguayan Guarani', 'code' => 'PYG', 'symbol' => '₲', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 107, 'name' => 'Malawi Kwacha','code' => 'MWK', 'symbol' => 'MK', 'precision' => '2','thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 108, 'name' => 'Zimbabwean Dollar', 'code' => 'ZWL', 'symbol' => 'Z$', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 109, 'name' => 'Cambodian Riel', 'code' => 'KHR', 'symbol' => '៛', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['id' => 110, 'name' => 'Vanuatu Vatu','code' => 'VUV', 'symbol' => 'VT', 'precision' => '0','thousand_separator' => ',','decimal_separator' => '.'],
|
||||
|
||||
];
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
$record = Currency::whereCode($currency['code'])->first();
|
||||
if ($record) {
|
||||
$record->name = $currency['name'];
|
||||
$record->symbol = $currency['symbol'];
|
||||
$record->precision = $currency['precision'];
|
||||
$record->thousand_separator = $currency['thousand_separator'];
|
||||
$record->decimal_separator = $currency['decimal_separator'];
|
||||
if (isset($currency['swap_currency_symbol'])) {
|
||||
$record->swap_currency_symbol = $currency['swap_currency_symbol'];
|
||||
}
|
||||
$record->save();
|
||||
} else {
|
||||
Currency::create($currency);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddMollieBankTransferToPaymentTypes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$type = new PaymentType();
|
||||
|
||||
$type->id = 34;
|
||||
$type->name = 'Mollie Bank Transfer';
|
||||
$type->gateway_type_id = GatewayType::BANK_TRANSFER;
|
||||
|
||||
$type->save();
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['id' => 53, 'name' => 'PagSeguro', 'provider' => 'PagSeguro', 'key' => 'ef498756b54db63c143af0ec433da803', 'fields' => '{"email":"","token":"","sandbox":false}'],
|
||||
['id' => 54, 'name' => 'PAYMILL', 'provider' => 'Paymill', 'key' => 'ca52f618a39367a4c944098ebf977e1c', 'fields' => '{"apiKey":""}'],
|
||||
['id' => 55, 'name' => 'Custom', 'provider' => 'Custom', 'is_offsite' => true, 'sort_order' => 21, 'key' => '54faab2ab6e3223dbe848b1686490baa', 'fields' => '{"name":"","text":""}'],
|
||||
['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","testMode":"false"}'],
|
||||
['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","testMode":false}'],
|
||||
];
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
@ -97,7 +97,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
|
||||
Gateway::query()->update(['visible' => 0]);
|
||||
|
||||
Gateway::whereIn('id', [1,7,15,20,39,46,55,50,57])->update(['visible' => 1]);
|
||||
Gateway::whereIn('id', [1,7,11,15,20,39,46,55,50,57])->update(['visible' => 1]);
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
Gateway::whereIn('id', [20])->update(['visible' => 0]);
|
||||
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"/": "fd698ff0c4251992cffae325bd810e94",
|
||||
"/": "d54a2d8f5df9a52b1936136260e327b3",
|
||||
"assets/NOTICES": "9eb7e2eb2888ea5bae5f536720db37cd",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||
@ -34,7 +34,7 @@ const RESOURCES = {
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"main.dart.js": "8fc88d4b5a3256d5f9ff5c0f1bdeb92b"
|
||||
"main.dart.js": "d0bf26e6fb4226713654b97cdfef6a53"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
2
public/js/clients/payments/stripe-sofort.js
vendored
2
public/js/clients/payments/stripe-sofort.js
vendored
@ -1,2 +1,2 @@
|
||||
/*! For license information please see stripe-sofort.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=6)}({6:function(e,t,n){e.exports=n("RFiP")},RFiP:function(e,t){var n,r,o,u;function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var i=null!==(n=null===(r=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===r?void 0:r.content)&&void 0!==n?n:"",a=null!==(o=null===(u=document.querySelector('meta[name="stripe-account-id"]'))||void 0===u?void 0:u.content)&&void 0!==o?o:"";new function e(t,n){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),c(this,"setupStripe",(function(){return r.stripe=Stripe(r.key),r.stripeConnect&&(r.stripe.stripeAccount=a),r})),c(this,"handle",(function(){var e={type:"sofort",customer:document.querySelector('meta[name="customer"]').content,amount:document.querySelector('meta[name="amount"]').content,currency:"eur",redirect:{return_url:document.querySelector('meta[name="return-url"]').content},sofort:{country:document.querySelector('meta[name="country"]').content}};document.getElementById("pay-now").addEventListener("click",(function(t){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),r.stripe.createSource(e).then((function(e){if(e.hasOwnProperty("source"))return window.location=e.source.redirect.url;document.getElementById("pay-now").disabled=!1,document.querySelector("#pay-now > svg").classList.add("hidden"),document.querySelector("#pay-now > span").classList.remove("hidden"),this.errors.textContent="",this.errors.textContent=e.error.message,this.errors.hidden=!1,document.getElementById("pay-now").disabled=!1}))}))})),this.key=t,this.errors=document.getElementById("errors"),this.stripeConnect=n}(i,a).setupStripe().handle()}});
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=6)}({6:function(e,t,n){e.exports=n("RFiP")},RFiP:function(e,t){var n,r,o,u;function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var c=null!==(n=null===(r=document.querySelector('meta[name="stripe-publishable-key"]'))||void 0===r?void 0:r.content)&&void 0!==n?n:"",l=null!==(o=null===(u=document.querySelector('meta[name="stripe-account-id"]'))||void 0===u?void 0:u.content)&&void 0!==o?o:"";new function e(t,n){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),i(this,"setupStripe",(function(){return r.stripe=Stripe(r.key),r.stripeConnect&&(r.stripe.stripeAccount=l),r})),i(this,"handle",(function(){document.getElementById("pay-now").addEventListener("click",(function(e){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),r.stripe.confirmSofortPayment(document.querySelector("meta[name=pi-client-secret").content,{payment_method:{sofort:{country:document.querySelector('meta[name="country"]').content}},return_url:document.querySelector('meta[name="return-url"]').content})}))})),this.key=t,this.errors=document.getElementById("errors"),this.stripeConnect=n}(c,l).setupStripe().handle()}});
|
2
public/js/clients/shared/pdf.js
vendored
2
public/js/clients/shared/pdf.js
vendored
File diff suppressed because one or more lines are too long
2
public/js/clients/statements/view.js
vendored
Normal file
2
public/js/clients/statements/view.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see view.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=26)}({26:function(e,t,n){e.exports=n("LcZE")},LcZE:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.url=new URL(document.querySelector("meta[name=pdf-url]").content),this.startDate="",this.endDate="",this.showPaymentsTable=!1,this.showAgingTable=!1}var t,r,a;return t=e,(r=[{key:"bindEventListeners",value:function(){var e=this;["#date-from","#date-to","#show-payments-table","#show-aging-table"].forEach((function(t){document.querySelector(t).addEventListener("change",(function(t){return e.handleValueChange(t)}))}))}},{key:"handleValueChange",value:function(e){"checkbox"===e.target.type?this[e.target.dataset.field]=e.target.checked:this[e.target.dataset.field]=e.target.value,this.updatePdf()}},{key:"composedUrl",get:function(){return this.url.search="",this.startDate.length>0&&this.url.searchParams.append("start_date",this.startDate),this.endDate.length>0&&this.url.searchParams.append("end_date",this.endDate),this.url.searchParams.append("show_payments_table",+this.showPaymentsTable),this.url.searchParams.append("show_aging_table",+this.showAgingTable),this.url.href}},{key:"updatePdf",value:function(){document.querySelector("meta[name=pdf-url]").content=this.composedUrl;var e=document.querySelector("#pdf-iframe");e&&(e.src=this.composedUrl),document.querySelector("meta[name=pdf-url]").dispatchEvent(new Event("change"))}},{key:"handle",value:function(){var e=this;this.bindEventListeners(),document.querySelector("#pdf-download").addEventListener("click",(function(){var t=new URL(e.composedUrl);t.searchParams.append("download",1),window.location.href=t.href}))}}])&&n(t.prototype,r),a&&n(t,a),e}())).handle()}});
|
9
public/js/clients/statements/view.js.LICENSE.txt
Normal file
9
public/js/clients/statements/view.js.LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
149609
public/main.dart.js
vendored
149609
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
151923
public/main.foss.dart.js
vendored
151923
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
154622
public/main.last.dart.js
vendored
154622
public/main.last.dart.js
vendored
File diff suppressed because one or more lines are too long
151087
public/main.next.dart.js
vendored
151087
public/main.next.dart.js
vendored
File diff suppressed because one or more lines are too long
4674
public/main.profile.dart.js
vendored
4674
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
151339
public/main.wasm.dart.js
vendored
151339
public/main.wasm.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=46021f35ee55aca9ff20",
|
||||
"/css/app.css": "/css/app.css?id=08bae341ed680d6ba544",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
@ -19,12 +19,13 @@
|
||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=a30464874dee84678344",
|
||||
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=df63bd9e9837a420fd5d",
|
||||
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=231571942310348aa616",
|
||||
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=f51400e03c5fdb6cdabe",
|
||||
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=1b8f9325aa6e8595e7fa",
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=85bcae0a646882e56b12",
|
||||
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
|
||||
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fc3055d6a099f523ea98",
|
||||
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=2a99d83305ba87bfa6cc",
|
||||
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=ca3ec4cea0de824f3a36",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=8d454e7090f119552a6c",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
|
||||
}
|
||||
|
4
public/vendor/livewire/livewire.js
vendored
4
public/vendor/livewire/livewire.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/livewire.js.map
vendored
2
public/vendor/livewire/livewire.js.map
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
2
public/vendor/livewire/manifest.json
vendored
@ -1 +1 @@
|
||||
{"/livewire.js":"/livewire.js?id=b09cb328e689f1bb8d77"}
|
||||
{"/livewire.js":"/livewire.js?id=21fa1dd78491a49255cd"}
|
42
resources/js/clients/payments/stripe-sofort.js
vendored
42
resources/js/clients/payments/stripe-sofort.js
vendored
@ -25,40 +25,26 @@ class ProcessSOFORT {
|
||||
};
|
||||
|
||||
handle = () => {
|
||||
let data = {
|
||||
type: 'sofort',
|
||||
customer: document.querySelector('meta[name="customer"]').content,
|
||||
amount: document.querySelector('meta[name="amount"]').content,
|
||||
currency: 'eur',
|
||||
redirect: {
|
||||
return_url: document.querySelector('meta[name="return-url"]')
|
||||
.content,
|
||||
},
|
||||
sofort: {
|
||||
country: document.querySelector('meta[name="country"]').content,
|
||||
},
|
||||
};
|
||||
|
||||
document.getElementById('pay-now').addEventListener('click', (e) => {
|
||||
document.getElementById('pay-now').disabled = true;
|
||||
document.querySelector('#pay-now > svg').classList.remove('hidden');
|
||||
document.querySelector('#pay-now > span').classList.add('hidden');
|
||||
|
||||
this.stripe.createSource(data).then(function(result) {
|
||||
if (result.hasOwnProperty('source')) {
|
||||
return (window.location = result.source.redirect.url);
|
||||
this.stripe.confirmSofortPayment(
|
||||
document.querySelector('meta[name=pi-client-secret').content,
|
||||
{
|
||||
payment_method: {
|
||||
sofort: {
|
||||
country: document.querySelector(
|
||||
'meta[name="country"]'
|
||||
).content,
|
||||
},
|
||||
},
|
||||
return_url: document.querySelector(
|
||||
'meta[name="return-url"]'
|
||||
).content,
|
||||
}
|
||||
|
||||
document.getElementById('pay-now').disabled = false;
|
||||
document.querySelector('#pay-now > svg').classList.add('hidden');
|
||||
document.querySelector('#pay-now > span').classList.remove('hidden');
|
||||
|
||||
this.errors.textContent = '';
|
||||
this.errors.textContent = result.error.message;
|
||||
this.errors.hidden = false;
|
||||
|
||||
document.getElementById('pay-now').disabled = false;
|
||||
});
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
9
resources/js/clients/shared/pdf.js
vendored
9
resources/js/clients/shared/pdf.js
vendored
@ -80,6 +80,15 @@ class PDF {
|
||||
.getElementById('zoom-out')
|
||||
.addEventListener('click', () => this.handleZoomChange());
|
||||
|
||||
document
|
||||
.querySelector('meta[name=pdf-url]')
|
||||
.addEventListener('change', () => {
|
||||
this.canvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.url = document.querySelector("meta[name='pdf-url']").content;
|
||||
|
||||
this.handle();
|
||||
})
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
95
resources/js/clients/statements/view.js
vendored
Normal file
95
resources/js/clients/statements/view.js
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
class Statement {
|
||||
constructor() {
|
||||
this.url = new URL(
|
||||
document.querySelector('meta[name=pdf-url]').content
|
||||
);
|
||||
this.startDate = '';
|
||||
this.endDate = '';
|
||||
this.showPaymentsTable = false;
|
||||
this.showAgingTable = false;
|
||||
}
|
||||
|
||||
bindEventListeners() {
|
||||
[
|
||||
'#date-from',
|
||||
'#date-to',
|
||||
'#show-payments-table',
|
||||
'#show-aging-table',
|
||||
].forEach((selector) => {
|
||||
document
|
||||
.querySelector(selector)
|
||||
.addEventListener('change', (event) =>
|
||||
this.handleValueChange(event)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
handleValueChange(event) {
|
||||
if (event.target.type === 'checkbox') {
|
||||
this[event.target.dataset.field] = event.target.checked;
|
||||
} else {
|
||||
this[event.target.dataset.field] = event.target.value;
|
||||
}
|
||||
|
||||
this.updatePdf();
|
||||
}
|
||||
|
||||
get composedUrl() {
|
||||
this.url.search = '';
|
||||
|
||||
if (this.startDate.length > 0) {
|
||||
this.url.searchParams.append('start_date', this.startDate);
|
||||
}
|
||||
|
||||
if (this.endDate.length > 0) {
|
||||
this.url.searchParams.append('end_date', this.endDate);
|
||||
}
|
||||
|
||||
this.url.searchParams.append(
|
||||
'show_payments_table',
|
||||
+this.showPaymentsTable
|
||||
);
|
||||
this.url.searchParams.append('show_aging_table', +this.showAgingTable);
|
||||
|
||||
return this.url.href;
|
||||
}
|
||||
|
||||
updatePdf() {
|
||||
document.querySelector('meta[name=pdf-url]').content = this.composedUrl;
|
||||
|
||||
let iframe = document.querySelector('#pdf-iframe');
|
||||
|
||||
if (iframe) {
|
||||
iframe.src = this.composedUrl;
|
||||
}
|
||||
|
||||
document
|
||||
.querySelector('meta[name=pdf-url]')
|
||||
.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.bindEventListeners();
|
||||
|
||||
document
|
||||
.querySelector('#pdf-download')
|
||||
.addEventListener('click', () => {
|
||||
let url = new URL(this.composedUrl);
|
||||
url.searchParams.append('download', 1);
|
||||
|
||||
window.location.href = url.href;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
new Statement().handle();
|
@ -1779,6 +1779,7 @@ $LANG = array(
|
||||
'lang_Bulgarian' => 'Bulgarian',
|
||||
'lang_Russian (Russia)' => 'Russian (Russia)',
|
||||
|
||||
|
||||
// Industries
|
||||
'industry_Accounting & Legal' => 'Accounting & Legal',
|
||||
'industry_Advertising' => 'Advertising',
|
||||
@ -2471,6 +2472,13 @@ $LANG = array(
|
||||
'currency_kazakhstani_tenge' => 'Kazakhstani Tenge',
|
||||
'currency_gibraltar_pound' => 'Gibraltar Pound',
|
||||
|
||||
'currency_gambia_dalasi' => 'Gambia Dalasi',
|
||||
'currency_paraguayan_guarani' => 'Paraguayan Guarani',
|
||||
'currency_malawi_kwacha' => 'Malawi Kwacha',
|
||||
'currency_zimbabwean_dollar' => 'Zimbabwean Dollar',
|
||||
'currency_cambodian_riel' => 'Cambodian Riel',
|
||||
'currency_vanuatu_vatu' => 'Vanuatu Vatu',
|
||||
|
||||
'review_app_help' => 'We hope you\'re enjoying using the app.<br/>If you\'d consider :link we\'d greatly appreciate it!',
|
||||
'writing_a_review' => 'writing a review',
|
||||
|
||||
@ -4305,6 +4313,7 @@ $LANG = array(
|
||||
'unable_to_verify_payment_method' => 'Unable to verify payment method.',
|
||||
'generic_gateway_error' => 'Gateway configuration error. Please check your credentials.',
|
||||
'my_documents' => 'My documents',
|
||||
'payment_method_cannot_be_preauthorized' => 'This payment method cannot be preauthorized.',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-build="{{ $build }}" user-agent="{{ $user_agent }}">
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-user-agent="{{ $user_agent }}">
|
||||
<head>
|
||||
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
|
||||
<!-- Version: {{ config('ninja.app_version') }} -->
|
||||
|
@ -257,6 +257,11 @@
|
||||
max-height: 160px;
|
||||
}
|
||||
|
||||
#statement-invoice-table-totals > p {
|
||||
margin-right: 2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
|
@ -345,6 +345,12 @@
|
||||
? document.getElementById(tableIdentifier).style.display = 'none'
|
||||
: '';
|
||||
});
|
||||
|
||||
// If we have elements in these tables, we can change label to "Statement" & hide entity details.
|
||||
if (document.querySelectorAll('#statement-payment-table > tbody, #statement-payment-table > tbody, #statement-aging-table-totals > tbody').length > 0) {
|
||||
document.querySelector('.entity-label').innerText = '$statement_label';
|
||||
document.querySelector('.entity-details-wrapper').style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
@ -54,8 +54,12 @@
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
height: 100%;
|
||||
.company-logo-wrapper {
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.company-logo-wrapper {
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.header-invoice-number {
|
||||
@ -266,8 +270,10 @@
|
||||
</div>
|
||||
|
||||
<div class="logo-and-partial-entity-info">
|
||||
<div class="company-logo-wrapper">
|
||||
<img class="company-logo" src="$company.logo"
|
||||
alt="$company.name logo">
|
||||
</div>
|
||||
<div class="top-right-side-section">
|
||||
<div dir="$dir">
|
||||
<span class="header-payment-due-label">$payment_due_label:</span>
|
||||
|
@ -77,7 +77,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!empty($company->present()->website()))
|
||||
@if(!is_null($company) && !empty($company->present()->website()))
|
||||
<div class="mt-5 text-center">
|
||||
<a class="button-link text-sm" href="{{ $company->present()->website() }}">
|
||||
{{ ctrans('texts.back_to', ['url' => parse_url($company->present()->website())['host'] ?? $company->present()->website() ]) }}
|
||||
|
@ -10,7 +10,7 @@
|
||||
alt="Background image">
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-span-2 h-screen flex">
|
||||
<div class="{{ $account && !$account->isPaid() ? 'col-span-2' : 'col-span-3' }} h-screen flex">
|
||||
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||
@if($account && !$account->isPaid())
|
||||
<div>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="col-span-2 h-screen flex">
|
||||
<div class="{{ $account && !$account->isPaid() ? 'col-span-2' : 'col-span-3' }} h-screen flex">
|
||||
<div class="m-auto w-1/2 md:w-1/3 lg:w-1/4">
|
||||
|
||||
@if($account && !$account->isPaid())
|
||||
|
@ -69,19 +69,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(auth('contact')->user())
|
||||
<a href="{{ route('client.invoices.index') }}" class="block mt-16 inline-flex items-center space-x-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-arrow-left">
|
||||
<line x1="19" y1="12" x2="5" y2="12"></line>
|
||||
<polyline points="12 19 5 12 12 5"></polyline>
|
||||
</svg>
|
||||
|
||||
<span>{{ ctrans('texts.client_portal') }}</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($subscription->service()->getPlans()->count() > 1)
|
||||
<div class="flex flex-col mt-10">
|
||||
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center rounded-full text-xs font-medium">
|
||||
|
@ -3,7 +3,7 @@
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ $entity->pdf_file_path(null, 'url', true) }}">
|
||||
<meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
@endpush
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="?mode=fullscreen"
|
||||
<a target="_blank" href="{{ $fullscreen_url ?? '?mode=fullscreen' }}"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -86,7 +86,7 @@
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white mt-4 p-4"></canvas>
|
||||
</div>
|
||||
@else
|
||||
<iframe src="{{ $entity->pdf_file_path(null, 'url', true) }}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
<iframe id="pdf-iframe" src="{{ $url ?? $entity->pdf_file_path(null, 'url', true) }}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
@endif
|
||||
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.bank_transfer'), 'card_title' =>
|
||||
ctrans('texts.bank_transfer')])
|
||||
|
||||
@section('gateway_content')
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
{{ __('texts.payment_method_cannot_be_preauthorized') }}
|
||||
@endcomponent
|
||||
@endsection
|
@ -7,6 +7,7 @@
|
||||
<meta name="amount" content="{{ $stripe_amount }}">
|
||||
<meta name="country" content="{{ $country }}">
|
||||
<meta name="customer" content="{{ $customer }}">
|
||||
<meta name="pi-client-secret" content="{{ $pi_client_secret }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
|
42
resources/views/portal/ninja2020/statement/index.blade.php
Normal file
42
resources/views/portal/ninja2020/statement/index.blade.php
Normal file
@ -0,0 +1,42 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
|
||||
@section('meta_title', ctrans('texts.statement'))
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ route('client.statement.raw') }}">
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
<div class="flex flex-col md:flex-row md:justify-between">
|
||||
<div class="flex flex-col md:flex-row md:items-center">
|
||||
<div class="flex">
|
||||
<label for="from" class="block w-full flex items-center mr-4">
|
||||
<span class="mr-2">{{ ctrans('texts.from') }}:</span>
|
||||
<input id="date-from" type="date" class="input w-full" data-field="startDate" value="{{ now()->startOfYear()->format('Y-m-d') }}">
|
||||
</label>
|
||||
|
||||
<label for="to" class="block w-full flex items-center mr-4">
|
||||
<span class="mr-2">{{ ctrans('texts.to') }}:</span>
|
||||
<input id="date-to" type="date" class="input w-full" data-field="endDate" value="{{ now()->format('Y-m-d') }}">
|
||||
</label>
|
||||
</div> <!-- End date range -->
|
||||
|
||||
<label for="show_payments" class="block flex items-center mr-4 mt-4 md:mt-0">
|
||||
<input id="show-payments-table" type="checkbox" data-field="showPaymentsTable" class="form-checkbox" autocomplete="off">
|
||||
<span class="ml-2">{{ ctrans('texts.show_payments') }}</span>
|
||||
</label> <!-- End show payments checkbox -->
|
||||
|
||||
<label for="show_aging" class="block flex items-center">
|
||||
<input id="show-aging-table" type="checkbox" data-field="showAgingTable" class="form-checkbox" autocomplete="off">
|
||||
<span class="ml-2">{{ ctrans('texts.show_aging') }}</span>
|
||||
</label> <!-- End show aging checkbox -->
|
||||
</div>
|
||||
<button id="pdf-download" class="button button-primary bg-primary mt-4 md:mt-0">{{ ctrans('texts.download') }}</button>
|
||||
</div>
|
||||
|
||||
@include('portal.ninja2020.components.pdf-viewer', ['url' => route('client.statement.raw')])
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script src="{{ asset('js/clients/statements/view.js') }}"></script>
|
||||
@endpush
|
@ -80,6 +80,9 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence
|
||||
|
||||
Route::resource('tasks', 'ClientPortal\TaskController')->only(['index']);
|
||||
|
||||
Route::get('statement', 'ClientPortal\StatementController@index')->name('statement');
|
||||
Route::get('statement/raw', 'ClientPortal\StatementController@raw')->name('statement.raw');
|
||||
|
||||
Route::post('upload', 'ClientPortal\UploadController')->name('upload.store');
|
||||
Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout');
|
||||
|
||||
|
@ -0,0 +1,72 @@
|
||||
<?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 Tests\Browser\ClientPortal\Gateways\Mollie;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
|
||||
class BankTransferTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testSuccessfulPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bank Transfer')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'paid')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Completed');
|
||||
});
|
||||
}
|
||||
|
||||
public function testPendingPayment(): void
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Bank Transfer')
|
||||
->waitForText('Test profile')
|
||||
->radio('final_state', 'open')
|
||||
->press('Continue')
|
||||
->waitForText('Details of the payment')
|
||||
->assertSee('Pending');
|
||||
});
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace Tests\Browser\ClientPortal\Gateways\Mollie;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
use Tests\DuskTestCase;
|
||||
@ -26,9 +27,9 @@ class CreditCardTest extends DuskTestCase
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
// $this->disableCompanyGateways();
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
// CompanyGateway::where('gateway_key', '3758e7f7c6f4cecf0f4f348b9a00f456')->restore();
|
||||
CompanyGateway::where('gateway_key', '1bd651fb213ca0c9d66ae3c336dc77e8')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user