diff --git a/VERSION.txt b/VERSION.txt index 9c4a47d193..2cc73c6438 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.22 \ No newline at end of file +5.7.23 \ No newline at end of file diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index db95dbb354..ebde417313 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -83,6 +83,7 @@ class BaseExport 'contact_custom_value4' => 'vendor_contact.custom_value4', 'email' => 'vendor_contact.email', 'status' => 'vendor.status', + 'classification' => 'vendor.classification', ]; protected array $client_report_keys = [ @@ -125,7 +126,9 @@ class BaseExport "contact_custom_value2" => "contact.custom_value2", "contact_custom_value3" => "contact.custom_value3", "contact_custom_value4" => "contact.custom_value4", - + 'payment_balance' => 'client.payment_balance', + 'credit_balance' => 'client.credit_balance', + 'classification' => 'client.classification', ]; protected array $invoice_report_keys = [ diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index ce070b5db6..981f051f94 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -71,7 +71,11 @@ class ClientExport extends BaseExport 'contact_custom_value3' => 'contact.custom_value3', 'contact_custom_value4' => 'contact.custom_value4', 'email' => 'contact.email', - 'status' => 'status' + 'status' => 'status', + 'payment_balance' => 'client.payment_balance', + 'credit_balance' => 'client.credit_balance', + 'classification' => 'client.classification', + ]; public function __construct(Company $company, array $input) @@ -223,6 +227,10 @@ class ClientExport extends BaseExport $entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : ''; } + if (in_array('client.classification', $this->input['report_keys']) && isset($client->classification)) { + $entity['client.classification'] = ctrans("texts.{$client->classification}") ?? ''; + } + return $entity; } diff --git a/app/Export/CSV/VendorExport.php b/app/Export/CSV/VendorExport.php index 74e9785b2f..805837ea07 100644 --- a/app/Export/CSV/VendorExport.php +++ b/app/Export/CSV/VendorExport.php @@ -139,7 +139,11 @@ class VendorExport extends BaseExport $entity['currency'] = $vendor->currency() ? $vendor->currency()->code : $vendor->company->currency()->code; } - $entity['status'] = $this->calculateStatus($vendor); + if (in_array('vendor.classification', $this->input['report_keys']) && isset($vendor->classification)) { + $entity['vendor.classification'] = ctrans("texts.{$vendor->classification}") ?? ''; + } + + // $entity['status'] = $this->calculateStatus($vendor); return $entity; } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 2044e6a306..7b305b09ff 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -123,11 +123,14 @@ class ClientController extends BaseController return $request->disallowUpdate(); } + /** @var \App\Models\User $user */ + $user = auth()->user(); + $client = $this->client_repo->save($request->all(), $client); $this->uploadLogo($request->file('company_logo'), $client->company, $client); - event(new ClientWasUpdated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + event(new ClientWasUpdated($client, $client->company, Ninja::eventVars($user ? $user->id : null))); return $this->itemResponse($client->fresh()); } @@ -141,7 +144,10 @@ class ClientController extends BaseController */ public function create(CreateClientRequest $request) { - $client = ClientFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $client = ClientFactory::create($user->company()->id, $user->id); return $this->itemResponse($client); } @@ -155,7 +161,10 @@ class ClientController extends BaseController */ public function store(StoreClientRequest $request) { - $client = $this->client_repo->save($request->all(), ClientFactory::create(auth()->user()->company()->id, auth()->user()->id)); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $client = $this->client_repo->save($request->all(), ClientFactory::create($user->company()->id, $user->id)); $client->load('contacts', 'primary_contact'); @@ -166,7 +175,7 @@ class ClientController extends BaseController $this->uploadLogo($request->file('company_logo'), $client->company, $client); - event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); + event(new ClientWasCreated($client, $client->company, Ninja::eventVars(auth()->user() ? $user->id : null))); return $this->itemResponse($client); } @@ -273,9 +282,12 @@ class ClientController extends BaseController public function merge(PurgeClientRequest $request, Client $client, string $mergeable_client) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + $m_client = Client::withTrashed() ->where('id', $this->decodePrimaryKey($mergeable_client)) - ->where('company_id', auth()->user()->company()->id) + ->where('company_id', $user->company()->id) ->first(); if (!$m_client) { diff --git a/app/Http/Controllers/TaskStatusController.php b/app/Http/Controllers/TaskStatusController.php index ce5f8da054..836dc9f88a 100644 --- a/app/Http/Controllers/TaskStatusController.php +++ b/app/Http/Controllers/TaskStatusController.php @@ -73,7 +73,10 @@ class TaskStatusController extends BaseController */ public function create(CreateTaskStatusRequest $request) { - $task_status = TaskStatusFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $task_status = TaskStatusFactory::create($user->company()->id, auth()->user()->id); return $this->itemResponse($task_status); } @@ -87,8 +90,10 @@ class TaskStatusController extends BaseController */ public function store(StoreTaskStatusRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); - $task_status = TaskStatusFactory::create(auth()->user()->company()->id, auth()->user()->id); + $task_status = TaskStatusFactory::create($user->company()->id, auth()->user()->id); $task_status->fill($request->all()); $task_status->save(); diff --git a/app/Http/Controllers/TaxRateController.php b/app/Http/Controllers/TaxRateController.php index fb60fbc40b..a480d76126 100644 --- a/app/Http/Controllers/TaxRateController.php +++ b/app/Http/Controllers/TaxRateController.php @@ -125,7 +125,10 @@ class TaxRateController extends BaseController */ public function create(CreateTaxRateRequest $request) { - $tax_rate = TaxRateFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $tax_rate = TaxRateFactory::create($user->company()->id, auth()->user()->id); return $this->itemResponse($tax_rate); } @@ -138,7 +141,10 @@ class TaxRateController extends BaseController */ public function store(StoreTaxRateRequest $request) { - $tax_rate = TaxRateFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $tax_rate = TaxRateFactory::create($user->company()->id, $user->id); $tax_rate->fill($request->all()); $tax_rate->save(); @@ -417,15 +423,33 @@ class TaxRateController extends BaseController */ public function bulk() { - $action = request()->input('action'); + /** @var \App\Models\User $user */ + $user = auth()->user(); + $action = request()->input('action'); $ids = request()->input('ids'); $tax_rates = TaxRate::withTrashed()->find($this->transformKeys($ids)); - $tax_rates->each(function ($tax_rate, $key) use ($action) { - if (auth()->user()->can('edit', $tax_rate)) { + $tax_rates->each(function ($tax_rate, $key) use ($action, $user) { + if ($user->can('edit', $tax_rate)) { + + if(in_array($action, ['archive','delete'])) { + $settings = $user->company()->settings; + + foreach(['tax_name1','tax_name2','tax_name3'] as $tax_name) { + + if($settings->{$tax_name} == $tax_rate->name) { + $settings->{$tax_name} = ''; + $settings->{str_replace("name", "rate", $tax_name)} = ''; + } + } + + $user->company()->saveSettings($settings, $user->company()); + } + $this->base_repo->{$action}($tax_rate); + } }); diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 3a1d3841bf..a8ae0fb5fb 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -93,7 +93,7 @@ class StoreClientRequest extends Request $rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; $rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)]; - $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; + $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,partnership,trust,charity,government,other'; return $rules; } diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 0c2f362887..13ee38e3ee 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -60,7 +60,7 @@ class UpdateClientRequest extends Request $rules['size_id'] = 'integer|nullable'; $rules['country_id'] = 'integer|nullable'; $rules['shipping_country_id'] = 'integer|nullable'; - $rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other'; + $rules['classification'] = 'bail|sometimes|nullable|in:individual,business,partnership,trust,charity,government,other'; if ($this->id_number) { $rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id); diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index 179b623880..2df3b8709c 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -30,7 +30,10 @@ class StoreUserRequest extends Request */ public function authorize() : bool { - return auth()->user()->isAdmin(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->isAdmin(); } public function rules() diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 183578d893..7919cbe238 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -330,7 +330,7 @@ class BaseDriver extends AbstractPaymentDriver $payment->gateway_type_id = $data['gateway_type_id']; $client_contact = $this->getContact(); - $client_contact_id = $client_contact ? $client_contact->id : null; + $client_contact_id = $client_contact ? $client_contact->id : $this->client->contacts()->first()->id; $payment->amount = $data['amount']; $payment->type_id = $data['payment_type']; @@ -430,9 +430,9 @@ class BaseDriver extends AbstractPaymentDriver public function getContact() { if ($this->invitation) { - return ClientContact::find($this->invitation->client_contact_id); + return ClientContact::withTrashed()->find($this->invitation->client_contact_id); } elseif (auth()->guard('contact')->user()) { - return auth()->user(); + return auth()->guard('contact')->user(); } else { return false; } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 103220c63d..8c6c92b6af 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -121,21 +121,24 @@ class CheckoutComPaymentDriver extends BaseDriver $this->is_four_api = true; //was four api, now known as previous. + /** @phpstan-ignore-next-line **/ $builder = CheckoutSdk::builder() ->previous() ->staticKeys() - ->environment($this->company_gateway->getConfigField('testMode') ? Environment::sandbox() : Environment::production()) + ->environment($this->company_gateway->getConfigField('testMode') ? Environment::sandbox() : Environment::production()) /** phpstan-ignore-line **/ ->publicKey($this->company_gateway->getConfigField('publicApiKey')) ->secretKey($this->company_gateway->getConfigField('secretApiKey')); $this->gateway = $builder->build(); } else { - - $builder = CheckoutSdk::builder()->staticKeys() + + /** @phpstan-ignore-next-line **/ + $builder = CheckoutSdk::builder() + ->staticKeys() + ->environment($this->company_gateway->getConfigField('testMode') ? Environment::sandbox() : Environment::production()) /** phpstan-ignore-line **/ ->publicKey($this->company_gateway->getConfigField('publicApiKey')) - ->secretKey($this->company_gateway->getConfigField('secretApiKey')) - ->environment($this->company_gateway->getConfigField('testMode') ? Environment::sandbox() : Environment::production()); + ->secretKey($this->company_gateway->getConfigField('secretApiKey')); $this->gateway = $builder->build(); @@ -221,6 +224,16 @@ class CheckoutComPaymentDriver extends BaseDriver $response = $this->gateway->getPaymentsClient()->refundPayment($payment->transaction_reference, $request); + + SystemLogger::dispatch( + array_merge(['message' => "Gateway Refund"], $response), + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_SUCCESS, + SystemLog::TYPE_CHECKOUT, + $payment->client, + $payment->company, + ); + return [ 'transaction_reference' => $response['action_id'], 'transaction_response' => json_encode($response), @@ -228,13 +241,21 @@ class CheckoutComPaymentDriver extends BaseDriver 'description' => $response['reference'], 'code' => 202, ]; + } catch (CheckoutApiException $e) { // API error throw new PaymentFailed($e->getMessage(), $e->getCode()); } catch (CheckoutArgumentException $e) { // Bad arguments - // throw new PaymentFailed($e->getMessage(), $e->getCode()); + SystemLogger::dispatch( + $e->getMessage(), + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_CHECKOUT, + $payment->client, + $payment->company, + ); return [ 'transaction_reference' => null, @@ -243,9 +264,17 @@ class CheckoutComPaymentDriver extends BaseDriver 'description' => $e->getMessage(), 'code' => $e->getCode(), ]; + } catch (CheckoutAuthorizationException $e) { - // throw new PaymentFailed("The was a problem with the Checkout Gateway Credentials.", $e->getCode()); + SystemLogger::dispatch( + $e->getMessage(), + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_CHECKOUT, + $payment->client, + $payment->company, + ); return [ 'transaction_reference' => null, @@ -268,13 +297,14 @@ class CheckoutComPaymentDriver extends BaseDriver $request = new CustomerRequest(); $phone = new Phone(); - // $phone->number = $this->client->present()->phone(); $phone->number = substr(str_pad($this->client->present()->phone(), 6, "0", STR_PAD_RIGHT), 0, 24); - $request->email = $this->client->present()->email(); $request->name = $this->client->present()->name(); $request->phone = $phone; + // if($this->company_gateway->update_details) + // $this->updateCustomer(); + try { $response = $this->gateway->getCustomersClient()->create($request); } catch (CheckoutApiException $e) { @@ -301,6 +331,27 @@ class CheckoutComPaymentDriver extends BaseDriver } } + public function updateCustomer() + { + $phone = new Phone(); + $phone->number = substr(str_pad($this->client->present()->phone(), 6, "0", STR_PAD_RIGHT), 0, 24); + + $request = new CustomerRequest(); + + $request->email = $this->client->present()->email(); + $request->name = $this->client->present()->name(); + $request->phone = $phone; + + try { + $response = $this->gateway->getCustomersClient()->update("customer_id", $request); + } catch (CheckoutApiException $e) { + + } catch (CheckoutAuthorizationException $e) { + + } + + } + /** * Boots a request for a token payment * diff --git a/app/PaymentDrivers/PayPalExpressPaymentDriver.php b/app/PaymentDrivers/PayPalExpressPaymentDriver.php index 6a59968b77..72c05a6030 100644 --- a/app/PaymentDrivers/PayPalExpressPaymentDriver.php +++ b/app/PaymentDrivers/PayPalExpressPaymentDriver.php @@ -206,6 +206,7 @@ class PayPalExpressPaymentDriver extends BaseDriver 'transactionId' => $this->payment_hash->hash.'-'.time(), 'ButtonSource' => 'InvoiceNinja_SP', 'solutionType' => 'Sole', + 'no_shipping' => $this->company_gateway->require_shipping_address ? 0 : 1, ]; } diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index 59139361a9..080356edc3 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -142,6 +142,11 @@ class CreditCard implements MethodInterface return $this->processSuccessfulPayment($response); } + if(is_array($response)) { + nlog("square"); + nlog($response); + } + return $this->processUnsuccessfulPayment($response); } @@ -293,7 +298,7 @@ class CreditCard implements MethodInterface $body->setFamilyName(''); $body->setEmailAddress($this->square_driver->client->present()->email()); $body->setAddress($billing_address); - $body->setPhoneNumber($this->square_driver->client->phone); + // $body->setPhoneNumber($this->square_driver->client->phone); $body->setReferenceId($this->square_driver->client->number); $body->setNote('Created by Invoice Ninja.'); @@ -309,8 +314,8 @@ class CreditCard implements MethodInterface return $result->getCustomer()->getId(); } else { $errors = $api_response->getErrors(); - - return $this->processUnsuccessfulPayment($errors); + nlog($errors); + return $this->processUnsuccessfulPayment($api_response); } } } diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/Invoice/EInvoice/FacturaEInvoice.php index 700bad3602..a11c6cc032 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/Invoice/EInvoice/FacturaEInvoice.php @@ -182,9 +182,9 @@ class FacturaEInvoice extends AbstractService Storage::makeDirectory($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first())); } - $this->fac->export(Storage::disk($disk)->path($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig"))); - - return $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig"); + // $this->fac->export(Storage::disk($disk)->path($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig"))); + return $this->fac->export(); + // return $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig"); } @@ -468,8 +468,11 @@ class FacturaEInvoice extends AbstractService { $company = $this->invoice->company; + if($company->getSetting('classification')) + return $this->setIndividualSeller(); + $seller = new FacturaeParty([ - "isLegalEntity" => $company->custom_value1, // Se asume true si se omite + "isLegalEntity" => true, "taxNumber" => $company->settings->vat_number, "name" => substr($company->present()->name(), 0, 40), "address" => substr($company->settings->address1, 0, 80), @@ -500,11 +503,49 @@ class FacturaEInvoice extends AbstractService return $this; } + + private function setIndividualSeller(): self + { + + $company = $this->invoice->company; + + $seller = new FacturaeParty([ + "isLegalEntity" => false, + "taxNumber" => $company->settings->vat_number, + "name" => $company->getSetting('classification') === 'individual' ? substr($company->owner()->present()->name(), 0, 40) : substr($company->present()->name(), 0, 40), + "address" => substr($company->settings->address1, 0, 80), + "postCode" => substr($this->invoice->client->postal_code, 0, 5), + "town" => substr($company->settings->city, 0, 50), + "province" => substr($company->settings->state, 0, 20), + "countryCode" => $company->country()->iso_3166_3, // Se asume España si se omite + // "book" => "0", // Libro + // "merchantRegister" => "RG", // Registro Mercantil + // "sheet" => "1", // Hoja + // "folio" => "2", // Folio + // "section" => "3", // Sección + // "volume" => "4", // Tomo + "email" => substr($company->settings->email, 0, 60), + "phone" => substr($company->settings->phone, 0, 15), + "fax" => "", + "website" => substr($company->settings->website, 0, 50), + // "contactPeople" => substr($company->owner()->present()->name(), 0, 40), + "firstSurname" => $company->owner()->present()->firstName(), + "lastSurname" => $company->owner()->present()->lastName(), + ]); + + $this->fac->setSeller($seller); + + return $this; + + + } + + private function buildBuyer(): self { $buyer = new FacturaeParty([ - "isLegalEntity" => $this->invoice->client->has_valid_vat_number, + "isLegalEntity" => $this->invoice->client->classification === 'individual' ? false : true, "taxNumber" => $this->invoice->client->vat_number, "name" => substr($this->invoice->client->present()->name(),0, 40), "firstSurname" => substr($this->invoice->client->present()->first_name(),0, 40), diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index dd9ef12b55..8899377af7 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -52,6 +52,7 @@ class MarkPaid extends AbstractService $this->invoice ->service() ->setExchangeRate() + ->clearPartial() ->updateBalance($this->payable_balance * -1) ->updatePaidToDate($this->payable_balance) ->setStatus(Invoice::STATUS_PAID) diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index b5b67358ab..3a649d4444 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -29,7 +29,9 @@ class RefundPayment private $credit_note; - private $total_refund; + private float $total_refund = 0; + + private float $credits_used = 0; private $gateway_refund_status; @@ -45,8 +47,6 @@ class RefundPayment $this->refund_data = $refund_data; - $this->total_refund = 0; - $this->gateway_refund_status = false; $this->activity_repository = new ActivityRepository(); @@ -56,9 +56,9 @@ class RefundPayment { $this->payment = $this ->calculateTotalRefund() //sets amount for the refund (needed if we are refunding multiple invoices in one payment) + ->updateCreditables() //return the credits first ->processGatewayRefund() //process the gateway refund if needed ->setStatus() //sets status of payment - ->updateCreditables() //return the credits first ->updatePaymentables() //update the paymentable items ->adjustInvoices() ->finalize() @@ -104,12 +104,14 @@ class RefundPayment */ private function processGatewayRefund() { - if ($this->refund_data['gateway_refund'] !== false && $this->total_refund > 0) { + $net_refund = ($this->total_refund - $this->credits_used); + + if ($this->refund_data['gateway_refund'] !== false && $net_refund > 0) { if ($this->payment->company_gateway) { - $response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $this->total_refund); + $response = $this->payment->company_gateway->driver($this->payment->client)->refund($this->payment, $net_refund); if($response['amount'] ?? false) - $this->total_refund = $response['amount']; + $net_refund = $response['amount']; if($response['voided'] ?? false) { @@ -123,7 +125,7 @@ class RefundPayment })->toArray(); } - $this->payment->refunded += $this->total_refund; + $this->payment->refunded += $net_refund; if ($response['success'] == false) { $this->payment->save(); @@ -132,7 +134,7 @@ class RefundPayment } } } else { - $this->payment->refunded += $this->total_refund; + $this->payment->refunded += $net_refund; } return $this; @@ -227,23 +229,29 @@ class RefundPayment */ private function updateCreditables() { + if ($this->payment->credits()->exists()) { + + $amount_to_refund = $this->total_refund; + //Adjust credits first!!! foreach ($this->payment->credits as $paymentable_credit) { $available_credit = $paymentable_credit->pivot->amount - $paymentable_credit->pivot->refunded; - if ($available_credit > $this->total_refund) { - $paymentable_credit->pivot->refunded += $this->total_refund; + if ($available_credit > $amount_to_refund) { + $paymentable_credit->pivot->refunded += $amount_to_refund; $paymentable_credit->pivot->save(); $paymentable_credit->service() ->setStatus(Credit::STATUS_SENT) - ->updateBalance($this->total_refund) - ->updatePaidToDate($this->total_refund * -1) + ->adjustBalance($amount_to_refund) + ->updatePaidToDate($amount_to_refund * -1) ->save(); - - $this->total_refund = 0; + + $this->credits_used += $amount_to_refund; + $amount_to_refund = 0; + } else { $paymentable_credit->pivot->refunded += $available_credit; $paymentable_credit->pivot->save(); @@ -254,10 +262,12 @@ class RefundPayment ->updatePaidToDate($available_credit * -1) ->save(); - $this->total_refund -= $available_credit; + $this->credits_used += $available_credit; + $amount_to_refund -= $available_credit; + } - if ($this->total_refund == 0) { + if ($amount_to_refund == 0) { break; } } diff --git a/composer.lock b/composer.lock index b54edc2dd8..30cf5bf3f2 100644 --- a/composer.lock +++ b/composer.lock @@ -485,16 +485,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.281.12", + "version": "3.282.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "22a92f08758db2b152843ea0875eeee5a467d8ff" + "reference": "79a3ed5bb573f592823f8b1cffe0dbac3132e6b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/22a92f08758db2b152843ea0875eeee5a467d8ff", - "reference": "22a92f08758db2b152843ea0875eeee5a467d8ff", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/79a3ed5bb573f592823f8b1cffe0dbac3132e6b4", + "reference": "79a3ed5bb573f592823f8b1cffe0dbac3132e6b4", "shasum": "" }, "require": { @@ -574,9 +574,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.281.12" + "source": "https://github.com/aws/aws-sdk-php/tree/3.282.0" }, - "time": "2023-09-22T18:12:27+00:00" + "time": "2023-09-28T18:09:20+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1353,16 +1353,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.7", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "8e0e268052b4a8974cb00215bb2892787021614f" + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/8e0e268052b4a8974cb00215bb2892787021614f", - "reference": "8e0e268052b4a8974cb00215bb2892787021614f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", "shasum": "" }, "require": { @@ -1378,9 +1378,9 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.34", + "phpstan/phpstan": "1.10.35", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.12", + "phpunit/phpunit": "9.6.13", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", @@ -1446,7 +1446,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.7" + "source": "https://github.com/doctrine/dbal/tree/3.7.0" }, "funding": [ { @@ -1462,20 +1462,20 @@ "type": "tidelift" } ], - "time": "2023-09-19T20:15:41+00:00" + "time": "2023-09-26T20:56:55+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { @@ -1507,9 +1507,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/event-manager", @@ -4287,16 +4287,16 @@ }, { "name": "laravel/framework", - "version": "v10.24.0", + "version": "v10.25.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "bcebd0a4c015d5c38aeec299d355a42451dd3726" + "reference": "6014dd456b414b305fb0b408404efdcec18e64bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/bcebd0a4c015d5c38aeec299d355a42451dd3726", - "reference": "bcebd0a4c015d5c38aeec299d355a42451dd3726", + "url": "https://api.github.com/repos/laravel/framework/zipball/6014dd456b414b305fb0b408404efdcec18e64bc", + "reference": "6014dd456b414b305fb0b408404efdcec18e64bc", "shasum": "" }, "require": { @@ -4314,7 +4314,7 @@ "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1", + "laravel/prompts": "^0.1.9", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", @@ -4396,7 +4396,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.10", + "orchestra/testbench-core": "^8.12", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", @@ -4483,20 +4483,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-19T15:25:04+00:00" + "time": "2023-09-28T14:08:59+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.8", + "version": "v0.1.10", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "68dcc65babf92e1fb43cba0b3f78fc3d8002709c" + "reference": "37ed55f6950d921a87d5beeab16d03f8de26b060" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/68dcc65babf92e1fb43cba0b3f78fc3d8002709c", - "reference": "68dcc65babf92e1fb43cba0b3f78fc3d8002709c", + "url": "https://api.github.com/repos/laravel/prompts/zipball/37ed55f6950d921a87d5beeab16d03f8de26b060", + "reference": "37ed55f6950d921a87d5beeab16d03f8de26b060", "shasum": "" }, "require": { @@ -4505,6 +4505,10 @@ "php": "^8.1", "symfony/console": "^6.2" }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, "require-dev": { "mockery/mockery": "^1.5", "pestphp/pest": "^2.3", @@ -4515,6 +4519,11 @@ "ext-pcntl": "Required for the spinner to be animated." }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, "autoload": { "files": [ "src/helpers.php" @@ -4529,9 +4538,9 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.8" + "source": "https://github.com/laravel/prompts/tree/v0.1.10" }, - "time": "2023-09-19T15:33:56+00:00" + "time": "2023-09-29T07:26:07+00:00" }, { "name": "laravel/serializable-closure", @@ -5825,16 +5834,16 @@ }, { "name": "microsoft/microsoft-graph", - "version": "1.106.0", + "version": "1.107.0", "source": { "type": "git", "url": "https://github.com/microsoftgraph/msgraph-sdk-php.git", - "reference": "a9f43d74131bb13cb1b5a999101d486b26601b8f" + "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/a9f43d74131bb13cb1b5a999101d486b26601b8f", - "reference": "a9f43d74131bb13cb1b5a999101d486b26601b8f", + "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", + "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", "shasum": "" }, "require": { @@ -5871,9 +5880,9 @@ "homepage": "https://developer.microsoft.com/en-us/graph", "support": { "issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues", - "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.106.0" + "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.107.0" }, - "time": "2023-09-08T06:02:27+00:00" + "time": "2023-09-27T06:43:40+00:00" }, { "name": "mollie/mollie-api-php", @@ -6361,16 +6370,16 @@ }, { "name": "nesbot/carbon", - "version": "2.70.0", + "version": "2.71.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d" + "reference": "98276233188583f2ff845a0f992a235472d9466a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d3298b38ea8612e5f77d38d1a99438e42f70341d", - "reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/98276233188583f2ff845a0f992a235472d9466a", + "reference": "98276233188583f2ff845a0f992a235472d9466a", "shasum": "" }, "require": { @@ -6463,7 +6472,7 @@ "type": "tidelift" } ], - "time": "2023-09-07T16:43:50+00:00" + "time": "2023-09-25T11:31:05+00:00" }, { "name": "nette/schema", @@ -8127,16 +8136,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.1", + "version": "1.24.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01" + "reference": "bcad8d995980440892759db0c32acae7c8e79442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", - "reference": "9f854d275c2dbf84915a5c0ec9a2d17d2cd86b01", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", "shasum": "" }, "require": { @@ -8168,9 +8177,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" }, - "time": "2023-09-18T12:18:02+00:00" + "time": "2023-09-26T12:28:12+00:00" }, { "name": "pragmarx/google2fa", @@ -9689,16 +9698,16 @@ }, { "name": "setasign/fpdi", - "version": "v2.4.1", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/Setasign/FPDI.git", - "reference": "f4ba73e5bc053ccc90b81717c5df1cb2ea7bae7b" + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Setasign/FPDI/zipball/f4ba73e5bc053ccc90b81717c5df1cb2ea7bae7b", - "reference": "f4ba73e5bc053ccc90b81717c5df1cb2ea7bae7b", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/ecf0459643ec963febfb9a5d529dcd93656006a4", + "reference": "ecf0459643ec963febfb9a5d529dcd93656006a4", "shasum": "" }, "require": { @@ -9749,7 +9758,7 @@ ], "support": { "issues": "https://github.com/Setasign/FPDI/issues", - "source": "https://github.com/Setasign/FPDI/tree/v2.4.1" + "source": "https://github.com/Setasign/FPDI/tree/v2.5.0" }, "funding": [ { @@ -9757,7 +9766,7 @@ "type": "tidelift" } ], - "time": "2023-07-27T08:12:09+00:00" + "time": "2023-09-28T10:46:27+00:00" }, { "name": "shopify/shopify-api", @@ -10330,16 +10339,16 @@ }, { "name": "stripe/stripe-php", - "version": "v12.4.0", + "version": "v12.5.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "7d0a90772fc1c179e370971264318208533324b9" + "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/7d0a90772fc1c179e370971264318208533324b9", - "reference": "7d0a90772fc1c179e370971264318208533324b9", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/a4249b4a90437844f6c35e8701f8c68acd206f56", + "reference": "a4249b4a90437844f6c35e8701f8c68acd206f56", "shasum": "" }, "require": { @@ -10384,9 +10393,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v12.4.0" + "source": "https://github.com/stripe/stripe-php/tree/v12.5.0" }, - "time": "2023-09-21T22:55:47+00:00" + "time": "2023-09-28T23:06:27+00:00" }, { "name": "symfony/console", @@ -15037,16 +15046,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.28.0", + "version": "v3.34.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "113e09fea3d2306319ffaa2423fe3de768b28cff" + "reference": "7c7a4ad2ed8fe50df3e25528218b13d383608f23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/113e09fea3d2306319ffaa2423fe3de768b28cff", - "reference": "113e09fea3d2306319ffaa2423fe3de768b28cff", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7c7a4ad2ed8fe50df3e25528218b13d383608f23", + "reference": "7c7a4ad2ed8fe50df3e25528218b13d383608f23", "shasum": "" }, "require": { @@ -15067,6 +15076,9 @@ "symfony/process": "^5.4 || ^6.0", "symfony/stopwatch": "^5.4 || ^6.0" }, + "conflict": { + "stevebauman/unfinalize": "*" + }, "require-dev": { "facile-it/paraunit": "^1.3 || ^2.0", "justinrainbow/json-schema": "^5.2", @@ -15120,7 +15132,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.28.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.34.0" }, "funding": [ { @@ -15128,7 +15140,7 @@ "type": "github" } ], - "time": "2023-09-22T20:43:40+00:00" + "time": "2023-09-29T15:34:26+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -15839,16 +15851,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.35", + "version": "1.10.36", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3" + "reference": "ffa3089511121a672e62969404e4fddc753f9b15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e730e5facb75ffe09dfb229795e8c01a459f26c3", - "reference": "e730e5facb75ffe09dfb229795e8c01a459f26c3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa3089511121a672e62969404e4fddc753f9b15", + "reference": "ffa3089511121a672e62969404e4fddc753f9b15", "shasum": "" }, "require": { @@ -15897,7 +15909,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T15:27:56+00:00" + "time": "2023-09-29T14:07:45+00:00" }, { "name": "phpunit/php-code-coverage", @@ -16567,16 +16579,16 @@ }, { "name": "sebastian/complexity", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a" + "reference": "68cfb347a44871f01e33ab0ef8215966432f6957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c70b73893e10757af9c6a48929fa6a333b56a97a", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68cfb347a44871f01e33ab0ef8215966432f6957", + "reference": "68cfb347a44871f01e33ab0ef8215966432f6957", "shasum": "" }, "require": { @@ -16589,7 +16601,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.1-dev" } }, "autoload": { @@ -16613,7 +16625,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.1.0" }, "funding": [ { @@ -16621,7 +16633,7 @@ "type": "github" } ], - "time": "2023-08-31T09:55:53+00:00" + "time": "2023-09-28T11:50:59+00:00" }, { "name": "sebastian/diff", diff --git a/config/ninja.php b/config/ninja.php index a025e53968..3ae8c96c13 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.22'), - 'app_tag' => env('APP_TAG','5.7.22'), + 'app_version' => env('APP_VERSION','5.7.23'), + 'app_tag' => env('APP_TAG','5.7.23'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/lang/en/texts.php b/lang/en/texts.php index d0cc6218e4..626c69b9a2 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5176,7 +5176,7 @@ $LANG = array( 'email_delivered' => 'Email Delivered', 'log' => 'Log', 'classification' => 'Classification', - 'stock_quantity' => 'Stock :quantity', + 'stock_quantity_number' => 'Stock :quantity', ); return $LANG; diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index 4a7b288daa..a983e3c3f0 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -16,6 +16,8 @@ trait CreatesApplication { $app = require __DIR__.'/../bootstrap/app.php'; + define('STDIN', fopen("php://stdin", "r")); + $app->make(Kernel::class)->bootstrap(); Hash::setRounds(4); diff --git a/tests/Feature/ClassificationTest.php b/tests/Feature/ClassificationTest.php index fe9df96007..5a08c8cfca 100644 --- a/tests/Feature/ClassificationTest.php +++ b/tests/Feature/ClassificationTest.php @@ -73,7 +73,7 @@ class ClassificationTest extends TestCase public function testValidation2Classification() { - $this->client->classification = 'company'; + $this->client->classification = 'business'; $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), @@ -84,7 +84,7 @@ class ClassificationTest extends TestCase $arr = $response->json(); - $this->assertEquals('company', $arr['data']['classification']); + $this->assertEquals('business', $arr['data']['classification']); } public function testValidation3Classification() diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index 1dcdd8f49f..fa05a5624f 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -497,7 +497,7 @@ class PaymentTest extends TestCase 'is_primary' => 1, ]); - + /** @var \App\Models\Invoice $invoice */ $invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id $invoice->client_id = $client->id; @@ -1310,15 +1310,14 @@ class PaymentTest extends TestCase ]; - try { - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->postJson('/api/v1/payments?include=invoices', $data); - } catch (ValidationException $e) { - $message = json_decode($e->validator->getMessageBag(), 1); - $this->assertNotNull($message); - } + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments?include=invoices', $data); + + $response->assertStatus(422); + } public function testPaymentWithSameInvoiceMultipleTimes() diff --git a/tests/Feature/RefundTest.php b/tests/Feature/RefundTest.php index f697b2a540..07a423d56d 100644 --- a/tests/Feature/RefundTest.php +++ b/tests/Feature/RefundTest.php @@ -11,21 +11,22 @@ namespace Tests\Feature; +use Tests\TestCase; +use App\Models\Credit; +use App\Models\Invoice; +use App\Models\Payment; +use Tests\MockAccountData; +use App\Models\ClientContact; use App\Factory\ClientFactory; use App\Factory\CreditFactory; use App\Factory\InvoiceFactory; -use App\Helpers\Invoice\InvoiceSum; -use App\Models\ClientContact; -use App\Models\Invoice; -use App\Models\Payment; use App\Utils\Traits\MakesHash; +use App\Helpers\Invoice\InvoiceSum; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; -use Tests\MockAccountData; -use Tests\TestCase; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -37,6 +38,8 @@ class RefundTest extends TestCase use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp() :void { parent::setUp(); @@ -53,7 +56,7 @@ class RefundTest extends TestCase $this->makeTestData(); - $this->withoutExceptionHandling(); + // $this->withoutExceptionHandling(); } /** @@ -82,10 +85,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $data = [ @@ -119,14 +122,12 @@ class RefundTest extends TestCase $response = false; - try { + $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/payments/refund', $data); - } catch (ValidationException $e) { - $message = json_decode($e->validator->getMessageBag(), 1); - } + ])->postJson('/api/v1/payments/refund', $data); + $arr = $response->json(); @@ -165,10 +166,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $this->invoice->setRelation('client', $this->client); @@ -217,23 +218,12 @@ class RefundTest extends TestCase 'date' => '2020/12/12', ]; - $response = false; - - try { $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/payments/refund', $data); - } catch (ValidationException $e) { - $message = json_decode($e->validator->getMessageBag(), 1); - - $this->assertNotNull($message); - \Log::error($message); - } - - if ($response) { - $response->assertStatus(302); - } + ])->postJson('/api/v1/payments/refund', $data); + $response->assertStatus(422); + } /** @@ -262,10 +252,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $data = [ @@ -346,10 +336,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $data = [ @@ -439,10 +429,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $data = [ @@ -485,10 +475,10 @@ class RefundTest extends TestCase $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $data = [ @@ -553,13 +543,13 @@ class RefundTest extends TestCase $this->invoice->line_items = $this->buildLineItems(); $this->invoice->uses_inclusive_taxes = false; - $this->invoice_client_id = $client->id; + $this->invoice->client_id = $client->id; $this->invoice->save(); - $this->invoice_calc = new InvoiceSum($this->invoice); - $this->invoice_calc->build(); + $invoice_calc = new InvoiceSum($this->invoice); + $invoice_calc->build(); - $this->invoice = $this->invoice_calc->getInvoice(); + $this->invoice = $invoice_calc->getInvoice(); $this->invoice->save(); $this->credit = CreditFactory::create($this->company->id, $this->user->id); @@ -650,4 +640,172 @@ class RefundTest extends TestCase } /*Additional scenarios*/ + + public function testRefundsWhenCreditsArePresent() + { + $i = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 1000, + 'balance' => 1000, + ]); + + $c = Credit::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 100, + 'balance' => 100, + ]); + + $data = [ + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $i->hashed_id, + 'amount' => 1000, + ], + ], + 'credits' => [ + [ + 'credit_id' => $c->hashed_id, + 'amount' => 100, + ], + ], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals(0, $c->fresh()->balance); + $this->assertEquals(0, $i->fresh()->balance); + + $payment_id = $arr['data']['id']; + + $refund = [ + 'id' => $payment_id, + 'client_id' => $this->client->hashed_id, + 'amount' => 10, + 'date' => now()->format('Y-m-d'), + 'invoices' => [ + [ + 'invoice_id' => $i->hashed_id, + 'amount' => 10, + ], + ] + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments/refund', $refund); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(0, $arr['data']['refunded']); + + $this->assertEquals(10, $c->fresh()->balance); + $this->assertEquals(10, $i->fresh()->balance); + + } + + public function testRefundsWithSplitCreditAndPaymentRefund() + { + $i = Invoice::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 1000, + 'balance' => 1000, + ]); + + $c = Credit::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'status_id' => Invoice::STATUS_SENT, + 'amount' => 100, + 'balance' => 100, + ]); + + $data = [ + 'client_id' => $this->client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $i->hashed_id, + 'amount' => 1000, + ], + ], + 'credits' => [ + [ + 'credit_id' => $c->hashed_id, + 'amount' => 100, + ], + ], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments', $data); + + $arr = $response->json(); + + $response->assertStatus(200); + + $this->assertEquals(0, $c->fresh()->balance); + $this->assertEquals(0, $i->fresh()->balance); + + $payment_id = $arr['data']['id']; + $payment = Payment::find($this->decodePrimaryKey($payment_id)); + + $this->assertEquals(900, $payment->amount); + $this->assertEquals(900, $payment->applied); + $this->assertEquals(0, $payment->refunded); + + $refund = [ + 'id' => $payment_id, + 'client_id' => $this->client->hashed_id, + 'amount' => 200, + 'date' => now()->format('Y-m-d'), + 'invoices' => [ + [ + 'invoice_id' => $i->hashed_id, + 'amount' => 200, + ], + ] + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments/refund', $refund); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(100, $arr['data']['refunded']); + + $this->assertEquals(100, $c->fresh()->balance); + $this->assertEquals(200, $i->fresh()->balance); + + $this->assertEquals(900, $payment->fresh()->amount); + $this->assertEquals(900, $payment->fresh()->applied); + $this->assertEquals(100, $payment->fresh()->refunded); + + } + } diff --git a/tests/Feature/TaxRateApiTest.php b/tests/Feature/TaxRateApiTest.php index 13008615ba..e8175ea789 100644 --- a/tests/Feature/TaxRateApiTest.php +++ b/tests/Feature/TaxRateApiTest.php @@ -11,13 +11,15 @@ namespace Tests\Feature; +use Tests\TestCase; +use App\Models\Company; +use App\Models\TaxRate; +use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; use Illuminate\Validation\ValidationException; -use Tests\MockAccountData; -use Tests\TestCase; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -44,6 +46,44 @@ class TaxRateApiTest extends TestCase Model::reguard(); } + public function testRemovingDefaultTaxes() + { + $t = TaxRate::factory()->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'name' => 'nastytax1', + 'rate' => 10, + ]); + + $settings = $this->company->settings; + $settings->tax_rate1 = $t->rate; + $settings->tax_name1 = $t->name; + + $this->company->saveSettings($settings, $this->company); + + $this->company->fresh(); + + $this->assertEquals('nastytax1', $this->company->settings->tax_name1); + $this->assertEquals(10, $this->company->settings->tax_rate1); + + $data = [ + 'ids' => [$this->encodePrimaryKey($t->id)], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/tax_rates/bulk?action=archive', $data); + + $response->assertStatus(200); + + $this->company = $this->company->fresh(); + + $this->assertEquals('', $this->company->getSetting('tax_name1')); + $this->assertEquals(0, $this->company->getSetting('tax_rate1')); + + } + public function testTaxRatesGetFilter() { $response = $this->withHeaders([ diff --git a/tests/Integration/CompanyLedgerTest.php b/tests/Integration/CompanyLedgerTest.php index 8a49278860..789d13ae8a 100644 --- a/tests/Integration/CompanyLedgerTest.php +++ b/tests/Integration/CompanyLedgerTest.php @@ -43,6 +43,8 @@ class CompanyLedgerTest extends TestCase public $account; + public $faker; + protected function setUp() :void { parent::setUp(); diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 96dbc6433e..25eef773c0 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -11,57 +11,58 @@ namespace Tests; -use App\DataMapper\ClientRegistrationFields; -use App\DataMapper\ClientSettings; -use App\DataMapper\CompanySettings; -use App\Factory\CompanyUserFactory; -use App\Factory\CreditFactory; -use App\Factory\InvoiceFactory; -use App\Factory\InvoiceInvitationFactory; -use App\Factory\InvoiceItemFactory; -use App\Factory\InvoiceToRecurringInvoiceFactory; -use App\Factory\PurchaseOrderFactory; -use App\Helpers\Invoice\InvoiceSum; -use App\Jobs\Company\CreateCompanyTaskStatuses; -use App\Models\Account; -use App\Models\BankIntegration; -use App\Models\BankTransaction; -use App\Models\BankTransactionRule; +use App\Models\Task; +use App\Models\User; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyGateway; -use App\Models\CompanyToken; use App\Models\Credit; -use App\Models\CreditInvitation; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; use App\Models\Expense; -use App\Models\ExpenseCategory; -use App\Models\GroupSetting; -use App\Models\InvoiceInvitation; use App\Models\Payment; use App\Models\Product; use App\Models\Project; -use App\Models\PurchaseOrderInvitation; -use App\Models\Quote; +use App\Models\TaxRate; +use App\Models\Scheduler; +use App\Models\TaskStatus; +use App\Utils\TruthSource; +use App\Models\CompanyToken; +use App\Models\GroupSetting; +use App\Models\ClientContact; +use App\Models\VendorContact; +use App\Factory\CreditFactory; +use App\Models\CompanyGateway; +use App\Models\RecurringQuote; +use Illuminate\Support\Carbon; +use App\Factory\InvoiceFactory; +use App\Models\BankIntegration; +use App\Models\BankTransaction; +use App\Models\ExpenseCategory; use App\Models\QuoteInvitation; +use App\Utils\Traits\MakesHash; +use App\Models\CreditInvitation; use App\Models\RecurringExpense; use App\Models\RecurringInvoice; -use App\Models\RecurringQuote; -use App\Models\Scheduler; -use App\Models\Task; -use App\Models\TaskStatus; -use App\Models\TaxRate; -use App\Models\User; -use App\Models\Vendor; -use App\Models\VendorContact; -use App\Utils\Traits\GeneratesCounter; -use App\Utils\Traits\MakesHash; -use App\Utils\TruthSource; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Cache; +use App\Models\InvoiceInvitation; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; +use App\Factory\CompanyUserFactory; +use App\Factory\InvoiceItemFactory; +use App\Helpers\Invoice\InvoiceSum; +use App\Models\BankTransactionRule; use Illuminate\Support\Facades\Hash; +use App\Factory\PurchaseOrderFactory; +use Illuminate\Support\Facades\Cache; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Support\Facades\Schema; +use App\Models\PurchaseOrderInvitation; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Storage; +use App\Factory\InvoiceInvitationFactory; +use App\DataMapper\ClientRegistrationFields; +use App\Jobs\Company\CreateCompanyTaskStatuses; +use App\Factory\InvoiceToRecurringInvoiceFactory; /** * Class MockAccountData. @@ -200,7 +201,9 @@ trait MockAccountData /* Warm up the cache !*/ $cached_tables = config('ninja.cached_tables'); - $this->artisan('db:seed --force'); + Artisan::call('db:seed', [ + '--force' => true + ]); foreach ($cached_tables as $name => $class) { // check that the table exists in case the migration is pending diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index 49abb228a8..cb6f15da13 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -49,7 +49,36 @@ class InvoiceTest extends TestCase $this->invoice_calc = new InvoiceSum($this->invoice); } -public function testGrossTaxAmountCalcuations() + public function testMarkPaidWithPartial() + { + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 50; + $line_items[] = $item; + + $this->invoice->partial = 5; + $this->invoice->partial_due_date = now()->addDay(); + $this->invoice->due_date = now()->addDays(10); + $this->invoice->line_items = $line_items; + $this->invoice->save(); + + $invoice_calc = new InvoiceSum($this->invoice); + + $invoice = $invoice_calc->build()->getInvoice()->service()->markSent()->save(); + + $this->assertEquals(5, $invoice->partial); + $this->assertNotNull($invoice->partial_due_date); + $this->assertEquals(50, $invoice->amount); + + $invoice = $invoice->service()->markPaid()->save(); + + $this->assertEquals(0, $invoice->partial); + $this->assertEquals(0, $invoice->balance); + + $this->assertNull($invoice->partial_due_date); + } + + public function testGrossTaxAmountCalcuations() { $invoice = InvoiceFactory::create($this->company->id, $this->user->id); $invoice->client_id = $this->client->id;