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()
|
||||
{
|
||||
|
||||
$this->invoice = $this->context['invoice'];
|
||||
$invoice_amount = $this->invoice->partial > 0 ? $this->invoice->partial : $this->invoice->balance;
|
||||
|
||||
$this->variables = $this->context['variables'];
|
||||
$this->amount = isset($this->context['payable_invoices']) ? array_sum(array_column($this->context['payable_invoices'], 'amount')) : $invoice_amount;
|
||||
MultiDB::setDb($this->invoice->company->db);
|
||||
$this->amount = array_sum(array_column($this->context['payable_invoices'], 'amount'));
|
||||
|
||||
$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) {
|
||||
$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()
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->context['invoice']->company->db);
|
||||
MultiDB::setDb($this->context['db']);
|
||||
|
||||
$invitation = InvoiceInvitation::find($this->context['invitation_id']);
|
||||
|
||||
$data = [
|
||||
'company_gateway_id' => $this->context['company_gateway_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_ip' => isset($this->context['signature_ip']) ? $this->context['signature_ip'] : false,
|
||||
'pre_payment' => false,
|
||||
|
@ -40,17 +40,15 @@ class UnderOverPayment extends Component
|
||||
|
||||
public function checkValue(array $payableInvoices)
|
||||
{
|
||||
nlog($payableInvoices);
|
||||
|
||||
$this->errors = '';
|
||||
|
||||
$settings = $this->context['settings'];
|
||||
$input_amount = 0;
|
||||
|
||||
foreach($payableInvoices as $invoice)
|
||||
foreach($payableInvoices as $key=>$invoice){
|
||||
$input_amount += Number::parseFloat($invoice['formatted_amount']);
|
||||
|
||||
nlog($input_amount);
|
||||
$payableInvoices[$key]['amount'] = $input_amount;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
|
@ -150,6 +150,7 @@ class InvoicePay extends Component
|
||||
$client = $invite->contact->client;
|
||||
$settings = $client->getMergedSettings();
|
||||
$this->context['settings'] = $settings;
|
||||
$this->context['db'] = $this->db;
|
||||
|
||||
$invoices = Invoice::find($this->transformKeys($this->invoices));
|
||||
$invoices = $invoices->filter(function ($i){
|
||||
@ -166,7 +167,6 @@ class InvoicePay extends Component
|
||||
//under-over / payment
|
||||
|
||||
//required fields
|
||||
|
||||
$this->terms_accepted = !$settings->show_accept_invoice_terms;
|
||||
$this->signature_accepted = !$settings->require_invoice_signature;
|
||||
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
|
||||
|
@ -104,85 +104,13 @@ class LivewireInstantPayment
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
$client = $invoices->first()->client;
|
||||
|
||||
/* pop non payable invoice from the $payable_invoices array */
|
||||
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
|
||||
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_invoice_collection = collect();
|
||||
|
||||
|
@ -41,7 +41,9 @@ class ZugferdEDocument extends AbstractService {
|
||||
*/
|
||||
public function run(): Expense
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$this->document = ZugferdDocumentReader::readAndGuessFromContent($this->tempdocument);
|
||||
$this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
|
||||
$this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount);
|
||||
@ -103,13 +105,18 @@ class ZugferdEDocument extends AbstractService {
|
||||
if ($taxid != null) {
|
||||
$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->address1 = $address_1;
|
||||
$vendor->address2 = $address_2;
|
||||
$vendor->city = $city;
|
||||
$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();
|
||||
$expense->vendor_id = $vendor->id;
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
namespace App\Utils;
|
||||
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class TempFile
|
||||
{
|
||||
public static function path($url): string
|
||||
@ -34,4 +37,32 @@ class TempFile
|
||||
|
||||
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">
|
||||
|
||||
<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') }}
|
||||
</dt>
|
||||
<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">
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center mb-2">
|
||||
<label>
|
||||
<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
|
||||
type="text"
|
||||
class="input mt-0 mr-4 relative"
|
||||
|
Loading…
x
Reference in New Issue
Block a user