mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
improvements for validation of over/under payments
This commit is contained in:
parent
01312996d8
commit
ee90539bd8
@ -44,14 +44,12 @@ class PaymentMethod extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->invoice = $this->context['invoice'];
|
|
||||||
$invoice_amount = $this->invoice->partial > 0 ? $this->invoice->partial : $this->invoice->balance;
|
|
||||||
|
|
||||||
$this->variables = $this->context['variables'];
|
$this->variables = $this->context['variables'];
|
||||||
$this->amount = isset($this->context['payable_invoices']) ? array_sum(array_column($this->context['payable_invoices'], 'amount')) : $invoice_amount;
|
$this->amount = array_sum(array_column($this->context['payable_invoices'], 'amount'));
|
||||||
MultiDB::setDb($this->invoice->company->db);
|
|
||||||
|
|
||||||
$this->methods = $this->invoice->client->service()->getPaymentMethods($this->amount);
|
MultiDB::setDb($this->context['db']);
|
||||||
|
|
||||||
|
$this->methods = $this->context['invitation']->contact->client->service()->getPaymentMethods($this->amount);
|
||||||
|
|
||||||
if(count($this->methods) == 1) {
|
if(count($this->methods) == 1) {
|
||||||
$this->dispatch('singlePaymentMethodFound', company_gateway_id: $this->methods[0]['company_gateway_id'], gateway_type_id: $this->methods[0]['gateway_type_id'], amount: $this->amount);
|
$this->dispatch('singlePaymentMethodFound', company_gateway_id: $this->methods[0]['company_gateway_id'], gateway_type_id: $this->methods[0]['gateway_type_id'], amount: $this->amount);
|
||||||
|
@ -34,14 +34,14 @@ class ProcessPayment extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|
||||||
MultiDB::setDb($this->context['invoice']->company->db);
|
MultiDB::setDb($this->context['db']);
|
||||||
|
|
||||||
$invitation = InvoiceInvitation::find($this->context['invitation_id']);
|
$invitation = InvoiceInvitation::find($this->context['invitation_id']);
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'company_gateway_id' => $this->context['company_gateway_id'],
|
'company_gateway_id' => $this->context['company_gateway_id'],
|
||||||
'payment_method_id' => $this->context['gateway_type_id'],
|
'payment_method_id' => $this->context['gateway_type_id'],
|
||||||
'payable_invoices' => [$this->context['payable_invoices']],
|
'payable_invoices' => $this->context['payable_invoices'],
|
||||||
'signature' => isset($this->context['signature']) ? $this->context['signature'] : false,
|
'signature' => isset($this->context['signature']) ? $this->context['signature'] : false,
|
||||||
'signature_ip' => isset($this->context['signature_ip']) ? $this->context['signature_ip'] : false,
|
'signature_ip' => isset($this->context['signature_ip']) ? $this->context['signature_ip'] : false,
|
||||||
'pre_payment' => false,
|
'pre_payment' => false,
|
||||||
|
@ -40,17 +40,15 @@ class UnderOverPayment extends Component
|
|||||||
|
|
||||||
public function checkValue(array $payableInvoices)
|
public function checkValue(array $payableInvoices)
|
||||||
{
|
{
|
||||||
nlog($payableInvoices);
|
|
||||||
|
|
||||||
$this->errors = '';
|
$this->errors = '';
|
||||||
|
|
||||||
$settings = $this->context['settings'];
|
$settings = $this->context['settings'];
|
||||||
$input_amount = 0;
|
$input_amount = 0;
|
||||||
|
|
||||||
foreach($payableInvoices as $invoice)
|
foreach($payableInvoices as $key=>$invoice){
|
||||||
$input_amount += Number::parseFloat($invoice['formatted_amount']);
|
$input_amount += Number::parseFloat($invoice['formatted_amount']);
|
||||||
|
$payableInvoices[$key]['amount'] = $input_amount;
|
||||||
nlog($input_amount);
|
}
|
||||||
|
|
||||||
if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0)
|
if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0)
|
||||||
{
|
{
|
||||||
@ -67,9 +65,11 @@ class UnderOverPayment extends Component
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$this->errors)
|
if(!$this->errors){
|
||||||
|
$this->context['payable_invoices'] = $payableInvoices;
|
||||||
$this->dispatch('payable-amount', payable_amount: $input_amount );
|
$this->dispatch('payable-amount', payable_amount: $input_amount );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
@ -150,6 +150,7 @@ class InvoicePay extends Component
|
|||||||
$client = $invite->contact->client;
|
$client = $invite->contact->client;
|
||||||
$settings = $client->getMergedSettings();
|
$settings = $client->getMergedSettings();
|
||||||
$this->context['settings'] = $settings;
|
$this->context['settings'] = $settings;
|
||||||
|
$this->context['db'] = $this->db;
|
||||||
|
|
||||||
$invoices = Invoice::find($this->transformKeys($this->invoices));
|
$invoices = Invoice::find($this->transformKeys($this->invoices));
|
||||||
$invoices = $invoices->filter(function ($i){
|
$invoices = $invoices->filter(function ($i){
|
||||||
@ -166,7 +167,6 @@ class InvoicePay extends Component
|
|||||||
//under-over / payment
|
//under-over / payment
|
||||||
|
|
||||||
//required fields
|
//required fields
|
||||||
|
|
||||||
$this->terms_accepted = !$settings->show_accept_invoice_terms;
|
$this->terms_accepted = !$settings->show_accept_invoice_terms;
|
||||||
$this->signature_accepted = !$settings->require_invoice_signature;
|
$this->signature_accepted = !$settings->require_invoice_signature;
|
||||||
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
|
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
|
||||||
|
@ -104,85 +104,13 @@ class LivewireInstantPayment
|
|||||||
->withTrashed()
|
->withTrashed()
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$client = $invoices->first()->client;
|
||||||
|
|
||||||
/* pop non payable invoice from the $payable_invoices array */
|
/* pop non payable invoice from the $payable_invoices array */
|
||||||
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
||||||
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first();
|
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* return early */
|
|
||||||
if ($payable_invoices->count() == 0) {
|
|
||||||
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.no_payable_invoices_selected')]);
|
|
||||||
return $this->getResponder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logic Loops for Under/Overpayments */
|
|
||||||
$invoices = Invoice::query()->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
|
|
||||||
|
|
||||||
$client = $invoices->first()->client;
|
|
||||||
$settings = $client->getMergedSettings();
|
|
||||||
|
|
||||||
/* This loop checks for under / over payments and returns the user if a check fails */
|
|
||||||
|
|
||||||
foreach ($payable_invoices as $payable_invoice) {
|
|
||||||
|
|
||||||
/*Match the payable invoice to the Model Invoice*/
|
|
||||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
|
||||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if company supports over & under payments.
|
|
||||||
* Determine the payable amount and the max payable. ie either partial or invoice balance
|
|
||||||
*/
|
|
||||||
|
|
||||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), $client->currency()->precision);
|
|
||||||
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
|
|
||||||
|
|
||||||
|
|
||||||
/*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/
|
|
||||||
if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) {
|
|
||||||
$payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) {
|
|
||||||
|
|
||||||
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]), 'redirect' => 'client.invoices.index']);
|
|
||||||
return $this->getResponder();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($settings->client_portal_allow_under_payment) {
|
|
||||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) {
|
|
||||||
|
|
||||||
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]), 'redirect' => 'client.invoices.index']);
|
|
||||||
return $this->getResponder();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($invoice_balance < $settings->client_portal_under_payment_minimum) {
|
|
||||||
// Skip the under payment rule.
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) {
|
|
||||||
|
|
||||||
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]), 'redirect' => 'client.invoices.index']);
|
|
||||||
return $this->getResponder();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we don't allow over payments and the amount exceeds the balance */
|
|
||||||
|
|
||||||
if (! $settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) {
|
|
||||||
|
|
||||||
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.over_payments_disabled'), 'redirect' => 'client.invoices.index']);
|
|
||||||
return $this->getResponder();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Iterate through invoices and add gateway fees and other payment metadata*/
|
|
||||||
|
|
||||||
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
||||||
$payable_invoice_collection = collect();
|
$payable_invoice_collection = collect();
|
||||||
|
|
||||||
|
@ -41,7 +41,9 @@ class ZugferdEDocument extends AbstractService {
|
|||||||
*/
|
*/
|
||||||
public function run(): Expense
|
public function run(): Expense
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
$this->document = ZugferdDocumentReader::readAndGuessFromContent($this->tempdocument);
|
$this->document = ZugferdDocumentReader::readAndGuessFromContent($this->tempdocument);
|
||||||
$this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
|
$this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
|
||||||
$this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount);
|
$this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount);
|
||||||
@ -103,13 +105,18 @@ class ZugferdEDocument extends AbstractService {
|
|||||||
if ($taxid != null) {
|
if ($taxid != null) {
|
||||||
$vendor->vat_number = $taxid;
|
$vendor->vat_number = $taxid;
|
||||||
}
|
}
|
||||||
$vendor->currency_id = Currency::whereCode($invoiceCurrency)->first()->id;
|
$vendor->currency_id = app('currencies')->first(function ($currency) use ($invoiceCurrency){
|
||||||
|
return $currency->code == $invoiceCurrency;
|
||||||
|
})->id;
|
||||||
|
|
||||||
$vendor->phone = $contact_phone;
|
$vendor->phone = $contact_phone;
|
||||||
$vendor->address1 = $address_1;
|
$vendor->address1 = $address_1;
|
||||||
$vendor->address2 = $address_2;
|
$vendor->address2 = $address_2;
|
||||||
$vendor->city = $city;
|
$vendor->city = $city;
|
||||||
$vendor->postal_code = $postcode;
|
$vendor->postal_code = $postcode;
|
||||||
$vendor->country_id = Country::where('iso_3166_2', $country)->first()->id;
|
$vendor->country_id = app('countries')->first(function ($c) use ($country){
|
||||||
|
return $c->iso_3166_2 == $country;
|
||||||
|
})->id;
|
||||||
|
|
||||||
$vendor->save();
|
$vendor->save();
|
||||||
$expense->vendor_id = $vendor->id;
|
$expense->vendor_id = $vendor->id;
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
namespace App\Utils;
|
namespace App\Utils;
|
||||||
|
|
||||||
|
use Illuminate\Http\File;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
class TempFile
|
class TempFile
|
||||||
{
|
{
|
||||||
public static function path($url): string
|
public static function path($url): string
|
||||||
@ -34,4 +37,32 @@ class TempFile
|
|||||||
|
|
||||||
return $file_path;
|
return $file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function UploadedFileFromRaw(string $fileData, string|null $fileName = null, string|null $mimeType = null): UploadedFile
|
||||||
|
{
|
||||||
|
// Create temp file and get its absolute path
|
||||||
|
$tempFile = tmpfile();
|
||||||
|
$tempFilePath = stream_get_meta_data($tempFile)['uri'];
|
||||||
|
|
||||||
|
// Save file data in file
|
||||||
|
file_put_contents($tempFilePath, $fileData);
|
||||||
|
|
||||||
|
$tempFileObject = new File($tempFilePath);
|
||||||
|
$file = new UploadedFile(
|
||||||
|
$tempFileObject->getPathname(),
|
||||||
|
$fileName ?: $tempFileObject->getFilename(),
|
||||||
|
$mimeType ?: $tempFileObject->getMimeType(),
|
||||||
|
0,
|
||||||
|
true // Mark it as test, since the file isn't from real HTTP POST.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close this file after response is sent.
|
||||||
|
// Closing the file will cause to remove it from temp director!
|
||||||
|
app()->terminating(function () use ($tempFile) {
|
||||||
|
fclose($tempFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
// return UploadedFile object
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div x-data="{ payableInvoices: @entangle('payableInvoices'), errors: @entangle('errors') }" class="px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
<div x-data="{ payableInvoices: @entangle('payableInvoices'), errors: @entangle('errors') }" class="px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
||||||
|
|
||||||
<dt class="text-sm font-medium leading-5 text-gray-500">
|
<dt class="text-sm font-medium leading-5 text-gray-500 mb-3">
|
||||||
{{ ctrans('texts.payment_amount') }}
|
{{ ctrans('texts.payment_amount') }}
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 flex flex-col">
|
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 flex flex-col">
|
||||||
@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
<template x-for="(invoice, index) in payableInvoices" :key="index">
|
<template x-for="(invoice, index) in payableInvoices" :key="index">
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center mb-2">
|
||||||
<label>
|
<label>
|
||||||
<span x-text="'{{ ctrans('texts.invoice') }} ' + invoice.number" class="mt-2"></span>
|
<span x-text="'{{ ctrans('texts.invoice') }} ' + invoice.number" class="mt-2"></span>
|
||||||
<span>{{ $currency->code }} ({{ $currency->symbol }})</span>
|
<span class="pr-2">{{ $currency->code }} ({{ $currency->symbol }})</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="input mt-0 mr-4 relative"
|
class="input mt-0 mr-4 relative"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user