From 171f2c82e65646eac52fce0d812a357db12cae1c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 28 Jan 2022 08:51:53 +1100 Subject: [PATCH 1/5] Move cors middle out of globals and into api only routes --- app/Http/Kernel.php | 3 ++- app/Http/Middleware/SessionDomains.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 2c28115120..597943b0d8 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,7 +73,7 @@ class Kernel extends HttpKernel ConvertEmptyStringsToNull::class, TrustProxies::class, // \Fruitcake\Cors\HandleCors::class, - Cors::class, + //Cors::class, ]; @@ -96,6 +96,7 @@ class Kernel extends HttpKernel 'api' => [ // 'throttle:300,1', + 'cors', 'bindings', 'query_logging', ], diff --git a/app/Http/Middleware/SessionDomains.php b/app/Http/Middleware/SessionDomains.php index 0881aad8ed..86fb61a32c 100644 --- a/app/Http/Middleware/SessionDomains.php +++ b/app/Http/Middleware/SessionDomains.php @@ -39,7 +39,7 @@ class SessionDomains } else{ - Cookie::queue(Cookie::forget('invoice_ninja_session', '/')); + // Cookie::queue(Cookie::forget('invoice_ninja_session', '/')); config(['session.domain' => $domain_name]); From 0cffb3d227f1dc43f62b959a0a3a363a6b0acd6b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 28 Jan 2022 10:06:07 +1100 Subject: [PATCH 2/5] Add hosted parameters --- app/Transformers/AccountTransformer.php | 2 ++ ...617_add_client_count_to_accounts_table.php | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 database/migrations/2022_01_27_223617_add_client_count_to_accounts_table.php diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 65408f35c7..11a6331bc4 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -83,6 +83,8 @@ class AccountTransformer extends EntityTransformer 'emails_sent' => (int) $account->emailsSent(), 'email_quota' => (int) $account->getDailyEmailLimit(), 'is_migrated' => (bool) $account->is_migrated, + 'hosted_client_count' => (int) $account->hosted_client_count, + 'hosted_company_count' => (int) $account->hosted_company_count, ]; } diff --git a/database/migrations/2022_01_27_223617_add_client_count_to_accounts_table.php b/database/migrations/2022_01_27_223617_add_client_count_to_accounts_table.php new file mode 100644 index 0000000000..2dc113d5c4 --- /dev/null +++ b/database/migrations/2022_01_27_223617_add_client_count_to_accounts_table.php @@ -0,0 +1,31 @@ +unsignedInteger('hosted_client_count')->nullable(); + $table->unsignedInteger('hosted_company_count')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} From e6759db35d8bde1c001f933d172540ee3dc75700 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 28 Jan 2022 10:06:26 +1100 Subject: [PATCH 3/5] Hosted metrics --- app/Http/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 597943b0d8..515d5f0df5 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,7 +73,7 @@ class Kernel extends HttpKernel ConvertEmptyStringsToNull::class, TrustProxies::class, // \Fruitcake\Cors\HandleCors::class, - //Cors::class, + Cors::class, ]; @@ -96,7 +96,7 @@ class Kernel extends HttpKernel 'api' => [ // 'throttle:300,1', - 'cors', + // 'cors', 'bindings', 'query_logging', ], From c7c26532c5cbbdecaa8e40f82dc9e65c5c7c8f63 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 28 Jan 2022 10:56:14 +1100 Subject: [PATCH 4/5] Allow negative invoices --- app/Http/Controllers/InvoiceController.php | 3 ++- app/Http/Requests/Invoice/UpdateInvoiceRequest.php | 4 ++-- app/Services/Invoice/MarkPaid.php | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index f23b049666..eaf62b4b8f 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -658,7 +658,8 @@ class InvoiceController extends BaseController // code... break; case 'mark_paid': - if ($invoice->balance < 0 || $invoice->status_id == Invoice::STATUS_PAID || $invoice->is_deleted === true) { + if ($invoice->status_id == Invoice::STATUS_PAID || $invoice->is_deleted === true) { + // if ($invoice->balance < 0 || $invoice->status_id == Invoice::STATUS_PAID || $invoice->is_deleted === true) { return $this->errorResponse(['message' => ctrans('texts.invoice_cannot_be_marked_paid')], 400); } diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index d008d7a36b..10d8665a96 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -58,8 +58,8 @@ class UpdateInvoiceRequest extends Request $rules['line_items'] = 'array'; $rules['discount'] = 'sometimes|numeric'; - if($this->input('status_id') != Invoice::STATUS_DRAFT) - $rules['balance'] = new InvoiceBalanceSanity($this->invoice, $this->all()); + // if($this->input('status_id') != Invoice::STATUS_DRAFT) + // $rules['balance'] = new InvoiceBalanceSanity($this->invoice, $this->all()); return $rules; } diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index 90d51588c4..026892f495 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -88,7 +88,6 @@ class MarkPaid extends AbstractService $this->invoice ->service() ->applyNumber() - // ->deletePdf() ->touchPdf() ->save(); From 548510d686c536ffb6e1f112bebbebf113ec853c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 28 Jan 2022 16:30:40 +1100 Subject: [PATCH 5/5] Improvements for credits --- VERSION.txt | 2 +- app/Exceptions/Handler.php | 2 +- app/Http/Controllers/CreditController.php | 5 ++ .../Controllers/HostedMigrationController.php | 5 +- app/Http/Middleware/Cors.php | 4 +- .../Requests/Credit/StoreCreditRequest.php | 4 +- .../Credit/ValidInvoiceCreditRule.php | 79 +++++++++++++++++++ app/Jobs/Account/CreateAccount.php | 3 +- app/Repositories/BaseRepository.php | 2 + config/ninja.php | 11 ++- 10 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 app/Http/ValidationRules/Credit/ValidInvoiceCreditRule.php diff --git a/VERSION.txt b/VERSION.txt index b6d99a4aca..7de98dd799 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.52 \ No newline at end of file +5.3.53 \ No newline at end of file diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index afe61dd9e7..0e0b70570a 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -192,7 +192,7 @@ class Handler extends ExceptionHandler } elseif ($exception instanceof MethodNotAllowedHttpException && $request->expectsJson()) { return response()->json(['message'=>'Method not supported for this route'], 404); } elseif ($exception instanceof ValidationException && $request->expectsJson()) { - nlog($exception->validator->getMessageBag()); + // nlog($exception->validator->getMessageBag()); return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422); } elseif ($exception instanceof RelationNotFoundException && $request->expectsJson()) { return response()->json(['message' => $exception->getMessage()], 400); diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 0675437752..82e6c7e022 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -203,6 +203,11 @@ class CreditController extends BaseController ->triggeredActions($request) ->save(); + if($credit->invoice_id){ + $credit = $credit->service()->markSent()->save(); + $credit->client->service()->updatePaidToDate(-1 * $credit->balance)->save(); + } + event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); return $this->itemResponse($credit); diff --git a/app/Http/Controllers/HostedMigrationController.php b/app/Http/Controllers/HostedMigrationController.php index e61f026d98..ff5c8d4ce5 100644 --- a/app/Http/Controllers/HostedMigrationController.php +++ b/app/Http/Controllers/HostedMigrationController.php @@ -42,7 +42,10 @@ class HostedMigrationController extends Controller } $account = CreateAccount::dispatchNow($request->all(), $request->getClientIp()); - + $account->hosted_client_count = 100; + $account->hosted_company_count = 10; + $account->save(); + $company = $account->companies->first(); $company_token = CompanyToken::where('user_id', auth()->user()->id) diff --git a/app/Http/Middleware/Cors.php b/app/Http/Middleware/Cors.php index fbeb6e892e..759a7310e8 100644 --- a/app/Http/Middleware/Cors.php +++ b/app/Http/Middleware/Cors.php @@ -16,7 +16,7 @@ class Cors // ALLOW OPTIONS METHOD $headers = [ 'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE', - 'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE', + 'Access-Control-Allow-Headers'=> 'X-API-PASSWORD-BASE64,X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE', ]; return Response::make('OK', 200, $headers); @@ -26,7 +26,7 @@ class Cors $response->headers->set('Access-Control-Allow-Origin', '*'); $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); - $response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE'); + $response->headers->set('Access-Control-Allow-Headers', 'X-API-PASSWORD-BASE64,X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-XSRF-TOKEN,X-LIVEWIRE'); $response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION'); $response->headers->set('X-APP-VERSION', config('ninja.app_version')); $response->headers->set('X-MINIMUM-CLIENT-VERSION', config('ninja.minimum_client_version')); diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index 5b0cd808ee..2f05fbac1e 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -13,6 +13,7 @@ namespace App\Http\Requests\Credit; use App\Http\Requests\Request; use App\Http\ValidationRules\Credit\UniqueCreditNumberRule; +use App\Http\ValidationRules\Credit\ValidInvoiceCreditRule; use App\Models\Credit; use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\MakesHash; @@ -58,7 +59,8 @@ class StoreCreditRequest extends Request $rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', auth()->user()->company()->id)]; $rules['discount'] = 'sometimes|numeric'; - + if($this->invoice_id) + $rules['invoice_id'] = new ValidInvoiceCreditRule(); $rules['line_items'] = 'array'; diff --git a/app/Http/ValidationRules/Credit/ValidInvoiceCreditRule.php b/app/Http/ValidationRules/Credit/ValidInvoiceCreditRule.php new file mode 100644 index 0000000000..ad9ec600bd --- /dev/null +++ b/app/Http/ValidationRules/Credit/ValidInvoiceCreditRule.php @@ -0,0 +1,79 @@ +checkIfCreditInvoiceValid($value); //if it exists, return false! + } + + /** + * @return string + */ + public function message() + { + return $this->error_message; + } + + /** + * @return bool + */ + private function checkIfCreditInvoiceValid($value) : bool + { + $invoice = Invoice::withTrashed()->find($value); + + if($invoice->balance >= $invoice->amount){ + $this->error_message = "Cannot reverse an invoice with no payment applied."; + return false; + } + + $existing_credit_amounts = $invoice->credits()->sum('amount'); + + if($this->sumCredit() > ($invoice->amount - $invoice->balance - $existing_credit_amounts)){ + $this->error_message = "Credit cannot exceed the payment / credits already applied to invoice."; + return false; + } + + return true; + } + + private function sumCredit() + { + $cost = 0; + + foreach(request()->input('line_items') as $item) + { + $cost += $item['cost'] * $item['quantity']; + } + + return $cost; + } +} diff --git a/app/Jobs/Account/CreateAccount.php b/app/Jobs/Account/CreateAccount.php index 3277c08479..a9b76f8dbd 100644 --- a/app/Jobs/Account/CreateAccount.php +++ b/app/Jobs/Account/CreateAccount.php @@ -82,7 +82,8 @@ class CreateAccount if(Ninja::isHosted()) { - + $sp794f3f->hosted_client_count = config('ninja.quotas.free.clients'); + $sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies'); $sp794f3f->trial_started = now(); $sp794f3f->trial_plan = 'pro'; diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index 8858e01332..6ae4bcae40 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -327,6 +327,8 @@ class BaseRepository if (! $model->design_id) $model->design_id = $this->decodePrimaryKey($client->getSetting('credit_design_id')); + if(array_key_exists('invoice_id', $data) && $data['invoice_id']) + $model->invoice_id = $data['invoice_id']; if($this->new_model) event('eloquent.created: App\Models\Credit', $model); diff --git a/config/ninja.php b/config/ninja.php index 14d1578f1b..2d35bf32a7 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -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.52', - 'app_tag' => '5.3.52', + 'app_version' => '5.3.53', + 'app_tag' => '5.3.53', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), @@ -134,14 +134,19 @@ return [ ], 'quotas' => [ 'free' => [ - 'clients' => 50, 'daily_emails' => 50, + 'clients' => 20, + 'max_companies' => 1, ], 'pro' => [ 'daily_emails' => 100, + 'clients' => 1000000, + 'max_companies' => 10, ], 'enterprise' => [ 'daily_emails' => 200, + 'clients' => 1000000, + 'max_companies' => 10, ], ], 'auth' => [