1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #7116 from turbo124/v5-develop

v5.3.45
This commit is contained in:
David Bomba 2022-01-12 22:12:52 +11:00 committed by GitHub
commit 978605495b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 286 additions and 28 deletions

View File

@ -1 +1 @@
5.3.44 5.3.45

View File

@ -401,7 +401,7 @@ class InvoiceController extends BaseController
$invoice = $this->invoice_repo->save($request->all(), $invoice); $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))); event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -23,6 +23,43 @@ use Illuminate\Http\Response;
class StaticController extends BaseController 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() public function __invoke()
{ {

View File

@ -150,7 +150,7 @@ class RequiredClientInfo extends Component
} }
if (Str::startsWith($field['name'], 'contact_')) { 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) { // if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} == 840) {
$this->show_form = true; $this->show_form = true;
} else { } else {

View File

@ -54,7 +54,10 @@ class StorePaymentRequest extends Request
if (isset($input['invoices']) && is_array($input['invoices']) !== false) { if (isset($input['invoices']) && is_array($input['invoices']) !== false) {
foreach ($input['invoices'] as $key => $value) { foreach ($input['invoices'] as $key => $value) {
$input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); $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() public function rules()
{ {
$rules = [ $rules = [
'amount' => 'numeric|required', 'amount' => 'sometimes|numeric',
'amount' => [new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()], 'amount' => [new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule()],
'client_id' => 'bail|required|exists:clients,id', 'client_id' => 'bail|required|exists:clients,id',
'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id', 'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id',
'invoices.*.amount' => 'bail|required',
'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()), 'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()),
'invoices.*.amount' => 'required',
'credits.*.credit_id' => 'bail|required|exists:credits,id', 'credits.*.credit_id' => 'bail|required|exists:credits,id',
'credits.*.credit_id' => new ValidCreditsRules($this->all()), 'credits.*.credit_id' => new ValidCreditsRules($this->all()),
'credits.*.amount' => ['required', new CreditsSumRule($this->all())], 'credits.*.amount' => ['required', new CreditsSumRule($this->all())],

View File

@ -58,7 +58,7 @@ class StoreUserRequest extends Request
{ {
$input = $this->all(); $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)) if(array_key_exists('email', $input))
$input['email'] = trim($input['email']); $input['email'] = trim($input['email']);

View File

@ -33,13 +33,18 @@ class CanAddUserRule implements Rule
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
$count = CompanyUser::query() /* If the user is active then we can add them to the company */
->where('company_user.account_id', auth()->user()->account_id) if(User::where('email', request()->input('email'))->where('account_id', auth()->user()->account_id)->where('is_deleted',0)->exists())
->join('users', 'users.id', '=', 'company_user.user_id') return true;
->whereNull('users.deleted_at')
->whereNull('company_user.deleted_at') /* Check that we have sufficient quota to allow this to happen */
->distinct() $count = CompanyUser::query()
->count('company_user.user_id'); ->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; return $count < auth()->user()->company()->account->num_users;

View File

@ -57,6 +57,11 @@ class ValidInvoicesRules implements Rule
$unique_array[] = $invoice['invoice_id']; $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(); $inv = Invoice::whereId($invoice['invoice_id'])->first();
if (! $inv) { if (! $inv) {

View File

@ -79,6 +79,11 @@ class ValidRefundableRequest implements Rule
{ {
$invoice = Invoice::whereId($invoice['invoice_id'])->whereCompanyId($payment->company_id)->withTrashed()->first(); $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()) { if ($payment->invoices()->exists()) {
$paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first(); $paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first();

View File

@ -59,19 +59,30 @@ class PaymentAmountsBalanceRule implements Rule
if (request()->input('credits') && is_array(request()->input('credits'))) { if (request()->input('credits') && is_array(request()->input('credits'))) {
foreach (request()->input('credits') as $credit) { 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'))) { if (request()->input('invoices') && is_array(request()->input('invoices'))) {
foreach (request()->input('invoices') as $invoice) { foreach (request()->input('invoices') as $invoice) {
$invoice_amounts += $invoice['amount'];
if(array_key_exists('amount', $invoice))
$invoice_amounts += $invoice['amount'];
} }
} else { } else {
return true; 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;
} }
} }

View File

@ -1866,10 +1866,19 @@ class Import implements ShouldQueue
private function processNinjaTokens(array $data) private function processNinjaTokens(array $data)
{ {
nlog("attempting to process Ninja Tokens"); nlog("attempting to process Ninja Tokens");
if(Ninja::isHosted()) if(Ninja::isHosted()){
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);
try{
\Modules\Admin\Jobs\Account\NinjaUser::dispatchNow($data, $this->company);
}
catch(\Exception $e){
nlog($e->getMessage());
}
}
} }

View File

@ -171,7 +171,7 @@ class Company extends BaseModel
public function users() 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() public function expense_categories()

View File

@ -189,6 +189,14 @@ class UserRepository extends BaseRepository
return; 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->is_deleted = false;
$user->save(); $user->save();
$user->restore(); $user->restore();

View File

@ -236,7 +236,7 @@ class Statement
private function invoiceStatuses() :array private function invoiceStatuses() :array
{ {
$status = 'all'; $status = 'all';
nlog($this->options);
if(array_key_exists('status', $this->options)) if(array_key_exists('status', $this->options))
$status = $this->options['status']; $status = $this->options['status'];

View File

@ -125,7 +125,7 @@ class AutoBillInvoice extends AbstractService
} }
catch(\Exception $e){ catch(\Exception $e){
nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage()); nlog("payment NOT captured for ". $this->invoice->number . " with error " . $e->getMessage());
// nlog($e->getMessage()); $this->invoice->service()->removeUnpaidGatewayFees()->save();
} }
if($payment){ if($payment){

View File

@ -88,7 +88,7 @@ class MarkPaid extends AbstractService
$this->invoice $this->invoice
->service() ->service()
->applyNumber() ->applyNumber()
->deletePdf() // ->deletePdf()
->touchPdf() ->touchPdf()
->save(); ->save();

View File

@ -37,6 +37,8 @@ class SendEmail
$this->payment->client->contacts->each(function ($contact) { $this->payment->client->contacts->each(function ($contact) {
if ($contact->email) { if ($contact->email) {
EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact); EmailPayment::dispatchNow($this->payment, $this->payment->company, $contact);
return false;
//11-01-2021 only send payment receipt to the first contact
} }
}); });
} }

View File

@ -99,6 +99,9 @@ trait MakesDates
public function translateDate($date, $format, $locale) public function translateDate($date, $format, $locale)
{ {
if(empty($date))
return '';
Carbon::setLocale($locale); Carbon::setLocale($locale);
try { try {

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.44', 'app_version' => '5.3.45',
'app_tag' => '5.3.44', 'app_tag' => '5.3.45',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

View File

@ -38,7 +38,10 @@
border-radius: 10px; border-radius: 10px;
} }
#entity-details p { margin-right: 20px; } #entity-details p {
margin-right: 20px;
white-space: nowrap;
}
.header-wrapper #entity-details { .header-wrapper #entity-details {
width: 100%; width: 100%;

View 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);
}
}
}

View File

@ -84,7 +84,7 @@ class CompanyLedgerTest extends TestCase
$settings = CompanySettings::defaults(); $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->website = 'www.invoiceninja.com';
$settings->address1 = 'Address 1'; $settings->address1 = 'Address 1';
$settings->address2 = 'Address 2'; $settings->address2 = 'Address 2';

View File

@ -192,7 +192,7 @@ trait MockAccountData
$settings = CompanySettings::defaults(); $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->company_logo = asset('images/new_logo.png');
$settings->website = 'www.invoiceninja.com'; $settings->website = 'www.invoiceninja.com';
$settings->address1 = 'Address 1'; $settings->address1 = 'Address 1';