mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
472a9f755f
@ -1 +1 @@
|
||||
5.0.44
|
||||
5.0.45
|
@ -227,7 +227,7 @@ class CreateSingleAccount extends Command
|
||||
|
||||
$settings = $client->settings;
|
||||
$settings->currency_id = "1";
|
||||
$settings->use_credits_payment = "always";
|
||||
// $settings->use_credits_payment = "always";
|
||||
|
||||
$client->settings = $settings;
|
||||
|
||||
|
@ -258,8 +258,10 @@ class CompanySettings extends BaseSettings
|
||||
public $client_portal_allow_over_payment = false; //@implemented
|
||||
|
||||
public $use_credits_payment = 'off'; //always, option, off //@implemented
|
||||
public $hide_empty_columns_on_pdf = false;
|
||||
|
||||
public static $casts = [
|
||||
'hide_empty_columns_on_pdf' => 'bool',
|
||||
'enable_reminder_endless' => 'bool',
|
||||
'use_credits_payment' => 'string',
|
||||
'recurring_invoice_number_pattern' => 'string',
|
||||
|
@ -22,6 +22,7 @@ class ExpenseCategoryFactory
|
||||
$expense->company_id = $company_id;
|
||||
$expense->name = '';
|
||||
$expense->is_deleted = false;
|
||||
$expense->color = '#fff';
|
||||
|
||||
return $expense;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->custom_value3 = $recurring_invoice->custom_value3;
|
||||
$invoice->custom_value4 = $recurring_invoice->custom_value4;
|
||||
$invoice->amount = $recurring_invoice->amount;
|
||||
$invoice->balance = $recurring_invoice->balance;
|
||||
// $invoice->balance = $recurring_invoice->balance;
|
||||
$invoice->user_id = $recurring_invoice->user_id;
|
||||
$invoice->assigned_user_id = $recurring_invoice->assigned_user_id;
|
||||
$invoice->company_id = $recurring_invoice->company_id;
|
||||
|
@ -21,6 +21,7 @@ class TaskStatusFactory
|
||||
$task_status->user_id = $user_id;
|
||||
$task_status->company_id = $company_id;
|
||||
$task_status->name = '';
|
||||
$task_status->color = '#fff';
|
||||
|
||||
return $task_status;
|
||||
}
|
||||
|
@ -29,3 +29,11 @@ function nlog($output, $context = []): void
|
||||
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('ray')) {
|
||||
function ray($payload)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ class InvoiceItemSum
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.$tax_rate.'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.floatval($tax_rate).'%'];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
|
@ -111,19 +111,19 @@ class InvoiceSum
|
||||
if ($this->invoice->tax_rate1 > 0) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate1);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.$this->invoice->tax_rate1.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate2 > 0) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate2);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.$this->invoice->tax_rate2.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate3 > 0) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate3);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.$this->invoice->tax_rate3.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -122,19 +122,19 @@ class InvoiceSumInclusive
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.$this->invoice->tax_rate1.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate2 > 0) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.$this->invoice->tax_rate2.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate3 > 0) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.$this->invoice->tax_rate3.'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -76,12 +76,15 @@ class InvitationController extends Controller
|
||||
{
|
||||
switch ($entity_string) {
|
||||
case 'invoice':
|
||||
$invitation->invoice->service()->markSent()->save();
|
||||
event(new InvoiceWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
case 'quote':
|
||||
$invitation->quote->service()->markSent()->save();
|
||||
event(new QuoteWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
case 'credit':
|
||||
$invitation->credit->service()->markSent()->save();
|
||||
event(new CreditWasViewed($invitation, $invitation->company, Ninja::eventVars()));
|
||||
break;
|
||||
default:
|
||||
|
@ -77,7 +77,9 @@ class InvoiceController extends Controller
|
||||
return $this->downloadInvoicePDF((array) $transformed_ids);
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
return redirect()
|
||||
->back()
|
||||
->with('message', ctrans('texts.no_action_provided'));
|
||||
}
|
||||
|
||||
private function makePayment(array $ids)
|
||||
@ -86,16 +88,30 @@ class InvoiceController extends Controller
|
||||
->whereClientId(auth()->user()->client->id)
|
||||
->get();
|
||||
|
||||
$total = $invoices->sum('balance');
|
||||
|
||||
//filter invoices which are payable
|
||||
$invoices = $invoices->filter(function ($invoice) {
|
||||
return $invoice->isPayable() && $invoice->balance > 0;
|
||||
});
|
||||
|
||||
//return early if no invoices.
|
||||
if ($invoices->count() == 0) {
|
||||
return back()->with(['warning' => 'No payable invoices selected']);
|
||||
return back()
|
||||
->with('message', ctrans('texts.no_payable_invoices_selected'));
|
||||
}
|
||||
|
||||
//iterate and sum the payable amounts either partial or balance
|
||||
$total = 0;
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
|
||||
if($invoice->partial > 0)
|
||||
$total += $invoice->partial;
|
||||
else
|
||||
$total += $invoice->balance;
|
||||
|
||||
}
|
||||
|
||||
//format data
|
||||
$invoices->map(function ($invoice) {
|
||||
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
$invoice->balance = Number::formatValue($invoice->balance, $invoice->client->currency());
|
||||
@ -104,6 +120,7 @@ class InvoiceController extends Controller
|
||||
return $invoice;
|
||||
});
|
||||
|
||||
//format totals
|
||||
$formatted_total = Number::formatMoney($total, auth()->user()->client);
|
||||
|
||||
$payment_methods = auth()->user()->client->getPaymentMethods($total);
|
||||
@ -117,6 +134,8 @@ class InvoiceController extends Controller
|
||||
'total' => $total,
|
||||
];
|
||||
|
||||
// nlog($data);
|
||||
|
||||
return $this->render('invoices.payment', $data);
|
||||
}
|
||||
|
||||
|
@ -12,14 +12,17 @@
|
||||
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Invoice\InjectSignature;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -83,8 +86,6 @@ class PaymentController extends Controller
|
||||
|
||||
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
|
||||
|
||||
//refactor from here!
|
||||
|
||||
/**
|
||||
* find invoices
|
||||
*
|
||||
@ -95,11 +96,13 @@ class PaymentController extends Controller
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->get();
|
||||
|
||||
/* 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()->isPayable();
|
||||
});
|
||||
|
||||
/*return early if no invoices*/
|
||||
|
||||
if ($payable_invoices->count() == 0) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
@ -108,23 +111,34 @@ class PaymentController extends Controller
|
||||
|
||||
$settings = auth()->user()->client->getMergedSettings();
|
||||
|
||||
/*iterate through invoices and add gateway fees and other payment metadata*/
|
||||
$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
|
||||
$payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||
// nlog($settings);
|
||||
|
||||
/* 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.
|
||||
// In case it doesn't this is where process should stop.
|
||||
/*
|
||||
* 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']), auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue($invoice->balance, auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), auth()->user()->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), auth()->user()->client->currency()->precision);
|
||||
} // We don't allow either of these, reset the amount to default invoice (to prevent inspect element payments).
|
||||
}
|
||||
|
||||
/* If we DO allow under payments check the minimum amount is present else return */
|
||||
|
||||
if ($settings->client_portal_allow_under_payment) {
|
||||
if ($payable_invoice['amount'] < $settings->client_portal_under_payment_minimum) {
|
||||
@ -133,23 +147,44 @@ class PaymentController extends Controller
|
||||
->with('message', ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]));
|
||||
}
|
||||
} else {
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue($invoice->balance, auth()->user()->client->currency()->precision);
|
||||
|
||||
/*Double check!!*/
|
||||
if ($payable_amount < $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.under_payments_disabled'));
|
||||
}
|
||||
} // Make sure 'amount' from form is not lower than 'amount' from invoice.
|
||||
}
|
||||
|
||||
if ($settings->client_portal_allow_over_payment == false) {
|
||||
/* If we don't allow over payments and the amount exceeds the balance */
|
||||
|
||||
if (!$settings->client_portal_allow_over_payment) {
|
||||
if ($payable_amount > $invoice_balance) {
|
||||
return redirect()
|
||||
->route('client.invoices.index')
|
||||
->with('message', ctrans('texts.over_payments_disabled'));
|
||||
}
|
||||
} // Make sure 'amount' from form is not higher than 'amount' from invoice.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*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();
|
||||
|
||||
foreach($payable_invoices as $payable_invoice)
|
||||
{
|
||||
nlog($payable_invoice);
|
||||
|
||||
$payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']);
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
|
||||
return $payable_invoice['invoice_id'] == $inv->hashed_id;
|
||||
});
|
||||
|
||||
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), auth()->user()->client->currency()->precision);
|
||||
$invoice_balance = Number::roundValue($invoice->balance, auth()->user()->client->currency()->precision);
|
||||
|
||||
$payable_invoice['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format());
|
||||
$payable_invoice['invoice_number'] = $invoice->number;
|
||||
@ -164,8 +199,9 @@ class PaymentController extends Controller
|
||||
|
||||
$payable_invoice['additional_info'] = $additional_info;
|
||||
|
||||
return $payable_invoice;
|
||||
});
|
||||
$payable_invoice_collection->push($payable_invoice);
|
||||
}
|
||||
//});
|
||||
|
||||
if (request()->has('signature') && !is_null(request()->signature) && !empty(request()->signature)) {
|
||||
$invoices->each(function ($invoice) use ($request) {
|
||||
@ -173,6 +209,8 @@ class PaymentController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
$payable_invoices = $payable_invoice_collection;
|
||||
|
||||
$payment_method_id = $request->input('payment_method_id');
|
||||
$invoice_totals = $payable_invoices->sum('amount');
|
||||
$first_invoice = $invoices->first();
|
||||
@ -196,7 +234,7 @@ class PaymentController extends Controller
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
$payment_hash->hash = Str::random(128);
|
||||
$payment_hash->data = ['invoices' => $payable_invoices->toArray()];
|
||||
$payment_hash->data = ['invoices' => $payable_invoices->toArray() , 'credits' => $credit_totals];
|
||||
$payment_hash->fee_total = $fee_totals;
|
||||
$payment_hash->fee_invoice_id = $first_invoice->id;
|
||||
$payment_hash->save();
|
||||
@ -221,12 +259,24 @@ class PaymentController extends Controller
|
||||
return $this->processCreditPayment($request, $data);
|
||||
}
|
||||
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($payment_method_id)
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentView($data);
|
||||
try {
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($payment_method_id)
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentView($data);
|
||||
} catch(\Exception $e) {
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_ERROR,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
auth('contact')->user()->client
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function response(PaymentResponseRequest $request)
|
||||
@ -235,12 +285,24 @@ class PaymentController extends Controller
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
|
||||
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($request->input('payment_method_id'))
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentResponse($request);
|
||||
try {
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($request->input('payment_method_id'))
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentResponse($request);
|
||||
} catch(\Exception $e) {
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
auth('contact')->user()->client
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,35 +327,7 @@ class PaymentController extends Controller
|
||||
$payment_hash->save();
|
||||
}
|
||||
|
||||
/* Iterate through the invoices and apply credits to them */
|
||||
collect($payment_hash->invoices())->each(function ($payable_invoice) use ($payment, $payment_hash) {
|
||||
$invoice = Invoice::find($this->decodePrimaryKey($payable_invoice->invoice_id));
|
||||
$amount = $payable_invoice->amount;
|
||||
|
||||
$credits = $payment_hash->fee_invoice
|
||||
->client
|
||||
->service()
|
||||
->getCredits();
|
||||
|
||||
foreach ($credits as $credit) {
|
||||
//starting invoice balance
|
||||
$invoice_balance = $invoice->balance;
|
||||
|
||||
//credit payment applied
|
||||
$credit->service()->applyPayment($invoice, $amount, $payment);
|
||||
|
||||
//amount paid from invoice calculated
|
||||
$remaining_balance = ($invoice_balance - $invoice->fresh()->balance);
|
||||
|
||||
//reduce the amount to be paid on the invoice from the NEXT credit
|
||||
$amount -= $remaining_balance;
|
||||
|
||||
//break if the invoice is no longer PAYABLE OR there is no more amount to be applied
|
||||
if (!$invoice->isPayable() || (int)$amount == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
$payment = $payment->service()->applyCredits($payment_hash)->save();
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
@ -509,9 +509,6 @@ class InvoiceController extends BaseController
|
||||
public function bulk()
|
||||
{
|
||||
|
||||
/*
|
||||
* WIP!
|
||||
*/
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
@ -174,8 +174,11 @@ class TaskStatusController extends BaseController
|
||||
*/
|
||||
public function store(StoreTaskStatusRequest $request)
|
||||
{
|
||||
nlog($request->all());
|
||||
|
||||
$task_status = TaskStatusFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
$task_status->fill($request->all());
|
||||
|
||||
$task_status->save();
|
||||
|
||||
return $this->itemResponse($task_status->fresh());
|
||||
|
@ -90,6 +90,11 @@ class UpdateClientRequest extends Request
|
||||
$input['group_settings_id'] = $this->decodePrimaryKey($input['group_settings_id']);
|
||||
}
|
||||
|
||||
/* If the user removes the currency we must always set the default */
|
||||
if (array_key_exists('settings', $input) && ! array_key_exists('currency_id', $input['settings'])) {
|
||||
$input['settings']['currency_id'] = (string) auth()->user()->company()->settings->currency_id;
|
||||
}
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (array_key_exists('settings', $input)) {
|
||||
|
@ -57,7 +57,7 @@ class UpdateCompanyRequest extends Request
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
// nlog($input);
|
||||
if (array_key_exists('settings', $input)) {
|
||||
$input['settings'] = $this->filterSaveableSettings($input['settings']);
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ class StoreExpenseRequest extends Request
|
||||
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
|
||||
}
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,10 @@ class UpdateExpenseRequest extends Request
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('documents', $input)) {
|
||||
unset($input['documents']);
|
||||
}
|
||||
|
||||
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
|
||||
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ class StoreExpenseCategoryRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\ExpenseCategory;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateExpenseCategoryRequest extends Request
|
||||
{
|
||||
@ -33,9 +34,21 @@ class UpdateExpenseCategoryRequest extends Request
|
||||
$rules = [];
|
||||
|
||||
if ($this->input('name')) {
|
||||
$rules['name'] = 'unique:expense_categories,name,'.$this->id.',id,company_id,'.$this->expense_category->company_id;
|
||||
// $rules['name'] = 'unique:expense_categories,name,'.$this->id.',id,company_id,'.$this->expense_category->company_id;
|
||||
$rules['name'] = Rule::unique('expense_categories')->where('company_id', auth()->user()->company()->id)->ignore($this->expense_category->id);
|
||||
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ class UpdateInvoiceRequest extends Request
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
|
||||
if (array_key_exists('documents', $input)) {
|
||||
unset($input['documents']);
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,10 @@ class StoreProjectRequest extends Request
|
||||
{
|
||||
$input = $this->decodePrimaryKeys($this->all());
|
||||
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,9 @@ class UpdateProjectRequest extends Request
|
||||
unset($input['client_id']);
|
||||
}
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,10 @@ class UpdateQuoteRequest extends Request
|
||||
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('documents', $input)) {
|
||||
unset($input['documents']);
|
||||
}
|
||||
|
||||
$input['id'] = $this->quote->id;
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -91,6 +91,10 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);
|
||||
}
|
||||
|
||||
if (array_key_exists('documents', $input)) {
|
||||
unset($input['documents']);
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,9 @@ class UpdateTaskRequest extends Request
|
||||
$input['status_id'] = $this->decodePrimaryKey($input['status_id']);
|
||||
}
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ class StoreTaskStatusRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\TaskStatus;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaskStatusRequest extends Request
|
||||
{
|
||||
@ -33,9 +34,21 @@ class UpdateTaskStatusRequest extends Request
|
||||
$rules = [];
|
||||
|
||||
if ($this->input('name')) {
|
||||
$rules['name'] = 'unique:task_statuses,name,'.$this->id.',id,company_id,'.$this->task_status->company_id;
|
||||
//$rules['name'] = 'unique:task_statuses,name,'.$this->id.',id,company_id,'.$this->task_status->company_id;
|
||||
$rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id);
|
||||
}
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('color', $input) && is_null($input['color']))
|
||||
$input['color'] = '#fff';
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -168,8 +168,12 @@ class CreateEntityPdf implements ShouldQueue
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
public function failed(\Exception $exception)
|
||||
public function failed($e)
|
||||
{
|
||||
nlog("help!");
|
||||
|
||||
}
|
||||
// public function failed(\Exception $exception)
|
||||
// {
|
||||
// nlog("help!");
|
||||
// }
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\DataMapper\Analytics\MigrationFailure;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Exceptions\MigrationValidatorFailed;
|
||||
@ -124,7 +125,7 @@ class Import implements ShouldQueue
|
||||
'task_statuses',
|
||||
'expenses',
|
||||
'tasks',
|
||||
// 'documents',
|
||||
'documents',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -964,56 +965,41 @@ class Import implements ShouldQueue
|
||||
$entity = Expense::where('id', $expense_id)->withTrashed()->first();
|
||||
}
|
||||
|
||||
$this->saveDocument(file_get_contents($resource['url']), $entity, $is_public = true);
|
||||
$file_url = $resource['url'];
|
||||
$file_name = basename($file_url);
|
||||
$file_path = sys_get_temp_dir().'/'.$file_name;
|
||||
|
||||
file_put_contents($file_path, file_get_contents($file_url), LOCK_EX);
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$file_info = $finfo->file($file_path);
|
||||
|
||||
nlog($resource['url']);
|
||||
nlog($file_url);
|
||||
nlog($file_name);
|
||||
nlog($file_path);
|
||||
nlog($file_info);
|
||||
nlog(filesize($file_path));
|
||||
|
||||
$uploaded_file = new UploadedFile(
|
||||
$file_path,
|
||||
$file_name,
|
||||
$file_info,
|
||||
filesize($file_path),
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
$this->saveDocument($uploaded_file, $entity, $is_public = true);
|
||||
|
||||
$uploaded_file = null;
|
||||
$finfo = null;
|
||||
$file_url = null;
|
||||
$file_name = null;
|
||||
$file_path = null;
|
||||
$file_info = null;
|
||||
}
|
||||
|
||||
// foreach ($data as $resource) {
|
||||
// $modified = $resource;
|
||||
|
||||
// if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && ! array_key_exists('invoices', $this->ids)) {
|
||||
// throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - invoices.');
|
||||
// }
|
||||
|
||||
// if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && ! array_key_exists('expenses', $this->ids)) {
|
||||
// throw new ResourceDependencyMissing('Processing documents failed, because of missing dependency - expenses.');
|
||||
// }
|
||||
|
||||
// /* Remove because of polymorphic joins. */
|
||||
// unset($modified['invoice_id']);
|
||||
// unset($modified['expense_id']);
|
||||
|
||||
// if (array_key_exists('invoice_id', $resource) && $resource['invoice_id'] && array_key_exists('invoices', $this->ids)) {
|
||||
// $modified['documentable_id'] = $this->transformId('invoices', $resource['invoice_id']);
|
||||
// $modified['documentable_type'] = Invoice::class;
|
||||
// }
|
||||
|
||||
// if (array_key_exists('expense_id', $resource) && $resource['expense_id'] && array_key_exists('expenses', $this->ids)) {
|
||||
// $modified['documentable_id'] = $this->transformId('expenses', $resource['expense_id']);
|
||||
// $modified['documentable_type'] = Expense::class;
|
||||
// }
|
||||
|
||||
// $modified['user_id'] = $this->processUserId($resource);
|
||||
// $modified['company_id'] = $this->company->id;
|
||||
|
||||
// $document = Document::create($modified);
|
||||
|
||||
// // $entity = $modified['documentable_type']::find($modified['documentable_id']);
|
||||
// // $entity->documents()->save($modified);
|
||||
|
||||
// $old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
|
||||
|
||||
// $this->ids['documents'] = [
|
||||
// "documents_{$old_user_key}" => [
|
||||
// 'old' => $resource['id'],
|
||||
// 'new' => $document->id,
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
|
||||
// Document::reguard();
|
||||
|
||||
// /*Improve memory handling by setting everything to null when we have finished*/
|
||||
// $data = null;
|
||||
}
|
||||
|
||||
private function processPaymentTerms(array $data) :void
|
||||
@ -1157,12 +1143,11 @@ class Import implements ShouldQueue
|
||||
|
||||
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
|
||||
|
||||
|
||||
$key = "expense_categories_{$resource['id']}";
|
||||
|
||||
$this->ids['expense_categories'][$key] = [
|
||||
'old' => $resource['id'],
|
||||
'new' => $expense_category->id,
|
||||
'new' => $expense_category->id,
|
||||
];
|
||||
|
||||
// $this->ids['expense_categories'] = [
|
||||
@ -1290,12 +1275,13 @@ class Import implements ShouldQueue
|
||||
|
||||
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id;
|
||||
|
||||
$this->ids['expenses'] = [
|
||||
"expenses_{$old_user_key}" => [
|
||||
'old' => $resource['id'],
|
||||
'new' => $expense->id,
|
||||
],
|
||||
$key = "expenses_{$resource['id']}";
|
||||
|
||||
$this->ids['expenses'][$key] = [
|
||||
'old' => $resource['id'],
|
||||
'new' => $expense->id,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
Expense::reguard();
|
||||
|
@ -88,7 +88,18 @@ class CreditEmailEngine extends BaseEmailEngine
|
||||
->setViewText(ctrans('texts.view_credit'));
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments([$this->credit->pdf_file_path()]);
|
||||
$this->setAttachments(['path' => $this->credit->pdf_file_path(), 'name' => basename($this->credit->pdf_file_path())]);
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->credit->documents as $document){
|
||||
// $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]);
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -98,6 +98,19 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments([$this->invoice->pdf_file_path()]);
|
||||
// $this->setAttachments(['path' => $this->invoice->pdf_file_path(), 'name' => basename($this->invoice->pdf_file_path())]);
|
||||
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->invoice->documents as $document){
|
||||
// $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]);
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -88,7 +88,20 @@ class QuoteEmailEngine extends BaseEmailEngine
|
||||
->setViewText(ctrans('texts.view_quote'));
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments([$this->invitation->pdf_file_path()]);
|
||||
// $this->setAttachments([$this->quote->pdf_file_path()]);
|
||||
$this->setAttachments(['path' => $this->quote->pdf_file_path(), 'name' => basename($this->quote->pdf_file_path())]);
|
||||
|
||||
}
|
||||
|
||||
//attach third party documents
|
||||
if($this->client->getSetting('document_email_attachment') !== false){
|
||||
|
||||
// Storage::url
|
||||
foreach($this->quote->documents as $document){
|
||||
// $this->setAttachments(['path'=>$document->filePath(),'name'=>$document->name]);
|
||||
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -84,8 +84,15 @@ class TemplateEmail extends Mailable
|
||||
|
||||
//conditionally attach files
|
||||
if ($settings->pdf_email_attachment !== false && ! empty($this->build_email->getAttachments())) {
|
||||
|
||||
//hosted | plan check here
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
$this->attach($file);
|
||||
|
||||
if(is_string($file))
|
||||
$this->attach($file);
|
||||
elseif(is_array($file))
|
||||
$this->attach($file['path'], ['as' => $file['name'], 'mime' => $file['mime']]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -509,14 +509,13 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
$payment_methods_intersect->push([$gateway->id => $type]);
|
||||
}
|
||||
} else {
|
||||
$payment_methods_intersect->push([$gateway->id => $type]);
|
||||
$payment_methods_intersect->push([$gateway->id => NULL]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//handle custom gateways as they are not unique'd()---------------------------------------------------------
|
||||
|
||||
|
||||
$payment_urls = [];
|
||||
|
||||
foreach ($payment_methods_intersect as $key => $child_array) {
|
||||
@ -525,11 +524,22 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
$fee_label = $gateway->calcGatewayFeeLabel($amount, $this);
|
||||
|
||||
$payment_urls[] = [
|
||||
'label' => $gateway->getTypeAlias($gateway_type_id) . $fee_label,
|
||||
'company_gateway_id' => $gateway_id,
|
||||
'gateway_type_id' => $gateway_type_id,
|
||||
];
|
||||
if(!$gateway_type_id){
|
||||
|
||||
$payment_urls[] = [
|
||||
'label' => $gateway->getConfigField('name') . $fee_label,
|
||||
'company_gateway_id' => $gateway_id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$payment_urls[] = [
|
||||
'label' => $gateway->getTypeAlias($gateway_type_id) . $fee_label,
|
||||
'company_gateway_id' => $gateway_id,
|
||||
'gateway_type_id' => $gateway_type_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,6 @@ class Company extends BaseModel
|
||||
protected $presenter = CompanyPresenter::class;
|
||||
|
||||
protected $fillable = [
|
||||
'hide_empty_columns_on_pdf',
|
||||
'calculate_expense_tax_by_amount',
|
||||
'invoice_expense_documents',
|
||||
'invoice_task_documents',
|
||||
@ -83,6 +82,8 @@ class Company extends BaseModel
|
||||
'is_disabled',
|
||||
'default_task_is_date_based',
|
||||
'enable_product_discount',
|
||||
'expense_inclusive_taxes',
|
||||
'expense_amount_is_pretax',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -378,6 +379,11 @@ class Company extends BaseModel
|
||||
return $this->hasMany(SystemLog::class)->orderBy('id', 'DESC')->take(50);
|
||||
}
|
||||
|
||||
public function system_log_relation()
|
||||
{
|
||||
return $this->hasMany(SystemLog::class)->orderBy('id', 'DESC');
|
||||
}
|
||||
|
||||
public function tokens_hashed()
|
||||
{
|
||||
return $this->hasMany(CompanyToken::class);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\GatewayType;
|
||||
use App\PaymentDrivers\BasePaymentDriver;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -58,10 +59,12 @@ class CompanyGateway extends BaseModel
|
||||
16 => ['card' => 'images/credit_cards/Test-Discover-Icon.png', 'text' => 'Discover'],
|
||||
];
|
||||
|
||||
// public function getFeesAndLimitsAttribute()
|
||||
// {
|
||||
// return json_decode($this->attributes['fees_and_limits']);
|
||||
// }
|
||||
public $gateway_consts = [
|
||||
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
|
||||
'd14dd26a37cecc30fdd65700bfb55b23' => 301,
|
||||
'3758e7f7c6f4cecf0f4f348b9a00f456' => 304,
|
||||
'3b6621f970ab18887c4f6dca78d3f8bb' => 305,
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
||||
@ -70,6 +73,15 @@ class CompanyGateway extends BaseModel
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function system_logs()
|
||||
{
|
||||
|
||||
return $this->company
|
||||
->system_log_relation
|
||||
->where('type_id', $this->gateway_consts[$this->gateway->key])
|
||||
->take(50);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
@ -254,10 +266,6 @@ class CompanyGateway extends BaseModel
|
||||
{
|
||||
$label = '';
|
||||
|
||||
if (! $this->feesEnabled()) {
|
||||
return $label;
|
||||
}
|
||||
|
||||
$fee = $this->calcGatewayFee($amount, $gateway_type_id);
|
||||
|
||||
if ($fee > 0) {
|
||||
@ -268,7 +276,7 @@ class CompanyGateway extends BaseModel
|
||||
return $label;
|
||||
}
|
||||
|
||||
public function calcGatewayFee($amount, $include_taxes = false, $gateway_type_id = GatewayType::CREDIT_CARD)
|
||||
public function calcGatewayFee($amount, $gateway_type_id, $include_taxes = false)
|
||||
{
|
||||
$fees_and_limits = $this->getFeesAndLimits($gateway_type_id);
|
||||
|
||||
|
@ -51,6 +51,11 @@ class Expense extends BaseModel
|
||||
'custom_value3',
|
||||
'custom_value4',
|
||||
'number',
|
||||
'tax_amount1',
|
||||
'tax_amount2',
|
||||
'tax_amount3',
|
||||
'uses_inclusive_taxes',
|
||||
'amount_is_pretax',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -27,6 +27,11 @@ class PaymentHash extends Model
|
||||
return $this->data->invoices;
|
||||
}
|
||||
|
||||
public function credits_total()
|
||||
{
|
||||
return isset($this->data->credits) ? $this->data->credits : 0;
|
||||
}
|
||||
|
||||
public function payment()
|
||||
{
|
||||
return $this->belongsTo(Payment::class)->withTrashed();
|
||||
|
@ -29,5 +29,10 @@ class TaskStatus extends BaseModel
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
protected $fillable = ['name','color','status_order'];
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'color',
|
||||
'status_order',
|
||||
];
|
||||
|
||||
}
|
||||
|
@ -213,6 +213,9 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
|
||||
$this->attachInvoices($payment, $this->payment_hash);
|
||||
|
||||
if($this->payment_hash->credits_total() > 0)
|
||||
$payment = $payment->service()->applyCredits($this->payment_hash)->save();
|
||||
|
||||
$payment->service()->updateInvoicePayment($this->payment_hash);
|
||||
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
|
||||
|
@ -158,10 +158,13 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
}
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
PaymentFailureMailer::dispatch($this->client, $response->getMessage(), $this->client->company, $response['PAYMENTINFO_0_AMT']);
|
||||
|
||||
$data = $response->getData();
|
||||
|
||||
PaymentFailureMailer::dispatch($this->client, $response->getMessage(), $this->client->company, $this->payment_hash->data->amount);
|
||||
|
||||
$message = [
|
||||
'server_response' => $response->getMessage(),
|
||||
'server_response' => $data['L_LONGMESSAGE0'],
|
||||
'data' => $this->payment_hash->data,
|
||||
];
|
||||
|
||||
|
@ -97,7 +97,7 @@ class InvoiceMigrationRepository extends BaseRepository
|
||||
}
|
||||
|
||||
foreach ($data['invitations'] as $invitation) {
|
||||
nlog($invitation);
|
||||
// nlog($invitation);
|
||||
|
||||
$new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id);
|
||||
$new_invitation->{$lcfirst_resource_id} = $model->id;
|
||||
|
@ -41,7 +41,7 @@ class AddGatewayFee extends AbstractService
|
||||
|
||||
public function run()
|
||||
{
|
||||
$gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount, $this->invoice->uses_inclusive_taxes, $this->gateway_type_id), $this->invoice->client->currency()->precision);
|
||||
$gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount, $this->gateway_type_id, $this->invoice->uses_inclusive_taxes), $this->invoice->client->currency()->precision);
|
||||
|
||||
if ((int)$gateway_fee == 0) {
|
||||
return $this->invoice;
|
||||
|
@ -14,7 +14,6 @@ namespace App\Services\Invoice;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
@ -82,7 +81,7 @@ class AutoBillInvoice extends AbstractService
|
||||
}
|
||||
|
||||
/* $gateway fee */
|
||||
$fee = $gateway_token->gateway->calcGatewayFee($amount, $this->invoice->uses_inclusive_taxes);
|
||||
$fee = $gateway_token->gateway->calcGatewayFee($amount, $gateway_token->gateway_type_id, $this->invoice->uses_inclusive_taxes);
|
||||
|
||||
//todo determine exact fee as per PaymentController
|
||||
|
||||
|
@ -15,9 +15,12 @@ use App\Factory\PaymentFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PaymentService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
private $payment;
|
||||
|
||||
public function __construct($payment)
|
||||
@ -97,6 +100,43 @@ class PaymentService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applyCredits($payment_hash)
|
||||
{
|
||||
/* Iterate through the invoices and apply credits to them */
|
||||
collect($payment_hash->invoices())->each(function ($payable_invoice) use ($payment_hash) {
|
||||
|
||||
$invoice = Invoice::find($this->decodePrimaryKey($payable_invoice->invoice_id));
|
||||
|
||||
$amount = $payable_invoice->amount;
|
||||
|
||||
$credits = $payment_hash->fee_invoice
|
||||
->client
|
||||
->service()
|
||||
->getCredits();
|
||||
|
||||
foreach ($credits as $credit) {
|
||||
//starting invoice balance
|
||||
$invoice_balance = $invoice->balance;
|
||||
|
||||
//credit payment applied
|
||||
$credit->service()->applyPayment($invoice, $amount, $this->payment);
|
||||
|
||||
//amount paid from invoice calculated
|
||||
$remaining_balance = ($invoice_balance - $invoice->fresh()->balance);
|
||||
|
||||
//reduce the amount to be paid on the invoice from the NEXT credit
|
||||
$amount -= $remaining_balance;
|
||||
|
||||
//break if the invoice is no longer PAYABLE OR there is no more amount to be applied
|
||||
if (!$invoice->isPayable() || (int)$amount == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->payment->save();
|
||||
|
@ -39,6 +39,7 @@ class UpdateInvoicePayment
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get();
|
||||
|
||||
collect($paid_invoices)->each(function ($paid_invoice) use ($invoices) {
|
||||
|
||||
$invoice = $invoices->first(function ($inv) use ($paid_invoice) {
|
||||
return $paid_invoice->invoice_id == $inv->hashed_id;
|
||||
});
|
||||
@ -49,6 +50,11 @@ class UpdateInvoicePayment
|
||||
$paid_amount = $paid_invoice->amount;
|
||||
}
|
||||
|
||||
/* Need to determine here is we have an OVER payment - if YES only apply the max invoice amount */
|
||||
if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance)
|
||||
$paid_amount = $invoice->balance;
|
||||
|
||||
/* Updates the company ledger */
|
||||
$this->payment
|
||||
->ledger()
|
||||
->updatePaymentBalance($paid_amount * -1);
|
||||
|
@ -88,7 +88,7 @@ trait PdfMakerUtilities
|
||||
return $processed;
|
||||
}
|
||||
|
||||
public function updateElementProperty($element, string $attribute, string $value)
|
||||
public function updateElementProperty($element, string $attribute, ?string $value)
|
||||
{
|
||||
// We have exception for "hidden" property.
|
||||
// hidden="true" or hidden="false" will both hide the element,
|
||||
|
@ -12,6 +12,8 @@
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\SystemLog;
|
||||
use App\Transformers\SystemLogTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use stdClass;
|
||||
@ -33,6 +35,7 @@ class CompanyGatewayTransformer extends EntityTransformer
|
||||
* @var array
|
||||
*/
|
||||
protected $availableIncludes = [
|
||||
'system_logs',
|
||||
'gateway',
|
||||
];
|
||||
|
||||
@ -81,4 +84,11 @@ class CompanyGatewayTransformer extends EntityTransformer
|
||||
|
||||
return $this->includeItem($company_gateway->gateway, $transformer, Gateway::class);
|
||||
}
|
||||
|
||||
public function includeSystemLogs(CompanyGateway $company_gateway)
|
||||
{
|
||||
$transformer = new SystemLogTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company_gateway->system_logs(), $transformer, SystemLog::class);
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,9 @@ class CompanyTransformer extends EntityTransformer
|
||||
'default_task_is_date_based' => (bool)$company->default_task_is_date_based,
|
||||
'enable_product_discount' => (bool)$company->enable_product_discount,
|
||||
'calculate_expense_tax_by_amount' =>(bool)$company->calculate_expense_tax_by_amount,
|
||||
'hide_empty_columns_on_pdf' => (bool) $company->hide_empty_columns_on_pdf,
|
||||
'hide_empty_columns_on_pdf' => false, //@deprecate
|
||||
'expense_inclusive_taxes' => (bool)$company->expense_inclusive_taxes,
|
||||
'expense_amount_is_pretax' =>( bool)$company->expense_amount_is_pretax,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,11 @@ class ExpenseTransformer extends EntityTransformer
|
||||
'archived_at' => (int) $expense->deleted_at,
|
||||
'created_at' => (int) $expense->created_at,
|
||||
'project_id' => $this->encodePrimaryKey($expense->project_id),
|
||||
'tax_amount1' => (float) $expense->tax_amount1,
|
||||
'tax_amount2' => (float) $expense->tax_amount2,
|
||||
'tax_amount3' => (float) $expense->tax_amount3,
|
||||
'uses_inclusive_taxes' => (bool) $expense->uses_inclusive_taxes,
|
||||
'amount_is_pretax' => (bool) $expense->amount_is_pretax,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,11 @@ class HtmlEngine
|
||||
$data['$invoice.due_date'] = &$data['$due_date'];
|
||||
$data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')];
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format()), 'label' => ctrans('texts.date')];
|
||||
$data['$invoice.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$quote.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$credit.datetime'] = &$data['$entity.datetime'];
|
||||
|
||||
// $data['$line_taxes'] = ['value' => $this->makeLineTaxes() ?: ' ', 'label' => ctrans('texts.taxes')];
|
||||
// $data['$invoice.line_taxes'] = &$data['$line_taxes'];
|
||||
|
||||
|
@ -71,6 +71,17 @@ trait MakesDates
|
||||
return $date->format($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a datedate.
|
||||
* @param Carbon|string $date Carbon object or date string
|
||||
* @param string $format The date display format
|
||||
* @return string The formatted date
|
||||
*/
|
||||
public function formatDatetime($date, string $format) :string
|
||||
{
|
||||
return Carbon::createFromTimestamp($date)->format($format . " g:i a");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a date.
|
||||
* @param Carbon/String $date Carbon object or date string
|
||||
|
@ -604,7 +604,7 @@ trait MakesInvoiceValues
|
||||
if ($item->is_amount_discount) {
|
||||
$data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->client);
|
||||
} else {
|
||||
$data[$key][$table_type.'.discount'] = $item->discount.'%';
|
||||
$data[$key][$table_type.'.discount'] = floatval($item->discount).'%';
|
||||
}
|
||||
} else {
|
||||
$data[$key][$table_type.'.discount'] = '';
|
||||
|
@ -68,6 +68,7 @@
|
||||
"webpatser/laravel-countries": "dev-master#75992ad"
|
||||
},
|
||||
"require-dev": {
|
||||
"php": "^7.4",
|
||||
"anahkiasen/former": "^4.2",
|
||||
"barryvdh/laravel-debugbar": "^3.4",
|
||||
"darkaonline/l5-swagger": "^8.0",
|
||||
@ -78,6 +79,7 @@
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"spatie/laravel-ray": "^1.3",
|
||||
"vimeo/psalm": "^4.0",
|
||||
"wildbit/postmark-php": "^4.0"
|
||||
},
|
||||
|
521
composer.lock
generated
521
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', ''),
|
||||
'app_version' => '5.0.44',
|
||||
'app_version' => '5.0.45',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
@ -41,8 +41,8 @@ class InvoiceFactory extends Factory
|
||||
'tax_rate2' => 17.5,
|
||||
//'tax_name3' => 'THIRDTAX',
|
||||
//'tax_rate3' => 5,
|
||||
'custom_value1' => $this->faker->date,
|
||||
'custom_value2' => rand(0, 1) ? 'yes' : 'no',
|
||||
// 'custom_value1' => $this->faker->date,
|
||||
//'custom_value2' => rand(0, 1) ? 'yes' : 'no',
|
||||
// 'custom_value3' => $this->faker->numberBetween(1,4),
|
||||
// 'custom_value4' => $this->faker->numberBetween(1,4),
|
||||
'is_deleted' => false,
|
||||
|
@ -31,6 +31,7 @@ class TaskStatusFactory extends Factory
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->text(7),
|
||||
'color' => '#fff',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +144,9 @@ class ImproveDecimalResolution extends Migration
|
||||
$table->integer('status_order')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->dropColumn('hide_empty_columns_on_pdf');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateSingaporeDollarSymbol extends Migration
|
||||
{
|
||||
use AppSetup;
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$currency = Currency::find(13);
|
||||
|
||||
if ($currency) {
|
||||
$currency->update(['symbol' => '$']);
|
||||
}
|
||||
|
||||
$this->buildCache(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ExpensesTableAdditionalFields extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('expenses', function (Blueprint $table) {
|
||||
$table->decimal('tax_amount1', 20, 6)->default();
|
||||
$table->decimal('tax_amount2', 20, 6)->default();
|
||||
$table->decimal('tax_amount3', 20, 6)->default();
|
||||
$table->boolean('uses_inclusive_taxes')->default(0);
|
||||
$table->boolean('amount_is_pretax')->default(1);
|
||||
});
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('expense_inclusive_taxes')->default(0);
|
||||
$table->boolean('expense_amount_is_pretax')->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
2
public/flutter_service_worker.js
vendored
2
public/flutter_service_worker.js
vendored
@ -31,7 +31,7 @@ const RESOURCES = {
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
||||
"/": "23224b5e03519aaa87594403d54412cf",
|
||||
"version.json": "1a0a7ed91cd721a8aad41df0c5f792b3",
|
||||
"main.dart.js": "dfd8a0dd3626cf7b7858e5cbd6e56a33",
|
||||
"main.dart.js": "3da75d88201ca57797d16afdf5b37442",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
||||
};
|
||||
|
||||
|
123128
public/main.dart.js
vendored
123128
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -3362,4 +3362,8 @@ return [
|
||||
'currency_albanian_lek' => 'Albanian Lek',
|
||||
|
||||
'endless' => 'Endless',
|
||||
'minimum_payment' => 'Minimum Payment',
|
||||
|
||||
'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.',
|
||||
'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.',
|
||||
];
|
||||
|
@ -99,8 +99,8 @@
|
||||
|
||||
function invokeServiceWorkerUpdateFlow() {
|
||||
// you have a better UI here, reloading is not a great user experince here.
|
||||
const confirmed = confirm('New version of the app is available. Refresh now');
|
||||
if (confirmed) {
|
||||
const confirmed = alert('New version of the app is available. Refresh now');
|
||||
if (confirmed == true) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace Tests\Feature;
|
||||
|
||||
use App\DataMapper\FeesAndLimits;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\GatewayType;
|
||||
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@ -197,7 +198,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(10, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(10, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentCalcuation()
|
||||
@ -230,7 +231,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(0.2, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(0.2, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountCalcuation()
|
||||
@ -263,7 +264,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(10.2, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(10.2, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndTaxCalcuation()
|
||||
@ -296,7 +297,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(11, $company_gateway->calcGatewayFee(10, true));
|
||||
$this->assertEquals(11, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD, true));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndTaxCalcuationInclusiveTaxes()
|
||||
@ -329,7 +330,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(10, $company_gateway->calcGatewayFee(10));
|
||||
$this->assertEquals(10, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndDoubleTaxCalcuation()
|
||||
@ -364,7 +365,7 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(12, $company_gateway->calcGatewayFee(10, true));
|
||||
$this->assertEquals(12, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD, true));
|
||||
}
|
||||
|
||||
public function testFeesAndLimitsFeePercentAndAmountAndDoubleTaxCalcuationWithFeeCap()
|
||||
@ -400,6 +401,6 @@ class CompanyGatewayApiTest extends TestCase
|
||||
|
||||
$company_gateway = CompanyGateway::find($id);
|
||||
|
||||
$this->assertEquals(1.2, $company_gateway->calcGatewayFee(10, true));
|
||||
$this->assertEquals(1.2, $company_gateway->calcGatewayFee(10, GatewayType::CREDIT_CARD, true));
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +102,10 @@ class CompanyGatewayResolutionTest extends TestCase
|
||||
*/
|
||||
public function testGatewayResolution()
|
||||
{
|
||||
$fee = $this->cg->calcGatewayFee(10, false, GatewayType::CREDIT_CARD);
|
||||
$fee = $this->cg->calcGatewayFee(10, GatewayType::CREDIT_CARD, false);
|
||||
$this->assertEquals(0.2, $fee);
|
||||
$fee = $this->cg->calcGatewayFee(10, false, GatewayType::BANK_TRANSFER);
|
||||
$this->assertEquals(0.1, $fee);
|
||||
// $fee = $this->cg->calcGatewayFee(10, GatewayType::CREDIT_CARD, false);
|
||||
// $this->assertEquals(0.1, $fee);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,7 +181,7 @@ class CompanyGatewayTest extends TestCase
|
||||
|
||||
$total = 10.93;
|
||||
$total_invoice_count = 5;
|
||||
$total_gateway_fee = round($cg->calcGatewayFee($total, true, GatewayType::CREDIT_CARD), 2);
|
||||
$total_gateway_fee = round($cg->calcGatewayFee($total, GatewayType::CREDIT_CARD, true), 2);
|
||||
|
||||
$this->assertEquals(1.58, $total_gateway_fee);
|
||||
|
||||
|
@ -540,7 +540,7 @@ trait MockAccountData
|
||||
$data[1]['fee_tax_rate2'] = '';
|
||||
$data[1]['fee_tax_name3'] = '';
|
||||
$data[1]['fee_tax_rate3'] = 0;
|
||||
|
||||
$data[1]['fee_cap'] = '';
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $this->company->id;
|
||||
$cg->user_id = $this->user->id;
|
||||
|
Loading…
x
Reference in New Issue
Block a user