mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 23:54:30 -04:00
Merge pull request #6593 from turbo124/v5-develop
Add withTrashed() when searching for MultiDB users
This commit is contained in:
commit
c9ea2ceaf3
@ -49,7 +49,7 @@ class InvoiceFactory
|
|||||||
$invoice->user_id = $user_id;
|
$invoice->user_id = $user_id;
|
||||||
$invoice->company_id = $company_id;
|
$invoice->company_id = $company_id;
|
||||||
$invoice->recurring_id = null;
|
$invoice->recurring_id = null;
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,18 +67,6 @@ class InvoiceFilters extends QueryFilters
|
|||||||
return $this->builder;
|
return $this->builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function client_id(string $client_id = '') :Builder
|
|
||||||
{
|
|
||||||
if (strlen($client_id) == 0) {
|
|
||||||
return $this->builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->builder->where('client_id', $this->decodePrimaryKey($client_id));
|
|
||||||
|
|
||||||
return $this->builder;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function number(string $number) :Builder
|
public function number(string $number) :Builder
|
||||||
{
|
{
|
||||||
return $this->builder->where('number', $number);
|
return $this->builder->where('number', $number);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Filters;
|
namespace App\Filters;
|
||||||
|
|
||||||
//use Illuminate\Database\Query\Builder;
|
//use Illuminate\Database\Query\Builder;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ use Illuminate\Http\Request;
|
|||||||
*/
|
*/
|
||||||
abstract class QueryFilters
|
abstract class QueryFilters
|
||||||
{
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* active status.
|
* active status.
|
||||||
*/
|
*/
|
||||||
@ -177,6 +180,18 @@ abstract class QueryFilters
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function client_id(string $client_id = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($client_id) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->builder->where('client_id', $this->decodePrimaryKey($client_id));
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function filter_deleted_clients($value)
|
public function filter_deleted_clients($value)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -34,11 +34,6 @@ class SystemLogFilters extends QueryFilters
|
|||||||
return $this->builder->where('event_id', $event_id);
|
return $this->builder->where('event_id', $event_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function client_id(int $client_id) :Builder
|
|
||||||
{
|
|
||||||
return $this->builder->where('client_id', $client_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter based on search text.
|
* Filter based on search text.
|
||||||
*
|
*
|
||||||
|
@ -30,7 +30,7 @@ class InvoiceSum
|
|||||||
|
|
||||||
public $invoice_item;
|
public $invoice_item;
|
||||||
|
|
||||||
public $total_taxes;
|
public $total_taxes = 0;
|
||||||
|
|
||||||
private $total;
|
private $total;
|
||||||
|
|
||||||
|
@ -20,7 +20,9 @@ use App\Utils\Number;
|
|||||||
use App\Utils\TempFile;
|
use App\Utils\TempFile;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use ZipStream\Option\Archive;
|
use ZipStream\Option\Archive;
|
||||||
use ZipStream\ZipStream;
|
use ZipStream\ZipStream;
|
||||||
@ -86,6 +88,10 @@ class InvoiceController extends Controller
|
|||||||
->with('message', ctrans('texts.no_action_provided'));
|
->with('message', ctrans('texts.no_action_provided'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $ids
|
||||||
|
* @return Factory|View|RedirectResponse
|
||||||
|
*/
|
||||||
private function makePayment(array $ids)
|
private function makePayment(array $ids)
|
||||||
{
|
{
|
||||||
$invoices = Invoice::whereIn('id', $ids)
|
$invoices = Invoice::whereIn('id', $ids)
|
||||||
@ -119,8 +125,8 @@ class InvoiceController extends Controller
|
|||||||
//format data
|
//format data
|
||||||
$invoices->map(function ($invoice) {
|
$invoices->map(function ($invoice) {
|
||||||
$invoice->service()->removeUnpaidGatewayFees()->save();
|
$invoice->service()->removeUnpaidGatewayFees()->save();
|
||||||
$invoice->balance = Number::formatValue($invoice->balance, $invoice->client->currency());
|
$invoice->balance = $invoice->balance > 0 ? Number::formatValue($invoice->balance, $invoice->client->currency()) : 0;
|
||||||
$invoice->partial = Number::formatValue($invoice->partial, $invoice->client->currency());
|
$invoice->partial = $invoice->partial > 0 ? Number::formatValue($invoice->partial, $invoice->client->currency()) : 0;
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
});
|
});
|
||||||
|
@ -60,6 +60,8 @@ class StoreUserRequest extends Request
|
|||||||
|
|
||||||
//unique user rule - check company_user table for user_id / company_id / account_id if none exist we can add the user. ELSE return false
|
//unique user rule - check company_user table for user_id / company_id / account_id if none exist we can add the user. ELSE return false
|
||||||
|
|
||||||
|
if(array_key_exists('email', $input))
|
||||||
|
$input['email'] = trim($input['email']);
|
||||||
|
|
||||||
if (isset($input['company_user'])) {
|
if (isset($input['company_user'])) {
|
||||||
if (! isset($input['company_user']['is_admin'])) {
|
if (! isset($input['company_user']['is_admin'])) {
|
||||||
|
@ -45,6 +45,8 @@ class UpdateUserRequest extends Request
|
|||||||
{
|
{
|
||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
|
if(array_key_exists('email', $input))
|
||||||
|
$input['email'] = trim($input['email']);
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class BaseTransformer
|
|||||||
public function getClient($client_name, $client_email) {
|
public function getClient($client_name, $client_email) {
|
||||||
$clients = $this->maps['company']->clients;
|
$clients = $this->maps['company']->clients;
|
||||||
|
|
||||||
$clients = $clients->where( 'name', $client_name );
|
$clients = $clients->where( 'id_number', $client_name );
|
||||||
|
|
||||||
if ( $clients->count() >= 1 ) {
|
if ( $clients->count() >= 1 ) {
|
||||||
return $clients->first()->id;
|
return $clients->first()->id;
|
||||||
|
@ -42,7 +42,7 @@ class ClientTransformer extends BaseTransformer {
|
|||||||
'work_phone' => $this->getString( $data, 'Phone' ),
|
'work_phone' => $this->getString( $data, 'Phone' ),
|
||||||
'private_notes' => $this->getString( $data, 'Notes' ),
|
'private_notes' => $this->getString( $data, 'Notes' ),
|
||||||
'website' => $this->getString( $data, 'Website' ),
|
'website' => $this->getString( $data, 'Website' ),
|
||||||
|
'id_number' => $this->getString( $data, 'Customer ID'),
|
||||||
'address1' => $this->getString( $data, 'Billing Address' ),
|
'address1' => $this->getString( $data, 'Billing Address' ),
|
||||||
'address2' => $this->getString( $data, 'Billing Street2' ),
|
'address2' => $this->getString( $data, 'Billing Street2' ),
|
||||||
'city' => $this->getString( $data, 'Billing City' ),
|
'city' => $this->getString( $data, 'Billing City' ),
|
||||||
|
@ -38,7 +38,7 @@ class InvoiceTransformer extends BaseTransformer {
|
|||||||
|
|
||||||
$transformed = [
|
$transformed = [
|
||||||
'company_id' => $this->maps['company']->id,
|
'company_id' => $this->maps['company']->id,
|
||||||
'client_id' => $this->getClient( $this->getString( $invoice_data, 'Company Name' ), null ),
|
'client_id' => $this->getClient( $this->getString( $invoice_data, 'Customer ID' ), null ),
|
||||||
'number' => $this->getString( $invoice_data, 'Invoice Number' ),
|
'number' => $this->getString( $invoice_data, 'Invoice Number' ),
|
||||||
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Invoice Date'] ) ) : null,
|
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Invoice Date'] ) ) : null,
|
||||||
'due_date' => isset( $invoice_data['Due Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Due Date'] ) ) : null,
|
'due_date' => isset( $invoice_data['Due Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Due Date'] ) ) : null,
|
||||||
@ -59,7 +59,7 @@ class InvoiceTransformer extends BaseTransformer {
|
|||||||
'notes' => $this->getString( $record, 'Item Description' ),
|
'notes' => $this->getString( $record, 'Item Description' ),
|
||||||
'cost' => $this->getFloat( $record, 'Item Price' ),
|
'cost' => $this->getFloat( $record, 'Item Price' ),
|
||||||
'quantity' => $this->getFloat( $record, 'Quantity' ),
|
'quantity' => $this->getFloat( $record, 'Quantity' ),
|
||||||
'discount' => $this->getFloat( $record, 'Discount Amount' ),
|
'discount' => $this->getString( $record, 'Discount Amount' ),
|
||||||
'is_amount_discount' => true,
|
'is_amount_discount' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@ class AutoBillCron
|
|||||||
|
|
||||||
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill");
|
nlog($auto_bill_partial_invoices->count(). " partial invoices to auto bill");
|
||||||
|
|
||||||
$auto_bill_partial_invoices->cursor()->each(function ($invoice) use($db){
|
$auto_bill_partial_invoices->cursor()->each(function ($invoice){
|
||||||
$this->runAutoBiller($invoice, $db);
|
$this->runAutoBiller($invoice, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||||
@ -69,8 +69,8 @@ class AutoBillCron
|
|||||||
|
|
||||||
nlog($auto_bill_invoices->count(). " full invoices to auto bill");
|
nlog($auto_bill_invoices->count(). " full invoices to auto bill");
|
||||||
|
|
||||||
$auto_bill_invoices->cursor()->each(function ($invoice) use($db){
|
$auto_bill_invoices->cursor()->each(function ($invoice){
|
||||||
$this->runAutoBiller($invoice, $db);
|
$this->runAutoBiller($invoice, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -115,8 +115,12 @@ class AutoBillCron
|
|||||||
info("Firing autobill for {$invoice->company_id} - {$invoice->number}");
|
info("Firing autobill for {$invoice->company_id} - {$invoice->number}");
|
||||||
|
|
||||||
try{
|
try{
|
||||||
MultiDB::setDB($db);
|
|
||||||
|
if($db)
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
|
||||||
$invoice->service()->autoBill()->save();
|
$invoice->service()->autoBill()->save();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(\Exception $e) {
|
catch(\Exception $e) {
|
||||||
nlog("Failed to capture payment for {$invoice->company_id} - {$invoice->number} ->" . $e->getMessage());
|
nlog("Failed to capture payment for {$invoice->company_id} - {$invoice->number} ->" . $e->getMessage());
|
||||||
|
@ -30,6 +30,7 @@ use App\Providers\MailServiceProvider;
|
|||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use Dacastro4\LaravelGmail\Facade\LaravelGmail;
|
use Dacastro4\LaravelGmail\Facade\LaravelGmail;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@ -118,10 +119,18 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
|
|
||||||
nlog("error failed with {$e->getMessage()}");
|
nlog("error failed with {$e->getMessage()}");
|
||||||
|
|
||||||
if($this->nmo->entity)
|
$message = $e->getMessage();
|
||||||
$this->entityEmailFailed($e->getMessage());
|
|
||||||
|
|
||||||
if(Ninja::isHosted())
|
if($e instanceof ClientException) { //postmark specific failure
|
||||||
|
|
||||||
|
$response = $e->getResponse();
|
||||||
|
$message = $response->Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->nmo->entity)
|
||||||
|
$this->entityEmailFailed($message);
|
||||||
|
|
||||||
|
if(Ninja::isHosted() && (!$e instanceof ClientException)) // Don't send postmark failures to Sentry
|
||||||
app('sentry')->captureException($e);
|
app('sentry')->captureException($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,6 +250,7 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
|
|
||||||
private function logMailError($errors, $recipient_object)
|
private function logMailError($errors, $recipient_object)
|
||||||
{
|
{
|
||||||
|
|
||||||
SystemLogger::dispatch(
|
SystemLogger::dispatch(
|
||||||
$errors,
|
$errors,
|
||||||
SystemLog::CATEGORY_MAIL,
|
SystemLog::CATEGORY_MAIL,
|
||||||
@ -249,19 +259,18 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
$recipient_object,
|
$recipient_object,
|
||||||
$this->nmo->company
|
$this->nmo->company
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
public function failed($exception = null)
|
|
||||||
{
|
|
||||||
nlog('mailer job failed');
|
|
||||||
nlog($exception->getMessage());
|
|
||||||
|
|
||||||
$job_failure = new EmailFailure($this->nmo->company->company_key);
|
$job_failure = new EmailFailure($this->nmo->company->company_key);
|
||||||
$job_failure->string_metric5 = 'failed_email';
|
$job_failure->string_metric5 = 'failed_email';
|
||||||
$job_failure->string_metric6 = substr($exception->getMessage(), 0, 150);
|
$job_failure->string_metric6 = substr($errors, 0, 150);
|
||||||
|
|
||||||
LightLogs::create($job_failure)
|
LightLogs::create($job_failure)
|
||||||
->batch();
|
->batch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function failed($exception = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -147,6 +147,8 @@ class SendRecurring implements ShouldQueue
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//important catch all here - we should never leave contacts send_email to false incase they are permanently set to false in the future.
|
||||||
|
$this->recurring_invoice->client->contacts()->update(['send_email' => true]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,12 +73,12 @@ class MultiDB
|
|||||||
public static function checkUserEmailExists($email) : bool
|
public static function checkUserEmailExists($email) : bool
|
||||||
{
|
{
|
||||||
if (! config('ninja.db.multi_db_enabled'))
|
if (! config('ninja.db.multi_db_enabled'))
|
||||||
return User::where(['email' => $email])->exists(); // true >= 1 emails found / false -> == emails found
|
return User::where(['email' => $email])->withTrashed()->exists(); // true >= 1 emails found / false -> == emails found
|
||||||
|
|
||||||
$current_db = config('database.default');
|
$current_db = config('database.default');
|
||||||
|
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
if (User::on($db)->where(['email' => $email])->exists()) { // if user already exists, validation will fail
|
if (User::on($db)->where(['email' => $email])->withTrashed()->exists()) { // if user already exists, validation will fail
|
||||||
self::setDb($current_db);
|
self::setDb($current_db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ class MultiDB
|
|||||||
$current_db = config('database.default');
|
$current_db = config('database.default');
|
||||||
|
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
if (User::on($db)->where(['email' => $email])->exists()) {
|
if (User::on($db)->where(['email' => $email])->withTrashed()->exists()) {
|
||||||
if (Company::on($db)->where(['company_key' => $company_key])->exists()) {
|
if (Company::on($db)->where(['company_key' => $company_key])->exists()) {
|
||||||
self::setDb($current_db);
|
self::setDb($current_db);
|
||||||
return true;
|
return true;
|
||||||
@ -196,7 +196,7 @@ class MultiDB
|
|||||||
//multi-db active
|
//multi-db active
|
||||||
foreach (self::$dbs as $db) {
|
foreach (self::$dbs as $db) {
|
||||||
|
|
||||||
if (User::on($db)->where('email', $email)->exists()){
|
if (User::on($db)->where('email', $email)->withTrashed()->exists()){
|
||||||
self::setDb($db);
|
self::setDb($db);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -478,7 +478,7 @@ class BaseDriver extends AbstractPaymentDriver
|
|||||||
$message,
|
$message,
|
||||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||||
$this::SYSTEM_LOG_TYPE,
|
SystemLog::TYPE_PAYTRACE,
|
||||||
$this->client,
|
$this->client,
|
||||||
$this->client->company,
|
$this->client->company,
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,8 @@ class BaseRepository
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
use SavesDocuments;
|
use SavesDocuments;
|
||||||
public $import_mode = false;
|
|
||||||
|
public $import_mode = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $entity
|
* @param $entity
|
||||||
|
@ -71,6 +71,7 @@ class ClientContactRepository extends BaseRepository
|
|||||||
$client->company->client_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]);
|
$client->company->client_contacts()->where('email', $update_contact->email)->update(['password' => $update_contact->password]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$update_contact->email = trim($contact['email']);
|
||||||
$update_contact->save();
|
$update_contact->save();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -170,10 +170,10 @@ class PaymentRepository extends BaseRepository {
|
|||||||
event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) );
|
event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
nlog("payment amount = {$payment->amount}");
|
// nlog("payment amount = {$payment->amount}");
|
||||||
nlog("payment applied = {$payment->applied}");
|
// nlog("payment applied = {$payment->applied}");
|
||||||
nlog("invoice totals = {$invoice_totals}");
|
// nlog("invoice totals = {$invoice_totals}");
|
||||||
nlog("credit totals = {$credit_totals}");
|
// nlog("credit totals = {$credit_totals}");
|
||||||
|
|
||||||
$payment->applied += ($invoice_totals - $credit_totals); //wont work because - check tests
|
$payment->applied += ($invoice_totals - $credit_totals); //wont work because - check tests
|
||||||
// $payment->applied += $invoice_totals; //wont work because - check tests
|
// $payment->applied += $invoice_totals; //wont work because - check tests
|
||||||
|
@ -84,25 +84,41 @@ class DeletePayment
|
|||||||
|
|
||||||
nlog("net deletable amount - refunded = {$net_deletable}");
|
nlog("net deletable amount - refunded = {$net_deletable}");
|
||||||
|
|
||||||
$paymentable_invoice->service()
|
if(!$paymentable_invoice->is_deleted)
|
||||||
->updateBalance($net_deletable)
|
{
|
||||||
->updatePaidToDate($net_deletable * -1)
|
$paymentable_invoice->service()
|
||||||
->save();
|
->updateBalance($net_deletable)
|
||||||
|
->updatePaidToDate($net_deletable * -1)
|
||||||
|
->save();
|
||||||
|
|
||||||
$paymentable_invoice->ledger()
|
$paymentable_invoice->ledger()
|
||||||
->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}")
|
->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}")
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
$paymentable_invoice->client
|
$paymentable_invoice->client
|
||||||
->service()
|
->service()
|
||||||
->updateBalance($net_deletable)
|
->updateBalance($net_deletable)
|
||||||
->updatePaidToDate($net_deletable * -1)
|
->updatePaidToDate($net_deletable * -1)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
if ($paymentable_invoice->balance == $paymentable_invoice->amount) {
|
if ($paymentable_invoice->balance == $paymentable_invoice->amount) {
|
||||||
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
|
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
|
||||||
} else {
|
} else {
|
||||||
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
|
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
//If the invoice is deleted we only update the meta data on the invoice
|
||||||
|
//and reduce the clients paid to date
|
||||||
|
$paymentable_invoice->service()
|
||||||
|
->updatePaidToDate($net_deletable * -1)
|
||||||
|
->save();
|
||||||
|
|
||||||
|
// $paymentable_invoice->client
|
||||||
|
// ->service()
|
||||||
|
// ->updatePaidToDate($net_deletable * -1)
|
||||||
|
// ->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -270,6 +270,8 @@ trait MakesInvoiceValues
|
|||||||
if (! is_array($items)) {
|
if (! is_array($items)) {
|
||||||
$data;
|
$data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$locale_info = localeconv();
|
||||||
|
|
||||||
foreach ($items as $key => $item) {
|
foreach ($items as $key => $item) {
|
||||||
if ($table_type == '$product' && $item->type_id != 1) {
|
if ($table_type == '$product' && $item->type_id != 1) {
|
||||||
@ -301,8 +303,10 @@ trait MakesInvoiceValues
|
|||||||
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client);
|
$data[$key][$table_type . ".{$_table_type}3"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->client);
|
||||||
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client);
|
$data[$key][$table_type . ".{$_table_type}4"] = $helpers->formatCustomFieldValue($this->client->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->client);
|
||||||
|
|
||||||
$data[$key][$table_type.'.quantity'] = Number::formatValue($item->quantity, $this->client->currency());
|
//$data[$key][$table_type.'.quantity'] = Number::formatValue($item->quantity, $this->client->currency());
|
||||||
|
|
||||||
|
//change quantity from localized number, to decimal format with no trailing zeroes 06/09/21
|
||||||
|
$data[$key][$table_type.'.quantity'] = rtrim($item->quantity, $locale_info['decimal_point']);
|
||||||
$data[$key][$table_type.'.unit_cost'] = Number::formatMoney($item->cost, $this->client);
|
$data[$key][$table_type.'.unit_cost'] = Number::formatMoney($item->cost, $this->client);
|
||||||
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
|
$data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->client);
|
||||||
|
|
||||||
@ -483,7 +487,7 @@ trait MakesInvoiceValues
|
|||||||
$output = (int)$raw - (int)$_value[1]; // 1 (:MONTH) - 4
|
$output = (int)$raw - (int)$_value[1]; // 1 (:MONTH) - 4
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($_operation == '/') {
|
if ($_operation == '/' && (int)$_value[1] != 0) {
|
||||||
$output = (int)$raw / (int)$_value[1]; // 1 (:MONTH) / 4
|
$output = (int)$raw / (int)$_value[1]; // 1 (:MONTH) / 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
376
tests/Feature/MultiPaymentDeleteTest.php
Normal file
376
tests/Feature/MultiPaymentDeleteTest.php
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
<?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://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Factory\CompanyTokenFactory;
|
||||||
|
use App\Factory\CompanyUserFactory;
|
||||||
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Factory\InvoiceItemFactory;
|
||||||
|
use App\Models\Account;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\CompanyToken;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Payment;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
class MultiPaymentDeleteTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions, MakesHash;
|
||||||
|
|
||||||
|
private $faker;
|
||||||
|
|
||||||
|
public function setUp() :void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->faker = \Faker\Factory::create();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testComplexRefundDeleteScenario()
|
||||||
|
{
|
||||||
|
$account = Account::factory()->create();
|
||||||
|
$company = Company::factory()->create([
|
||||||
|
'account_id' => $account->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$account->default_company_id = $company->id;
|
||||||
|
$account->save();
|
||||||
|
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'account_id' => $account->id,
|
||||||
|
'confirmation_code' => '11',
|
||||||
|
'email' => $this->faker->unique()->safeEmail,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cu = CompanyUserFactory::create($user->id, $company->id, $account->id);
|
||||||
|
$cu->is_owner = true;
|
||||||
|
$cu->is_admin = true;
|
||||||
|
$cu->save();
|
||||||
|
|
||||||
|
$token = new CompanyToken;
|
||||||
|
$token->user_id = $user->id;
|
||||||
|
$token->company_id = $company->id;
|
||||||
|
$token->account_id = $account->id;
|
||||||
|
$token->name = 'test token';
|
||||||
|
$token->token = 'okeytokey';
|
||||||
|
$token->is_system = true;
|
||||||
|
$token->save();
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'company_id' => $company->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ClientContact::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'is_primary' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ClientContact::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'company_id' => $company->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'company_id' => $company->id,
|
||||||
|
'number' => (string)$this->faker->randomNumber(6),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = InvoiceFactory::create($company->id,$user->id);
|
||||||
|
$invoice->client_id = $client->id;
|
||||||
|
$invoice->status_id = Invoice::STATUS_DRAFT;
|
||||||
|
|
||||||
|
$line_items = [];
|
||||||
|
|
||||||
|
$item = InvoiceItemFactory::create();
|
||||||
|
$item->quantity = 1;
|
||||||
|
$item->cost = 325;
|
||||||
|
$item->type_id = 1;
|
||||||
|
|
||||||
|
$line_items[] = $item;
|
||||||
|
|
||||||
|
$invoice->line_items = $line_items;
|
||||||
|
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
|
||||||
|
$this->assertEquals(0, $client->balance);
|
||||||
|
$this->assertEquals(0, $client->paid_to_date);
|
||||||
|
$this->assertEquals(0, $invoice->balance);
|
||||||
|
//mark sent
|
||||||
|
|
||||||
|
$invoice = $invoice->service()->markSent()->save();
|
||||||
|
|
||||||
|
$invoice->fresh();
|
||||||
|
$invoice->client->fresh();
|
||||||
|
|
||||||
|
$this->assertEquals(325, $invoice->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->paid_to_date);
|
||||||
|
$this->assertEquals(325, $invoice->client->balance);
|
||||||
|
|
||||||
|
//payment 163
|
||||||
|
//
|
||||||
|
$data = [
|
||||||
|
'amount' => 163.0,
|
||||||
|
'client_id' => $this->encodePrimaryKey($client->id),
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $this->encodePrimaryKey($invoice->id),
|
||||||
|
'amount' => 163,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date' => '2019/12/12',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/', $data);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
$payment_id = $arr['data']['id'];
|
||||||
|
$payment_1 = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
|
||||||
|
|
||||||
|
//payment 162
|
||||||
|
$this->assertEquals(162, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(162, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(163, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'amount' => 162.0,
|
||||||
|
'client_id' => $this->encodePrimaryKey($client->id),
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $this->encodePrimaryKey($invoice->id),
|
||||||
|
'amount' => 162,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date' => '2019/12/12',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/', $data);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
$payment_id = $arr['data']['id'];
|
||||||
|
$payment_2 = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(325, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//refund payment 2 by 63 dollars
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $this->encodePrimaryKey($payment_2->id),
|
||||||
|
'amount' => 63,
|
||||||
|
'date' => '2021/12/12',
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $invoice->hashed_id,
|
||||||
|
'amount' => 63,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/refund', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(63, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(63, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(262, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//delete payment 2
|
||||||
|
//
|
||||||
|
$data = [
|
||||||
|
'ids' => [$this->encodePrimaryKey($payment_2->id)],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/bulk?action=delete', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(162, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(162, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(163, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
// Pay 162 again and create payment #3
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'amount' => 162.0,
|
||||||
|
'client_id' => $this->encodePrimaryKey($client->id),
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $this->encodePrimaryKey($invoice->id),
|
||||||
|
'amount' => 162,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date' => '2019/12/12',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/', $data);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
$payment_id = $arr['data']['id'];
|
||||||
|
$payment_3 = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
|
||||||
|
|
||||||
|
$invoice->fresh();
|
||||||
|
$invoice->client->fresh();
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(325, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//refund payment 3 by 63
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $this->encodePrimaryKey($payment_3->id),
|
||||||
|
'amount' => 63,
|
||||||
|
'date' => '2021/12/12',
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $invoice->hashed_id,
|
||||||
|
'amount' => 63,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/refund', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(63, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(63, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(262, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
//payment 4 for 63
|
||||||
|
$data = [
|
||||||
|
'amount' => 63.0,
|
||||||
|
'client_id' => $this->encodePrimaryKey($client->id),
|
||||||
|
'invoices' => [
|
||||||
|
[
|
||||||
|
'invoice_id' => $this->encodePrimaryKey($invoice->id),
|
||||||
|
'amount' => 63,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date' => '2019/12/12',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/', $data);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
$payment_id = $arr['data']['id'];
|
||||||
|
$payment_4 = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(325, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
// delete payment 3
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
$data = [
|
||||||
|
'ids' => [$this->encodePrimaryKey($payment_4->id)],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/bulk?action=delete', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(63, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(63, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(262, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//set discount of 63 to invoice
|
||||||
|
|
||||||
|
$invoice = $invoice->fresh();
|
||||||
|
$invoice->discount = 63;
|
||||||
|
$invoice->is_amount_discount = true;
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
$invoice->calc()->getInvoice()->save();
|
||||||
|
$invoice->service()->updateStatus()->save();
|
||||||
|
$invoice->ledger()->updateInvoiceBalance(-63, "Update adjustment for invoice {$invoice->number}");
|
||||||
|
$invoice->client->service()->updateBalance(-63)->save();
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(262, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//now delete the invoice
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'ids' => [$invoice->hashed_id],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/invoices/bulk?action=delete', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
|
||||||
|
//Delete payment 4 which is for $162
|
||||||
|
$data = [
|
||||||
|
'ids' => [$this->encodePrimaryKey($payment_1->id)],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $token->token,
|
||||||
|
])->post('/api/v1/payments/bulk?action=delete', $data);
|
||||||
|
|
||||||
|
$this->assertEquals(0, $invoice->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->balance);
|
||||||
|
$this->assertEquals(0, $invoice->client->fresh()->paid_to_date);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user