From 3eb9688a830fe33e6ba2c6bd597f0fd102e4d21b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 30 Mar 2021 21:08:02 +1100 Subject: [PATCH 1/3] fix subscriptions schema --- app/Services/Quote/QuoteService.php | 2 +- ...148_add_price_column_to_subscriptions_table.php | 14 +++++++++++--- .../ninja2020/quotes/includes/actions.blade.php | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index b7684620827e..5c6ab236e2c0 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -110,7 +110,7 @@ class QuoteService $this->invoice ->service() - ->markSent() + // ->markSent() ->createInvitations() ->deletePdf() ->save(); diff --git a/database/migrations/2021_03_26_201148_add_price_column_to_subscriptions_table.php b/database/migrations/2021_03_26_201148_add_price_column_to_subscriptions_table.php index 1514d8c75f32..c4f1e453008d 100644 --- a/database/migrations/2021_03_26_201148_add_price_column_to_subscriptions_table.php +++ b/database/migrations/2021_03_26_201148_add_price_column_to_subscriptions_table.php @@ -21,6 +21,17 @@ class AddPriceColumnToSubscriptionsTable extends Migration Schema::table('recurring_invoices', function (Blueprint $table) { $table->unsignedInteger('subscription_id')->nullable(); }); + + Schema::table('subscriptions', function (Blueprint $table) { + $table->unsignedInteger('group_id')->nullable()->change(); + $table->text('product_ids')->nullable()->change(); + $table->text('recurring_product_ids')->nullable()->change(); + $table->text('auto_bill')->nullable()->change(); + $table->text('promo_code')->nullable()->change(); + $table->unsignedInteger('frequency_id')->nullable()->change(); + $table->text('plan_map')->nullable()->change(); + }); + } /** @@ -30,8 +41,5 @@ class AddPriceColumnToSubscriptionsTable extends Migration */ public function down() { - Schema::table('subscriptions', function (Blueprint $table) { - // - }); } } diff --git a/resources/views/portal/ninja2020/quotes/includes/actions.blade.php b/resources/views/portal/ninja2020/quotes/includes/actions.blade.php index bc49946a1c29..28f796cd2beb 100644 --- a/resources/views/portal/ninja2020/quotes/includes/actions.blade.php +++ b/resources/views/portal/ninja2020/quotes/includes/actions.blade.php @@ -8,7 +8,7 @@

- {{ ctrans('texts.pending_approval') }} + {{ ctrans('texts.approve') }}

