diff --git a/app/Events/InsuranceSubscribed.php b/app/Events/InsuranceSubscribed.php new file mode 100644 index 0000000..8f075d0 --- /dev/null +++ b/app/Events/InsuranceSubscribed.php @@ -0,0 +1,21 @@ +subscription = $subscription; + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php old mode 100644 new mode 100755 index b375c61..6c6f4f2 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,8 +2,15 @@ namespace App\Exceptions; +use App\Traits\ApiResponser; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\ServerException; use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Database\QueryException; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; use Laravel\Lumen\Exceptions\Handler as ExceptionHandler; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -11,6 +18,8 @@ use Throwable; class Handler extends ExceptionHandler { + use ApiResponser; + /** * A list of the exception types that should not be reported. * @@ -28,7 +37,7 @@ class Handler extends ExceptionHandler * * This is a great spot to send exceptions to Sentry, Bugsnag, etc. * - * @param \Throwable $exception + * @param \Throwable $exception * @return void * * @throws \Exception @@ -41,14 +50,93 @@ class Handler extends ExceptionHandler /** * Render an exception into an HTTP response. * - * @param \Illuminate\Http\Request $request - * @param \Throwable $exception - * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse + * @param \Illuminate\Http\Request $request + * @param \Throwable $exception + * @return \Symfony\Component\HttpFoundation\Response * * @throws \Throwable */ public function render($request, Throwable $exception) { - return parent::render($request, $exception); +// return parent::render($request, $exception); + if ($exception instanceof HttpException) { + $code = $exception->getStatusCode(); + $message = Response::$statusTexts[$code]; + + return $this->errorResponse($message, $code); + } + + if ($exception instanceof ModelNotFoundException) { + $model = strtolower(class_basename($exception->getModel())); + + return $this->errorResponse(trans('errors.model_not_found', ['model' => $model]), + Response::HTTP_NOT_FOUND); + } + + if ($exception instanceof AuthorizationException) { + return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED); + } + + if ($exception instanceof ValidationException) { + $errors = $exception->validator->errors()->getMessages(); + $message = ''; + foreach ($errors as $key => $val) { + foreach ($val as $validation) { + $message .= trans('errors.validation_error', ['field' => $key, 'validation' => $validation]); + } + $message .= "\n"; + } + return $this->errorResponse($message, Response::HTTP_UNPROCESSABLE_ENTITY); + } + + + if ($exception instanceof AuthenticationException) { + return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED); + } + + if ($exception instanceof QueryException) { + return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); + } + + if ($exception instanceof ServerException) { + return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); + } + + if ($exception instanceof \ErrorException) { + return $this->errorResponse($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); + } + + if ($exception instanceof ClientException) { + $message = $exception->getResponse()->getBody()->getContents(); + $error = json_decode($message); + $code = $exception->getCode(); + + if ($error) { + if (isset($error->message)) { + $message = json_decode($error->message); + if (isset($message->errorMessage)) + return $this->errorResponse($message->errorMessage, $code); + return $this->errorResponse($error->message, $code); + } + if (isset($error->error)) { + try { + $message = json_decode($error->error); + if (isset($message->message)) + return $this->errorResponse($message->message, $code); + } catch (\Exception $e) { + Log::error($e->getMessage()); + } + return $this->errorResponse(json_encode($error->error), $code); + } + } + return $this->errorResponse($message, $code); + } + + if (env('APP_DEBUG', false)) { + return parent::render($request, $exception); + } + + return $this->errorResponse(trans('errors.unexpected_error'), + Response::HTTP_INTERNAL_SERVER_ERROR); } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 0ccb918..320b3d9 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,9 +2,23 @@ namespace App\Http\Controllers; +use App\Traits\ApiResponser; use Laravel\Lumen\Routing\Controller as BaseController; +/** + * @OA\Info( + * title="Nano Service API", + * version="1.0.0", + * @OA\Contact( + * email="administrateur@ilink-app.com", + * url = "https://ilink-app.com/", + * name="Developer Team" + * ) + * ) + * + */ class Controller extends BaseController { // + use ApiResponser; } diff --git a/app/Http/Controllers/ExampleController.php b/app/Http/Controllers/ExampleController.php deleted file mode 100644 index aab066e..0000000 --- a/app/Http/Controllers/ExampleController.php +++ /dev/null @@ -1,18 +0,0 @@ - $countryId]); + + 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", + ['nhc_id' => $insurance->nhc_id]); + + foreach ($months_prices as $mp) { + $mp->min_amount = $this->toMoneyWithCurrencyCode($mp->min_amount, $country->currency_code ?? 'XAF'); + } + $insurance->months_prices = $months_prices; + unset($insurance->nhc_id); + } + + return $this->successResponse($insurances); + } + + /** + * @OA\Post( + * path="/insurances/bonus-amount", + * summary="Calculer le montant de la prime", + * tags={"Assurances"}, + * security={{"api_key":{}}}, + * @OA\RequestBody( + * description="Corps de la requete", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property(property="network_id", + * type="integer", + * example = 250, + * description="ID du reseau de l'assureur" + * ), + * @OA\Property(property="month_price_id", + * type="integer", + * example=2, + * description="ID de la grille de prix choisit lors de la souscription" + * ), + * @OA\Property( + * property="beneficiaries", + * description="Listes de quelques infos sur les beneficiaires ou ayants droit", + * example = {{"birthdate":"1998-10-05","affiliation":"CHILD"}} + * ), + * ), + * example = {"network_id":250,"month_price_id":3,"beneficiaries":{{"birthdate":"1998-10-05","affiliation":"CHILD"}}} + * ) + * ), + * @OA\Response( + * response=200, + * description="OK", + * @OA\JsonContent( + * ref="#/components/schemas/ApiResponse", + * example = {"status":200,"response":{"bonus_amount":75000,"bonus_amount_formatted":"75 000FCFA"},"error":null}, + * ) + * ) + * ) + */ + public function calculateBonusAmount(Request $request) + { + $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.*.birthdate' => 'required|date_format:Y-m-d|before:today', + 'beneficiaries.*.affiliation' => 'required|in:CHILD,SPOUSE' + ]); + + $networkConfig = NhNetworksConfig::where('network_id', $request->input('network_id'))->first(); + if (!isset($networkConfig) || $networkConfig->configWallet->type != 'ilink_sante') + return $this->errorResponse(trans('errors.nano_health_not_activated')); + + $monthPrice = $networkConfig->monthsPricesGrid()->where('id', $request->input('month_price_id'))->first(); + if (!isset($monthPrice)) + return $this->errorResponse(trans('errors.incorrect_selected_amount')); + + $bonus = 0; + foreach ($request->input('beneficiaries') as $b) { + $age = date_diff(date_create($b['birthdate']), date_create('now'))->y; + $levels = $networkConfig->yearsPricesGrid->filter(function ($level) use ($age) { + return $level->min_age <= $age && $level->max_age >= $age; + }); + + foreach ($levels as $level) { + $bonus += $level->markup_percentage * $monthPrice->min_amount / 100; + } + } + + return $this->successResponse([ + 'bonus_amount' => $bonus, + 'bonus_amount_formatted' => $this->toMoneyWithNetwork($bonus, $request->input('network_id')) + ]); + } + + + /** + * @OA\Post( + * path="/insurances/subscribe", + * summary="Souscrire à une assurance", + * tags={"Assurances"}, + * security={{"api_key":{}}}, + * @OA\RequestBody( + * description="Corps de la requete", + * required=true, + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema(ref="#/components/schemas/subscribe_incurance"), + * example = {"network_id":250,"user_id":20,"month_price_id":3,"bonus_amount":20000,"beneficiaries":{{"lastname":"Djery","firstname":"DI","gender":"M","birthdate":"2001-10-05", + * "affiliation":"CHILD","birthdate_proof":"CERTIFIED_COPY","birthdate_proof_doc":"birth.jpg","justice_doc":"just.png","marriage_certificate_doc":"mariage.png", + * "id_document_type":"CNI","id_document_front":"cni_front.jpg","id_document_back":"cni_front.jpg"}}} + * ) + * ), + * @OA\Response( + * response=200, + * description="OK", + * @OA\JsonContent( + * ref="#/components/schemas/ApiResponse", + * example = {"status":200,"response":"Transaction réussie","error":null} + * ) + * ) + * ) + */ + public function subscribe(Request $request) + { + /** + * @OA\Schema( + * schema="subscribe_incurance", + * title = "Souscription à une assurance", + * required={"network_id", "user_id" , "month_price_id","bonus_amount" , "beneficiaries"}, + * @OA\Property(property="network_id", + * type="integer", + * example = 250, + * description="ID du reseau de l'assureur" + * ), + * @OA\Property(property="user_id", + * type="integer", + * example=300, + * description="ID l'utilisateur identifié" + * ), + * @OA\Property(property="month_price_id", + * type="integer", + * example=2, + * description="ID de la grille de prix choisit lors de la souscription" + * ), + * @OA\Property(property="bonus_amount", + * type="double", + * example=30000, + * description="Montant de la prime" + * ), + * @OA\Property(property="beneficiaries", + * type="array", + * description="Listes des beneficiaires ou ayants droit", + * @OA\Items(ref="#/components/schemas/beneficiaries") + * ) + * ) + * + * @OA\Schema( + * schema="beneficiaries", + * title = "Beneficiaires ou ayants droit", + * required={"lastname","gender", "birthdate", "affiliation" }, + * @OA\Property(property="lastname", + * type="string", + * example = "Djery", + * description="Noms" + * ), + * @OA\Property(property="firstname", + * type="string", + * example="DI", + * description="Prenoms" + * ), + * @OA\Property(property="gender", + * type="string", + * enum = {"M" ,"F"}, + * example= "M", + * description="Sexe" + * ), + * @OA\Property(property="birthdate", + * type="string", + * example= "2001-10-05", + * description="Date de naissance" + * ), + * @OA\Property(property="affiliation", + * type="string", + * enum = {"CHILD" ,"SPOUSE"}, + * example= "CHILD", + * description="Affiliation" + * ), + * @OA\Property(property="birthdate_proof", + * type="string", + * enum = {"CERTIFIED_COPY" ,"CERTIFICATE"}, + * example="CERTIFIED_COPY", + * description="Copie légalisée acte de naissance ou certificat de naissance" + * ), + * @OA\Property(property="birthdate_proof_doc", + * type="string", + * example="birthdate_proof_doc.jpg", + * description="Copie légalisée acte de naissance ou certificat de naissance" + * ), + * @OA\Property(property="justice_doc", + * type="string", + * example="justice_doc.jpg", + * description="Une page document de justice si enfant adopté ou sous tutelle" + * ), + * @OA\Property(property="marriage_certificate_doc", + * type="string", + * example="marriage_certificate_doc.jpg", + * description="Une page de l'acte de mariage" + * ), + * @OA\Property(property="id_document_type", + * type="string", + * example="CNI", + * description="Type de piece d'identité cni , carte sejour, permis" + * ), + * @OA\Property(property="id_document_front", + * type="string", + * example="id_document_front.jpg", + * description="Pièce identité recto" + * ), + * @OA\Property(property="id_document_back", + * type="string", + * example="id_document_back.jpg", + * description="Pièce identité verso" + * ), + * ) + */ + $this->validate($request, [ + 'network_id' => 'required|integer|exists:networks,id', + 'user_id' => 'required|integer|exists:users,id', + 'month_price_id' => 'required|integer|exists:nh_months_prices_grid,id', + 'bonus_amount' => 'required|numeric|min:0', + 'beneficiaries' => 'required|array', + 'beneficiaries.*.lastname' => 'required|string', + 'beneficiaries.*.gender' => 'required|in:M,F', + 'beneficiaries.*.birthdate' => 'required|date_format:Y-m-d|before:today', + 'beneficiaries.*.affiliation' => 'required|in:CHILD,SPOUSE', + 'beneficiaries.*.birthdate_proof' => 'required_if:beneficiaries.*.affiliation,CHILD|in:CERTIFIED_COPY,CERTIFICATE', + 'beneficiaries.*.birthdate_proof_doc' => 'required_if:beneficiaries.*.affiliation,CHILD|string', + 'beneficiaries.*.justice_doc' => 'required_if:beneficiaries.*.affiliation,CHILD|string', + 'beneficiaries.*.marriage_certificate_doc' => 'required_if:beneficiaries.*.affiliation,SPOUSE|string', + 'beneficiaries.*.id_document_type' => 'required_if:beneficiaries.*.affiliation,SPOUSE|string', + 'beneficiaries.*.id_document_front' => 'required_if:beneficiaries.*.affiliation,SPOUSE|string', + 'beneficiaries.*.id_document_back' => 'required_if:beneficiaries.*.affiliation,SPOUSE|string', + ]); + + $identification = Identification::where('id_user', $request->input('user_id'))->first(); + if (!isset($identification)) + return $this->errorResponse(trans('errors.user_identification_required')); + + $networkConfig = NhNetworksConfig::where('network_id', $request->input('network_id'))->first(); + if (!isset($networkConfig) || $networkConfig->configWallet->type != 'ilink_sante') + return $this->errorResponse(trans('errors.nano_health_not_activated')); + + $networkConfig = NhNetworksConfig::where('network_id', $request->input('network_id'))->first(); + if (sizeof($request->input('beneficiaries')) > $networkConfig->max_number_of_beneficiaries) + return $this->errorResponse(trans('errors.number_of_beneficiaries_exceeded')); + + $monthPrice = $networkConfig->monthsPricesGrid()->where('id', $request->input('month_price_id'))->first(); + if (!isset($monthPrice)) + return $this->errorResponse(trans('errors.incorrect_selected_amount')); + + try { + DB::beginTransaction(); + $subscription = new NhInsurancesSubscription($request->all()); + $subscription->number_of_beneficiaries = sizeof($request->input('beneficiaries')); + $subscription->insurance_subscription_id = $this->generateSubscriptionID(); + $subscription->number_of_months = $monthPrice->number_of_months; + $subscription->amount = $monthPrice->min_amount; + $subscription->state = InsuranceSubscriptionState::UNDER_VALIDATION; + $subscription->save(); + + foreach ($request->input('beneficiaries') as $b) { + $beneficiary = new NhInsurancesHavingRight($b); + $beneficiary->insurance_subscription_id = $subscription->insurance_subscription_id; + $beneficiary->save(); + } + + NhInsurancesSubscriptionsHistory::create([ + 'action' => 'ADD', + 'insurance_subscription_id' => $subscription->insurance_subscription_id, + 'insurance_subscription_state' => $subscription->state, + 'insurance_subscription' => json_encode($subscription) + ]); + + + Event::dispatch(new InsuranceSubscribed($subscription)); + + 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 { + $code = $this->generateTransactionCode(); + $codeCorrect = NhInsurancesSubscription::where('insurance_subscription_id', $code)->count() < 0; + } while ($codeCorrect); + return $code; + } +} diff --git a/app/Http/Middleware/AuthenticateAccess.php b/app/Http/Middleware/AuthenticateAccess.php index 546c18a..e2a8b08 100755 --- a/app/Http/Middleware/AuthenticateAccess.php +++ b/app/Http/Middleware/AuthenticateAccess.php @@ -16,7 +16,7 @@ class AuthenticateAccess */ public function handle($request, Closure $next) { - $validKeys = explode(',' ,env('ACCEPTED_KEYS')); + $validKeys = explode(',', config('services.accepted_keys')); if(in_array($request->header('Authorization') , $validKeys)){ return $next($request); } diff --git a/app/InsuranceSubscriptionState.php b/app/InsuranceSubscriptionState.php new file mode 100644 index 0000000..dcc4664 --- /dev/null +++ b/app/InsuranceSubscriptionState.php @@ -0,0 +1,12 @@ +subscription; + $user = $subscription->user; + try { + $client = new Client([ + 'base_uri' => config('services.notification_service.base_uri'), + ]); + $headers = [ + '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->bonus_amount, $subscription->network_id), 'number_of_beneficiaries' => $subscription->number_of_beneficiaries]); + $body->email = $user->email; + + $response = $client->request('POST', '/send-mail', ['json' => $body, 'headers' => $headers]); + } catch (Throwable $t) { + Log::error('-------- User notification not sent-----------'); + Log::error($t->getMessage() . '\n' . $t->getTraceAsString()); + } + } +} diff --git a/app/Models/ConfigWallet.php b/app/Models/ConfigWallet.php new file mode 100755 index 0000000..127b635 --- /dev/null +++ b/app/Models/ConfigWallet.php @@ -0,0 +1,160 @@ + 'float', + 'taux_com_client_depot' => 'float', + 'taux_com_ag_retrait' => 'float', + 'taux_com_ag_depot' => 'float', + 'taux_com_sup_depot' => 'float', + 'taux_com_sup_retrait' => 'float', + 'part_banque_retrait' => 'float', + 'part_banque_depot' => 'float', + 'frais_min_banque_depot' => 'float', + 'id_network' => 'int', + 'taux_com_user_wallet_carte' => 'float', + 'taux_com_user_carte_wallet' => 'float', + 'taux_com_user_carte_cash' => 'float', + 'taux_com_wallet_ag_envoi_cash_carte' => 'float', + 'taux_com_wallet_ag_carte_cash' => 'float', + 'taux_com_wallet_ag_depot_carte' => 'float', + 'taux_com_ag_envoi_cash' => 'float', + 'taux_com_sup_envoi_cash' => 'float', + 'taux_com_hyp_envoi_cash' => 'float', + 'taux_com_ag_retrait_cash' => 'float', + 'taux_com_sup_retrait_cash' => 'float', + 'taux_com_hyp_retrait_cash' => 'float', + 'taux_com_ag_depot_cash_carte' => 'float', + 'taux_com_sup_depot_cash_carte' => 'float', + 'taux_com_hyp_depot_cash_carte' => 'float', + 'taux_com_banque_depot_cash_carte' => 'float', + 'taux_com_ag_retrait_carte_cash' => 'float', + 'taux_com_sup_retrait_carte_cash' => 'float', + 'taux_com_hyp_retrait_carte_cash' => 'float', + 'taux_com_banque_retrait_carte_cash' => 'float', + 'taux_com_hyp_retrait_carte_cash_ilink' => 'float', + 'taux_com_banque_retrait_carte_cash_ilink' => 'float', + 'taux_com_hyp_envoi_wallet_carte_ilink' => 'float', + 'taux_com_banque_envoi_wallet_carte_ilink' => 'float', + 'has_nano_credit' => 'int', + 'limite_credit_min' => 'float', + 'limite_credit_max' => 'float', + 'taux_com_ag_nano_credit' => 'float', + 'taux_com_sup_nano_credit' => 'float', + 'taux_com_hyp_nano_credit' => 'float', + ]; + + protected $fillable = [ + 'taux_com_client_retrait', + 'taux_com_client_depot', + 'taux_com_ag_retrait', + 'taux_com_ag_depot', + 'taux_com_sup_depot', + 'taux_com_sup_retrait', + 'part_banque_retrait', + 'part_banque_depot', + 'frais_min_banque_depot', + 'id_network', + 'taux_com_user_wallet_carte', + 'taux_com_user_carte_wallet', + 'taux_com_user_carte_cash', + 'taux_com_wallet_ag_envoi_cash_carte', + 'taux_com_wallet_ag_carte_cash', + 'taux_com_wallet_ag_depot_carte', + 'taux_com_ag_envoi_cash', + 'taux_com_sup_envoi_cash', + 'taux_com_hyp_envoi_cash', + 'taux_com_ag_retrait_cash', + 'taux_com_sup_retrait_cash', + 'taux_com_hyp_retrait_cash', + 'taux_com_ag_depot_cash_carte', + 'taux_com_sup_depot_cash_carte', + 'taux_com_hyp_depot_cash_carte', + 'taux_com_banque_depot_cash_carte', + 'taux_com_ag_retrait_carte_cash', + 'taux_com_sup_retrait_carte_cash', + 'taux_com_hyp_retrait_carte_cash', + 'taux_com_banque_retrait_carte_cash', + 'taux_com_hyp_retrait_carte_cash_ilink', + 'taux_com_banque_retrait_carte_cash_ilink', + 'taux_com_hyp_envoi_wallet_carte_ilink', + 'taux_com_banque_envoi_wallet_carte_ilink', + 'type', + 'has_nano_credit', + 'limite_credit_min', + 'limite_credit_max', + 'taux_com_ag_nano_credit', + 'taux_com_sup_nano_credit', + 'taux_com_hyp_nano_credit' + ]; + + public function network() + { + return $this->belongsTo(Network::class, 'id_network'); + } +} diff --git a/app/Models/CountriesCurrency.php b/app/Models/CountriesCurrency.php new file mode 100644 index 0000000..4741c30 --- /dev/null +++ b/app/Models/CountriesCurrency.php @@ -0,0 +1,51 @@ + 'int', + 'longitude' => 'float', + 'latitude' => 'float', + 'idCurrency' => 'int' + ]; + + protected $fillable = [ + 'id', + 'code_dial', + 'name', + 'code_country', + 'longitude', + 'latitude', + 'idCurrency', + 'currency_code', + 'currency_name_en', + 'currency_name_fr' + ]; +} diff --git a/app/Models/Identification.php b/app/Models/Identification.php new file mode 100644 index 0000000..c9300e5 --- /dev/null +++ b/app/Models/Identification.php @@ -0,0 +1,103 @@ + 'int', + 'status' => 'int', + 'idNetwork' => 'int', + 'country_id' => 'int' + ]; + + protected $dates = [ + 'birth_date', + 'expiry_date_document', + 'createdAt' + ]; + + protected $fillable = [ + 'firstname', + 'lastname', + 'birth_date', + 'town', + 'country', + 'identity_document', + 'id_identity_document', + 'expiry_date_document', + 'id_user', + 'status', + 'createdAt', + 'user_image', + 'document_image_front', + 'document_image_back', + 'idNetwork', + 'country_id' + ]; + + public function country() + { + return $this->belongsTo(Country::class); + } + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function network() + { + return $this->belongsTo(Network::class, 'idNetwork'); + } + + public function rules() + { + return [ + 'lastname' => 'required', + 'birth_date' => 'required|date|before_or_equal:today', + 'town' => 'required', + 'country' => 'required', + 'identity_document' => 'required', + 'id_identity_document' => 'required', + 'expiry_date_document' => 'required|date|after_or_equal:today', + 'id_user' => 'required_without_all:phone_number|integer|min:0|not_in:0', + 'phone_number' => 'required_without_all:id_user' + ]; + } +} diff --git a/app/Models/Network.php b/app/Models/Network.php new file mode 100755 index 0000000..b638161 --- /dev/null +++ b/app/Models/Network.php @@ -0,0 +1,56 @@ + 'int', + 'id_networkAgent' => 'int', + 'status' => 'int' + ]; + + protected $fillable = [ + 'country_id', + 'name', + 'id_networkAgent', + 'status' + ]; + + public function config_wallets() + { + return $this->hasMany(ConfigWallet::class, 'id_network'); + } + + public function identifications() + { + return $this->hasMany(Identification::class, 'idNetwork'); + } + + public function country() + { + return $this->belongsTo(CountriesCurrency::class, 'country_id'); + } +} diff --git a/app/Models/NhAct.php b/app/Models/NhAct.php index 7e4eee0..3e35c7a 100644 --- a/app/Models/NhAct.php +++ b/app/Models/NhAct.php @@ -17,8 +17,8 @@ use Illuminate\Database\Eloquent\Model; * @property string $name * @property string $billing_type * @property string $authorization_type - * @property Carbon|null $created_at - * @property Carbon|null $updated_at + * @property Carbon $created_at + * @property Carbon $updated_at * * @package App\Models */ diff --git a/app/Models/NhInsurancesHavingRight.php b/app/Models/NhInsurancesHavingRight.php new file mode 100644 index 0000000..e833f11 --- /dev/null +++ b/app/Models/NhInsurancesHavingRight.php @@ -0,0 +1,61 @@ + 'int', + 'user_id' => 'int', + 'number_of_months' => 'int', + 'amount' => 'int', + 'number_of_beneficiaries' => 'int', + 'bonus_amount' => 'float' + ]; + + protected $fillable = [ + 'insurance_subscription_id', + 'network_id', + 'user_id', + 'number_of_months', + 'amount', + 'number_of_beneficiaries', + 'bonus_amount', + 'state' + ]; + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + +} diff --git a/app/Models/NhInsurancesSubscriptionsHistory.php b/app/Models/NhInsurancesSubscriptionsHistory.php new file mode 100644 index 0000000..f37111f --- /dev/null +++ b/app/Models/NhInsurancesSubscriptionsHistory.php @@ -0,0 +1,41 @@ + 'int' + ]; + + protected $fillable = [ + 'action', + 'insurance_subscription_id', + 'nh_validating_doctor_id', + 'insurance_subscription_state', + 'insurance_subscription' + ]; +} diff --git a/app/Models/NhMonthsPricesGrid.php b/app/Models/NhMonthsPricesGrid.php index 6c870b1..6d6d5d7 100644 --- a/app/Models/NhMonthsPricesGrid.php +++ b/app/Models/NhMonthsPricesGrid.php @@ -16,8 +16,8 @@ use Illuminate\Database\Eloquent\Model; * @property int $nh_network_config_id * @property float $number_of_months * @property float $min_amount - * @property Carbon|null $created_at - * @property Carbon|null $updated_at + * @property Carbon $created_at + * @property Carbon $updated_at * * @package App\Models */ diff --git a/app/Models/NhNetworksConfig.php b/app/Models/NhNetworksConfig.php index 23c60c6..c21e719 100644 --- a/app/Models/NhNetworksConfig.php +++ b/app/Models/NhNetworksConfig.php @@ -24,8 +24,8 @@ use Illuminate\Database\Eloquent\Model; * @property float $long_term_affection_percentage_insured * @property float $exoneration_percentage_insurer * @property float $exoneration_percentage_insured - * @property Carbon|null $created_at - * @property Carbon|null $updated_at + * @property Carbon $created_at + * @property Carbon $updated_at * * @package App\Models */ @@ -33,7 +33,7 @@ class NhNetworksConfig extends Model { protected $table = 'nh_networks_configs'; - protected $casts = [ + protected $casts = [ 'network_id' => 'int', 'max_number_of_beneficiaries' => 'float', 'age_limit_of_child_beneficiary' => 'float', @@ -46,7 +46,7 @@ class NhNetworksConfig extends Model 'exoneration_percentage_insured' => 'float' ]; - protected $fillable = [ + protected $fillable = [ 'network_id', 'provider_billing_period', 'max_number_of_beneficiaries', @@ -59,4 +59,19 @@ class NhNetworksConfig extends Model 'exoneration_percentage_insurer', 'exoneration_percentage_insured' ]; + + public function configWallet() + { + return $this->hasOne(ConfigWallet::class, 'id_network', 'network_id'); + } + + public function monthsPricesGrid() + { + return $this->hasMany(NhMonthsPricesGrid::class, 'nh_network_config_id', 'id'); + } + + public function yearsPricesGrid() + { + return $this->hasMany(NhYearsPricesGrid::class, 'nh_network_config_id', 'id'); + } } diff --git a/app/Models/NhProviderClass.php b/app/Models/NhProviderClass.php new file mode 100644 index 0000000..db4d53c --- /dev/null +++ b/app/Models/NhProviderClass.php @@ -0,0 +1,35 @@ + 'int' + ]; + + protected $fillable = [ + 'nh_network_config_id', + 'name' + ]; +} diff --git a/app/Models/NhValidatingDoctor.php b/app/Models/NhValidatingDoctor.php new file mode 100644 index 0000000..07475eb --- /dev/null +++ b/app/Models/NhValidatingDoctor.php @@ -0,0 +1,52 @@ + 'int' + ]; + + protected $hidden = [ + 'password', + 'token' + ]; + + protected $fillable = [ + 'nh_network_config_id', + 'firstname', + 'lastname', + 'email', + 'phone', + 'password', + 'salt', + 'token' + ]; +} diff --git a/app/Models/NhYearsPricesGrid.php b/app/Models/NhYearsPricesGrid.php index c0254f4..2545b38 100644 --- a/app/Models/NhYearsPricesGrid.php +++ b/app/Models/NhYearsPricesGrid.php @@ -17,8 +17,8 @@ use Illuminate\Database\Eloquent\Model; * @property float $min_age * @property float $max_age * @property float $markup_percentage - * @property Carbon|null $created_at - * @property Carbon|null $updated_at + * @property Carbon $created_at + * @property Carbon $updated_at * * @package App\Models */ diff --git a/app/Models/User.php b/app/Models/User.php index e8c48fc..b74b85a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,24 +2,67 @@ namespace App\Models; +use Carbon\Carbon; use Illuminate\Auth\Authenticatable; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Laravel\Lumen\Auth\Authorizable; +/** + * Class User + * + * @property int $id + * @property string $uid + * @property string $firstname + * @property string $lastname + * @property string $phone + * @property string $email + * @property string $user_code + * @property string $numero_carte + * @property Carbon $expiration_date + * @property string $adresse + * @property float $solde + * @property string $encrypted_password + * @property string $salt + * @property string $validation_code + * @property int $active + * @property Carbon $date_modified + * @property Carbon $date_created + * @property int $network_id + * @property int $group_id + * @property float $balance_credit + * @property float $balance_epargne + * @property Carbon|null $date_adhesion + * @property int|null $id_bank_country + * @property string|null $iban + * + * @package App\Models + */ class User extends Model implements AuthenticatableContract, AuthorizableContract { use Authenticatable, Authorizable, HasFactory; - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'name', 'email', + protected $table = 'users'; + public $timestamps = false; + + protected $casts = [ + 'solde' => 'float', + 'active' => 'int', + 'network_id' => 'int', + 'group_id' => 'int', + 'balance_credit' => 'float', + 'balance_epargne' => 'float', + 'id_bank_country' => 'int' + ]; + + protected $dates = [ + 'expiration_date', + 'date_modified', + 'date_created', + 'date_adhesion' ]; /** @@ -28,6 +71,36 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac * @var array */ protected $hidden = [ - 'password', + 'encrypted_password' + ]; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'uid', + 'firstname', + 'lastname', + 'phone', + 'email', + 'user_code', + 'numero_carte', + 'expiration_date', + 'adresse', + 'solde', + 'encrypted_password', + 'salt', + 'validation_code', + 'active', + 'date_modified', + 'date_created', + 'group_id', + 'balance_credit', + 'balance_epargne', + 'date_adhesion', + 'id_bank_country', + 'iban' ]; } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6830e60..b9fa6ce 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Events\InsuranceSubscribed; +use App\Listeners\NotifyUser; use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider @@ -15,5 +17,8 @@ class EventServiceProvider extends ServiceProvider \App\Events\ExampleEvent::class => [ \App\Listeners\ExampleListener::class, ], + InsuranceSubscribed::class => [ + NotifyUser::class + ] ]; } diff --git a/app/Traits/Helper.php b/app/Traits/Helper.php index b020577..15ce1be 100644 --- a/app/Traits/Helper.php +++ b/app/Traits/Helper.php @@ -4,7 +4,43 @@ namespace App\Traits; +use App\Models\Country; +use Brick\Money\Context\AutoContext; +use Brick\Money\Money; +use Illuminate\Support\Facades\DB; + trait Helper { + public function toMoneyWithNetwork($amount, $id_network) + { + $currency = collect(DB::select('SELECT cu.code FROM networks n INNER JOIN countries c ON c.id = n.country_id INNER JOIN currencies cu ON cu.id = c.idCurrency + WHERE n.id = :id', ['id' => $id_network]))->first(); + $money = Money::of(round($amount, 2), $currency ? $currency->code : 'XAF', new AutoContext()); + return $money->formatTo(app()->getLocale()); + } + + public function toMoney($amount, $id_country) + { + $country = Country::findOrFail($id_country); + $money = Money::of(round($amount, 2), $country->currency->code, new AutoContext()); + return $money->formatTo(app()->getLocale()); + } + + public function toMoneyWithCurrencyCode($amount, $currency_code) + { + $money = Money::of(round($amount, 2), $currency_code, new AutoContext()); + return $money->formatTo(app()->getLocale()); + } + + public function generateTransactionCode($length = 12) + { + $characters = '23456789ABCDEFGHJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; + } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 4492468..d44e64f 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -61,6 +61,7 @@ $app->singleton( $app->configure('app'); $app->configure('swagger-lume'); +$app->configure('services'); /* |-------------------------------------------------------------------------- @@ -96,7 +97,7 @@ $app->configure('swagger-lume'); // $app->register(App\Providers\AppServiceProvider::class); // $app->register(App\Providers\AuthServiceProvider::class); -// $app->register(App\Providers\EventServiceProvider::class); +$app->register(App\Providers\EventServiceProvider::class); $app->register(\SwaggerLume\ServiceProvider::class); $app->register(\MigrationsGenerator\MigrationsGeneratorServiceProvider::class); diff --git a/composer.json b/composer.json index b2e7b7b..a9c4b2b 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "darkaonline/swagger-lume": "^8.0", "guzzlehttp/guzzle": "^7.3", "kitloong/laravel-migrations-generator": "^5.0", - "laravel/lumen-framework": "^8.0" + "laravel/lumen-framework": "^8.0", + "ext-json": "*" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/config/services.php b/config/services.php new file mode 100755 index 0000000..5f43c97 --- /dev/null +++ b/config/services.php @@ -0,0 +1,9 @@ + env('ACCEPTED_KEYS'), + 'notification_service' => [ + 'base_uri' => env('NOTIFICATION_SERVICE_URL'), + 'key' => env('NOTIFICATION_SERVICE_KEY') + ] +]; diff --git a/config/swagger-lume.php b/config/swagger-lume.php index 387d42d..60f4c25 100644 --- a/config/swagger-lume.php +++ b/config/swagger-lume.php @@ -7,7 +7,7 @@ return [ | Edit to set the api's title |-------------------------------------------------------------------------- */ - 'title' => 'Swagger Lume API', + 'title' => 'iLink APP API', ], 'routes' => [ @@ -45,8 +45,10 @@ return [ |-------------------------------------------------------------------------- */ 'middleware' => [ +// 'api' => ['docs'], 'api' => [], 'asset' => [], +// 'docs' => ['docs'], 'docs' => [], 'oauth2_callback' => [], ], @@ -142,6 +144,12 @@ return [ ], ], */ + 'api_key' => [ // Unique name of security + 'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2". + 'description' => "Api Key", + 'name' => 'Authorization', // The name of the header or query parameter to be used. + 'in' => 'header', // The location of the API key. Valid values are "query" or "header". + ], ], /* diff --git a/database/migrations/2021_10_18_134558_create_nh_insurances_subscriptions_table.php b/database/migrations/2021_10_18_134558_create_nh_insurances_subscriptions_table.php new file mode 100644 index 0000000..28ff8e6 --- /dev/null +++ b/database/migrations/2021_10_18_134558_create_nh_insurances_subscriptions_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('insurance_subscription_id')->unique()->comment("Code unique generé à chaque souscription"); + $table->integer('network_id'); + $table->integer('user_id'); + $table->integer('number_of_months')->comment("Durée de couverture en mois"); + $table->integer('amount')->comment("Montant de la durée de couverture"); + $table->integer('number_of_beneficiaries'); + $table->decimal('bonus_amount', 10, 2)->default(0); + $table->enum('state', ['UNDER_VALIDATION', 'ACCEPTED', 'REJECTED', 'UNDER_STOPPING', 'STOPPED'])->default('UNDER_VALIDATION'); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('nh_insurances_subscriptions'); + } +} diff --git a/database/migrations/2021_10_18_135541_create_nh_insurances_having_rights.php b/database/migrations/2021_10_18_135541_create_nh_insurances_having_rights.php new file mode 100644 index 0000000..52d7e14 --- /dev/null +++ b/database/migrations/2021_10_18_135541_create_nh_insurances_having_rights.php @@ -0,0 +1,47 @@ +id(); + $table->string('insurance_subscription_id'); + $table->string('lastname'); + $table->string('firstname')->nullable(); + $table->enum('gender', ['M', 'F'])->default('M'); + $table->date('birthdate'); + $table->enum('affiliation', ['CHILD', 'SPOUSE'])->default('CHILD')->comment("Affiliation: enfant ou conjoint"); + $table->enum('birthdate_proof', ['CERTIFIED_COPY', 'CERTIFICATE'])->default('CERTIFIED_COPY')->nullable() + ->comment("Pour enfant ayant droit - Copie légalisée acte de naissance ou certificat de naissance"); + $table->string('birthdate_proof_doc')->nullable()->comment("Nom du document uploadé"); + $table->string('justice_doc')->nullable()->comment("Une page document de justice si enfant adopté ou sous tutelle (image non obligatoire)"); + $table->string('marriage_certificate_doc')->nullable()->comment("Pour conjoint ayant droit - Une page de l'acte de mariage"); + $table->string('id_document_type')->nullable()->comment("Pièce identité ( cni , carte sejour, permis)"); + $table->string('id_document_front')->nullable()->comment("Pièce identité recto"); + $table->string('id_document_back')->nullable()->comment("Pièce identité verso"); + $table->timestamp('deleted_at')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('nh_insurances_having_rights'); + } +} diff --git a/database/migrations/2021_10_18_152142_create_nh_insurances_subscriptions_history_table.php b/database/migrations/2021_10_18_152142_create_nh_insurances_subscriptions_history_table.php new file mode 100644 index 0000000..479b39a --- /dev/null +++ b/database/migrations/2021_10_18_152142_create_nh_insurances_subscriptions_history_table.php @@ -0,0 +1,37 @@ +id(); + $table->enum('action', ['ADD', 'EDIT'])->comment("Action effectuée"); + $table->string('insurance_subscription_id'); + $table->integer('nh_validating_doctor_id')->nullable(); + $table->string('insurance_subscription_state'); + $table->text('insurance_subscription'); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('nh_insurances_subscriptions_history'); + } +} diff --git a/public/swagger-ui-assets/favicon-16x16.png b/public/swagger-ui-assets/favicon-16x16.png new file mode 100644 index 0000000..8b194e6 Binary files /dev/null and b/public/swagger-ui-assets/favicon-16x16.png differ diff --git a/public/swagger-ui-assets/favicon-32x32.png b/public/swagger-ui-assets/favicon-32x32.png new file mode 100644 index 0000000..249737f Binary files /dev/null and b/public/swagger-ui-assets/favicon-32x32.png differ diff --git a/public/swagger-ui-assets/index.html b/public/swagger-ui-assets/index.html new file mode 100644 index 0000000..f45ec88 --- /dev/null +++ b/public/swagger-ui-assets/index.html @@ -0,0 +1,57 @@ + + + +
+ +>>u&y;if(_!==f>>>u&y)break;_&&(l+=(1<o&&(c=c.removeBefore(r,u,i-l)),c&&f
a&&(a=c.size),i(u)||(c=c.map((function(e){return he(e)}))),r.push(c)}return a>e.size&&(e=e.setSize(a)),mt(e,t,r)}function qt(e){return e p&&(p=e.lineIndent),X(i))f++;else{if(e.lineIndent 0){for(o=i,a=0;o>0;o--)(i=ne(s=e.input.charCodeAt(++e.position)))>=0?a=(a<<4)+i:le(e,"expected hexadecimal character");e.result+=oe(a),e.position++}else le(e,"unknown escape sequence");n=r=e.position}else X(s)?(he(e,n,r,!0),be(e,ge(e,!1,t)),n=r=e.position):e.position===e.lineStart&&ye(e)?le(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}le(e,"unexpected end of the stream within a double quoted scalar")}(e,h)?g=!0:!function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!ee(r)&&!te(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&le(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),H.call(e.anchorMap,n)||le(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],ge(e,!0,-1),!0}(e)?function(e,t,n){var r,o,a,i,s,u,c,l,p=e.kind,f=e.result;if(ee(l=e.input.charCodeAt(e.position))||te(l)||35===l||38===l||42===l||33===l||124===l||62===l||39===l||34===l||37===l||64===l||96===l)return!1;if((63===l||45===l)&&(ee(r=e.input.charCodeAt(e.position+1))||n&&te(r)))return!1;for(e.kind="scalar",e.result="",o=a=e.position,i=!1;0!==l;){if(58===l){if(ee(r=e.input.charCodeAt(e.position+1))||n&&te(r))break}else if(35===l){if(ee(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&ye(e)||n&&te(l))break;if(X(l)){if(s=e.line,u=e.lineStart,c=e.lineIndent,ge(e,!1,-1),e.lineIndent>=t){i=!0,l=e.input.charCodeAt(e.position);continue}e.position=a,e.line=s,e.lineStart=u,e.lineIndent=c;break}}i&&(he(e,o,a,!1),be(e,e.line-s),o=a=e.position,i=!1),Q(l)||(a=e.position+1),l=e.input.charCodeAt(++e.position)}return he(e,o,a,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,h,1===n)&&(g=!0,null===e.tag&&(e.tag="?")):(g=!0,null===e.tag&&null===e.anchor||le(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===m&&(g=u&&_e(e,d))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&le(e,'unacceptable node kind for !> tag; it should be "scalar", not "'+e.kind+'"'),c=0,l=e.implicitTypes.length;cs)return q();var e=o.next();return r||t===M?e:U(t,u-1,t===N?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,s){return t.call(n,e,o,s)&&++i&&r(e,o,a)})),i},r.__iteratorUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(R,o),s=!0;return new F((function(){if(!s)return q();var e=i.next();if(e.done)return e;var o=e.value,u=o[0],c=o[1];return t.call(n,c,u,a)?r===R?e:U(r,u,c,e):(s=!1,q())}))},r}function an(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,u=0;return e.__iterate((function(e,a,c){if(!s||!(s=t.call(n,e,a,c)))return u++,o(e,r?a:u-1,i)})),u},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(R,a),u=!0,c=0;return new F((function(){var e,a,l;do{if((e=s.next()).done)return r||o===M?e:U(o,c++,o===N?void 0:e.value[1],e);var p=e.value;a=p[0],l=p[1],u&&(u=t.call(n,l,a,i))}while(u);return o===R?e:U(o,a,l,e)}))},o}function sn(e,t){var n=s(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?se(e):ue(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||n&&s(a)||u(e)&&u(a))return a}var c=new te(o);return n?c=c.toKeyedSeq():u(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function un(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=0,s=!1;function u(e,c){var l=this;e.__iterate((function(e,o){return(!t||cs&&(n=s-u),a=n;a>=0;a--){for(var p=!0,f=0;fo&&(r=o):r=o;var a=t.length;if(a%2!=0)throw new TypeError("Invalid hex string");r>a/2&&(r=a/2);for(var i=0;io)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var a=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var O=4096;function k(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;o