diff --git a/VERSION.txt b/VERSION.txt index 37dfb683ad1a..da61e7f79e19 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.1.17 \ No newline at end of file +5.1.18 \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 24a661eae68f..1d3be1016fce 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -11,6 +11,7 @@ namespace App\Console; +use App\Jobs\Cron\BillingSubscriptionCron; use App\Jobs\Cron\RecurringInvoicesCron; use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\CompanySizeCheck; @@ -53,6 +54,8 @@ class Kernel extends ConsoleKernel $schedule->job(new UpdateExchangeRates)->daily()->withoutOverlapping(); + $schedule->job(new BillingSubscriptionCron)->daily()->withoutOverlapping(); + $schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping(); /* Run hosted specific jobs */ diff --git a/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php b/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php new file mode 100644 index 000000000000..73fc2d1f2f87 --- /dev/null +++ b/app/Events/BillingSubscription/BillingSubscriptionWasCreated.php @@ -0,0 +1,55 @@ +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/ClientSubscription/ClientSubscriptionWasCreated.php b/app/Events/ClientSubscription/ClientSubscriptionWasCreated.php new file mode 100644 index 000000000000..ce08f8793247 --- /dev/null +++ b/app/Events/ClientSubscription/ClientSubscriptionWasCreated.php @@ -0,0 +1,55 @@ +client_subscription = $client_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/Factory/BillingSubscriptionFactory.php b/app/Factory/BillingSubscriptionFactory.php new file mode 100644 index 000000000000..f9f051293e9c --- /dev/null +++ b/app/Factory/BillingSubscriptionFactory.php @@ -0,0 +1,27 @@ +company_id = $company_id; + $billing_subscription->user_id = $user_id; + + return $billing_subscription; + } +} diff --git a/app/Http/Controllers/BillingSubscriptionController.php b/app/Http/Controllers/BillingSubscriptionController.php new file mode 100644 index 000000000000..199d51005114 --- /dev/null +++ b/app/Http/Controllers/BillingSubscriptionController.php @@ -0,0 +1,410 @@ +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/ClientSubscriptionController.php b/app/Http/Controllers/ClientSubscriptionController.php new file mode 100644 index 000000000000..bf0a9b39b167 --- /dev/null +++ b/app/Http/Controllers/ClientSubscriptionController.php @@ -0,0 +1,410 @@ +client_subscription_repo = $client_subscription_repo; + } + + /** + * Show the list of ClientSubscriptions. + * + * @return Response + * + * @OA\Get( + * path="/api/v1/client_subscriptions", + * operationId="getClientSubscriptions", + * tags={"client_subscriptions"}, + * summary="Gets a list of client_subscriptions", + * description="Lists client_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 client_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/ClientSubscription"), + * ), + * @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 + { + $client_subscriptions = ClientSubscription::query()->company(); + + return $this->listResponse($client_subscriptions); + } + + /** + * Show the form for creating a new resource. + * + * @param CreateClientSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/client_subscriptions/create", + * operationId="getClientSubscriptionsCreate", + * tags={"client_subscriptions"}, + * summary="Gets a new blank client_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 client_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/ClientSubscription"), + * ), + * @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(CreateClientSubscriptionRequest $request): \Illuminate\Http\Response + { + $client_subscription = ClientSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); + + return $this->itemResponse($client_subscription); + } + + /** + * Store a newly created resource in storage. + * + * @param StoreClientSubscriptionRequest $request The request + * + * @return Response + * + * + * @OA\Post( + * path="/api/v1/client_subscriptions", + * operationId="storeClientSubscription", + * tags={"client_subscriptions"}, + * summary="Adds a client_subscriptions", + * description="Adds an client_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 client_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/ClientSubscription"), + * ), + * @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(StoreClientSubscriptionRequest $request): \Illuminate\Http\Response + { + $client_subscription = $this->client_subscription_repo->save($request->all(), ClientSubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id)); + + event(new ClientsubscriptionWasCreated($client_subscription, $client_subscription->company, Ninja::eventVars())); + + return $this->itemResponse($client_subscription); + } + + /** + * Display the specified resource. + * + * @param ShowClientSubscriptionRequest $request The request + * @param Invoice $client_subscription The invoice + * + * @return Response + * + * + * @OA\Get( + * path="/api/v1/client_subscriptions/{id}", + * operationId="showClientSubscription", + * tags={"client_subscriptions"}, + * summary="Shows an client_subscriptions", + * description="Displays an client_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 ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the ClientSubscription 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/ClientSubscription"), + * ), + * @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(ShowClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($client_subscription); + } + + /** + * Show the form for editing the specified resource. + * + * @param EditClientSubscriptionRequest $request The request + * @param Invoice $client_subscription The invoice + * + * @return Response + * + * @OA\Get( + * path="/api/v1/client_subscriptions/{id}/edit", + * operationId="editClientSubscription", + * tags={"client_subscriptions"}, + * summary="Shows an client_subscriptions for editting", + * description="Displays an client_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 ClientSubscription 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/ClientSubscription"), + * ), + * @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(EditClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + return $this->itemResponse($client_subscription); + } + + /** + * Update the specified resource in storage. + * + * @param UpdateClientSubscriptionRequest $request The request + * @param ClientSubscription $client_subscription The invoice + * + * @return Response + * + * + * @OA\Put( + * path="/api/v1/client_subscriptions/{id}", + * operationId="updateClientSubscription", + * tags={"client_subscriptions"}, + * summary="Updates an client_subscriptions", + * description="Handles the updating of an client_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 ClientSubscription Hashed ID", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the client_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/ClientSubscription"), + * ), + * @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(UpdateClientSubscriptionRequest $request, ClientSubscription $client_subscription) + { + if ($request->entityIsDeleted($client_subscription)) { + return $request->disallowUpdate(); + } + + $client_subscription = $this->client_subscription_repo->save($request->all(), $client_subscription); + + return $this->itemResponse($client_subscription); + } + + /** + * Remove the specified resource from storage. + * + * @param DestroyClientSubscriptionRequest $request + * @param ClientSubscription $invoice + * + * @return Response + * + * @throws \Exception + * @OA\Delete( + * path="/api/v1/client_subscriptions/{id}", + * operationId="deleteClientSubscription", + * tags={"client_subscriptions"}, + * summary="Deletes a client_subscriptions", + * description="Handles the deletion of an client_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 ClientSubscription 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(DestroyClientSubscriptionRequest $request, ClientSubscription $client_subscription): \Illuminate\Http\Response + { + $this->client_subscription_repo->delete($client_subscription); + + return $this->itemResponse($client_subscription->fresh()); + } +} diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php index a6f011bde5f3..d51638fc3980 100644 --- a/app/Http/Controllers/ConnectedAccountController.php +++ b/app/Http/Controllers/ConnectedAccountController.php @@ -13,12 +13,18 @@ namespace App\Http\Controllers; use App\Libraries\MultiDB; use App\Libraries\OAuth\Providers\Google; -use Illuminate\Http\Request; +use App\Models\CompanyUser; +use App\Transformers\CompanyUserTransformer; use Google_Client; +use Illuminate\Http\Request; class ConnectedAccountController extends BaseController { + protected $entity_type = CompanyUser::class; + + protected $entity_transformer = CompanyUserTransformer::class; + public function __construct() { parent::__construct(); @@ -128,8 +134,10 @@ class ConnectedAccountController extends BaseController auth()->user()->save(); //$ct = CompanyUser::whereUserId(auth()->user()->id); + $ct = CompanyUser::whereUserId(auth()->user()->id); - return $this->listResponse(auth()->user()); + return $this->listResponse($ct); + // return $this->listResponse(auth()->user()); } return response() diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 77ab97a7d900..59e400e60c45 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -396,6 +396,8 @@ class InvoiceController extends BaseController } $invoice = $this->invoice_repo->save($request->all(), $invoice); + + $invoice->service()->deletePdf(); event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars())); @@ -697,14 +699,14 @@ class InvoiceController extends BaseController } break; case 'cancel': - $invoice = $invoice->service()->handleCancellation()->save(); + $invoice = $invoice->service()->handleCancellation()->deletePdf()->save(); if (! $bulk) { $this->itemResponse($invoice); } break; case 'reverse': - $invoice = $invoice->service()->handleReversal()->save(); + $invoice = $invoice->service()->handleReversal()->deletePdf()->save(); if (! $bulk) { $this->itemResponse($invoice); @@ -720,7 +722,7 @@ class InvoiceController extends BaseController } //touch reminder1,2,3_sent + last_sent here if the email is a reminder. - $invoice->service()->touchReminder($this->reminder_template)->save(); + $invoice->service()->touchReminder($this->reminder_template)->deletePdf()->save(); $invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) use ($invoice) { EmailEntity::dispatch($invitation, $invoice->company, $this->reminder_template); diff --git a/app/Http/Controllers/OpenAPI/BillingSubscription.php b/app/Http/Controllers/OpenAPI/BillingSubscription.php new file mode 100644 index 000000000000..fad9b1eac32f --- /dev/null +++ b/app/Http/Controllers/OpenAPI/BillingSubscription.php @@ -0,0 +1,35 @@ +getMessage()); return redirect() diff --git a/app/Http/Middleware/PasswordProtection.php b/app/Http/Middleware/PasswordProtection.php index 5d1ad6581faf..6b7532c69ac8 100644 --- a/app/Http/Middleware/PasswordProtection.php +++ b/app/Http/Middleware/PasswordProtection.php @@ -95,4 +95,4 @@ class PasswordProtection } -} +} \ No newline at end of file diff --git a/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php new file mode 100644 index 000000000000..f4dfbe25d4b0 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/CreateBillingSubscriptionRequest.php @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000000..399cdceb2f0b --- /dev/null +++ b/app/Http/Requests/BillingSubscription/DestroyBillingSubscriptionRequest.php @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000000..94d285770b65 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/EditBillingSubscriptionRequest.php @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000000..18ba6ab68f7d --- /dev/null +++ b/app/Http/Requests/BillingSubscription/ShowBillingSubscriptionRequest.php @@ -0,0 +1,40 @@ +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 new file mode 100644 index 000000000000..0c5a64c2ead6 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/StoreBillingSubscriptionRequest.php @@ -0,0 +1,60 @@ +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 new file mode 100644 index 000000000000..78d41deb35e6 --- /dev/null +++ b/app/Http/Requests/BillingSubscription/UpdateBillingSubscriptionRequest.php @@ -0,0 +1,42 @@ +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/ClientSubscription/CreateClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/CreateClientSubscriptionRequest.php new file mode 100644 index 000000000000..ca8afd2e5229 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/CreateClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('create', ClientSubscription::class); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php new file mode 100644 index 000000000000..609b88bcf1e6 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/DestroyClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php new file mode 100644 index 000000000000..bf152757d735 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/EditClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php new file mode 100644 index 000000000000..bf2430e7ba5e --- /dev/null +++ b/app/Http/Requests/ClientSubscription/ShowClientSubscriptionRequest.php @@ -0,0 +1,40 @@ +user()->can('view', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php new file mode 100644 index 000000000000..5c29cd1a44a5 --- /dev/null +++ b/app/Http/Requests/ClientSubscription/StoreClientSubscriptionRequest.php @@ -0,0 +1,38 @@ +user()->can('create', ClientSubscription::class); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return []; + } +} diff --git a/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php b/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php new file mode 100644 index 000000000000..47f989631d8b --- /dev/null +++ b/app/Http/Requests/ClientSubscription/UpdateClientSubscriptionRequest.php @@ -0,0 +1,42 @@ +user()->can('edit', $this->client_subscription); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + // + ]; + } +} diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index df4edd1d94b3..a67b610c70d8 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('subscription_id', $input) && is_string($input['subscription_id'])) { + $input['subscription_id'] = $this->decodePrimaryKey($input['subscription_id']); + } + if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { $input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']); } diff --git a/app/Jobs/Cron/BillingSubscriptionCron.php b/app/Jobs/Cron/BillingSubscriptionCron.php new file mode 100644 index 000000000000..3aef58c290cd --- /dev/null +++ b/app/Jobs/Cron/BillingSubscriptionCron.php @@ -0,0 +1,72 @@ +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/Models/BillingSubscription.php b/app/Models/BillingSubscription.php new file mode 100644 index 000000000000..486e3134d607 --- /dev/null +++ b/app/Models/BillingSubscription.php @@ -0,0 +1,71 @@ + 'boolean', + 'plan_map' => 'object', + 'webhook_configuration' => 'object', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + + 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/ClientSubscription.php b/app/Models/ClientSubscription.php new file mode 100644 index 000000000000..bf38456dbc53 --- /dev/null +++ b/app/Models/ClientSubscription.php @@ -0,0 +1,55 @@ + 'boolean', + 'updated_at' => 'timestamp', + 'created_at' => 'timestamp', + 'deleted_at' => 'timestamp', + ]; + + public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Company::class); + } + + public function recurring_invoice(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(RecurringInvoice::class); + } + + public function client(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Client::class); + } + + public function subscription(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(BillingSubscription::class); + } +} diff --git a/app/Models/Company.php b/app/Models/Company.php index 16bc053a5a5a..3d1745444829 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -159,6 +159,16 @@ class Company extends BaseModel return $this->hasMany(ExpenseCategory::class)->withTrashed(); } + public function client_subscriptions() + { + return $this->hasMany(ClientSubscription::class)->withTrashed(); + } + + public function billing_subscriptions() + { + return $this->hasMany(BillingSubscription::class)->withTrashed(); + } + public function task_statuses() { return $this->hasMany(TaskStatus::class)->withTrashed(); diff --git a/app/Models/Credit.php b/app/Models/Credit.php index aecb446a18a5..8acf5a3dd9eb 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -77,6 +77,7 @@ class Credit extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index f4cd530696a8..22a9698a5bd4 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -91,6 +91,7 @@ class Invoice extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Models/Quote.php b/app/Models/Quote.php index 1f2b740ea81b..2f0c414ec75b 100644 --- a/app/Models/Quote.php +++ b/app/Models/Quote.php @@ -78,6 +78,7 @@ class Quote extends BaseModel 'design_id', 'assigned_user_id', 'exchange_rate', + 'subscription_id', ]; protected $casts = [ diff --git a/app/Observers/BillingSubscriptionObserver.php b/app/Observers/BillingSubscriptionObserver.php new file mode 100644 index 000000000000..dc813c732544 --- /dev/null +++ b/app/Observers/BillingSubscriptionObserver.php @@ -0,0 +1,72 @@ +isAdmin() || $user->hasPermission('create_billing_subscription') || $user->hasPermission('create_all'); + } +} diff --git a/app/Policies/ClientSubscriptionPolicy.php b/app/Policies/ClientSubscriptionPolicy.php new file mode 100644 index 000000000000..e85df1028e4d --- /dev/null +++ b/app/Policies/ClientSubscriptionPolicy.php @@ -0,0 +1,31 @@ +isAdmin() || $user->hasPermission('create_client_subscription') || $user->hasPermission('create_all'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 3d96377fa950..c017572692c3 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,7 +12,9 @@ namespace App\Providers; use App\Models\Account; +use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyToken; @@ -26,7 +28,9 @@ use App\Models\Quote; use App\Models\Task; use App\Models\User; use App\Observers\AccountObserver; +use App\Observers\BillingSubscriptionObserver; use App\Observers\ClientObserver; +use App\Observers\ClientSubscriptionObserver; use App\Observers\CompanyGatewayObserver; use App\Observers\CompanyObserver; use App\Observers\CompanyTokenObserver; @@ -75,9 +79,10 @@ class AppServiceProvider extends ServiceProvider Schema::defaultStringLength(191); - User::observe(UserObserver::class); Account::observe(AccountObserver::class); + BillingSubscription::observe(BillingSubscriptionObserver::class); Client::observe(ClientObserver::class); + ClientSubscription::observe(ClientSubscriptionObserver::class); Company::observe(CompanyObserver::class); CompanyGateway::observe(CompanyGatewayObserver::class); CompanyToken::observe(CompanyTokenObserver::class); @@ -89,6 +94,7 @@ class AppServiceProvider extends ServiceProvider Proposal::observe(ProposalObserver::class); Quote::observe(QuoteObserver::class); Task::observe(TaskObserver::class); + User::observe(UserObserver::class); // Queue::before(function (JobProcessing $event) { // // \Log::info('Event Job '.$event->connectionName); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 23d6f3a59220..d71a5ae1435f 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -12,7 +12,9 @@ namespace App\Providers; use App\Models\Activity; +use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyToken; @@ -37,7 +39,9 @@ use App\Models\User; use App\Models\Vendor; use App\Models\Webhook; use App\Policies\ActivityPolicy; +use App\Policies\BillingSubscriptionPolicy; use App\Policies\ClientPolicy; +use App\Policies\ClientSubscriptionPolicy; use App\Policies\CompanyGatewayPolicy; use App\Policies\CompanyPolicy; use App\Policies\CompanyTokenPolicy; @@ -73,7 +77,9 @@ class AuthServiceProvider extends ServiceProvider */ protected $policies = [ Activity::class => ActivityPolicy::class, + BillingSubscription::class => BillingSubscriptionPolicy::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 new file mode 100644 index 000000000000..68be2473468d --- /dev/null +++ b/app/Repositories/BillingSubscriptionRepository.php @@ -0,0 +1,28 @@ +fill($data) + ->save(); + + return $billing_subscription; + } +} diff --git a/app/Repositories/ClientSubscriptionRepository.php b/app/Repositories/ClientSubscriptionRepository.php new file mode 100644 index 000000000000..4cb88cb2ca53 --- /dev/null +++ b/app/Repositories/ClientSubscriptionRepository.php @@ -0,0 +1,28 @@ +fill($data) + ->save(); + + return $client_subscription; + } +} diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index b45e5eb19b6e..66e5c176cad2 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -22,6 +22,7 @@ use App\Models\Task; use App\Services\Client\ClientService; use App\Utils\Traits\MakesHash; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Storage; class InvoiceService { @@ -273,8 +274,9 @@ class InvoiceService public function deletePdf() { - UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); - + //UnlinkFile::dispatchNow(config('filesystems.default'), $this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); + Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath() . $this->invoice->number.'.pdf'); + return $this; } diff --git a/app/Services/Invoice/MarkPaid.php b/app/Services/Invoice/MarkPaid.php index e3f2c1c5bb54..7f827a5fd842 100644 --- a/app/Services/Invoice/MarkPaid.php +++ b/app/Services/Invoice/MarkPaid.php @@ -74,6 +74,7 @@ class MarkPaid extends AbstractService ->updatePaidToDate($payment->amount) ->setStatus(Invoice::STATUS_PAID) ->applyNumber() + ->deletePdf() ->save(); if ($this->invoice->client->getSetting('client_manual_payment_notification')) diff --git a/app/Services/Invoice/MarkSent.php b/app/Services/Invoice/MarkSent.php index 1767e61e94f3..c5149a462577 100644 --- a/app/Services/Invoice/MarkSent.php +++ b/app/Services/Invoice/MarkSent.php @@ -47,6 +47,7 @@ class MarkSent extends AbstractService ->applyNumber() ->setDueDate() ->updateBalance($this->invoice->amount) + ->deletePdf() ->save(); $this->client->service()->updateBalance($this->invoice->balance)->save(); diff --git a/app/Transformers/BillingSubscriptionTransformer.php b/app/Transformers/BillingSubscriptionTransformer.php new file mode 100644 index 000000000000..8adbd2568ac2 --- /dev/null +++ b/app/Transformers/BillingSubscriptionTransformer.php @@ -0,0 +1,73 @@ + $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, + '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/ClientSubscriptionTransformer.php b/app/Transformers/ClientSubscriptionTransformer.php new file mode 100644 index 000000000000..5ed3c31d5539 --- /dev/null +++ b/app/Transformers/ClientSubscriptionTransformer.php @@ -0,0 +1,78 @@ + $this->encodePrimaryKey($client_subscription->id), + 'subscription_id' => $this->encodePrimaryKey($client_subscription->subscription_id), + 'company_id' => $this->encodePrimaryKey($client_subscription->company_id), + 'recurring_invoice_id' => $this->encodePrimaryKey($client_subscription->recurring_invoice_id), + 'client_id' => $this->encodePrimaryKey($client_subscription->client_id), + 'trial_started' => (string)$client_subscription->trial_started ?: '', + 'trial_ends' => (string)$client_subscription->trial_ends ?: '', + 'is_deleted' => (bool)$client_subscription->is_deleted, + 'created_at' => (int)$client_subscription->created_at, + 'updated_at' => (int)$client_subscription->updated_at, + 'archived_at' => (int)$client_subscription->deleted_at, + ]; + } + + public function includeClient(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new ClientTransformer($this->serializer); + + return $this->includeItem($client_subscription->client, $transformer, Client::class); + } + + public function includeRecurringInvoice(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new RecurringInvoiceTransformer($this->serializer); + + return $this->includeItem($client_subscription->recurring_invoice, $transformer, RecurringInvoice::class); + } + + public function includeSubscription(ClientSubscription $client_subscription): \League\Fractal\Resource\Item + { + $transformer = new BillingSubscriptionTransformer($this->serializer); + + return $this->includeItem($client_subscription->subscription, $transformer, BillingSubscription::class); + } +} + diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 308cf6cde8b7..8620fefd7b2a 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -13,7 +13,9 @@ namespace App\Transformers; use App\Models\Account; use App\Models\Activity; +use App\Models\BillingSubscription; use App\Models\Client; +use App\Models\ClientSubscription; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\CompanyLedger; @@ -90,6 +92,7 @@ class CompanyTransformer extends EntityTransformer 'system_logs', 'expense_categories', 'task_statuses', + 'client_subscriptions', ]; /** @@ -358,4 +361,18 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->system_logs, $transformer, SystemLog::class); } + + public function includeClientSubscriptions(Company $company) + { + $transformer = new ClientSubscriptionTransformer($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); + } } diff --git a/app/Transformers/CreditTransformer.php b/app/Transformers/CreditTransformer.php index 8d48f1b0b264..27d052e91cc4 100644 --- a/app/Transformers/CreditTransformer.php +++ b/app/Transformers/CreditTransformer.php @@ -137,6 +137,7 @@ class CreditTransformer extends EntityTransformer 'entity_type' => 'credit', 'exchange_rate' => (float) $credit->exchange_rate, 'paid_to_date' => (float) $credit->paid_to_date, + 'subscription_id' => $this->encodePrimaryKey($credit->subscription_id), ]; } } diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index 7a31a4b7760c..836c15e2c86d 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -141,6 +141,7 @@ class InvoiceTransformer extends EntityTransformer 'reminder3_sent' => $invoice->reminder3_sent ?: '', 'reminder_last_sent' => $invoice->reminder_last_sent ?: '', 'paid_to_date' => (float) $invoice->paid_to_date, + 'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id), ]; } diff --git a/app/Transformers/QuoteTransformer.php b/app/Transformers/QuoteTransformer.php index ae8cd498c0b9..a3ecfc9938ac 100644 --- a/app/Transformers/QuoteTransformer.php +++ b/app/Transformers/QuoteTransformer.php @@ -139,6 +139,8 @@ class QuoteTransformer extends EntityTransformer 'exchange_rate' => (float) $quote->exchange_rate, 'paid_to_date' => (float) $quote->paid_to_date, 'project_id' => $this->encodePrimaryKey($quote->project_id), + 'subscription_id' => $this->encodePrimaryKey($quote->subscription_id), + ]; } } diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index d2aebc62d02e..7e522f426acb 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -26,7 +26,7 @@ class Helpers $elements['signature'] = ''; $elements['settings'] = new stdClass; $elements['whitelabel'] = true; - + $elements['company'] = ''; return $elements; } @@ -35,7 +35,8 @@ class Helpers $elements['signature'] = $_settings->email_signature; $elements['settings'] = $_settings; $elements['whitelabel'] = $client->user->account->isPaid() ? true : false; - + $elements['company'] = $client->company; + return $elements; } diff --git a/config/ninja.php b/config/ninja.php index f30406bf40b2..b91ee2fa492b 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.17', + 'app_version' => '5.1.18', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', false), diff --git a/config/queue.php b/config/queue.php index 6fb44cc4f7a1..dc2d4665ea47 100644 --- a/config/queue.php +++ b/config/queue.php @@ -13,7 +13,7 @@ return [ | */ - 'default' => env('QUEUE_CONNECTION', 'database'), + 'default' => env('QUEUE_CONNECTION', 'sync'), /* |-------------------------------------------------------------------------- diff --git a/database/factories/BillingSubscriptionFactory.php b/database/factories/BillingSubscriptionFactory.php new file mode 100644 index 000000000000..2fdf320cd5f4 --- /dev/null +++ b/database/factories/BillingSubscriptionFactory.php @@ -0,0 +1,38 @@ +increments('id'); + $table->unsignedInteger('user_id'); + $table->unsignedInteger('assigned_user_id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('product_id'); + $table->boolean('is_recurring')->default(false); + $table->unsignedInteger('frequency_id'); + $table->string('auto_bill')->default(''); + $table->string('promo_code')->default(''); + $table->float('promo_discount')->default(0); + $table->boolean('is_amount_discount')->default(false); + $table->boolean('allow_cancellation')->default(true); + $table->boolean('per_seat_enabled')->default(false); + $table->unsignedInteger('min_seats_limit'); + $table->unsignedInteger('max_seats_limit'); + $table->boolean('trial_enabled')->default(false); + $table->unsignedInteger('trial_duration'); + $table->boolean('allow_query_overrides')->default(false); + $table->boolean('allow_plan_changes')->default(false); + $table->mediumText('plan_map'); + $table->unsignedInteger('refund_period')->nullable(); + $table->mediumText('webhook_configuration'); + $table->softDeletes('deleted_at', 6); + $table->boolean('is_deleted')->default(false); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->index(['company_id', 'deleted_at']); + }); + + Schema::create('client_subscriptions', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('company_id'); + $table->unsignedInteger('subscription_id'); + $table->unsignedInteger('recurring_invoice_id'); + $table->unsignedInteger('client_id'); + $table->unsignedInteger('trial_started')->nullable(); + $table->unsignedInteger('trial_ends')->nullable(); + $table->boolean('is_deleted')->default(false); + $table->softDeletes('deleted_at', 6); + $table->timestamps(); + $table->foreign('subscription_id')->references('id')->on('billing_subscriptions'); + $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); + $table->foreign('client_id')->references('id')->on('clients'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->index(['company_id', 'deleted_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('billing_subscriptions'); + Schema::dropIfExists('client_subscriptions'); + } +} diff --git a/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php new file mode 100644 index 000000000000..ab3da33f51bd --- /dev/null +++ b/database/migrations/2021_03_09_132242_add_currency_id_to_billing_subscriptions_table.php @@ -0,0 +1,41 @@ +unsignedInteger('currency_id')->nullable(); + }); + + Schema::table('invoices', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); + + Schema::table('quotes', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); + + Schema::table('credits', function (Blueprint $table) { + $table->unsignedInteger('subscription_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + } +} diff --git a/resources/views/email/auth/password-reset.blade.php b/resources/views/email/auth/password-reset.blade.php index ac5ebb54e274..de1673f452e8 100644 --- a/resources/views/email/auth/password-reset.blade.php +++ b/resources/views/email/auth/password-reset.blade.php @@ -1,4 +1,4 @@ -@component('email.template.master', ['design' => 'light', 'whitelabel' => false]) +@component('email.template.master', ['design' => 'light', 'whitelabel' => false, 'company' => $company]) @slot('header') @include('email.components.header', ['logo' => $logo]) diff --git a/resources/views/email/template/dark.blade.php b/resources/views/email/template/dark.blade.php index 5686fde210ba..00f04ff77bf8 100644 --- a/resources/views/email/template/dark.blade.php +++ b/resources/views/email/template/dark.blade.php @@ -1,7 +1,7 @@ @component('email.template.master', ['design' => 'dark', 'settings' => $settings, 'whitelabel' => $whitelabel]) @slot('header') - @include('email.components.header', ['logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) + @include('email.components.header', ['logo' => $company->present()->logo($settings)]) @endslot {!! $body !!} diff --git a/resources/views/email/template/light.blade.php b/resources/views/email/template/light.blade.php index 071de7226d99..064cc9d357e6 100644 --- a/resources/views/email/template/light.blade.php +++ b/resources/views/email/template/light.blade.php @@ -1,7 +1,7 @@ @component('email.template.master', ['design' => 'light', 'settings' => $settings, 'whitelabel' => $whitelabel]) @slot('header') - @include('email.components.header', ['logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png']) + @include('email.components.header', ['logo' => $company->present()->logo($settings)]) @endslot {!! $body !!} diff --git a/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php b/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php index d9863d7b84ea..22b7caadea9b 100644 --- a/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php +++ b/resources/views/portal/ninja2020/components/general/sidebar/desktop.blade.php @@ -2,7 +2,7 @@