From b5e0276b1cb7f280805fc8f56895d00c951e6b47 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 31 Mar 2021 08:57:14 +1100 Subject: [PATCH 2/3] Mark approved quotes as sent --- app/Services/Quote/QuoteService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index 5c6ab236e2c0..b7684620827e 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -110,7 +110,7 @@ class QuoteService $this->invoice ->service() - // ->markSent() + ->markSent() ->createInvitations() ->deletePdf() ->save(); From 8e2c07b0df41c02af0b9b996460cd8eee1feabea Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 31 Mar 2021 09:58:50 +1100 Subject: [PATCH 3/3] Fixes --- VERSION.txt | 2 +- app/Console/Commands/PostUpdate.php | 3 +- app/Console/Kernel.php | 6 +- app/DataMapper/CompanySettings.php | 5 +- .../BillingSubscriptionWasCreated.php | 55 --- app/Events/Credit/CreditWasViewed.php | 1 + app/Exceptions/Handler.php | 8 +- app/Factory/BillingSubscriptionFactory.php | 27 -- app/Factory/CompanyFactory.php | 3 +- app/Http/Controllers/Auth/LoginController.php | 39 +- app/Http/Controllers/BaseController.php | 15 +- .../BillingSubscriptionController.php | 410 ------------------ .../BillingSubscriptionPurchaseController.php | 30 -- .../ConnectedAccountController.php | 73 +++- app/Http/Controllers/EmailController.php | 2 +- app/Http/Controllers/InvoiceController.php | 4 +- .../OpenAPI/BillingSubscription.php | 2 +- app/Http/Controllers/QuoteController.php | 13 +- app/Http/Controllers/SelfUpdateController.php | 10 +- app/Http/Controllers/UserController.php | 13 +- app/Http/Livewire/BillingPortalPurchase.php | 234 +++++++++- .../CreateBillingSubscriptionRequest.php | 40 -- .../DestroyBillingSubscriptionRequest.php | 40 -- .../EditBillingSubscriptionRequest.php | 40 -- .../ShowBillingSubscriptionRequest.php | 40 -- .../StoreBillingSubscriptionRequest.php | 60 --- .../UpdateBillingSubscriptionRequest.php | 42 -- .../Requests/Client/StoreClientRequest.php | 4 + .../Requests/Credit/UpdateCreditRequest.php | 6 +- .../Requests/Invoice/ActionInvoiceRequest.php | 2 - .../Requests/Invoice/UpdateInvoiceRequest.php | 6 +- app/Http/Requests/Quote/StoreQuoteRequest.php | 5 +- app/Http/Requests/Request.php | 4 + .../User/DetachCompanyUserRequest.php | 4 +- app/Http/ViewComposers/PortalComposer.php | 1 + app/Jobs/Company/CreateCompany.php | 1 + app/Jobs/Cron/BillingSubscriptionCron.php | 72 --- app/Jobs/Cron/RecurringInvoicesCron.php | 2 + app/Jobs/RecurringInvoice/SendRecurring.php | 24 +- app/Jobs/Util/Import.php | 5 +- .../Credit/CreditEmailedNotification.php | 2 +- .../Invoice/InvoiceEmailedNotification.php | 2 +- .../InvoiceFailedEmailNotification.php | 2 +- .../Quote/QuoteEmailedNotification.php | 2 +- app/Mail/Engine/QuoteEmailEngine.php | 1 + app/Mail/SupportMessageSent.php | 2 +- app/Mail/TemplateEmail.php | 6 +- app/Models/BaseModel.php | 4 +- app/Models/BillingSubscription.php | 76 ---- app/Models/Company.php | 13 +- app/Models/Presenters/CompanyPresenter.php | 4 +- app/Observers/BillingSubscriptionObserver.php | 72 --- .../Authorize/AuthorizeCreditCard.php | 12 +- app/PaymentDrivers/BaseDriver.php | 8 +- app/PaymentDrivers/StripePaymentDriver.php | 10 +- app/Policies/BillingSubscriptionPolicy.php | 31 -- app/Providers/AppServiceProvider.php | 6 +- app/Providers/AuthServiceProvider.php | 8 +- .../BillingSubscriptionRepository.php | 28 -- app/Repositories/PaymentRepository.php | 1 - .../RecurringInvoiceRepository.php | 12 - app/Repositories/UserRepository.php | 16 +- .../BillingSubscriptionService.php | 209 --------- app/Services/Client/PaymentMethod.php | 2 +- app/Services/Invoice/InvoiceService.php | 21 + app/Services/PdfMaker/Design.php | 6 +- .../BillingSubscriptionTransformer.php | 74 ---- app/Transformers/CompanyTransformer.php | 17 +- .../RecurringInvoiceTransformer.php | 1 + app/Transformers/UserTransformer.php | 1 + app/Utils/HtmlEngine.php | 11 +- app/Utils/Traits/CleanLineItems.php | 2 +- .../Traits/Notifications/UserNotifies.php | 4 +- composer.json | 2 +- composer.lock | 196 +++++---- config/app.php | 2 +- config/ninja.php | 2 +- .../factories/BillingSubscriptionFactory.php | 38 -- ...add_unique_constraints_on_all_entities.php | 4 - resources/lang/en/texts.php | 4 +- .../views/billing-portal/purchase.blade.php | 4 +- .../views/email/template/custom.blade.php | 2 +- .../billing-portal-purchase.blade.php | 129 ++++-- .../stripe/includes/card_widget.blade.php | 2 +- .../portal/ninja2020/invoices/show.blade.php | 2 +- .../quotes/includes/actions.blade.php | 2 +- .../recurring_invoices/show.blade.php | 6 +- routes/api.php | 4 +- routes/client.php | 4 +- tailwind.config.js | 3 + tests/Feature/BillingSubscriptionApiTest.php | 134 ------ .../PaymentDrivers/AuthorizeTest.php | 22 + 92 files changed, 741 insertions(+), 1840 deletions(-) delete mode 100644 app/Events/BillingSubscription/BillingSubscriptionWasCreated.php delete mode 100644 app/Factory/BillingSubscriptionFactory.php delete mode 100644 app/Http/Controllers/BillingSubscriptionController.php delete mode 100644 app/Http/Controllers/ClientPortal/BillingSubscriptionPurchaseController.php delete mode 100644 app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php delete mode 100644 app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php delete mode 100644 app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php delete mode 100644 app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php delete mode 100644 app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php delete mode 100644 app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php delete mode 100644 app/Jobs/Cron/BillingSubscriptionCron.php delete mode 100644 app/Models/BillingSubscription.php delete mode 100644 app/Observers/BillingSubscriptionObserver.php delete mode 100644 app/Policies/BillingSubscriptionPolicy.php delete mode 100644 app/Repositories/BillingSubscriptionRepository.php delete mode 100644 app/Services/BillingSubscription/BillingSubscriptionService.php delete mode 100644 app/Transformers/BillingSubscriptionTransformer.php delete mode 100644 database/factories/BillingSubscriptionFactory.php delete mode 100644 tests/Feature/BillingSubscriptionApiTest.php diff --git a/VERSION.txt b/VERSION.txt index 6935e0e042d6..2b7afee56bdb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.1.29 \ No newline at end of file +5.1.32 \ No newline at end of file diff --git a/app/Console/Commands/PostUpdate.php b/app/Console/Commands/PostUpdate.php index 4f861e1988e4..00cce48d61c8 100644 --- a/app/Console/Commands/PostUpdate.php +++ b/app/Console/Commands/PostUpdate.php @@ -52,8 +52,7 @@ class PostUpdate extends Command nlog("finished migrating"); - exec('vendor/bin/composer install --no-dev'); - exec('vendor/bin/composer dump'); + exec('vendor/bin/composer install --no-dev -o'); nlog("finished running composer install "); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 61c3f3f7ece4..2cf0371b23e4 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,7 +11,7 @@ namespace App\Console; -use App\Jobs\Cron\BillingSubscriptionCron; +use App\Jobs\Cron\SubscriptionCron; use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\CompanySizeCheck; @@ -44,7 +44,7 @@ class Kernel extends ConsoleKernel protected function schedule(Schedule $schedule) { - $schedule->job(new VersionCheck)->daily()->withoutOverlapping(); + $schedule->job(new VersionCheck)->daily(); $schedule->command('ninja:check-data')->daily()->withoutOverlapping(); @@ -54,7 +54,7 @@ class Kernel extends ConsoleKernel $schedule->job(new UpdateExchangeRates)->daily()->withoutOverlapping(); - $schedule->job(new BillingSubscriptionCron)->daily()->withoutOverlapping(); + $schedule->job(new SubscriptionCron)->daily()->withoutOverlapping(); $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index ce9b344e323e..049f807d5743 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -109,7 +109,7 @@ class CompanySettings extends BaseSettings public $shared_invoice_quote_counter = false; //@implemented public $shared_invoice_credit_counter = false; //@implemented - public $recurring_number_prefix = 'R'; //@implemented + public $recurring_number_prefix = ''; //@implemented public $reset_counter_frequency_id = '0'; //@implemented public $reset_counter_date = ''; //@implemented public $counter_padding = 4; //@implemented @@ -574,7 +574,8 @@ class CompanySettings extends BaseSettings public static function notificationDefaults() :stdClass { $notification = new stdClass; - $notification->email = ['all_notifications']; + $notification->email = []; + // $notification->email = ['all_notifications']; return $notification; } diff --git a/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php b/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php deleted file mode 100644 index 73fc2d1f2f87..000000000000 --- a/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php +++ /dev/null @@ -1,55 +0,0 @@ -billing_subscription = $billing_subscription; - $this->company = $company; - $this->event_vars = $event_vars; - } - - /** - * Get the channels the event should broadcast on. - * - * @return \Illuminate\Broadcasting\Channel|array - */ - public function broadcastOn() - { - return new PrivateChannel('channel-name'); - } -} diff --git a/app/Events/Credit/CreditWasViewed.php b/app/Events/Credit/CreditWasViewed.php index fdf9b8708f0e..0bd2405c0d79 100644 --- a/app/Events/Credit/CreditWasViewed.php +++ b/app/Events/Credit/CreditWasViewed.php @@ -12,6 +12,7 @@ namespace App\Events\Credit; use App\Models\Company; +use App\Models\CreditInvitation; use Illuminate\Queue\SerializesModels; /** diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3351dc65a4e1..9ecf8470c940 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -94,7 +94,9 @@ class Handler extends ExceptionHandler } } - parent::report($exception); + if(config('ninja.expanded_logging')) + parent::report($exception); + } private function validException($exception) @@ -105,7 +107,7 @@ class Handler extends ExceptionHandler if (strpos($exception->getMessage(), 'Permission denied') !== false) return false; - if (strpos($exception->getMessage(), 'flock()') !== false) + if (strpos($exception->getMessage(), 'flock') !== false) return false; if (strpos($exception->getMessage(), 'expects parameter 1 to be resource') !== false) @@ -114,6 +116,8 @@ class Handler extends ExceptionHandler if (strpos($exception->getMessage(), 'fwrite()') !== false) return false; + if(strpos($exception->getMessage(), 'LockableFile') !== false) + return false; return true; } diff --git a/app/Factory/BillingSubscriptionFactory.php b/app/Factory/BillingSubscriptionFactory.php deleted file mode 100644 index f9f051293e9c..000000000000 --- a/app/Factory/BillingSubscriptionFactory.php +++ /dev/null @@ -1,27 +0,0 @@ -company_id = $company_id; - $billing_subscription->user_id = $user_id; - - return $billing_subscription; - } -} diff --git a/app/Factory/CompanyFactory.php b/app/Factory/CompanyFactory.php index 928fd871f37f..5faf3df95cf4 100644 --- a/app/Factory/CompanyFactory.php +++ b/app/Factory/CompanyFactory.php @@ -35,7 +35,8 @@ class CompanyFactory $company->custom_fields = (object) []; $company->subdomain = ''; $company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095 - $company->default_password_timeout = 30 * 60000; + $company->default_password_timeout = 1800000; + return $company; } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 4dd0db5320a5..12cdb5ef6283 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -193,8 +193,7 @@ class LoginController extends BaseController } $user->setCompany($user->account->default_company); - $timeout = auth()->user()->company()->default_password_timeout; - + $timeout = auth()->user()->company()->default_password_timeout / 60000; Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout); $cu = CompanyUser::query() @@ -312,6 +311,8 @@ class LoginController extends BaseController Auth::login($existing_user, true); $existing_user->setCompany($existing_user->account->default_company); + $timeout = $existing_user->company()->default_password_timeout / 60000; + Cache::put($existing_user->hashed_id.'_logged_in', Str::random(64), $timeout); $cu = CompanyUser::query() ->where('user_id', auth()->user()->id); @@ -322,34 +323,6 @@ class LoginController extends BaseController } if ($user) { - - // we are no longer accessing the permissions for gmail - email permissions here - - // $client = new Google_Client(); - // $client->setClientId(config('ninja.auth.google.client_id')); - // $client->setClientSecret(config('ninja.auth.google.client_secret')); - // $client->setRedirectUri(config('ninja.app_url')); - - // $token = false; - - // try{ - // $token = $client->authenticate(request()->input('server_auth_code')); - // } - // catch(\Exception $e) { - - // return response() - // ->json(['message' => ctrans('texts.invalid_credentials')], 401) - // ->header('X-App-Version', config('ninja.app_version')) - // ->header('X-Api-Version', config('ninja.minimum_client_version')); - - // } - - // $refresh_token = ''; - - // if (array_key_exists('refresh_token', $token)) { - // $refresh_token = $token['refresh_token']; - // } - $name = OAuth::splitName($google->harvestName($user)); @@ -359,8 +332,8 @@ class LoginController extends BaseController 'password' => '', 'email' => $google->harvestEmail($user), 'oauth_user_id' => $google->harvestSubField($user), - 'oauth_user_token' => $token, - 'oauth_user_refresh_token' => $refresh_token, + // 'oauth_user_token' => $token, + // 'oauth_user_refresh_token' => $refresh_token, 'oauth_provider_id' => 'google', ]; @@ -372,6 +345,8 @@ class LoginController extends BaseController auth()->user()->email_verified_at = now(); auth()->user()->save(); + $timeout = auth()->user()->company()->default_password_timeout / 60000; + Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout); $ct = CompanyUser::whereUserId(auth()->user()->id); diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 4fa44c019a2a..ea5b8d97deb5 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -92,11 +92,13 @@ class BaseController extends Controller 'company.quotes.invitations.company', 'company.quotes.documents', 'company.tasks.documents', + 'company.subscriptions', 'company.tax_rates', 'company.tokens_hashed', 'company.vendors.contacts.company', 'company.vendors.documents', 'company.webhooks', + 'company.system_logs', ]; private $mini_load = [ @@ -214,7 +216,7 @@ class BaseController extends Controller if(!$user->hasPermission('view_client')) $query->where('clients.user_id', $user->id)->orWhere('clients.assigned_user_id', $user->id); - + }, 'company.company_gateways' => function ($query) use ($user) { $query->whereNotNull('updated_at'); @@ -339,7 +341,14 @@ class BaseController extends Controller if(!$user->isAdmin()) $query->where('activities.user_id', $user->id); - + + }, + 'company.subscriptions'=> function ($query) use($updated_at, $user) { + $query->where('updated_at', '>=', $updated_at); + + if(!$user->isAdmin()) + $query->where('subscriptions.user_id', $user->id); + } ] ); @@ -435,7 +444,7 @@ class BaseController extends Controller if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON) { $this->entity_type = null; } - + $resource = new Item($item, $transformer, $this->entity_type); if (auth()->user() && request()->include_static) { diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php deleted file mode 100644 index 199d51005114..000000000000 --- a/app/Http/Controllers/BillingSubscriptionController.php +++ /dev/null @@ -1,410 +0,0 @@ -billing_subscription_repo = $billing_subscription_repo; - } - - /** - * Show the list of BillingSubscriptions. - * - * @return Response - * - * @OA\Get( - * path="/api/v1/billing_subscriptions", - * operationId="getBillingSubscriptions", - * tags={"billing_subscriptions"}, - * summary="Gets a list of billing_subscriptions", - * description="Lists billing_subscriptions.", - * - * @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 billing_subscriptions", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 index(): \Illuminate\Http\Response - { - $billing_subscriptions = BillingSubscription::query()->company(); - - return $this->listResponse($billing_subscriptions); - } - - /** - * Show the form for creating a new resource. - * - * @param CreateBillingSubscriptionRequest $request The request - * - * @return Response - * - * - * @OA\Get( - * path="/api/v1/billing_subscriptions/create", - * operationId="getBillingSubscriptionsCreate", - * tags={"billing_subscriptions"}, - * summary="Gets a new blank billing_subscriptions object", - * description="Returns a blank object with default values", - * @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 blank billing_subscriptions object", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 create(CreateBillingSubscriptionRequest $request): \Illuminate\Http\Response - { - $billing_subscription = BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); - - return $this->itemResponse($billing_subscription); - } - - /** - * Store a newly created resource in storage. - * - * @param StoreBillingSubscriptionRequest $request The request - * - * @return Response - * - * - * @OA\Post( - * path="/api/v1/billing_subscriptions", - * operationId="storeBillingSubscription", - * tags={"billing_subscriptions"}, - * summary="Adds a billing_subscriptions", - * description="Adds an billing_subscriptions to the system", - * @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="Returns the saved billing_subscriptions object", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 store(StoreBillingSubscriptionRequest $request): \Illuminate\Http\Response - { - $billing_subscription = $this->billing_subscription_repo->save($request->all(), BillingSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id)); - - event(new BillingsubscriptionWasCreated($billing_subscription, $billing_subscription->company, Ninja::eventVars())); - - return $this->itemResponse($billing_subscription); - } - - /** - * Display the specified resource. - * - * @param ShowBillingSubscriptionRequest $request The request - * @param Invoice $billing_subscription The invoice - * - * @return Response - * - * - * @OA\Get( - * path="/api/v1/billing_subscriptions/{id}", - * operationId="showBillingSubscription", - * tags={"billing_subscriptions"}, - * summary="Shows an billing_subscriptions", - * description="Displays an billing_subscriptions by id", - * @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\Parameter( - * name="id", - * in="path", - * description="The BillingSubscription Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the BillingSubscription object", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 show(ShowBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response - { - return $this->itemResponse($billing_subscription); - } - - /** - * Show the form for editing the specified resource. - * - * @param EditBillingSubscriptionRequest $request The request - * @param Invoice $billing_subscription The invoice - * - * @return Response - * - * @OA\Get( - * path="/api/v1/billing_subscriptions/{id}/edit", - * operationId="editBillingSubscription", - * tags={"billing_subscriptions"}, - * summary="Shows an billing_subscriptions for editting", - * description="Displays an billing_subscriptions by id", - * @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\Parameter( - * name="id", - * in="path", - * description="The BillingSubscription Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the invoice object", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 edit(EditBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response - { - return $this->itemResponse($billing_subscription); - } - - /** - * Update the specified resource in storage. - * - * @param UpdateBillingSubscriptionRequest $request The request - * @param BillingSubscription $billing_subscription The invoice - * - * @return Response - * - * - * @OA\Put( - * path="/api/v1/billing_subscriptions/{id}", - * operationId="updateBillingSubscription", - * tags={"billing_subscriptions"}, - * summary="Updates an billing_subscriptions", - * description="Handles the updating of an billing_subscriptions by id", - * @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\Parameter( - * name="id", - * in="path", - * description="The BillingSubscription Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns the billing_subscriptions object", - * @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\JsonContent(ref="#/components/schemas/BillingSubscription"), - * ), - * @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 update(UpdateBillingSubscriptionRequest $request, BillingSubscription $billing_subscription) - { - if ($request->entityIsDeleted($billing_subscription)) { - return $request->disallowUpdate(); - } - - $billing_subscription = $this->billing_subscription_repo->save($request->all(), $billing_subscription); - - return $this->itemResponse($billing_subscription); - } - - /** - * Remove the specified resource from storage. - * - * @param DestroyBillingSubscriptionRequest $request - * @param BillingSubscription $invoice - * - * @return Response - * - * @throws \Exception - * @OA\Delete( - * path="/api/v1/billing_subscriptions/{id}", - * operationId="deleteBillingSubscription", - * tags={"billing_subscriptions"}, - * summary="Deletes a billing_subscriptions", - * description="Handles the deletion of an billing_subscriptions by id", - * @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\Parameter( - * name="id", - * in="path", - * description="The BillingSubscription Hashed ID", - * example="D2J234DFA", - * required=true, - * @OA\Schema( - * type="string", - * format="string", - * ), - * ), - * @OA\Response( - * response=200, - * description="Returns a HTTP status", - * @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 destroy(DestroyBillingSubscriptionRequest $request, BillingSubscription $billing_subscription): \Illuminate\Http\Response - { - $this->billing_subscription_repo->delete($billing_subscription); - - return $this->itemResponse($billing_subscription->fresh()); - } -} diff --git a/app/Http/Controllers/ClientPortal/BillingSubscriptionPurchaseController.php b/app/Http/Controllers/ClientPortal/BillingSubscriptionPurchaseController.php deleted file mode 100644 index a6036cd83566..000000000000 --- a/app/Http/Controllers/ClientPortal/BillingSubscriptionPurchaseController.php +++ /dev/null @@ -1,30 +0,0 @@ - $billing_subscription, - 'hash' => Str::uuid()->toString(), - ]); - } -} diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index 03347318d6fc..d74e8c43b0e5 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -14,9 +14,12 @@ namespace App\Http\Controllers; use App\Libraries\MultiDB; use App\Libraries\OAuth\Providers\Google; use App\Models\CompanyUser; +use App\Models\User; use App\Transformers\CompanyUserTransformer; +use App\Transformers\UserTransformer; use Google_Client; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; class ConnectedAccountController extends BaseController { @@ -95,7 +98,51 @@ class ConnectedAccountController extends BaseController $client->setClientId(config('ninja.auth.google.client_id')); $client->setClientSecret(config('ninja.auth.google.client_secret')); $client->setRedirectUri(config('ninja.app_url')); - $token = $client->authenticate(request()->input('server_auth_code')); + $refresh_token = ''; + $token = ''; + + $connected_account = [ + 'email' => $google->harvestEmail($user), + 'oauth_user_id' => $google->harvestSubField($user), + 'oauth_provider_id' => 'google', + 'email_verified_at' =>now() + ]; + + auth()->user()->update($connected_account); + auth()->user()->email_verified_at = now(); + auth()->user()->save(); + + $timeout = auth()->user()->company()->default_password_timeout; + Cache::put(auth()->user()->hashed_id.'_logged_in', Str::random(64), $timeout); + + return $this->itemResponse(auth()->user()); + + } + + return response() + ->json(['message' => ctrans('texts.invalid_credentials')], 401) + ->header('X-App-Version', config('ninja.app_version')) + ->header('X-Api-Version', config('ninja.minimum_client_version')); + } + + + + public function handleGmailOauth(Request $request) + { + + $user = false; + + $google = new Google(); + + $user = $google->getTokenResponse($request->input('id_token')); + + if ($user) { + + $client = new Google_Client(); + $client->setClientId(config('ninja.auth.google.client_id')); + $client->setClientSecret(config('ninja.auth.google.client_secret')); + $client->setRedirectUri(config('ninja.app_url')); + $token = $client->authenticate($request->input('server_auth_code')); $refresh_token = ''; @@ -104,7 +151,6 @@ class ConnectedAccountController extends BaseController } $connected_account = [ - 'password' => '', 'email' => $google->harvestEmail($user), 'oauth_user_id' => $google->harvestSubField($user), 'oauth_user_token' => $token, @@ -116,17 +162,32 @@ class ConnectedAccountController extends BaseController auth()->user()->update($connected_account); auth()->user()->email_verified_at = now(); auth()->user()->save(); - - //$ct = CompanyUser::whereUserId(auth()->user()->id); - //return $this->listResponse($ct); + $this->activateGmail(auth()->user()); + return $this->itemResponse(auth()->user()); - // return $this->listResponse(auth()->user()); + } return response() ->json(['message' => ctrans('texts.invalid_credentials')], 401) ->header('X-App-Version', config('ninja.app_version')) ->header('X-Api-Version', config('ninja.minimum_client_version')); + + } + + private function activateGmail(User $user) + { + $company = $user->company(); + $settings = $company->settings; + + if($settings->email_sending_method == 'default') + { + $settings->email_sending_method = 'gmail'; + $settings->gmail_sending_user_id = (string)$user->hashed_id; + + $company->settings = $settings; + $company->save(); + } } } diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index f53f47348ef8..cdd13ea89094 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -131,7 +131,7 @@ class EmailController extends BaseController $entity_obj->service()->markSent()->save(); - EmailEntity::dispatch($invitation, $invitation->company, $template, $data) + EmailEntity::dispatch($invitation->fresh(), $invitation->company, $template, $data) ->delay(now()->addSeconds(5)); } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index b9e99002f42d..e64bac17a292 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -392,7 +392,7 @@ class InvoiceController extends BaseController } if ($invoice->isLocked()) { - return response()->json(['message' => ctrans('texts.locked_invoice')]); + return response()->json(['message' => ctrans('texts.locked_invoice')], 403); } $invoice = $this->invoice_repo->save($request->all(), $invoice); @@ -795,6 +795,8 @@ class InvoiceController extends BaseController $file_path = $invoice->service()->getInvoicePdf($contact); +nlog($file_path); + return response()->download($file_path, basename($file_path)); } diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php index fad9b1eac32f..c088160781d6 100644 --- a/app/Http/Controllers/OpenAPI/BillingSubscription.php +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -1,7 +1,7 @@ json(['message' => ctrans('texts.sent_message')], 200); } - if ($action == 'convert') { + if ($action == 'convert' || $action == 'convert_to_invoice') { $this->entity_type = Quote::class; $this->entity_transformer = QuoteTransformer::class; @@ -572,7 +572,6 @@ class QuoteController extends BaseController * description="Performs a custom action on an Quote. The current range of actions are as follows - - clone_to_Quote - clone_to_quote - history - delivery_note @@ -580,6 +579,8 @@ class QuoteController extends BaseController - download - archive - delete + - convert + - convert_to_invoice - email", * @OA\Parameter(ref="#/components/parameters/X-Api-Secret"), * @OA\Parameter(ref="#/components/parameters/X-Api-Token"), @@ -640,6 +641,14 @@ class QuoteController extends BaseController private function performAction(Quote $quote, $action, $bulk = false) { switch ($action) { + case 'convert': + case 'convert_to_invoice': + + $this->entity_type = Invoice::class; + $this->entity_transformer = InvoiceTransformer::class; + return $this->itemResponse($quote->service()->convertToInvoice()); + + break; case 'clone_to_invoice': $this->entity_type = Invoice::class; diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index 1ecc2ae675ba..7daf38feed40 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -66,10 +66,16 @@ class SelfUpdateController extends BaseController $repo = new GitRepository(base_path()); nlog('Are there changes to pull? '.$repo->hasChanges()); + $output = ''; try { - $res = $repo->pull(); + // $res = $repo->pull(); + + $output = $repo->execute('pull origin'); + } catch (GitException $e) { + + nlog($output); nlog($e->getMessage()); return response()->json(['message'=>$e->getMessage()], 500); } @@ -78,7 +84,7 @@ class SelfUpdateController extends BaseController Artisan::call('ninja:post-update'); }); - return response()->json(['message' => ''], 200); + return response()->json(['message' => $output], 200); } public function checkVersion() diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 202354eb4c54..3b91f594de4c 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -608,11 +608,18 @@ class UserController extends BaseController */ public function detach(DetachCompanyUserRequest $request, User $user) { - if($user->isOwner()) - return response()->json(['message', 'Cannot detach owner.'],400); + + if ($request->entityIsDeleted($user)) { + return $request->disallowUpdate(); + } $company_user = CompanyUser::whereUserId($user->id) - ->whereCompanyId(auth()->user()->companyId())->first(); + ->whereCompanyId(auth()->user()->companyId()) + ->withTrashed() + ->first(); + + if($company_user->is_owner) + return response()->json(['message', 'Cannot detach owner.'], 401); $token = $company_user->token->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first(); diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 619a9a8366c1..94d5e007cbcd 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -12,48 +12,148 @@ namespace App\Http\Livewire; use App\Factory\ClientFactory; +use App\Models\Subscription; use App\Models\ClientContact; +use App\Models\Invoice; use App\Repositories\ClientContactRepository; use App\Repositories\ClientRepository; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; use Livewire\Component; class BillingPortalPurchase extends Component { + /** + * Random hash generated by backend to handle the tracking of state. + * + * @var string + */ public $hash; - public $heading_text = 'Log in'; + /** + * Top level text on the left side of billing page. + * + * @var string + */ + public $heading_text; + + /** + * E-mail address model for user input. + * + * @var string + */ public $email; + /** + * Password model for user input. + * + * @var string + */ public $password; - public $billing_subscription; + /** + * Instance of subscription. + * + * @var Subscription + */ + public $subscription; + /** + * Instance of client contact. + * + * @var null|ClientContact + */ public $contact; + /** + * Rules for validating the form. + * + * @var \string[][] + */ protected $rules = [ 'email' => ['required', 'email'], ]; + /** + * Id for CompanyGateway record. + * + * @var string|integer + */ public $company_gateway_id; + /** + * Id for GatewayType. + * + * @var string|integer + */ public $payment_method_id; + /** + * List of steps that frontend form follows. + * + * @var array + */ public $steps = [ 'passed_email' => false, 'existing_user' => false, 'fetched_payment_methods' => false, 'fetched_client' => false, + 'show_start_trial' => false, ]; + /** + * List of payment methods fetched from client. + * + * @var array + */ public $methods = []; + /** + * Instance of \App\Models\Invoice + * + * @var Invoice + */ public $invoice; + /** + * Coupon model for user input + * + * @var string + */ public $coupon; + /** + * Quantity for seats + * + * @var int + */ + public $quantity = 1; + + /** + * First-hit request data (queries, locales...). + * + * @var array + */ + public $request_data; + + /** + * @var string + */ + public $price; + + public function mount() + { + $this->price = $this->subscription->service()->price(); + } + + /** + * Handle user authentication + * + * @return $this|bool|void + */ public function authenticate() { $this->validate(); @@ -81,38 +181,79 @@ class BillingPortalPurchase extends Component } } + /** + * Create a blank client. Used for new customers purchasing. + * + * @return mixed + * @throws \Laracasts\Presenter\Exceptions\PresenterException + */ protected function createBlankClient() { - $company = $this->billing_subscription->company; - $user = $this->billing_subscription->user; + $company = $this->subscription->company; + $user = $this->subscription->user; $client_repo = new ClientRepository(new ClientContactRepository()); - $client = $client_repo->save([ + $data = [ 'name' => 'Client Name', 'contacts' => [ ['email' => $this->email], - ] - ], ClientFactory::create($company->id, $user->id)); + ], + 'settings' => [], + ]; + + if (array_key_exists('locale', $this->request_data)) { + $request = $this->request_data; + + $record = Cache::get('languages')->filter(function ($item) use ($request) { + return $item->locale == $request['locale']; + })->first(); + + if ($record) { + $data['settings']['language_id'] = (string)$record->id; + } + } + + $client = $client_repo->save($data, ClientFactory::create($company->id, $user->id)); return $client->contacts->first(); } + /** + * Fetching payment methods from the client. + * + * @param ClientContact $contact + * @return $this + */ protected function getPaymentMethods(ClientContact $contact): self { - $this->steps['fetched_payment_methods'] = true; - - $this->methods = $contact->client->service()->getPaymentMethods(1000); - - $this->heading_text = 'Pick a payment method'; - Auth::guard('contact')->login($contact); $this->contact = $contact; + if ($this->subscription->trial_enabled) { + $this->heading_text = ctrans('texts.plan_trial'); + $this->steps['show_start_trial'] = true; + + return $this; + } + + $this->steps['fetched_payment_methods'] = true; + + $this->methods = $contact->client->service()->getPaymentMethods(1000); + + $this->heading_text = ctrans('texts.payment_methods'); + return $this; } + /** + * Middle method between selecting payment method & + * submitting the from to the backend. + * + * @param $company_gateway_id + * @param $gateway_type_id + */ public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id) { $this->company_gateway_id = $company_gateway_id; @@ -121,10 +262,13 @@ class BillingPortalPurchase extends Component $this->handleBeforePaymentEvents(); } + /** + * Method to handle events before payments. + * + * @return void + */ public function handleBeforePaymentEvents() { - - //stubs $data = [ 'client_id' => $this->contact->client->id, 'date' => now()->format('Y-m-d'), @@ -133,31 +277,75 @@ class BillingPortalPurchase extends Component 'client_contact_id' => $this->contact->hashed_id, ]], 'user_input_promo_code' => $this->coupon, - 'quantity' => 1, // Option to increase quantity + 'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon, + 'quantity' => $this->quantity, ]; - $this->invoice = $this->billing_subscription + $this->invoice = $this->subscription ->service() ->createInvoice($data) ->service() ->markSent() + ->fillDefaults() ->save(); Cache::put($this->hash, [ + 'subscription_id' => $this->subscription->id, 'email' => $this->email ?? $this->contact->email, 'client_id' => $this->contact->client->id, - 'invoice_id' => $this->invoice->id], - now()->addMinutes(60) + 'invoice_id' => $this->invoice->id, + now()->addMinutes(60)] ); $this->emit('beforePaymentEventsCompleted'); } - - //this isn't managed here - this is taken care of in the BS - public function applyCouponCode() + /** + * Proxy method for starting the trial. + * + * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function handleTrial() { - dd('Applying coupon code: ' . $this->coupon); + return $this->subscription->service()->startTrial([ + 'email' => $this->email ?? $this->contact->email, + 'quantity' => $this->quantity, + 'contact_id' => $this->contact->id, + ]); + } + + /** + * Update quantity property. + * + * @param string $option + * @return int + */ + public function updateQuantity(string $option): int + { + if ($this->quantity == 1 && $option == 'decrement') { + return $this->quantity; + } + + if ($this->quantity >= $this->subscription->max_seats_limit && $option == 'increment') { + return $this->quantity; + } + + if ($option == 'increment') { + $this->quantity++; + return $this->price = (int)$this->price + $this->subscription->product->price; + } + + $this->quantity--; + $this->price = (int)$this->price - $this->subscription->product->price; + + return 0; + } + + public function handleCoupon() + { + if ($this->coupon == $this->subscription->promo_code) { + $this->price = $this->subscription->promo_price; + } } public function render() diff --git a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php deleted file mode 100644 index f4dfbe25d4b0..000000000000 --- a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php +++ /dev/null @@ -1,40 +0,0 @@ -user()->can('create', BillingSubscription::class); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - // - ]; - } -} diff --git a/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php deleted file mode 100644 index 399cdceb2f0b..000000000000 --- a/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php +++ /dev/null @@ -1,40 +0,0 @@ -user()->can('edit', $this->billing_subscription); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - // - ]; - } -} diff --git a/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php deleted file mode 100644 index 94d285770b65..000000000000 --- a/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php +++ /dev/null @@ -1,40 +0,0 @@ -user()->can('edit', $this->billing_subscription); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - // - ]; - } -} diff --git a/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php deleted file mode 100644 index 18ba6ab68f7d..000000000000 --- a/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php +++ /dev/null @@ -1,40 +0,0 @@ -user()->can('view', $this->billing_subscription); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - // - ]; - } -} diff --git a/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php deleted file mode 100644 index 0c5a64c2ead6..000000000000 --- a/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php +++ /dev/null @@ -1,60 +0,0 @@ -user()->can('create', BillingSubscription::class); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - 'user_id' => ['sometimes'], - 'product_id' => ['sometimes'], - 'assigned_user_id' => ['sometimes'], - 'company_id' => ['sometimes'], - 'is_recurring' => ['sometimes'], - 'frequency_id' => ['sometimes'], - 'auto_bill' => ['sometimes'], - 'promo_code' => ['sometimes'], - 'promo_discount' => ['sometimes'], - 'is_amount_discount' => ['sometimes'], - 'allow_cancellation' => ['sometimes'], - 'per_set_enabled' => ['sometimes'], - 'min_seats_limit' => ['sometimes'], - 'max_seats_limit' => ['sometimes'], - 'trial_enabled' => ['sometimes'], - 'trial_duration' => ['sometimes'], - 'allow_query_overrides' => ['sometimes'], - 'allow_plan_changes' => ['sometimes'], - 'plan_map' => ['sometimes'], - 'refund_period' => ['sometimes'], - 'webhook_configuration' => ['sometimes'], - ]; - } -} diff --git a/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php deleted file mode 100644 index 78d41deb35e6..000000000000 --- a/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php +++ /dev/null @@ -1,42 +0,0 @@ -user()->can('edit', $this->billing_subscription); - } - - /** - * Get the validation rules that apply to the request. - * - * @return array - */ - public function rules() - { - return [ - // - ]; - } -} diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index 313ee11eb6ae..432ad1fe61d7 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -47,6 +47,10 @@ class StoreClientRequest extends Request $rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; } + if (isset($this->number)) { + $rules['number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id); + } + /* Ensure we have a client name, and that all emails are unique*/ //$rules['name'] = 'required|min:1'; $rules['settings'] = new ValidClientGroupSettingsRule(); diff --git a/app/Http/Requests/Credit/UpdateCreditRequest.php b/app/Http/Requests/Credit/UpdateCreditRequest.php index 2c7c43d39bf9..617321a9ce89 100644 --- a/app/Http/Requests/Credit/UpdateCreditRequest.php +++ b/app/Http/Requests/Credit/UpdateCreditRequest.php @@ -67,8 +67,10 @@ class UpdateCreditRequest extends Request $input = $this->decodePrimaryKeys($input); - $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; - + if (isset($input['line_items'])) { + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + } + $input['id'] = $this->credit->id; $this->replace($input); diff --git a/app/Http/Requests/Invoice/ActionInvoiceRequest.php b/app/Http/Requests/Invoice/ActionInvoiceRequest.php index fbe5c8c9c222..17c27ad7ab90 100644 --- a/app/Http/Requests/Invoice/ActionInvoiceRequest.php +++ b/app/Http/Requests/Invoice/ActionInvoiceRequest.php @@ -45,8 +45,6 @@ class ActionInvoiceRequest extends Request { $input = $this->all(); - $this->invoice = Invoice::find($this->decodePrimary($invoice_id)); - if (!array_key_exists('action', $input)) { $this->error_msg = 'Action is a required field'; } elseif (!$this->invoiceDeletable($this->invoice)) { diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index 0fb69a64e3f6..ec08c200bb90 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -66,8 +66,10 @@ class UpdateInvoiceRequest extends Request $input['id'] = $this->invoice->id; - $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; - + if (isset($input['line_items'])) { + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + } + if (array_key_exists('documents', $input)) { unset($input['documents']); } diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index c75e571c9d64..cdb5be52f064 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -16,6 +16,7 @@ use App\Http\ValidationRules\Quote\UniqueQuoteNumberRule; use App\Models\Quote; use App\Utils\Traits\CleanLineItems; use App\Utils\Traits\MakesHash; +use Illuminate\Validation\Rule; class StoreQuoteRequest extends Request { @@ -48,7 +49,9 @@ class StoreQuoteRequest extends Request $rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000'; } - $rules['number'] = new UniqueQuoteNumberRule($this->all()); + $rules['number'] = ['nullable',Rule::unique('quotes')->where('company_id', auth()->user()->company()->id)]; + + // $rules['number'] = new UniqueQuoteNumberRule($this->all()); $rules['line_items'] = 'array'; return $rules; diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index a67b610c70d8..5e59e5327659 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -70,6 +70,10 @@ class Request extends FormRequest public function decodePrimaryKeys($input) { + if (array_key_exists('group_id', $input) && is_string($input['group_id'])) { + $input['group_id'] = $this->decodePrimaryKey($input['group_id']); + } + if (array_key_exists('subscription_id', $input) && is_string($input['subscription_id'])) { $input['subscription_id'] = $this->decodePrimaryKey($input['subscription_id']); } diff --git a/app/Http/Requests/User/DetachCompanyUserRequest.php b/app/Http/Requests/User/DetachCompanyUserRequest.php index 2b4dcf932e8d..7a40aaa9189b 100644 --- a/app/Http/Requests/User/DetachCompanyUserRequest.php +++ b/app/Http/Requests/User/DetachCompanyUserRequest.php @@ -13,12 +13,14 @@ namespace App\Http\Requests\User; use App\Http\Requests\Request; use App\Models\User; +use App\Utils\Traits\ChecksEntityStatus; use App\Utils\Traits\MakesHash; class DetachCompanyUserRequest extends Request { use MakesHash; - + use ChecksEntityStatus; + /** * Determine if the user is authorized to make this request. * diff --git a/app/Http/ViewComposers/PortalComposer.php b/app/Http/ViewComposers/PortalComposer.php index d27835e7e5d7..fd0ebd4d6804 100644 --- a/app/Http/ViewComposers/PortalComposer.php +++ b/app/Http/ViewComposers/PortalComposer.php @@ -79,6 +79,7 @@ class PortalComposer $data[] = ['title' => ctrans('texts.credits'), 'url' => 'client.credits.index', 'icon' => 'credit-card']; $data[] = ['title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield']; $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download']; + $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar']; if (auth()->user('contact')->client->getSetting('enable_client_portal_tasks')) { $data[] = ['title' => ctrans('texts.tasks'), 'url' => 'client.dashboard', 'icon' => 'clock']; diff --git a/app/Jobs/Company/CreateCompany.php b/app/Jobs/Company/CreateCompany.php index bc19b9a9f770..80abe9b56948 100644 --- a/app/Jobs/Company/CreateCompany.php +++ b/app/Jobs/Company/CreateCompany.php @@ -59,6 +59,7 @@ class CreateCompany $company->enabled_modules = config('ninja.enabled_modules'); $company->subdomain = isset($this->request['subdomain']) ? $this->request['subdomain'] : ''; $company->custom_fields = new \stdClass; + $company->default_password_timeout = 1800000; $company->save(); return $company; diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php deleted file mode 100644 index 3aef58c290cd..000000000000 --- a/app/Jobs/Cron/BillingSubscriptionCron.php +++ /dev/null @@ -1,72 +0,0 @@ -loopSubscriptions(); - } else { - //multiDB environment, need to - foreach (MultiDB::$dbs as $db) { - - MultiDB::setDB($db); - $this->loopSubscriptions(); - - } - } - } - - private function loopSubscriptions() - { - $client_subs = ClientSubscription::whereNull('deleted_at') - ->cursor() - ->each(function ($cs){ - $this->processSubscription($cs); - }); - } - - /* Our daily cron should check - - 1. Is the subscription still in trial phase? - 2. Check the recurring invoice and its remaining_cycles to see whether we need to cancel or perform any other function. - 3. Any notifications that need to fire? - */ - private function processSubscription($client_subscription) - { - - } -} diff --git a/app/Jobs/Cron/RecurringInvoicesCron.php b/app/Jobs/Cron/RecurringInvoicesCron.php index 0a04f8761946..06bdd4db1599 100644 --- a/app/Jobs/Cron/RecurringInvoicesCron.php +++ b/app/Jobs/Cron/RecurringInvoicesCron.php @@ -42,6 +42,7 @@ class RecurringInvoicesCron if (! config('ninja.db.multi_db_enabled')) { $recurring_invoices = RecurringInvoice::whereDate('next_send_date', '<=', now()) + ->whereNotNull('next_send_date') ->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('remaining_cycles', '!=', '0') ->with('company') @@ -62,6 +63,7 @@ class RecurringInvoicesCron MultiDB::setDB($db); $recurring_invoices = RecurringInvoice::whereDate('next_send_date', '<=', now()) + ->whereNotNull('next_send_date') ->where('status_id', RecurringInvoice::STATUS_ACTIVE) ->where('remaining_cycles', '!=', '0') ->with('company') diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index 843c722d1d6c..e0dec9726c3e 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -35,6 +35,8 @@ class SendRecurring implements ShouldQueue protected $db; + public $tries = 1; + /** * Create a new job instance. * @@ -63,6 +65,7 @@ class SendRecurring implements ShouldQueue ->markSent() ->applyNumber() ->createInvitations() + ->fillDefaults() ->save(); nlog("Invoice {$invoice->number} created"); @@ -74,16 +77,6 @@ class SendRecurring implements ShouldQueue } }); - //Admin notification for recurring invoice sent. - if ($invoice->invitations->count() >= 1) { - $invoice->entityEmailEvent($invoice->invitations->first(), 'invoice', 'email_template_invoice'); - } - - if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $this->recurring_invoice->auto_bill_enabled) { - nlog("attempting to autobill {$invoice->number}"); - $invoice->service()->autoBill()->save(); - } - nlog("updating recurring invoice dates"); /* Set next date here to prevent a recurring loop forming */ $this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate()->format('Y-m-d'); @@ -101,6 +94,17 @@ class SendRecurring implements ShouldQueue $this->recurring_invoice->save(); + //Admin notification for recurring invoice sent. + if ($invoice->invitations->count() >= 1) { + $invoice->entityEmailEvent($invoice->invitations->first(), 'invoice', 'email_template_invoice'); + } + + if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $this->recurring_invoice->auto_bill_enabled) { + nlog("attempting to autobill {$invoice->number}"); + $invoice->service()->autoBill()->save(); + } + + } public function failed($exception = null) diff --git a/app/Jobs/Util/Import.php b/app/Jobs/Util/Import.php index b60c210d6c66..6a06eac4446a 100644 --- a/app/Jobs/Util/Import.php +++ b/app/Jobs/Util/Import.php @@ -424,7 +424,10 @@ class Import implements ShouldQueue unset($modified['password']); //cant import passwords. $user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true); - + $user->email_verified_at = now(); + $user->confirmation_code = ''; + $user->save(); + $user_agent = array_key_exists('token_name', $resource) ?: request()->server('HTTP_USER_AGENT'); CreateCompanyToken::dispatchNow($this->company, $user, $user_agent); diff --git a/app/Listeners/Credit/CreditEmailedNotification.php b/app/Listeners/Credit/CreditEmailedNotification.php index dd7f3bd5e06f..48dcf314623e 100644 --- a/app/Listeners/Credit/CreditEmailedNotification.php +++ b/app/Listeners/Credit/CreditEmailedNotification.php @@ -54,7 +54,7 @@ class CreditEmailedNotification implements ShouldQueue // $notification = new EntitySentNotification($event->invitation, 'credit'); - $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent']); + $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'credit', ['all_notifications', 'credit_sent', 'credit_sent_all']); if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { unset($methods[$key]); diff --git a/app/Listeners/Invoice/InvoiceEmailedNotification.php b/app/Listeners/Invoice/InvoiceEmailedNotification.php index 1bffd1d37649..4a3b7f77e64f 100644 --- a/app/Listeners/Invoice/InvoiceEmailedNotification.php +++ b/app/Listeners/Invoice/InvoiceEmailedNotification.php @@ -60,7 +60,7 @@ class InvoiceEmailedNotification implements ShouldQueue // $notification = new EntitySentNotification($event->invitation, 'invoice'); /* Returns an array of notification methods */ - $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']); + $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all']); /* If one of the methods is email then we fire the EntitySentMailer */ if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { diff --git a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php index aeda48fbfb62..d235b4a95cc2 100644 --- a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php +++ b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php @@ -56,7 +56,7 @@ class InvoiceFailedEmailNotification // $notification = new EntitySentNotification($event->invitation, 'invoice'); - $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']); + $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent', 'invoice_sent_all']); if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { unset($methods[$key]); diff --git a/app/Listeners/Quote/QuoteEmailedNotification.php b/app/Listeners/Quote/QuoteEmailedNotification.php index 7cb0995f638d..43ec02df255c 100644 --- a/app/Listeners/Quote/QuoteEmailedNotification.php +++ b/app/Listeners/Quote/QuoteEmailedNotification.php @@ -55,7 +55,7 @@ class QuoteEmailedNotification implements ShouldQueue // $notification = new EntitySentNotification($event->invitation, 'quote'); - $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent']); + $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'quote', ['all_notifications', 'quote_sent', 'quote_sent_all']); if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { unset($methods[$key]); diff --git a/app/Mail/Engine/QuoteEmailEngine.php b/app/Mail/Engine/QuoteEmailEngine.php index 9d85d8b68dc3..548728bb124b 100644 --- a/app/Mail/Engine/QuoteEmailEngine.php +++ b/app/Mail/Engine/QuoteEmailEngine.php @@ -11,6 +11,7 @@ namespace App\Mail\Engine; +use App\Models\Account; use App\Utils\HtmlEngine; use App\Utils\Number; diff --git a/app/Mail/SupportMessageSent.php b/app/Mail/SupportMessageSent.php index 2dd7d51bea12..64877f12a662 100644 --- a/app/Mail/SupportMessageSent.php +++ b/app/Mail/SupportMessageSent.php @@ -45,7 +45,7 @@ class SupportMessageSent extends Mailable $log_file->seek(PHP_INT_MAX); $last_line = $log_file->key(); - $lines = new LimitIterator($log_file, $last_line - 10, $last_line); + $lines = new LimitIterator($log_file, $last_line - 100, $last_line); $log_lines = iterator_to_array($lines); } diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 8bf643e3be8a..ea47aab1487b 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -14,6 +14,7 @@ namespace App\Mail; use App\Models\Client; use App\Models\ClientContact; use App\Models\User; +use App\Utils\HtmlEngine; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; @@ -52,6 +53,9 @@ class TemplateEmail extends Mailable $company = $this->client->company; + $html_variables = (new HtmlEngine($this->invitation))->makeValues(); + +//str_replace(array_keys($html_variables), array_values($html_variables), $settings->email_signature) $this->from(config('mail.from.address'), $this->company->present()->name()); if (strlen($settings->bcc_email) > 1) @@ -71,7 +75,7 @@ class TemplateEmail extends Mailable 'view_link' => $this->build_email->getViewLink(), 'view_text' => $this->build_email->getViewText(), 'title' => '', - 'signature' => $settings->email_signature, + 'signature' => str_replace(array_keys($html_variables), array_values($html_variables), $settings->email_signature), 'settings' => $settings, 'company' => $company, 'whitelabel' => $this->client->user->account->isPaid() ? true : false, diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 8d818643d6bb..ab0e0ee4b88f 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -191,7 +191,9 @@ class BaseModel extends Model public function numberFormatter() { - $formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $this->number); + $number = strlen($this->number) > 1 ? $this->number : class_basename($this); + + $formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $number); // Remove any runs of periods (thanks falstro!) $formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number); diff --git a/app/Models/BillingSubscription.php b/app/Models/BillingSubscription.php deleted file mode 100644 index 50b68eae796f..000000000000 --- a/app/Models/BillingSubscription.php +++ /dev/null @@ -1,76 +0,0 @@ - 'boolean', - 'plan_map' => 'object', - 'webhook_configuration' => 'object', - 'updated_at' => 'timestamp', - 'created_at' => 'timestamp', - 'deleted_at' => 'timestamp', - ]; - - public function service() - { - return new BillingSubscriptionService($this); - } - - public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo - { - return $this->belongsTo(Company::class); - } - - public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo - { - return $this->belongsTo(User::class); - } - - public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo - { - return $this->belongsTo(Product::class); - } -} diff --git a/app/Models/Company.php b/app/Models/Company.php index 1982f6a3c72c..73f2d1e8c954 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -159,15 +159,10 @@ class Company extends BaseModel { return $this->hasMany(ExpenseCategory::class)->withTrashed(); } - - public function client_subscriptions() + + public function subscriptions() { - return $this->hasMany(ClientSubscription::class)->withTrashed(); - } - - public function billing_subscriptions() - { - return $this->hasMany(BillingSubscription::class)->withTrashed(); + return $this->hasMany(Subscription::class)->withTrashed(); } public function task_statuses() @@ -396,7 +391,7 @@ class Company extends BaseModel public function system_logs() { - return $this->hasMany(SystemLog::class)->orderBy('id', 'DESC')->take(50); + return $this->hasMany(SystemLog::class)->orderBy('id', 'DESC')->take(100); } public function system_log_relation() diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php index 8baaf73751a2..d7cb21a24b56 100644 --- a/app/Models/Presenters/CompanyPresenter.php +++ b/app/Models/Presenters/CompanyPresenter.php @@ -97,13 +97,13 @@ class CompanyPresenter extends EntityPresenter } } - public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw) + public function getSpcQrCode($client_currency, $invoice_number, $balance_due_raw, $user_iban) { $settings = $this->entity->settings; return - "SPC\n0200\n1\nCH860021421411198240K\nK\n{$this->name}\n{$settings->address1}\n{$settings->postal_code} {$settings->city}\n\n\nCH\n\n\n\n\n\n\n\n{$balance_due_raw}\n{$client_currency}\n\n\n\n\n\n\n\nNON\n\n{$invoice_number}\nEPD\n"; + "SPC\n0200\n1\n{$user_iban}\nK\n{$this->name}\n{$settings->address1}\n{$settings->postal_code} {$settings->city}\n\n\nCH\n\n\n\n\n\n\n\n{$balance_due_raw}\n{$client_currency}\n\n\n\n\n\n\n\nNON\n\n{$invoice_number}\nEPD\n"; } } diff --git a/app/Observers/BillingSubscriptionObserver.php b/app/Observers/BillingSubscriptionObserver.php deleted file mode 100644 index dc813c732544..000000000000 --- a/app/Observers/BillingSubscriptionObserver.php +++ /dev/null @@ -1,72 +0,0 @@ -getTransactionResponse()->getMessages() !== null){ + $code = $response->getTransactionResponse()->getMessages()[0]->getCode(); + $description = $response->getTransactionResponse()->getMessages()[0]->getDescription(); + } + return [ 'transaction_reference' => $response->getTransactionResponse()->getTransId(), 'amount' => $vars['amount'], 'auth_code' => $response->getTransactionResponse()->getAuthCode(), - 'code' => $response->getTransactionResponse()->getMessages()[0]->getCode(), - 'description' => $response->getTransactionResponse()->getMessages()[0]->getDescription(), + 'code' => $code, + 'description' => $description, 'invoices' => $vars['invoices'], ]; } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index afba9ba52f23..d3052847aefe 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -30,7 +30,7 @@ use App\Models\Invoice; use App\Models\Payment; use App\Models\PaymentHash; use App\Models\SystemLog; -use App\Services\BillingSubscription\BillingSubscriptionService; +use App\Services\Subscription\SubscriptionService; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use App\Utils\Traits\SystemLogTrait; @@ -241,7 +241,11 @@ class BaseDriver extends AbstractPaymentDriver event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); - //(new BillingSubscriptionService)->completePurchase($this->payment_hash); + if (property_exists($this->payment_hash->data, 'billing_context')) { + $billing_subscription = \App\Models\Subscription::find($this->payment_hash->data->billing_context->subscription_id); + + (new SubscriptionService($billing_subscription))->completePurchase($this->payment_hash); + } return $payment->service()->applyNumber()->save(); } diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index f8bdf10c0f79..2bdc4f5330c9 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -100,22 +100,19 @@ class StripePaymentDriver extends BaseDriver // GatewayType::APPLE_PAY, // TODO:: Missing implementation ]; - if ($this->company_gateway->getSofortEnabled() - && $this->client + if ($this->client && isset($this->client->country) && in_array($this->client->country->iso_3166_3, ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'])) { $types[] = GatewayType::SOFORT; } - if ($this->company_gateway->getAchEnabled() - && $this->client + if ($this->client && isset($this->client->country) && in_array($this->client->country->iso_3166_3, ['USA'])) { $types[] = GatewayType::BANK_TRANSFER; } - if ($this->company_gateway->getAlipayEnabled() - && $this->client + if ($this->client && isset($this->client->country) && in_array($this->client->country->iso_3166_3, ['AUS', 'DNK', 'DEU', 'ITA', 'LUX', 'NOR', 'SVN', 'GBR', 'AUT', 'EST', 'GRC', 'JPN', 'MYS', 'PRT', 'ESP', 'USA', 'BEL', 'FIN', 'HKG', 'LVA', 'NLD', 'SGP', 'SWE', 'CAN', 'FRA', 'IRL', 'LTU', 'NZL', 'SVK', 'CHE'])) { $types[] = GatewayType::ALIPAY; @@ -178,7 +175,6 @@ class StripePaymentDriver extends BaseDriver $fields[] = ['name' => 'client_address_line_2', 'label' => ctrans('texts.address2'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required']; - $fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required']; $fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required']; } diff --git a/app/Policies/BillingSubscriptionPolicy.php b/app/Policies/BillingSubscriptionPolicy.php deleted file mode 100644 index aee6b5e190de..000000000000 --- a/app/Policies/BillingSubscriptionPolicy.php +++ /dev/null @@ -1,31 +0,0 @@ -isAdmin() || $user->hasPermission('create_billing_subscription') || $user->hasPermission('create_all'); - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c017572692c3..058ec2835aa0 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,7 +12,7 @@ namespace App\Providers; use App\Models\Account; -use App\Models\BillingSubscription; +use App\Models\Subscription; use App\Models\Client; use App\Models\ClientSubscription; use App\Models\Company; @@ -28,7 +28,7 @@ use App\Models\Quote; use App\Models\Task; use App\Models\User; use App\Observers\AccountObserver; -use App\Observers\BillingSubscriptionObserver; +use App\Observers\SubscriptionObserver; use App\Observers\ClientObserver; use App\Observers\ClientSubscriptionObserver; use App\Observers\CompanyGatewayObserver; @@ -80,7 +80,7 @@ class AppServiceProvider extends ServiceProvider Schema::defaultStringLength(191); Account::observe(AccountObserver::class); - BillingSubscription::observe(BillingSubscriptionObserver::class); + Subscription::observe(SubscriptionObserver::class); Client::observe(ClientObserver::class); ClientSubscription::observe(ClientSubscriptionObserver::class); Company::observe(CompanyObserver::class); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index d71a5ae1435f..5057e976c64b 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,9 +12,8 @@ namespace App\Providers; use App\Models\Activity; -use App\Models\BillingSubscription; +use App\Models\Subscription; use App\Models\Client; -use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyToken; @@ -39,7 +38,7 @@ use App\Models\User; use App\Models\Vendor; use App\Models\Webhook; use App\Policies\ActivityPolicy; -use App\Policies\BillingSubscriptionPolicy; +use App\Policies\SubscriptionPolicy; use App\Policies\ClientPolicy; use App\Policies\ClientSubscriptionPolicy; use App\Policies\CompanyGatewayPolicy; @@ -77,9 +76,8 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Activity::class => ActivityPolicy::class, - BillingSubscription::class => BillingSubscriptionPolicy::class, + Subscription::class => SubscriptionPolicy::class, Client::class => ClientPolicy::class, - ClientSubscription::class => ClientSubscriptionPolicy::class, Company::class => CompanyPolicy::class, CompanyToken::class => CompanyTokenPolicy::class, CompanyGateway::class => CompanyGatewayPolicy::class, diff --git a/app/Repositories/BillingSubscriptionRepository.php b/app/Repositories/BillingSubscriptionRepository.php deleted file mode 100644 index 68be2473468d..000000000000 --- a/app/Repositories/BillingSubscriptionRepository.php +++ /dev/null @@ -1,28 +0,0 @@ -fill($data) - ->save(); - - return $billing_subscription; - } -} diff --git a/app/Repositories/PaymentRepository.php b/app/Repositories/PaymentRepository.php index 1bd177c7fd56..95e3c4ba4c91 100644 --- a/app/Repositories/PaymentRepository.php +++ b/app/Repositories/PaymentRepository.php @@ -182,7 +182,6 @@ class PaymentRepository extends BaseRepository { $company_currency = $client->company->settings->currency_id; if ($company_currency != $client_currency) { - $currency = $client->currency(); $exchange_rate = new CurrencyApi(); diff --git a/app/Repositories/RecurringInvoiceRepository.php b/app/Repositories/RecurringInvoiceRepository.php index b74560bc1b1e..96b37281bef6 100644 --- a/app/Repositories/RecurringInvoiceRepository.php +++ b/app/Repositories/RecurringInvoiceRepository.php @@ -24,18 +24,6 @@ class RecurringInvoiceRepository extends BaseRepository { $invoice = $this->alternativeSave($data, $invoice); - // $invoice->fill($data); - - // $invoice->save(); - - // $invoice_calc = new InvoiceSum($invoice); - - // $invoice->service() - // ->applyNumber() - // ->createInvitations() - // ->save(); - - // $invoice = $invoice_calc->build()->getRecurringInvoice(); return $invoice; } diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 7c5ce316a17d..826eebb568ab 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -148,9 +148,9 @@ class UserRepository extends BaseRepository event(new UserWasDeleted($user, auth()->user(), $company, Ninja::eventVars())); - // $user->is_deleted = true; - // $user->save(); - // $user->delete(); + $user->is_deleted = true; + $user->save(); + $user->delete(); return $user->fresh(); @@ -177,7 +177,17 @@ class UserRepository extends BaseRepository return; } + $user->is_deleted = false; + $user->save(); $user->restore(); + // $user->company_user->restore(); + + $cu = CompanyUser::withTrashed() + ->where('user_id', $user->id) + ->where('company_id', auth()->user()->company()->id) + ->first(); + + $cu->restore(); event(new UserWasRestored($user, auth()->user(), auth()->user()->company, Ninja::eventVars())); diff --git a/app/Services/BillingSubscription/BillingSubscriptionService.php b/app/Services/BillingSubscription/BillingSubscriptionService.php deleted file mode 100644 index 7b5013299fb6..000000000000 --- a/app/Services/BillingSubscription/BillingSubscriptionService.php +++ /dev/null @@ -1,209 +0,0 @@ -billing_subscription = $billing_subscription; - } - - public function completePurchase(PaymentHash $payment_hash) - { - - if (!property_exists($payment_hash, 'billing_context')) { - return; - } - - // At this point we have some state carried from the billing page - // to this, available as $payment_hash->data->billing_context. Make something awesome ⭐ - - // create client subscription record - // - // create recurring invoice if is_recurring - // - - - } - - public function startTrial(array $data) - { - - } - - public function createInvoice($data): ?\App\Models\Invoice - { - $invoice_repo = new InvoiceRepository(); - - $data['line_items'] = $this->createLineItems($data); - - /* - If trial_enabled -> return early - - -- what we need to know that we don't already - -- Has a promo code been entered, and does it match - -- Is this a recurring subscription - -- - - 1. Is this a recurring product? - 2. What is the quantity? ie is this a multi seat product ( does this mean we need this value stored in the client sub?) - */ - - return $invoice_repo->save($data, InvoiceFactory::create($this->billing_subscription->company_id, $this->billing_subscription->user_id)); - - } - - private function createLineItems($data): array - { - $line_items = []; - - $product = $this->billing_subscription->product; - - $item = new InvoiceItem; - $item->quantity = $data['quantity']; - $item->product_key = $product->product_key; - $item->notes = $product->notes; - $item->cost = $product->price; - $item->tax_rate1 = $product->tax_rate1 ?: 0; - $item->tax_name1 = $product->tax_name1 ?: ''; - $item->tax_rate2 = $product->tax_rate2 ?: 0; - $item->tax_name2 = $product->tax_name2 ?: ''; - $item->tax_rate3 = $product->tax_rate3 ?: 0; - $item->tax_name3 = $product->tax_name3 ?: ''; - $item->custom_value1 = $product->custom_value1 ?: ''; - $item->custom_value2 = $product->custom_value2 ?: ''; - $item->custom_value3 = $product->custom_value3 ?: ''; - $item->custom_value4 = $product->custom_value4 ?: ''; - - //$item->type_id need to switch whether the subscription is a service or product - - $line_items[] = $item; - - - //do we have a promocode? enter this as a line item. - if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->billing_subscription->promo_code) && $this->billing_subscription->promo_discount > 0) - $line_items[] = $this->createPromoLine($data); - - return $line_items; - } - - private function createPromoLine($data) - { - - $product = $this->billing_subscription->product; - - $discounted_amount = 0; - $discount = 0; - $amount = $data['quantity'] * $product->cost; - - if ($this->billing_subscription->is_amount_discount == true) { - $discount = $this->billing_subscription->promo_discount; - } - else { - $discount = round($amount * ($this->billing_subscription->promo_discount / 100), 2); - } - - $discounted_amount = $amount - $discount; - - $item = new InvoiceItem; - $item->quantity = 1; - $item->product_key = ctrans('texts.promo_code'); - $item->notes = ctrans('texts.promo_code'); - $item->cost = $discounted_amount; - $item->tax_rate1 = $product->tax_rate1 ?: 0; - $item->tax_name1 = $product->tax_name1 ?: ''; - $item->tax_rate2 = $product->tax_rate2 ?: 0; - $item->tax_name2 = $product->tax_name2 ?: ''; - $item->tax_rate3 = $product->tax_rate3 ?: 0; - $item->tax_name3 = $product->tax_name3 ?: ''; - - return $item; - - } - - private function convertInvoiceToRecurring() - { - //The first invoice is a plain invoice - the second is fired on the recurring schedule. - } - - public function createClientSubscription($payment_hash, $recurring_invoice_id = null) - { - //create the client sub record - - //?trial enabled? - $cs = new ClientSubscription(); - $cs->subscription_id = $this->billing_subscription->id; - $cs->company_id = $this->billing_subscription->company_id; - - // client_id - $cs->save(); - - $this->client_subscription = $cs; - - } - - public function triggerWebhook($payment_hash) - { - //hit the webhook to after a successful onboarding - //$client = xxxxxxx - //todo webhook - - $body = [ - 'billing_subscription' => $this->billing_subscription, - 'client_subscription' => $this->client_subscription, - // 'client' => $client->toArray(), - ]; - - - $client = new \GuzzleHttp\Client(['headers' => $this->billing_subscription->webhook_configuration->post_purchase_headers]); - - $response = $client->{$this->billing_subscription->webhook_configuration->post_purchase_rest_method}($this->billing_subscription->post_purchase_url,[ - RequestOptions::JSON => ['body' => $body] - ]); - - SystemLogger::dispatch( - $body, - SystemLog::CATEGORY_WEBHOOK, - SystemLog::EVENT_WEBHOOK_RESPONSE, - SystemLog::TYPE_WEBHOOK_RESPONSE, - //$client, - ); - - } - - public function fireNotifications() - { - //scan for any notification we are required to send - } - - -} diff --git a/app/Services/Client/PaymentMethod.php b/app/Services/Client/PaymentMethod.php index 84b214b2fefc..b235aad129f2 100644 --- a/app/Services/Client/PaymentMethod.php +++ b/app/Services/Client/PaymentMethod.php @@ -186,7 +186,7 @@ class PaymentMethod foreach ($child_array as $gateway_id => $gateway_type_id) { $gateway = CompanyGateway::find($gateway_id); - $fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client); + $fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client, $gateway_type_id); if(!$gateway_type_id){ diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index 755291c8e572..72e47ceecd49 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -14,6 +14,7 @@ namespace App\Services\Invoice; use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Invoice\InvoiceWorkflowSettings; use App\Jobs\Util\UnlinkFile; +use App\Libraries\Currency\Conversion\CurrencyApi; use App\Models\CompanyGateway; use App\Models\Expense; use App\Models\Invoice; @@ -62,7 +63,25 @@ class InvoiceService return $this; } + /** + * Sets the exchange rate on the invoice if the client currency + * is different to the company currency. + */ + public function setExchangeRate() + { + $client_currency = $this->invoice->client->getSetting('currency_id'); + $company_currency = $this->invoice->company->settings->currency_id; + + if ($company_currency != $client_currency) { + + $exchange_rate = new CurrencyApi(); + + $this->invoice->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, now()); + } + + return $this; + } /** * Applies the recurring invoice number. * @return $this InvoiceService object @@ -132,6 +151,8 @@ class InvoiceService { $this->invoice = (new MarkSent($this->invoice->client, $this->invoice))->run(); + $this->setExchangeRate(); + return $this; } diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 36106d7ff031..6468cbe7b549 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -431,7 +431,7 @@ class Design extends BaseDesign { $_variables = array_key_exists('variables', $this->context) ? $this->context['variables'] - : ['values' => ['$entity.public_notes' => nl2br($this->entity->public_notes), '$entity.terms' => $this->entity->terms, '$entity_footer' => $this->entity->footer], 'labels' => []]; + : ['values' => ['$entity.public_notes' => $this->entity->public_notes, '$entity.terms' => $this->entity->terms, '$entity_footer' => $this->entity->footer], 'labels' => []]; if ($this->type == 'delivery_note') { return []; @@ -443,9 +443,9 @@ class Design extends BaseDesign ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'p', 'content' => strtr($_variables['values']['$entity.public_notes'], $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column;'], 'elements' => [ - ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left;']], + ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], ['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], - ['element' => 'span', 'content' => strtr($_variables['values']['$entity_footer'], $_variables), 'properties' => ['data-ref' => 'total_table-footer', 'style' => 'text-align: left;']], + ['element' => 'span', 'content' => strtr($_variables['values']['$entity_footer'], $_variables), 'properties' => ['data-ref' => 'total_table-footer', 'style' => 'text-align: left; margin-top: 1rem;']], ]], ['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']], ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [ diff --git a/app/Transformers/BillingSubscriptionTransformer.php b/app/Transformers/BillingSubscriptionTransformer.php deleted file mode 100644 index 3dc50cd78235..000000000000 --- a/app/Transformers/BillingSubscriptionTransformer.php +++ /dev/null @@ -1,74 +0,0 @@ - $this->encodePrimaryKey($billing_subscription->id), - 'user_id' => $this->encodePrimaryKey($billing_subscription->user_id), - 'product_id' => $this->encodePrimaryKey($billing_subscription->product_id), - 'assigned_user_id' => $this->encodePrimaryKey($billing_subscription->assigned_user_id), - 'company_id' => $this->encodePrimaryKey($billing_subscription->company_id), - 'is_recurring' => (bool)$billing_subscription->is_recurring, - 'frequency_id' => (string)$billing_subscription->frequency_id, - 'auto_bill' => (string)$billing_subscription->auto_bill, - 'promo_code' => (string)$billing_subscription->promo_code, - 'promo_discount' => (float)$billing_subscription->promo_discount, - 'is_amount_discount' => (bool)$billing_subscription->is_amount_discount, - 'allow_cancellation' => (bool)$billing_subscription->allow_cancellation, - 'per_seat_enabled' => (bool)$billing_subscription->per_set_enabled, - 'min_seats_limit' => (int)$billing_subscription->min_seats_limit, - 'max_seats_limit' => (int)$billing_subscription->max_seats_limit, - 'trial_enabled' => (bool)$billing_subscription->trial_enabled, - 'trial_duration' => (int)$billing_subscription->trial_duration, - 'allow_query_overrides' => (bool)$billing_subscription->allow_query_overrides, - 'allow_plan_changes' => (bool)$billing_subscription->allow_plan_changes, - 'plan_map' => (string)$billing_subscription->plan_map, - 'refund_period' => (int)$billing_subscription->refund_period, - 'webhook_configuration' => (string)$billing_subscription->webhook_configuration, - 'purchase_page' => (string)route('client.subscription.purchase', $billing_subscription->hashed_id), - 'is_deleted' => (bool)$billing_subscription->is_deleted, - 'created_at' => (int)$billing_subscription->created_at, - 'updated_at' => (int)$billing_subscription->updated_at, - 'archived_at' => (int)$billing_subscription->deleted_at, - ]; - } - - public function includeProduct(BillingSubscription $billing_subscription): \League\Fractal\Resource\Item - { - $transformer = new ProductTransformer($this->serializer); - - return $this->includeItem($billing_subscription->product, $transformer, Product::class); - } -} diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 8620fefd7b2a..2541dfd0f90d 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -13,7 +13,7 @@ namespace App\Transformers; use App\Models\Account; use App\Models\Activity; -use App\Models\BillingSubscription; +use App\Models\Subscription; use App\Models\Client; use App\Models\ClientSubscription; use App\Models\Company; @@ -92,7 +92,7 @@ class CompanyTransformer extends EntityTransformer 'system_logs', 'expense_categories', 'task_statuses', - 'client_subscriptions', + 'subscriptions', ]; /** @@ -362,17 +362,10 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->system_logs, $transformer, SystemLog::class); } - public function includeClientSubscriptions(Company $company) + public function includeSubscriptions(Company $company) { - $transformer = new ClientSubscriptionTransformer($this->serializer); + $transformer = new SubscriptionTransformer($this->serializer); - return $this->includeCollection($company->client_subscriptions, $transformer, ClientSubscription::class); - } - - public function includeBillingSubscriptions(Company $company) - { - $transformer = new BillingSubscriptionTransformer($this->serializer); - - return $this->includeCollection($company->billing_subscriptions, $transformer, BillingSubscription::class); + return $this->includeCollection($company->subscriptions, $transformer, Subscription::class); } } diff --git a/app/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php index 73f4d3f7d8d5..790696118810 100644 --- a/app/Transformers/RecurringInvoiceTransformer.php +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -139,6 +139,7 @@ class RecurringInvoiceTransformer extends EntityTransformer 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, 'due_date_days' => (string) $invoice->due_date_days ?: '', 'paid_to_date' => (float) $invoice->paid_to_date, + 'subscription_id' => (string)$this->encodePrimaryKey($invoice->subscription_id), ]; } } diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index 6c550feff39a..40939a89eedb 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -61,6 +61,7 @@ class UserTransformer extends EntityTransformer 'last_confirmed_email_address' => (string) $user->last_confirmed_email_address ?: '', 'google_2fa_secret' => (bool) $user->google_2fa_secret, 'has_password' => (bool) $user->has_password, + 'oauth_user_token' => empty($user->oauth_user_token) ? '' : '***', ]; } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 19514aa33b73..ff4ceaa17eba 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -160,8 +160,8 @@ class HtmlEngine $data['$invoice.subtotal'] = &$data['$subtotal']; if ($this->entity->partial > 0) { - $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')]; - $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')]; + $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')]; + $data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.balance_due')]; } else { $data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')]; $data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')]; @@ -192,11 +192,12 @@ class HtmlEngine $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; $data['$invoice.taxes'] = &$data['$taxes']; + $data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')]; $data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')]; $data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')]; $data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')]; $data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')]; - $data['$invoice.public_notes'] = ['value' => nl2br($this->entity->public_notes) ?: '', 'label' => ctrans('texts.public_notes')]; + $data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')]; $data['$entity.public_notes'] = &$data['$invoice.public_notes']; $data['$public_notes'] = &$data['$invoice.public_notes']; @@ -259,7 +260,7 @@ class HtmlEngine $data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')]; $data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')]; - $data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : 'no contact name on record', 'label' => ctrans('texts.contact_name')]; + $data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->client->present()->name(), 'label' => ctrans('texts.contact_name')]; $data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')]; $data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')]; @@ -287,7 +288,7 @@ class HtmlEngine $data['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => '']; - $data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance), 'label' => '']; + $data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => '']; $logo = $this->company->present()->logo($this->settings); diff --git a/app/Utils/Traits/CleanLineItems.php b/app/Utils/Traits/CleanLineItems.php index b9cb852db1f0..46e02aa15f25 100644 --- a/app/Utils/Traits/CleanLineItems.php +++ b/app/Utils/Traits/CleanLineItems.php @@ -28,7 +28,7 @@ trait CleanLineItems $cleaned_items = []; foreach ($items as $item) { - $cleaned_items[] = $this->cleanLineItem($item); + $cleaned_items[] = $this->cleanLineItem((array)$item); } return $cleaned_items; diff --git a/app/Utils/Traits/Notifications/UserNotifies.php b/app/Utils/Traits/Notifications/UserNotifies.php index 91455c16ea5c..efff7001004f 100644 --- a/app/Utils/Traits/Notifications/UserNotifies.php +++ b/app/Utils/Traits/Notifications/UserNotifies.php @@ -34,10 +34,12 @@ trait UserNotifies array_push($required_permissions, 'all_user_notifications'); } - if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect($required_permissions, ['all_user_notifications'])) >= 1 || count(array_intersect($required_permissions, ['all_notifications'])) >= 1) { + if (count(array_intersect($required_permissions, $notifications->email)) >= 1 || count(array_intersect(['all_user_notifications'], $notifications->email)) >= 1 || count(array_intersect(['all_notifications'],$notifications->email)) >= 1) { array_push($notifiable_methods, 'mail'); } +nlog($notifiable_methods); + // if(count(array_intersect($required_permissions, $notifications->slack)) >=1) // array_push($notifiable_methods, 'slack'); diff --git a/composer.json b/composer.json index c615a98e5d06..6a8e1559d033 100644 --- a/composer.json +++ b/composer.json @@ -106,7 +106,7 @@ }, "scripts": { "post-install-cmd": [ - "vendor/bin/snappdf download" + "if [ \"${IS_DOCKER:-false}\" != \"true\" ]; then vendor/bin/snappdf download; fi" ], "post-update-cmd": [ "vendor/bin/snappdf download" diff --git a/composer.lock b/composer.lock index ff823c82b0e2..bd215d815783 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "113acad46f6d9eea9f9f5bd4501428d1", + "content-hash": "2307a2f3214da0d1cc772cc1a1405682", "packages": [ { "name": "authorizenet/authorizenet", @@ -55,16 +55,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.173.28", + "version": "3.175.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "593baa5930896bb443c437032daf4016e1e3878d" + "reference": "fce65d31f033c39cd3615fd2d3e503e212d81a3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/593baa5930896bb443c437032daf4016e1e3878d", - "reference": "593baa5930896bb443c437032daf4016e1e3878d", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fce65d31f033c39cd3615fd2d3e503e212d81a3e", + "reference": "fce65d31f033c39cd3615fd2d3e503e212d81a3e", "shasum": "" }, "require": { @@ -139,9 +139,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.173.28" + "source": "https://github.com/aws/aws-sdk-php/tree/3.175.1" }, - "time": "2021-03-12T19:29:55+00:00" + "time": "2021-03-22T18:13:37+00:00" }, { "name": "bacon/bacon-qr-code", @@ -198,16 +198,16 @@ }, { "name": "beganovich/snappdf", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/beganovich/snappdf.git", - "reference": "63ad3f1a0eec7bffc3a3c85f286769fca9a33fd5" + "reference": "5c0a7e2e2c33441c0dd9d1fcbfc8de71c3cc920c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beganovich/snappdf/zipball/63ad3f1a0eec7bffc3a3c85f286769fca9a33fd5", - "reference": "63ad3f1a0eec7bffc3a3c85f286769fca9a33fd5", + "url": "https://api.github.com/repos/beganovich/snappdf/zipball/5c0a7e2e2c33441c0dd9d1fcbfc8de71c3cc920c", + "reference": "5c0a7e2e2c33441c0dd9d1fcbfc8de71c3cc920c", "shasum": "" }, "require": { @@ -245,9 +245,9 @@ "description": "Convert webpages or HTML into the PDF file using Chromium or Google Chrome.", "support": { "issues": "https://github.com/beganovich/snappdf/issues", - "source": "https://github.com/beganovich/snappdf/tree/v1.5.0" + "source": "https://github.com/beganovich/snappdf/tree/v1.6.0" }, - "time": "2021-01-10T17:06:47+00:00" + "time": "2021-03-19T21:20:07+00:00" }, { "name": "brick/math", @@ -2010,16 +2010,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.163.0", + "version": "v0.165.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "8e326f378a1f505064912fddd19fd93bbdcc80fb" + "reference": "c8d7ff2c9cb6cad6e88bc5825491c47b8b2e52fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/8e326f378a1f505064912fddd19fd93bbdcc80fb", - "reference": "8e326f378a1f505064912fddd19fd93bbdcc80fb", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/c8d7ff2c9cb6cad6e88bc5825491c47b8b2e52fd", + "reference": "c8d7ff2c9cb6cad6e88bc5825491c47b8b2e52fd", "shasum": "" }, "require": { @@ -2045,9 +2045,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.163.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.165.0" }, - "time": "2021-03-06T12:20:02+00:00" + "time": "2021-03-21T11:20:02+00:00" }, { "name": "google/auth", @@ -2331,16 +2331,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.7.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/35ea11d335fd638b5882ff1725228b3d35496ab1", + "reference": "35ea11d335fd638b5882ff1725228b3d35496ab1", "shasum": "" }, "require": { @@ -2400,9 +2400,9 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.7.0" + "source": "https://github.com/guzzle/psr7/tree/1.8.1" }, - "time": "2020-09-30T07:37:11+00:00" + "time": "2021-03-21T16:25:00+00:00" }, { "name": "hashids/hashids", @@ -2783,16 +2783,16 @@ }, { "name": "laravel/framework", - "version": "v8.32.1", + "version": "v8.33.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7c37b64f8153c16b6406f5c28cf37828ebbe8846" + "reference": "354c57b8cb457549114074c500944455a288d6cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7c37b64f8153c16b6406f5c28cf37828ebbe8846", - "reference": "7c37b64f8153c16b6406f5c28cf37828ebbe8846", + "url": "https://api.github.com/repos/laravel/framework/zipball/354c57b8cb457549114074c500944455a288d6cc", + "reference": "354c57b8cb457549114074c500944455a288d6cc", "shasum": "" }, "require": { @@ -2947,7 +2947,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-03-09T15:37:45+00:00" + "time": "2021-03-16T19:42:32+00:00" }, { "name": "laravel/slack-notification-channel", @@ -3845,16 +3845,16 @@ }, { "name": "livewire/livewire", - "version": "v2.4.0", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "8055af7730938cd607616fde122825ed960a9b71" + "reference": "b0cb782674673a67ddfd5910d2fcb5308bb32857" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/8055af7730938cd607616fde122825ed960a9b71", - "reference": "8055af7730938cd607616fde122825ed960a9b71", + "url": "https://api.github.com/repos/livewire/livewire/zipball/b0cb782674673a67ddfd5910d2fcb5308bb32857", + "reference": "b0cb782674673a67ddfd5910d2fcb5308bb32857", "shasum": "" }, "require": { @@ -3905,7 +3905,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.4.0" + "source": "https://github.com/livewire/livewire/tree/v2.4.1" }, "funding": [ { @@ -3913,7 +3913,7 @@ "type": "github" } ], - "time": "2021-02-23T17:44:50+00:00" + "time": "2021-03-22T14:03:36+00:00" }, { "name": "maennchen/zipstream-php", @@ -6919,16 +6919,16 @@ }, { "name": "stripe/stripe-php", - "version": "v7.75.0", + "version": "v7.76.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "d377a667cd789b99ccab768441a5a2160cc4ea80" + "reference": "47e66d4186712be33c593fe820dccf270a04d5d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/d377a667cd789b99ccab768441a5a2160cc4ea80", - "reference": "d377a667cd789b99ccab768441a5a2160cc4ea80", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/47e66d4186712be33c593fe820dccf270a04d5d6", + "reference": "47e66d4186712be33c593fe820dccf270a04d5d6", "shasum": "" }, "require": { @@ -6974,9 +6974,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v7.75.0" + "source": "https://github.com/stripe/stripe-php/tree/v7.76.0" }, - "time": "2021-02-22T14:31:21+00:00" + "time": "2021-03-22T16:50:21+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -9664,16 +9664,16 @@ }, { "name": "turbo124/beacon", - "version": "1.0.6", + "version": "1.0.7", "source": { "type": "git", "url": "https://github.com/turbo124/beacon.git", - "reference": "2f38612c1bb4c292d154c8fba478bdf8c019fcd9" + "reference": "d48227fdfafc463cce055f36b149f9cb1d9b8f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/turbo124/beacon/zipball/2f38612c1bb4c292d154c8fba478bdf8c019fcd9", - "reference": "2f38612c1bb4c292d154c8fba478bdf8c019fcd9", + "url": "https://api.github.com/repos/turbo124/beacon/zipball/d48227fdfafc463cce055f36b149f9cb1d9b8f81", + "reference": "d48227fdfafc463cce055f36b149f9cb1d9b8f81", "shasum": "" }, "require": { @@ -9721,9 +9721,9 @@ "turbo124" ], "support": { - "source": "https://github.com/turbo124/beacon/tree/1.0.6" + "source": "https://github.com/turbo124/beacon/tree/1.0.7" }, - "time": "2021-03-09T09:25:50+00:00" + "time": "2021-03-23T09:54:29+00:00" }, { "name": "vlucas/phpdotenv", @@ -9881,30 +9881,35 @@ }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^7.2 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -9928,9 +9933,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "source": "https://github.com/webmozarts/assert/tree/1.10.0" }, - "time": "2020-07-08T17:02:28+00:00" + "time": "2021-03-09T10:59:23+00:00" }, { "name": "webpatser/laravel-countries", @@ -11079,16 +11084,16 @@ }, { "name": "filp/whoops", - "version": "2.9.2", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "df7933820090489623ce0be5e85c7e693638e536" + "reference": "f6e14679f948d8a5cfb866fa7065a30c66bd64d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/df7933820090489623ce0be5e85c7e693638e536", - "reference": "df7933820090489623ce0be5e85c7e693638e536", + "url": "https://api.github.com/repos/filp/whoops/zipball/f6e14679f948d8a5cfb866fa7065a30c66bd64d3", + "reference": "f6e14679f948d8a5cfb866fa7065a30c66bd64d3", "shasum": "" }, "require": { @@ -11138,7 +11143,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.9.2" + "source": "https://github.com/filp/whoops/tree/2.11.0" }, "funding": [ { @@ -11146,20 +11151,20 @@ "type": "github" } ], - "time": "2021-01-24T12:00:00+00:00" + "time": "2021-03-19T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.18.3", + "version": "v2.18.4", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "ab99202fccff2a9f97592fbe1b5c76dd06df3513" + "reference": "06f764e3cb6d60822d8f5135205f9d32b5508a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ab99202fccff2a9f97592fbe1b5c76dd06df3513", - "reference": "ab99202fccff2a9f97592fbe1b5c76dd06df3513", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/06f764e3cb6d60822d8f5135205f9d32b5508a31", + "reference": "06f764e3cb6d60822d8f5135205f9d32b5508a31", "shasum": "" }, "require": { @@ -11242,7 +11247,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.3" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.18.4" }, "funding": [ { @@ -11250,7 +11255,7 @@ "type": "github" } ], - "time": "2021-03-10T19:39:05+00:00" + "time": "2021-03-20T14:52:33+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -12016,16 +12021,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.12.2", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "245710e971a030f42e08f4912863805570f23d39" + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", - "reference": "245710e971a030f42e08f4912863805570f23d39", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", "shasum": "" }, "require": { @@ -12077,9 +12082,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" }, - "time": "2020-12-19T10:15:11+00:00" + "time": "2021-03-17T13:42:18+00:00" }, { "name": "phpunit/php-code-coverage", @@ -12401,16 +12406,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.2", + "version": "9.5.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4" + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4", - "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c73c6737305e779771147af66c96ca6a7ed8a741", + "reference": "c73c6737305e779771147af66c96ca6a7ed8a741", "shasum": "" }, "require": { @@ -12488,7 +12493,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.4" }, "funding": [ { @@ -12500,7 +12505,7 @@ "type": "github" } ], - "time": "2021-02-02T14:45:58+00:00" + "time": "2021-03-23T07:16:29+00:00" }, { "name": "sebastian/cli-parser", @@ -13468,16 +13473,16 @@ }, { "name": "swagger-api/swagger-ui", - "version": "v3.45.0", + "version": "v3.45.1", "source": { "type": "git", "url": "https://github.com/swagger-api/swagger-ui.git", - "reference": "1ba7af074f97c872a64a415e0507c11cf8f3601b" + "reference": "72506e581f860244f3c74de5a2fb9809e53d1876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/1ba7af074f97c872a64a415e0507c11cf8f3601b", - "reference": "1ba7af074f97c872a64a415e0507c11cf8f3601b", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/72506e581f860244f3c74de5a2fb9809e53d1876", + "reference": "72506e581f860244f3c74de5a2fb9809e53d1876", "shasum": "" }, "type": "library", @@ -13523,9 +13528,9 @@ ], "support": { "issues": "https://github.com/swagger-api/swagger-ui/issues", - "source": "https://github.com/swagger-api/swagger-ui/tree/v3.45.0" + "source": "https://github.com/swagger-api/swagger-ui/tree/v3.45.1" }, - "time": "2021-03-11T17:20:14+00:00" + "time": "2021-03-19T17:14:16+00:00" }, { "name": "symfony/debug", @@ -13853,20 +13858,20 @@ }, { "name": "vimeo/psalm", - "version": "4.6.2", + "version": "4.6.4", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b" + "reference": "97fe86c4e158b5a57c5150aa5055c38b5a809aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/bca09d74adc704c4eaee36a3c3e9d379e290fc3b", - "reference": "bca09d74adc704c4eaee36a3c3e9d379e290fc3b", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/97fe86c4e158b5a57c5150aa5055c38b5a809aab", + "reference": "97fe86c4e158b5a57c5150aa5055c38b5a809aab", "shasum": "" }, "require": { - "amphp/amp": "^2.1", + "amphp/amp": "^2.4.2", "amphp/byte-stream": "^1.5", "composer/package-versions-deprecated": "^1.8.0", "composer/semver": "^1.4 || ^2.0 || ^3.0", @@ -13892,7 +13897,6 @@ "psalm/psalm": "self.version" }, "require-dev": { - "amphp/amp": "^2.4.2", "bamarni/composer-bin-plugin": "^1.2", "brianium/paratest": "^4.0||^6.0", "ext-curl": "*", @@ -13952,9 +13956,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.6.2" + "source": "https://github.com/vimeo/psalm/tree/4.6.4" }, - "time": "2021-02-26T02:24:18+00:00" + "time": "2021-03-16T23:28:18+00:00" }, { "name": "webmozart/path-util", @@ -14092,7 +14096,7 @@ "ext-libxml": "*" }, "platform-dev": { - "php": "^7.4" + "php": "^7.3|^7.4" }, "plugin-api-version": "2.0.0" } diff --git a/config/app.php b/config/app.php index c1dd0812a8ef..b5a5b1bbabe7 100644 --- a/config/app.php +++ b/config/app.php @@ -53,7 +53,7 @@ return [ */ 'url' => env('APP_URL', 'http://localhost'), - + 'mix_url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- | Application Timezone diff --git a/config/ninja.php b/config/ninja.php index a5109ce77401..f6d58aa59ed6 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -13,7 +13,7 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', ''), - 'app_version' => '5.1.29', + 'app_version' => '5.1.32', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/database/factories/BillingSubscriptionFactory.php b/database/factories/BillingSubscriptionFactory.php deleted file mode 100644 index 2fdf320cd5f4..000000000000 --- a/database/factories/BillingSubscriptionFactory.php +++ /dev/null @@ -1,38 +0,0 @@ -unique(['company_id', 'number']); }); - Schema::table('payment_hashes', function (Blueprint $table) { - $table->unique(['hash']); - }); - Schema::table('recurring_invoices', function (Blueprint $table) { $table->string('number')->change(); $table->unique(['company_id', 'number']); diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 06a296e0945c..408d8a4add7b 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -3968,7 +3968,7 @@ $LANG = array( 'list_of_recurring_invoices' => 'List of recurring invoices', 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', 'cancellation' => 'Cancellation', - 'about_cancellation' => 'In case you want to stop the recurring invoice,\n please click the request the cancellation.', + 'about_cancellation' => 'In case you want to stop the recurring invoice, please click the request the cancellation.', 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.', 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', 'list_of_payments' => 'List of payments', @@ -4177,6 +4177,7 @@ $LANG = array( 'migration_auth_label' => 'Let\'s continue by authenticating.', 'api_secret' => 'API secret', 'migration_api_secret_notice' => 'You can find API_SECRET in the .env file or Invoice Ninja v5. If property is missing, leave field blank.', + 'billing_coupon_notice' => 'Your discount will be applied on the checkout.', 'use_last_email' => 'Use last email', 'activate_company' => 'Activate Company', 'activate_company_help' => 'Enable emails, recurring invoices and notifications', @@ -4200,6 +4201,7 @@ $LANG = array( 'invoice_task_datelog' => 'Invoice Task Datelog', 'invoice_task_datelog_help' => 'Add date details to the invoice line items', 'promo_code' => 'Promo code', + 'recurring_invoice_issued_to' => 'Recurring invoice issued to', ); return $LANG; diff --git a/resources/views/billing-portal/purchase.blade.php b/resources/views/billing-portal/purchase.blade.php index 0bb3cba62bb5..4a723896429f 100644 --- a/resources/views/billing-portal/purchase.blade.php +++ b/resources/views/billing-portal/purchase.blade.php @@ -1,8 +1,8 @@ @extends('portal.ninja2020.layout.clean') -@section('meta_title', $billing_subscription->product->product_key) +@section('meta_title', ctrans('texts.purchase')) @section('body') - @livewire('billing-portal-purchase', ['billing_subscription' => $billing_subscription, 'contact' => auth('contact')->user(), 'hash' => $hash]) + @livewire('billing-portal-purchase', ['subscription' => $subscription, 'contact' => auth('contact')->user(), 'hash' => $hash, 'request_data' => $request_data]) @stop @push('footer') diff --git a/resources/views/email/template/custom.blade.php b/resources/views/email/template/custom.blade.php index 6335a9eb877b..c6e607594375 100644 --- a/resources/views/email/template/custom.blade.php +++ b/resources/views/email/template/custom.blade.php @@ -1 +1 @@ -{{ $body }} \ No newline at end of file +{!! $body !!} \ No newline at end of file diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php index 8a7afa8ce4e5..d74698642873 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchase.blade.php @@ -1,18 +1,80 @@
-
-
- {{ $billing_subscription->company->present()->name }} +
+
+ {{ $subscription->company->present()->name }} -

