Add endpoints to validate and reject subscription

This commit is contained in:
Djery-Tom 2021-10-27 13:48:07 +01:00
parent 3bc0883854
commit cef6d86d0f
20 changed files with 368 additions and 61 deletions

View File

@ -26,3 +26,5 @@ NOTIFICATION_SERVICE_KEY=RfXvPQzQRgwpzQYPnLfWpZzgx4QseHlg
SWAGGER_GENERATE_ALWAYS=true
SWAGGER_DOCS_TOKEN=ZfMqCAdHHrSH8ADdXreIejgjJtOwsH4K
SENTRY_LARAVEL_DSN=https://cb0057643b0c4ce5805e49b5b54bd5c3@o1053292.ingest.sentry.io/6037678
SENTRY_TRACES_SAMPLE_RATE=1

View File

@ -0,0 +1,31 @@
<?php
namespace App\Events;
use App\Models\NhInsurancesSubscription;
use App\Traits\Helper;
class InsuranceEvent extends Event
{
use Helper;
public $subscription;
public $mailTitle;
public $mailMessage;
public $notification;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(NhInsurancesSubscription $subscription, string $mailTitle, string $mailMessage,
string $notification = null)
{
//
$this->subscription = $subscription;
$this->mailTitle = $mailTitle;
$this->mailMessage = $mailMessage;
$this->notification = $notification;
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Events;
use App\Models\NhInsurancesSubscription;
class InsuranceSubscribed extends Event
{
public $subscription;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(NhInsurancesSubscription $subscription)
{
//
$this->subscription = $subscription;
}
}

View File

@ -44,6 +44,10 @@ class Handler extends ExceptionHandler
*/
public function report(Throwable $exception)
{
if (app()->bound('sentry') && $this->shouldReport($exception)) {
app('sentry')->captureException($exception);
}
parent::report($exception);
}

View File

@ -2,7 +2,8 @@
namespace App\Http\Controllers;
use App\Events\InsuranceSubscribed;
use App\Events\InsuranceEvent;
use App\Events\InsuranceSubscriptionAccepted;
use App\InsuranceSubscriptionState;
use App\Models\CountriesCurrency;
use App\Models\Identification;
@ -36,15 +37,15 @@ class InsuranceController extends Controller
/**
* @OA\Get(
* path="/insurances/countries/{countryId}",
* summary="Afficher la liste des assurances d'un pays",
* path="/insurances",
* summary="Afficher la liste des assurances ( par pays )",
* tags={"Assurances"},
* security={{"api_key":{}}},
* @OA\Parameter(
* parameter="countryId",
* name="countryId",
* parameter="country_id",
* name="country_id",
* description="ID du pays",
* in="path",
* in="query",
* required=true,
* @OA\Schema(
* type="integer",
@ -66,13 +67,17 @@ class InsuranceController extends Controller
* )
* )
*/
public function getInsurancesByCountryId($countryId)
public function getInsurances(Request $request)
{
$country = CountriesCurrency::findOrFail($countryId);
$this->validate($request, [
'country_id' => 'nullable|integer|exists:countries,id'
]);
$country = CountriesCurrency::findOrFail($request->input('country_id'));
$insurances = DB::select("SELECT n.id , n.name , nhc.age_limit_of_insured_and_spouse, nhc.age_limit_of_child_beneficiary, nhc.max_number_of_beneficiaries, nhc.id as nhc_id
FROM networks n JOIN configWallet cw ON cw.id_network = n.id JOIN nh_networks_configs nhc
ON nhc.network_id = n.id WHERE n.country_id = :countryId AND cw.type = 'ilink_sante' AND n.status = 1", ['countryId' => $countryId]);
ON nhc.network_id = n.id WHERE n.country_id = :countryId AND cw.type = 'ilink_sante' AND n.status = 1", ['countryId' => $request->input('country_id')]);
foreach ($insurances as $insurance) {
$months_prices = DB::select("SELECT id, number_of_months , min_amount FROM nh_months_prices_grid WHERE nh_network_config_id = :nhc_id",
@ -90,7 +95,7 @@ class InsuranceController extends Controller
/**
* @OA\Post(
* path="/insurances/bonus-amount",
* path="/insurances/subscriptions/bonus-amount",
* summary="Calculer le montant de la prime",
* tags={"Assurances"},
* security={{"api_key":{}}},
@ -134,7 +139,7 @@ class InsuranceController extends Controller
$this->validate($request, [
'network_id' => 'required|integer|exists:networks,id',
'month_price_id' => 'required|integer|exists:nh_months_prices_grid,id',
'beneficiaries' => 'required|array',
'beneficiaries' => 'nullable|array',
'beneficiaries.*.birthdate' => 'required|date_format:Y-m-d|before:today',
'beneficiaries.*.affiliation' => 'required|in:CHILD,SPOUSE'
]);
@ -182,7 +187,7 @@ class InsuranceController extends Controller
/**
* @OA\Post(
* path="/insurances/subscribe",
* path="/insurances/subscriptions",
* summary="Souscrire à une assurance",
* tags={"Assurances"},
* security={{"api_key":{}}},
@ -315,7 +320,7 @@ class InsuranceController extends Controller
'user_id' => 'required|integer|exists:users,id',
'password' => 'required|string',
'month_price_id' => 'required|integer|exists:nh_months_prices_grid,id',
'beneficiaries' => 'required|array',
'beneficiaries' => 'nullable|array',
'beneficiaries.*.lastname' => 'required|string',
'beneficiaries.*.gender' => 'required|in:M,F',
'beneficiaries.*.birthdate' => 'required|date_format:Y-m-d|before:today',
@ -341,8 +346,8 @@ class InsuranceController extends Controller
return $this->errorResponse(trans('errors.nano_health_not_activated'));
// Verification de l'age du beneficiaire
$insuredAge = date_diff(date_create($identification->birthdate), date_create('now'))->y;
if ($insuredAge < $networkConfig->age_limit_of_insured_and_spouse) {
$insuredAge = date_diff(date_create($identification->birth_date), date_create('now'))->y;
if ($insuredAge > $networkConfig->age_limit_of_insured_and_spouse) {
return $this->errorResponse(trans('errors.minimal_age_required'));
}
@ -384,7 +389,8 @@ class InsuranceController extends Controller
]);
Event::dispatch(new InsuranceSubscribed($subscription));
Event::dispatch(new InsuranceEvent($subscription, trans('messages.insurance_subscription'), trans('messages.insurance_subscription_mail', ['name' => $subscription->user->lastname, 'subscription_id' => $subscription->insurance_subscription_id,
'bonus_amount' => $this->toMoneyWithNetwork($subscription->total_bonus_amount, $subscription->network_id), 'number_of_beneficiaries' => $subscription->number_of_beneficiaries])));
DB::commit();
return $this->successResponse(trans('messages.successful_transaction'));
@ -398,7 +404,7 @@ class InsuranceController extends Controller
/**
* @OA\Post(
* path="/insurances/upload-images",
* path="/insurances/subscriptions/upload-images",
* summary="Uploader les images de l'assurance",
* tags={"Assurances"},
* security={{"api_key":{}}},
@ -445,6 +451,72 @@ class InsuranceController extends Controller
return $this->successResponse($files);
}
public function validateSubscription($id, Request $request)
{
$this->validate($request, [
'agent_id' => 'required|integer|exists:agents,id'
]);
try {
DB::beginTransaction();
$subscription = NhInsurancesSubscription::findOrFail($id);
$subscription->state = InsuranceSubscriptionState::ACCEPTED;
$subscription->save();
NhInsurancesSubscriptionsHistory::create([
'action' => 'EDIT',
'insurance_subscription_id' => $subscription->insurance_subscription_id,
'insurance_subscription_state' => $subscription->state,
'agent_id' => $request->input('agent_id'),
'insurance_subscription' => json_encode($subscription)
]);
Event::dispatch(new InsuranceEvent($subscription, trans('messages.insurance_subscription_accepted'), trans('messages.insurance_subscription_accepted_mail', ['name' => $subscription->user->lastname, 'subscription_id' => $subscription->insurance_subscription_id,
'bonus_amount' => $this->toMoneyWithNetwork($subscription->total_bonus_amount, $subscription->network_id), 'user_code' => $subscription->user->user_code,
'number_of_beneficiaries' => $subscription->number_of_beneficiaries]), trans('messages.insurance_subscription_accepted_notification', ['subscription_id' => $subscription->insurance_subscription_id])));
DB::commit();
return $this->successResponse(trans('messages.successful_transaction'));
} catch (Throwable $e) {
Log::error($e->getMessage() . '\n' . $e->getTraceAsString());
DB::rollBack();
return $this->errorResponse(trans('errors.unexpected_error'), 500);
}
}
public function rejectSubscription($id, Request $request)
{
$this->validate($request, [
'agent_id' => 'required|integer|exists:agents,id',
'reason' => 'required'
]);
try {
DB::beginTransaction();
$subscription = NhInsurancesSubscription::findOrFail($id);
$subscription->reason = $request->input('reason');
$subscription->state = InsuranceSubscriptionState::REJECTED;
$subscription->save();
NhInsurancesSubscriptionsHistory::create([
'action' => 'EDIT',
'insurance_subscription_id' => $subscription->insurance_subscription_id,
'insurance_subscription_state' => $subscription->state,
'agent_id' => $request->input('agent_id'),
'insurance_subscription' => json_encode($subscription)
]);
Event::dispatch(new InsuranceEvent($subscription, trans('messages.insurance_subscription_rejected'), trans('messages.insurance_subscription_rejected_mail', ['name' => $subscription->user->lastname, 'subscription_id' => $subscription->insurance_subscription_id,
'bonus_amount' => $this->toMoneyWithNetwork($subscription->total_bonus_amount, $subscription->network_id), 'reason' => $request->input('reason'),
'number_of_beneficiaries' => $subscription->number_of_beneficiaries]), trans('messages.insurance_subscription_rejected_notification', ['subscription_id' => $subscription->insurance_subscription_id])));
DB::commit();
return $this->successResponse(trans('messages.successful_transaction'));
} catch (Throwable $e) {
Log::error($e->getMessage() . '\n' . $e->getTraceAsString());
DB::rollBack();
return $this->errorResponse(trans('errors.unexpected_error'), 500);
}
}
private function generateSubscriptionID(): string
{
do {

View File

@ -7,6 +7,7 @@ abstract class InsuranceSubscriptionState
const UNDER_VALIDATION = 'UNDER_VALIDATION';
const ACCEPTED = 'ACCEPTED';
const REJECTED = 'REJECTED';
const CURRENT = 'CURRENT';
const UNDER_STOPPING = 'UNDER_STOPPING';
const STOPPED = 'STOPPED';
}

View File

@ -3,7 +3,7 @@
namespace App\Listeners;
use App\Events\ExampleEvent;
use App\Events\InsuranceSubscribed;
use App\Events\InsuranceEvent;
use App\Traits\Helper;
use GuzzleHttp\Client;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -28,10 +28,10 @@ class NotifyUser
/**
* Handle the event.
*
* @param InsuranceSubscribed $event
* @param InsuranceEvent $event
* @return void
*/
public function handle(InsuranceSubscribed $event)
public function handle(InsuranceEvent $event)
{
//
$subscription = $event->subscription;
@ -44,12 +44,25 @@ class NotifyUser
'Authorization' => config('services.notification_service.key'),
];
$body = new \stdClass();
$body->title = trans('messages.insurance_subscription');
$body->message = trans('messages.insurance_subscription_mail', ['name' => $user->lastname, 'subscription_id' => $subscription->insurance_subscription_id,
'bonus_amount' => $this->toMoneyWithNetwork($subscription->total_bonus_amount, $subscription->network_id), 'number_of_beneficiaries' => $subscription->number_of_beneficiaries]);
$body->title = $event->mailTitle;
$body->message = $event->mailMessage;
$body->email = $user->email;
$response = $client->request('POST', '/send-mail', ['json' => $body, 'headers' => $headers]);
$client->request('POST', '/send-mail', ['json' => $body, 'headers' => $headers]);
if (isset($event->notification)) {
$body = new \stdClass();
$body->user_code = $user->user_code;
$body->message = $event->notification;
$body->data = [];
try {
$body->date = $this->getCurrentTimeByCountryCode($user->network->country->code_country);
} catch (Throwable $t) {
Log::error($t->getMessage());
$body->date = date('Y-m-d H:i:s');
}
$client->request('POST', '/onesignal/pushToUser', ['json' => $body, 'headers' => $headers]);
}
} catch (Throwable $t) {
Log::error('-------- User notification not sent-----------');
Log::error($t->getMessage() . '\n' . $t->getTraceAsString());

View File

@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\Model;
* @property int $id
* @property string $action
* @property string $insurance_subscription_id
* @property int|null $nh_validating_doctor_id
* @property int|null $agent_id
* @property string $insurance_subscription_state
* @property string $insurance_subscription
* @property Carbon $created_at
@ -28,13 +28,13 @@ class NhInsurancesSubscriptionsHistory extends Model
protected $table = 'nh_insurances_subscriptions_history';
protected $casts = [
'nh_validating_doctor_id' => 'int'
'agent_id' => 'int'
];
protected $fillable = [
'action',
'insurance_subscription_id',
'nh_validating_doctor_id',
'agent_id',
'insurance_subscription_state',
'insurance_subscription'
];

View File

@ -2,7 +2,7 @@
namespace App\Providers;
use App\Events\InsuranceSubscribed;
use App\Events\InsuranceEvent;
use App\Listeners\NotifyUser;
use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider;
@ -17,7 +17,7 @@ class EventServiceProvider extends ServiceProvider
\App\Events\ExampleEvent::class => [
\App\Listeners\ExampleListener::class,
],
InsuranceSubscribed::class => [
InsuranceEvent::class => [
NotifyUser::class
]
];

View File

@ -4,6 +4,7 @@
namespace App\Traits;
use App\Models\CountriesCurrency;
use App\Models\Country;
use Brick\Money\Context\AutoContext;
use Brick\Money\Money;
@ -101,4 +102,23 @@ trait Helper
// Return compressed image
return $destination;
}
// Obtenir l'heure en fonction du pays de l'utilisateur
public function getCurrentTime($id_country)
{
$country = CountriesCurrency::find($id_country);
$country_code = isset($country) ? $country->code_country : 'GA';
$timezone = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $country_code);
$date = (sizeof($timezone) > 0) ? new \DateTime('now', new \DateTimeZone($timezone[0])) : new \DateTime();
return $date->format('Y-m-d H:i:s');
}
// Obtenir l'heure en fonction du code du pays de l'utilisateur
public function getCurrentTimeByCountryCode($country_code = 'GA')
{
$timezone = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $country_code);
$date = (sizeof($timezone) > 0) ? new \DateTime('now', new \DateTimeZone($timezone[0])) : new \DateTime();
return $date->format('Y-m-d H:i:s');
}
}

View File

@ -62,6 +62,7 @@ $app->singleton(
$app->configure('app');
$app->configure('swagger-lume');
$app->configure('services');
$app->configure('sentry');
/*
|--------------------------------------------------------------------------
@ -100,7 +101,8 @@ $app->configure('services');
$app->register(App\Providers\EventServiceProvider::class);
$app->register(\SwaggerLume\ServiceProvider::class);
$app->register(\MigrationsGenerator\MigrationsGeneratorServiceProvider::class);
$app->register('Sentry\Laravel\ServiceProvider');
$app->register('Sentry\Laravel\Tracing\ServiceProvider');
/*
|--------------------------------------------------------------------------

View File

@ -6,13 +6,14 @@
"type": "project",
"require": {
"php": "^7.3|^8.0",
"ext-gd": "*",
"ext-json": "*",
"brick/money": "^0.5.2",
"darkaonline/swagger-lume": "^8.0",
"guzzlehttp/guzzle": "^7.3",
"kitloong/laravel-migrations-generator": "^5.0",
"laravel/lumen-framework": "^8.0",
"ext-json": "*",
"ext-gd": "*"
"sentry/sentry-laravel": "^2.9"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

57
config/sentry.php Normal file
View File

@ -0,0 +1,57 @@
<?php
return [
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
// capture release as git sha
// 'release' => trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')),
// When left empty or `null` the Laravel environment will be used
'environment' => env('SENTRY_ENVIRONMENT'),
'breadcrumbs' => [
// Capture Laravel logs in breadcrumbs
'logs' => true,
// Capture SQL queries in breadcrumbs
'sql_queries' => true,
// Capture bindings on SQL queries logged in breadcrumbs
'sql_bindings' => true,
// Capture queue job information in breadcrumbs
'queue_info' => true,
// Capture command information in breadcrumbs
'command_info' => true,
],
'tracing' => [
// Trace queue jobs as their own transactions
'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false),
// Capture queue jobs as spans when executed on the sync driver
'queue_jobs' => true,
// Capture SQL queries as spans
'sql_queries' => true,
// Try to find out where the SQL query originated from and add it to the query spans
'sql_origin' => true,
// Capture views as spans
'views' => true,
// Indicates if the tracing integrations supplied by Sentry should be loaded
'default_integrations' => true,
],
// @see: https://docs.sentry.io/platforms/php/configuration/options/#send-default-pii
'send_default_pii' => false,
'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)),
'controllers_base_namespace' => env('SENTRY_CONTROLLERS_BASE_NAMESPACE', 'App\\Http\\Controllers'),
];

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDueDatesInNhInsurancesSubscriptionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('nh_insurances_subscriptions', function (Blueprint $table) {
$table->dateTime('start_at')->nullable()->after('state')->comment("Date de debut de l'assurance");
$table->dateTime('end_at')->nullable()->after('start_at')->comment("Date d'echeance");
$table->text('reason')->nullable()->after('end_at')->comment("Raison de traitement de la subscription");
DB::statement("ALTER TABLE nh_insurances_subscriptions MODIFY state
ENUM('UNDER_VALIDATION', 'ACCEPTED', 'REJECTED', 'UNDER_STOPPING', 'STOPPED', 'CURRENT') DEFAULT 'UNDER_VALIDATION' NOT NULL;");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('nh_insurances_subscriptions', function (Blueprint $table) {
$table->dropColumn(['start_at', 'end_at', 'reason']);
});
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateActorInNhInsurancesSubscriptionsHistoryTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('nh_insurances_subscriptions_history', function (Blueprint $table) {
$table->dropColumn('nh_validating_doctor_id');
$table->integer('agent_id')->nullable()->after('insurance_subscription_id')
->comment("ID de l'agent acteur de l'operation");
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('nh_insurances_subscriptions_history', function (Blueprint $table) {
$table->integer('nh_validating_doctor_id')->nullable();
$table->dropColumn('agent_id');
});
}
}

View File

@ -25,5 +25,5 @@ return [
'nano_health_not_activated' => "Nano health is not activated for this network",
'number_of_beneficiaries_exceeded' => 'The number of beneficiaries is greater than the authorized limit',
'incorrect_selected_amount' => 'The amount selected is incorrect',
'minimal_age_required' => "You do not have the minimum age required to subscribe to this insurance"
'minimal_age_required' => "Your age is above the age limit required to subscribe to this insurance"
];

View File

@ -24,5 +24,30 @@ Your subscription request is being validated.
- ID: :subscription_id
- Bonus amount: :bonus_amount
- Number of beneficiaries: :number_of_beneficiaries
"
",
'insurance_subscription_accepted' => "Insurance subscription accepted",
'insurance_subscription_accepted_mail' => "Mr/Mrs :name ,
Your application has been accepted.
Application information :
- ID: :subscription_id
- Policyholder number: :user_code
- Amount to be paid: :bonus_amount
- Number of beneficiaries : :number_of_beneficiaries
Make sure your wallet has enough credit to activate your insurance by payment.
",
'insurance_subscription_accepted_notification' => "Your :subscription_id application has been accepted.
Please ensure that your wallet has sufficient credit to activate your insurance",
'insurance_subscription_rejected' => "Insurance subscription rejected",
'insurance_subscription_rejected_mail' => "Mr/Mrs :name ,
Your application has been rejected.
Application information :
- ID: :subscription_id
- Premium amount: :bonus_amount
- Number of beneficiaries : :number_of_beneficiaries
Reason for rejection: :reason ",
'insurance_subscription_rejected_notification' => "Your :subscription_id application has been rejected"
];

View File

@ -25,5 +25,5 @@ return [
'nano_health_not_activated' => "Le nano santé n'est pas activé pour ce réseau",
'number_of_beneficiaries_exceeded' => "Le nombre d'ayant droit est superieur à la limite autorisée",
'incorrect_selected_amount' => 'Le montant choisi est incorrect',
'minimal_age_required' => "Vous n'avez pas l'âge minimum requis pour souscrire à cette assurance"
'minimal_age_required' => "Votre âge est supérieur à l'âge limite requis pour souscrire à cette assurance"
];

View File

@ -24,5 +24,30 @@ Votre demande de souscription est en cours de validation.
- ID : :subscription_id
- Montant de la prime : :bonus_amount
- Nombre d'ayants droit : :number_of_beneficiaries
"
",
'insurance_subscription_accepted' => "Souscription à l'assurance acceptée",
'insurance_subscription_accepted_mail' => "M/Mme :name ,
Votre demande de souscription a été acceptée.
Informations de la demande :
- ID : :subscription_id
- Numéro d'assuré : :user_code
- Montant à payer : :bonus_amount
- Nombre d'ayants droit : :number_of_beneficiaries
Assurer-vous que votre wallet a le crédit suffisant pour activer votre assurance par le payement.
",
'insurance_subscription_accepted_notification' => "Votre demande de souscription :subscription_id a été acceptée.
Assurer-vous que votre wallet a le crédit suffisant pour activer votre assurance",
'insurance_subscription_rejected' => "Souscription à l'assurance rejetée",
'insurance_subscription_rejected_mail' => "M/Mme :name ,
Votre demande de souscription a été rejetée.
Informations de la demande :
- ID : :subscription_id
- Montant de la prime : :bonus_amount
- Nombre d'ayants droit : :number_of_beneficiaries
Motif du rejet : :reason ",
'insurance_subscription_rejected_notification' => "Votre demande de souscription :subscription_id a été rejetée.",
];

View File

@ -15,9 +15,13 @@
$router->group(['prefix' => '', 'middleware' => 'auth'], function () use ($router) {
// Insurances routes
$router->group(['prefix' => '/insurances'], function () use ($router) {
$router->get('countries/{countryId}', 'InsuranceController@getInsurancesByCountryId');
$router->post('bonus-amount', 'InsuranceController@calculateBonusAmount');
$router->post('subscribe', 'InsuranceController@subscribe');
$router->post('upload-images', 'InsuranceController@uploadImages');
$router->get('', 'InsuranceController@getInsurances');
$router->group(['prefix' => '/subscriptions'], function () use ($router) {
$router->post('bonus-amount', 'InsuranceController@calculateBonusAmount');
$router->post('upload-images', 'InsuranceController@uploadImages');
$router->post('', 'InsuranceController@subscribe');
$router->put('{id}/validate', 'InsuranceController@validateSubscription');
$router->put('{id}/reject', 'InsuranceController@rejectSubscription');
});
});
});