mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 21:22:58 +01:00
commit
978605495b
@ -1 +1 @@
|
||||
5.3.44
|
||||
5.3.45
|
@ -401,7 +401,7 @@ class InvoiceController extends BaseController
|
||||
|
||||
$invoice = $this->invoice_repo->save($request->all(), $invoice);
|
||||
|
||||
$invoice->service()->triggeredActions($request)->deletePdf()->touchPdf();
|
||||
$invoice->service()->triggeredActions($request)->touchPdf();
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
|
@ -23,6 +23,43 @@ use Illuminate\Http\Response;
|
||||
class StaticController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* Show the list of Invoices.
|
||||
*
|
||||
* @param InvoiceFilters $filters The filters
|
||||
*
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/statics",
|
||||
* operationId="getStatics",
|
||||
* tags={"statics"},
|
||||
* summary="Gets a list of statics",
|
||||
* description="Lists all statics",
|
||||
*
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A list of static data",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
|
||||
|
@ -150,7 +150,7 @@ class RequiredClientInfo extends Component
|
||||
}
|
||||
|
||||
if (Str::startsWith($field['name'], 'contact_')) {
|
||||
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field})) {
|
||||
if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) {
|
||||
// if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} == 840) {
|
||||
$this->show_form = true;
|
||||
} else {
|
||||
|
@ -54,7 +54,10 @@ class StorePaymentRequest extends Request
|
||||
if (isset($input['invoices']) && is_array($input['invoices']) !== false) {
|
||||
foreach ($input['invoices'] as $key => $value) {
|
||||
$input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']);
|
||||
$invoices_total += $value['amount'];
|
||||
|
||||
if(array_key_exists('amount', $value))
|
||||
$invoices_total += $value['amount'];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,12 +94,12 @@ class StorePaymentRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'amount' => 'numeric|required',
|
||||
'amount' => 'sometimes|numeric',
|
||||
'amount' => [new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()],
|
||||
'client_id' => 'bail|required|exists:clients,id',
|
||||
'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id',
|
||||
'invoices.*.amount' => 'bail|required',
|
||||
'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()),
|
||||
'invoices.*.amount' => 'required',
|
||||
'credits.*.credit_id' => 'bail|required|exists:credits,id',
|
||||
'credits.*.credit_id' => new ValidCreditsRules($this->all()),
|
||||
'credits.*.amount' => ['required', new CreditsSumRule($this->all())],
|
||||
|
@ -58,7 +58,7 @@ class StoreUserRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
//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']);
|
||||
|
@ -33,13 +33,18 @@ class CanAddUserRule implements Rule
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
|
||||
$count = CompanyUser::query()
|
||||
->where('company_user.account_id', auth()->user()->account_id)
|
||||
->join('users', 'users.id', '=', 'company_user.user_id')
|
||||
->whereNull('users.deleted_at')
|
||||
->whereNull('company_user.deleted_at')
|
||||
->distinct()
|
||||
->count('company_user.user_id');
|
||||
/* If the user is active then we can add them to the company */
|
||||
if(User::where('email', request()->input('email'))->where('account_id', auth()->user()->account_id)->where('is_deleted',0)->exists())
|
||||
return true;
|
||||
|
||||
/* Check that we have sufficient quota to allow this to happen */
|
||||
$count = CompanyUser::query()
|
||||
->where('company_user.account_id', auth()->user()->account_id)
|
||||
->join('users', 'users.id', '=', 'company_user.user_id')
|
||||
->whereNull('users.deleted_at')
|
||||
->whereNull('company_user.deleted_at')
|
||||
->distinct()
|
||||
->count('company_user.user_id');
|
||||
|
||||
return $count < auth()->user()->company()->account->num_users;
|
||||
|
||||
|
@ -57,6 +57,11 @@ class ValidInvoicesRules implements Rule
|
||||
|
||||
$unique_array[] = $invoice['invoice_id'];
|
||||
|
||||
if(!array_key_exists('amount', $invoice)){
|
||||
$this->error_msg = ctrans('texts.amount') . " required";
|
||||
return false;
|
||||
}
|
||||
|
||||
$inv = Invoice::whereId($invoice['invoice_id'])->first();
|
||||
|
||||
if (! $inv) {
|
||||
|
@ -79,6 +79,11 @@ class ValidRefundableRequest implements Rule
|
||||
{
|
||||
$invoice = Invoice::whereId($invoice['invoice_id'])->whereCompanyId($payment->company_id)->withTrashed()->first();
|
||||
|
||||
if(!$invoice){
|
||||
$this->error_msg = "Invoice not found for refund";
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($payment->invoices()->exists()) {
|
||||
$paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first();
|
||||
|
||||
|
@ -59,19 +59,30 @@ class PaymentAmountsBalanceRule implements Rule
|
||||
|
||||
if (request()->input('credits') && is_array(request()->input('credits'))) {
|
||||
foreach (request()->input('credits') as $credit) {
|
||||
$payment_amounts += $credit['amount'];
|
||||
|
||||
if(array_key_exists('amount', $credit))
|
||||
$payment_amounts += $credit['amount'];
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->input('invoices') && is_array(request()->input('invoices'))) {
|
||||
foreach (request()->input('invoices') as $invoice) {
|
||||
$invoice_amounts += $invoice['amount'];
|
||||
|
||||
if(array_key_exists('amount', $invoice))
|
||||
$invoice_amounts += $invoice['amount'];
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
// nlog(request()->input('invoices'));
|
||||
// nlog($payment_amounts);
|
||||
// nlog($invoice_amounts);
|
||||
|
||||
nlog($payment_amounts ." >= " . $invoice_amounts);
|
||||
|
||||
return $payment_amounts >= $invoice_amounts;
|
||||
|
||||
return $payment_amounts >= $invoice_amounts;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1866,10 +1866,19 @@ class Import implements ShouldQueue
|
||||
|
||||
private function processNinjaTokens(array $data)
|
||||
{
|
||||
|
||||
nlog("attempting to process Ninja Tokens");
|
||||
|
||||
if(Ninja::isHosted())
|
||||
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);
|
||||
if(Ninja::isHosted()){
|
||||
|
||||
try{
|
||||
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ class Company extends BaseModel
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id');
|
||||
return $this->hasManyThrough(User::class, CompanyUser::class, 'company_id', 'id', 'id', 'user_id')->withTrashed();
|
||||
}
|
||||
|
||||
public function expense_categories()
|
||||
|
@ -189,6 +189,14 @@ class UserRepository extends BaseRepository
|
||||
return;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
$count = User::where('account_id', auth()->user()->account_id)->count();
|
||||
if($count >= auth()->user()->account->num_users)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
$user->is_deleted = false;
|
||||
$user->save();
|
||||
$user->restore();
|
||||
|
@ -236,7 +236,7 @@ class Statement
|
||||
private function invoiceStatuses() :array
|
||||
{
|
||||
$status = 'all';
|
||||
nlog($this->options);
|
||||
|
||||
if(array_key_exists('status', $this->options))
|
||||
$status = $this->options['status'];
|
||||
|
||||
|
@ -125,7 +125,7 @@ class AutoBillInvoice extends AbstractService
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage());
|
||||
// nlog($e->getMessage());
|
||||
$this->invoice->service()->removeUnpaidGatewayFees()->save();
|
||||
}
|
||||
|
||||
if($payment){
|
||||
|
@ -88,7 +88,7 @@ class MarkPaid extends AbstractService
|
||||
$this->invoice
|
||||
->service()
|
||||
->applyNumber()
|
||||
->deletePdf()
|
||||
// ->deletePdf()
|
||||
->touchPdf()
|
||||
->save();
|
||||
|
||||
|
@ -37,6 +37,8 @@ class SendEmail
|
||||
$this->payment->client->contacts->each(function ($contact) {
|
||||
if ($contact->email) {
|
||||
EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact);
|
||||
return false;
|
||||
//11-01-2021 only send payment receipt to the first contact
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -99,6 +99,9 @@ trait MakesDates
|
||||
|
||||
public function translateDate($date, $format, $locale)
|
||||
{
|
||||
if(empty($date))
|
||||
return '';
|
||||
|
||||
Carbon::setLocale($locale);
|
||||
|
||||
try {
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.44',
|
||||
'app_tag' => '5.3.44',
|
||||
'app_version' => '5.3.45',
|
||||
'app_tag' => '5.3.45',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -38,7 +38,10 @@
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#entity-details p { margin-right: 20px; }
|
||||
#entity-details p {
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-wrapper #entity-details {
|
||||
width: 100%;
|
||||
|
167
tests/Feature/Payments/StorePaymentValidationTest.php
Normal file
167
tests/Feature/Payments/StorePaymentValidationTest.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?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\Payments;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\WithoutEvents;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\MockUnitData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class StorePaymentValidationTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
use WithoutEvents;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function testValidPayment()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'amount' => 0,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
],
|
||||
'date' => '2019/12/12',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($e->validator->getMessageBag());
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testValidPaymentWithAmount()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'amount' => 0,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $this->invoice->hashed_id,
|
||||
'amount' => 10,
|
||||
],
|
||||
],
|
||||
'credits' => [
|
||||
[
|
||||
'credit_id' => $this->credit->hashed_id,
|
||||
'amount' => 5
|
||||
]
|
||||
],
|
||||
'date' => '2019/12/12',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($e->validator->getMessageBag());
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
public function testValidPaymentWithInvalidData()
|
||||
{
|
||||
|
||||
|
||||
$data = [
|
||||
'amount' => 0,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $this->invoice->hashed_id,
|
||||
],
|
||||
],
|
||||
'credits' => [
|
||||
[
|
||||
'credit_id' => $this->credit->hashed_id,
|
||||
'amount' => 5
|
||||
]
|
||||
],
|
||||
'date' => '2019/12/12',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments/', $data);
|
||||
}catch(ValidationException $e){
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class CompanyLedgerTest extends TestCase
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
|
||||
$settings->company_logo = 'https://app.invoiceninja.com/favicon-v2.png';
|
||||
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
|
||||
$settings->website = 'www.invoiceninja.com';
|
||||
$settings->address1 = 'Address 1';
|
||||
$settings->address2 = 'Address 2';
|
||||
|
@ -192,7 +192,7 @@ trait MockAccountData
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
|
||||
$settings->company_logo = 'https://app.invoiceninja.com/favicon-v2.png';
|
||||
$settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png';
|
||||
// $settings->company_logo = asset('images/new_logo.png');
|
||||
$settings->website = 'www.invoiceninja.com';
|
||||
$settings->address1 = 'Address 1';
|
||||
|
Loading…
Reference in New Issue
Block a user