- {{ $billing_subscription->product->product_key }} -

+
+ @if(!empty($subscription->product_ids)) +

+ One-time purchase +

+ @endif -

{{ $billing_subscription->product->notes }}

+ @if(!empty($subscription->recurring_product_ids)) +

+ Subscription +

+ @endif - {{ ctrans('texts.total') }}: +

+ {{ $subscription->name }} +

+
-

{{ App\Utils\Number::formatMoney($billing_subscription->product->price, $billing_subscription->company) }}

+ @if(!empty($subscription->product_ids)) +
+

+ One-time purchases: +

+ + @foreach($subscription->service()->products() as $product) +
+
{{ $product->product_key }}
+
+ {{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }} + {{-- (1x)--}} +
+
+ @endforeach +
+ @endif + + @if(!empty($subscription->recurring_product_ids)) +
+

+ Recurring purchases: +

+ + @foreach($subscription->service()->recurring_products() as $product) +
+
{{ $product->product_key }}
+
+ {{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }} + {{-- (1x)--}} +
+
+ @endforeach +
+ @endif + +
+
+
+
+ +
+

+ {{ ctrans('texts.total') }}: {{ $price }} +

+
+
@if(auth('contact')->user()) @@ -29,17 +91,17 @@
-
+
-

{{ $heading_text }}

+

{{ $heading_text ?? ctrans('texts.login') }}

@if (session()->has('message')) @component('portal.ninja2020.components.message') {{ session('message') }} @endcomponent @endif - @if($this->steps['fetched_payment_methods']) + @if($steps['fetched_payment_methods'])
@endforeach
+ @elseif($steps['show_start_trial']) + + @csrf +

Some text about the trial goes here. Details about the days, etc.

+ + + + + @else
@csrf @@ -100,27 +173,27 @@
@endif -
-
-
+ @if(!empty($subscription->promo_code) && !$subscription->trial_enabled) +
+
+
+
+ +
+ Have a coupon code? +
-
- Have a coupon code? -
-
+
+ @csrf - - @csrf - -
- -
-
+ + + @endif
diff --git a/resources/views/portal/ninja2020/gateways/stripe/includes/card_widget.blade.php b/resources/views/portal/ninja2020/gateways/stripe/includes/card_widget.blade.php index e5a9df327f3b..213661e3edba 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/includes/card_widget.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/includes/card_widget.blade.php @@ -7,7 +7,7 @@ @unless(isset($show_card_element) && $show_card_element == false) @component('portal.ninja2020.components.general.card-element-single') -
+
@endcomponent @endunless
diff --git a/resources/views/portal/ninja2020/invoices/show.blade.php b/resources/views/portal/ninja2020/invoices/show.blade.php index b1744051b1c4..f0d4c68451bc 100644 --- a/resources/views/portal/ninja2020/invoices/show.blade.php +++ b/resources/views/portal/ninja2020/invoices/show.blade.php @@ -62,7 +62,7 @@

{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}} - - {{ ctrans('texts.paid') }} + - {{ \App\Models\Invoice::stringStatus($invoice->status_id) }}

diff --git a/resources/views/portal/ninja2020/quotes/includes/actions.blade.php b/resources/views/portal/ninja2020/quotes/includes/actions.blade.php index bc49946a1c29..28f796cd2beb 100644 --- a/resources/views/portal/ninja2020/quotes/includes/actions.blade.php +++ b/resources/views/portal/ninja2020/quotes/includes/actions.blade.php @@ -8,7 +8,7 @@

- {{ ctrans('texts.pending_approval') }} + {{ ctrans('texts.approve') }}

diff --git a/resources/views/portal/ninja2020/recurring_invoices/show.blade.php b/resources/views/portal/ninja2020/recurring_invoices/show.blade.php index f126a683aa9b..3509eed15064 100644 --- a/resources/views/portal/ninja2020/recurring_invoices/show.blade.php +++ b/resources/views/portal/ninja2020/recurring_invoices/show.blade.php @@ -57,7 +57,8 @@
-
+ +
@@ -72,7 +73,8 @@
- + @include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
diff --git a/routes/api.php b/routes/api.php index f3c18e421f60..99ffe08a17a8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -24,6 +24,7 @@ Route::group(['middleware' => ['api_secret_check', 'email_db']], function () { }); Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () { + Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain'); Route::get('ping', 'PingController@index')->name('ping'); Route::get('health_check', 'PingController@health')->name('health_check'); @@ -37,6 +38,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk'); Route::post('connected_account', 'ConnectedAccountController@index'); + Route::post('connected_account/gmail', 'ConnectedAccountController@handleGmailOauth'); Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit @@ -174,7 +176,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a // Route::post('hooks', 'SubscriptionController@subscribe')->name('hooks.subscribe'); // Route::delete('hooks/{subscription_id}', 'SubscriptionController@unsubscribe')->name('hooks.unsubscribe'); - Route::resource('billing_subscriptions', 'BillingSubscriptionController'); + Route::resource('subscriptions', 'SubscriptionController'); Route::resource('cliente_subscriptions', 'ClientSubscriptionController'); }); diff --git a/routes/client.php b/routes/client.php index dc9bf5774a93..72a9b3636cca 100644 --- a/routes/client.php +++ b/routes/client.php @@ -71,12 +71,14 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence Route::get('documents/{document}/download', 'ClientPortal\DocumentController@download')->name('documents.download'); Route::resource('documents', 'ClientPortal\DocumentController')->only(['index', 'show']); + Route::resource('subscriptions', 'ClientPortal\SubscriptionController')->only(['index']); + Route::post('upload', 'ClientPortal\UploadController')->name('upload.store'); Route::get('logout', 'Auth\ContactLoginController@logout')->name('logout'); }); -Route::get('client/subscription/{billing_subscription}/purchase', 'ClientPortal\BillingSubscriptionPurchaseController@index')->name('client.subscription.purchase'); +Route::get('client/subscription/{subscription}/purchase', 'ClientPortal\SubscriptionPurchaseController@index')->name('client.subscription.purchase'); Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'client.'], function () { /*Invitation catches*/ diff --git a/tailwind.config.js b/tailwind.config.js index e521b70f92af..d8ef70be4c62 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,6 +1,9 @@ const defaultTheme = require("tailwindcss/defaultTheme"); module.exports = { + future: { + purgeLayersByDefault: true + }, purge: [ './resources/views/portal/ninja2020/**/*.blade.php', './resources/views/email/template/**/*.blade.php', diff --git a/tests/Feature/BillingSubscriptionApiTest.php b/tests/Feature/BillingSubscriptionApiTest.php deleted file mode 100644 index 54c6162ef5ff..000000000000 --- a/tests/Feature/BillingSubscriptionApiTest.php +++ /dev/null @@ -1,134 +0,0 @@ -makeTestData(); - - Session::start(); - - $this->faker = \Faker\Factory::create(); - - Model::reguard(); - } - - public function testBillingSubscriptionsGet() - { - $product = Product::factory()->create([ - 'company_id' => $this->company->id, - 'user_id' => $this->user->id, - ]); - - $billing_subscription = BillingSubscription::factory()->create([ - 'product_id' => $product->id, - 'company_id' => $this->company->id, - ]); - - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->get('/api/v1/billing_subscriptions/' . $this->encodePrimaryKey($billing_subscription->id)); - - $response->assertStatus(200); - } - - public function testBillingSubscriptionsPost() - { - $product = Product::factory()->create([ - 'company_id' => $this->company->id, - 'user_id' => $this->user->id, - ]); - - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/billing_subscriptions', ['product_id' => $product->id, 'allow_cancellation' => true]); - - $response->assertStatus(200); - } - - public function testBillingSubscriptionPut() - { - $product = Product::factory()->create([ - 'company_id' => $this->company->id, - 'user_id' => $this->user->id, - ]); - - $response1 = $this - ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token]) - ->post('/api/v1/billing_subscriptions', ['product_id' => $product->id]) - ->assertStatus(200) - ->json(); - - $response2 = $this - ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token]) - ->put('/api/v1/billing_subscriptions/' . $response1['data']['id'], ['allow_cancellation' => true]) - ->assertStatus(200) - ->json(); - - $this->assertNotEquals($response1['data']['allow_cancellation'], $response2['data']['allow_cancellation']); - } - - /* - TypeError : Argument 1 passed to App\Transformers\BillingSubscriptionTransformer::transform() must be an instance of App\Models\BillingSubscription, bool given, called in /var/www/html/vendor/league/fractal/src/Scope.php on line 407 - /var/www/html/app/Transformers/BillingSubscriptionTransformer.php:35 - /var/www/html/vendor/league/fractal/src/Scope.php:407 - /var/www/html/vendor/league/fractal/src/Scope.php:349 - /var/www/html/vendor/league/fractal/src/Scope.php:235 - /var/www/html/app/Http/Controllers/BaseController.php:395 - /var/www/html/app/Http/Controllers/BillingSubscriptionController.php:408 - */ - public function testBillingSubscriptionDeleted() - { - - $product = Product::factory()->create([ - 'company_id' => $this->company->id, - 'user_id' => $this->user->id, - ]); - - $billing_subscription = BillingSubscription::factory()->create([ - 'product_id' => $product->id, - 'company_id' => $this->company->id, - ]); - - $response = $this - ->withHeaders(['X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token]) - ->delete('/api/v1/billing_subscriptions/' . $this->encodePrimaryKey($billing_subscription->id)) - ->assertStatus(200) - ->json(); - - } -} diff --git a/tests/Integration/PaymentDrivers/AuthorizeTest.php b/tests/Integration/PaymentDrivers/AuthorizeTest.php index 54928a2beef2..33934756a289 100644 --- a/tests/Integration/PaymentDrivers/AuthorizeTest.php +++ b/tests/Integration/PaymentDrivers/AuthorizeTest.php @@ -310,6 +310,28 @@ class AuthorizeTest extends TestCase $controller = new CreateTransactionController($request); $response = $controller->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX); + // nlog($response); + nlog($response->getTransactionResponse()->getMessages() !== null); + nlog($response->getTransactionResponse()->getMessages()); + nlog($response->getTransactionResponse()->getMessages()[0]); + //nlog($response->getTransactionResponse()->getMessages()[0]->getCode()); + + $code = ''; + $description = ''; + + if($response->getTransactionResponse()->getMessages() !== null){ + $code = $response->getTransactionResponse()->getMessages()[0]->getCode(); + $description = $response->getTransactionResponse()->getMessages()[0]->getDescription(); + } + + $log = [ + 'transaction_reference' => $response->getTransactionResponse()->getTransId(), + 'auth_code' => $response->getTransactionResponse()->getAuthCode(), + 'code' => $code, + 'description' => $description, + ]; + + if ($response != null) { if ($response->getMessages()->getResultCode() == 'Ok') { $tresponse = $response->getTransactionResponse();