Update Member Import

This commit is contained in:
2022-07-24 14:30:19 +07:00
parent 0f4c84da6a
commit 42e4129a79
21 changed files with 1184 additions and 164 deletions

View File

@@ -0,0 +1,188 @@
<?php
namespace Modules\Internal\Http\Controllers\Api;
use App\Exceptions\ImportRowException;
use App\Models\Corporate;
use App\Models\Member;
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Row;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Storage;
use Modules\Internal\Services\MemberEnrollmentService;
class MemberController extends Controller
{
public function __construct(MemberEnrollmentService $memberEnrollmentService)
{
$this->memberEnrollmentService = $memberEnrollmentService;
}
/**
* Display a listing of the resource.
* @return Renderable
*/
public function index(Request $request, $corporate_id)
{
$benefits = Member::query()
->filter($request->all())
// ->where('corporate_id', $corporate_id)
->paginate(20)
->appends($request->all());
return $benefits;
}
/**
* Show the form for creating a new resource.
* @return Renderable
*/
public function create()
{
return view('internal::create');
}
/**
* Store a newly created resource in storage.
* @param Request $request
* @return Renderable
*/
public function store(Request $request)
{
//
}
/**
* Show the specified resource.
* @param int $id
* @return Renderable
*/
public function show($id)
{
return view('internal::show');
}
/**
* Show the form for editing the specified resource.
* @param int $id
* @return Renderable
*/
public function edit($id)
{
return view('internal::edit');
}
/**
* Update the specified resource in storage.
* @param Request $request
* @param int $id
* @return Renderable
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
* @param int $id
* @return Renderable
*/
public function destroy($id)
{
//
}
public function import(Request $request, $corporate_id)
{
$request->validate([
'file' => 'required|file|mimes:xls,xlsx,csv,txt',
]);
$corporate = Corporate::findOrFail($corporate_id);
$file_name = now()->getPreciseTimestamp(3).'-'.$request->file('file')->getClientOriginalName();
$file = $request->file('file')->storeAs('temp', $file_name);
$reader = ReaderEntityFactory::createReaderFromFile(Storage::path('temp/'.$file_name));
$reader->open(Storage::path('temp/'.$file_name));
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToFile(Storage::disk('public')->path('temp/result-'.$file_name));
$headers_map_to_table_fields = Member::$doc_headers_to_field_map;
// Write Header to File
$result_headers = array_keys($headers_map_to_table_fields);
array_push($result_headers, 'Ingestion Status');
$singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($result_headers));
$writer->addRow($singleRow);
$imported_member_data = 0;
$failed_member_data = [];
foreach ($reader->getSheetIterator() as $sheet) {
$doc_headers_indexes = [];
foreach ($sheet->getRowIterator() as $index => $row) {
if ($index == 1) { // First Row Must be Header
foreach ($row->getCells() as $index => $cell) {
// Clear up the string and remove all spaces
$title = $cell->getValue();
$title = preg_replace( "/\r|\n/", " ", $title );
$title = preg_replace('/\xc2\xa0/', " ", $title );
$title = rtrim($title);
$title = ltrim($title);
$doc_headers_indexes[$index] = $title;
}
} else { // Next Row Should be Data
$new_member_data = [];
foreach ($row->getCells() as $header_index => $cell) {
if (isset($headers_map_to_table_fields[$doc_headers_indexes[$header_index]])) {
$new_member_data[$headers_map_to_table_fields[$doc_headers_indexes[$header_index]]] = $cell->getValue();
}
}
try {
$rowResponse = $this->memberEnrollmentService->handleImportRow($corporate, $new_member_data);
// Write Success Result to File
array_push($new_member_data, 'SUCCESS');
$singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($new_member_data));
$writer->addRow($singleRow);
$imported_member_data++;
} catch (ImportRowException $e) {
// Write Data Validation Error to File
array_push($new_member_data, $e->getMessage());
$singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($new_member_data));
$writer->addRow($singleRow);
$failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()];
} catch (\Exception $e) {
// Write Server Error to File
array_push($new_member_data, "Server Error");
$singleRow = WriterEntityFactory::createRow($this->memberEnrollmentService->makeResultRow($new_member_data));
$writer->addRow($singleRow);
$failed_member_data[] = ['row_number' => $index, 'error' => $e->getMessage()];
}
}
}
break; //only read first sheet
}
$reader->close();
$writer->close();
Storage::delete('temp/'.$file_name);
// throw(404);
return [
'total_success_row' => $imported_member_data,
'total_failed_row' => count($failed_member_data),
'failed_row' => $failed_member_data,
'result_file' => [
'url' => Storage::disk('public')->url('temp/result-'.$file_name),
'name' => 'result-'.$file_name,
]
];
}
}

View File

@@ -7,6 +7,7 @@ use Modules\Internal\Http\Controllers\Api\CorporateBenefitController;
use Modules\Internal\Http\Controllers\Api\CorporateController;
use Modules\Internal\Http\Controllers\Api\CorporatePlanController;
use Modules\Internal\Http\Controllers\Api\DivisionController;
use Modules\Internal\Http\Controllers\Api\MemberController;
use Modules\Internal\Http\Controllers\Api\PlanController;
/*
@@ -57,5 +58,9 @@ Route::prefix('internal')->group(function () {
Route::post('corporates/{corporate_id}/divisions', [DivisionController::class, 'store']);
Route::get('corporates/{corporate_id}/divisions/{id}/edit', [DivisionController::class, 'edit']);
Route::put('corporates/{corporate_id}/divisions/{id}', [DivisionController::class, 'update']);
Route::get('corporates/{corporate_id}/members', [MemberController::class, 'index']);
Route::post('corporates/{corporate_id}/members/import', [MemberController::class, 'import']);
});
});

View File

@@ -0,0 +1,537 @@
<?php
namespace Modules\Internal\Services;
use App\Exceptions\ImportRowException;
use App\Helpers\Helper;
use App\Models\Corporate;
use App\Models\CorporateDivision;
use App\Models\CorporatePlan;
use App\Models\Member;
use App\Models\MemberPolicy;
use App\Models\Plan;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Row;
use Carbon\Carbon;
use DB;
class MemberEnrollmentService
{
public function __construct(Member $member)
{
$this->member = $member;
}
protected function validateRow($row)
{
if (empty($row['record_type'])) {
throw new ImportRowException(__('enrollment.RECORD_TYPE_REQUIRED'), 0, null, $row);
}
if (empty($row['payor_id'])) {
throw new ImportRowException(__('enrollment.PAYOR_ID_REQUIRED'), 0, null, $row);
}
if (empty($row['member_id'])) {
throw new ImportRowException(__('enrollment.MEMBER_ID_REQUIRED'), 0, null, $row);
}
if ($row['record_type'] == 'P') {
if (!empty($row['principal_id'])) {
throw new ImportRowException(__('enrollment.PRINCIPAL_ID_NOT_REQUIRED'), 0, null, $row);
}
if (empty($row['corporate_id'])) {
throw new ImportRowException(__('enrollment.CORPORATE_ID_REQUIRED'), 0, null, $row);
}
}
if ($row['record_type'] == 'D') {
if (empty($row['principal_id'])) {
throw new ImportRowException(__('enrollment.PRINCIPAL_ID_REQUIRED'), 0, null, $row);
}
}
// TOOD RECORD BCA ONLY
if ($row['record_type'] == 'D' && !empty($row['branch_code'])) {
throw new ImportRowException(__('enrollment.BRANCH_CODE_NOT_REQUIRED'), 0, null, $row);
}
// TODO BANK VALIDATION
// if ($row['record_type'] == 'D' && !empty($row['branch_code'])) {
// throw new ImportRowException(__('enrollment.BRANCH_CODE_NOT_REQUIRED'), 0, null, $row);
// }
if (!empty($row['language']) && !in_array($row['language'], ['M', 'E', 'C', 'I', 'O'])) {
throw new ImportRowException(__('enrollment.INVALID_LANGUAGE'), 0, null, $row);
}
if (!empty($row['type_of_work']) && !in_array($row['type_of_work'], ['0', '1', '2', '3'])) {
throw new ImportRowException(__('enrollment.INVALID_TYPE_OF_WORK'), 0, null, $row);
}
if (!empty($row['race']) && !in_array($row['race'], ['M', 'C', 'I', 'O'])) {
throw new ImportRowException(__('enrollment.INVALID_RACE'), 0, null, $row);
}
if (empty($row['policy_number'])) {
throw new ImportRowException(__('enrollment.POLICY_NUMBER_REQUIRED'), 0, null, $row);
}
if (!empty($row['marital_status']) && !in_array($row['marital_status'], ['S', 'M', 'D'])) {
throw new ImportRowException(__('enrollment.INVALID_MARITAL_STATUS'), 0, null, $row);
}
if (empty($row['member_effective_date']) ) {
throw new ImportRowException(__('enrollment.MEMBER_EFFECTIVE_REQUIRED'), 0, null, $row);
}
// TODO EFFECTIVE DATE VALIDATION
if (empty($row['member_expiry_date']) ) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_REQUIRED'), 0, null, $row);
}
// TODO EFFECTIVE DATE VALIDATION
// TODO FKTP VALIDATION
// TODO FKRTL VALIDATION
if (!empty($row['marital_status']) && !in_array($row['marital_status'], ['S', 'M', 'D'])) {
throw new ImportRowException(__('enrollment.INVALID_MARITAL_STATUS'), 0, null, $row);
}
if (empty($row['name'])) {
throw new ImportRowException(__('enrollment.NAME_REQUIRED'), 0, null, $row);
}
if (!empty($row['telephone_mobile'])
&& substr($row['telephone_mobile'], 0, 4) != '+628') {
throw new ImportRowException(__('enrollment.PHONE_INVALID'), 0, null, $row);
}
if (!empty($row['email'])
&& !filter_var($row['email'], FILTER_VALIDATE_EMAIL)) {
throw new ImportRowException(__('enrollment.EMAIL_INVALID'), 0, null, $row);
}
if (empty($row['date_of_birth'])) {
throw new ImportRowException(__('enrollment.DATE_OF_BIRTH_REQUIRED'), 0, null, $row);
}
// TODO DOB FORMAT VALIDATION
if (empty($row['sex'])) {
throw new ImportRowException(__('enrollment.SEX_REQUIRED'), 0, null, $row);
}
if (empty($row['sex'])) {
throw new ImportRowException(__('enrollment.SEX_REQUIRED'), 0, null, $row);
}
}
public function handleImportRow(Corporate $corporate, $row)
{
try {
$member_data = [
"name" => $row['name'],
"member_id" => $row['member_id'],
"payor_id" => $row['payor_id'],
"nik" => $row['nik'],
"birth_date" => Carbon::parse($row['date_of_birth']),
"gender" => Helper::genderNormalization($row['sex']),
"language" => $row['language'],
"race" => $row['race'],
"marital_status" => $row['marital_status'],
"record_type" => $row['record_type'],
"principal_id" => $row['principal_id'],
"relation_with_principal" => $row['relationship_with_principal'],
"bpjs_class" => $row['bpjs_class'],
];
switch ($row['record_mode']) {
case "1": // New Member
$member = Member::query()
->where('member_id', $row['member_id'])
->first();
if ($member) {
throw new ImportRowException(__('enrollment.MEMBER_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
} else {
$member = new Member();
}
$memberPolicy = $member->policies()
->where('policy_id', $row['policy_number'])
->first();
if ($memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
$this->validateRow($row);
try {
DB::beginTransaction();
$member->fill($member_data);
if ($member->save()) {
$memberPolicy = new MemberPolicy();
$memberPolicy->fill([
'member_id' => $member->id,
'policy_id' => $row['policy_number'],
'start' => Carbon::parse($row['member_effective_date']),
'end' => Carbon::parse($row['member_expiry_date']),
'status' => 'active'
]);
$memberPolicy->save();
if (!empty($row['division'])) {
$division_id = CorporateDivision::where('code', $row['division_code'])->where('')->pluck('id');
}
$member->employeds()->create([
'corporate_id' => $corporate->id,
'branch_code' => $row['branch_code'],
'division_id' => $division_id ?? null,
'nik' => $row['nik']
]);
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new ImportRowException($e->getMessage(), $e->getCode(), $e, $row);
}
break;
case "2": // Member Information Update (Without Replacement Card)
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->with('member')
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
$memberPolicy->member->fill($member_data);
if (!$memberPolicy->member->isDirty()) {
throw new ImportRowException(__('enrollment.MEMBER_NO_CHANGE'), 0, null, $row);
}
return $memberPolicy->member->save();
break;
case "3": // Member Deletion
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
return $memberPolicy->delete();
break;
case "4": // Member Update Start and End Date
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
$memberPolicy->fill([
'start' => $row['start_date'],
'end' => $row['end_date']
]);
if (!$memberPolicy->isDirty()) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_NO_CHANGE'), 0, null, $row);
}
if (Carbon::parse($row['member_effective_date']) > Carbon::parse($row['end_date'])) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row);
}
return $memberPolicy->save();
break;
case "5": // Member Renewal Policy (without card)
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if (Carbon::parse($row['member_effective_date']) > Carbon::parse($row['member_expiry_date'])) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row);
}
if (Carbon::parse($memberPolicy->end) > Carbon::parse($row['member_expiry_date']
|| $memberPolicy->end > Carbon::parse($row['member_expiry_date']))) {
throw new ImportRowException(__('enrollment.MEMBER_RENEWAL_STILL_ACTIVE'), 0, null, $row);
}
$memberPolicy->fill([
'start' => $row['member_expiry_date'],
'end' => $row['member_expiry_date']
]);
if (!$memberPolicy->isDirty()) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_NO_CHANGE'), 0, null, $row);
}
return $memberPolicy->save();
break;
case "6": // Member Renewal Policy (with card)
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if (Carbon::parse($row['member_effective_date']) > Carbon::parse($row['member_expiry_date'])) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row);
}
if (Carbon::parse($memberPolicy->end) > Carbon::parse($row['member_expiry_date']
|| $memberPolicy->end > Carbon::parse($row['member_expiry_date']))) {
throw new ImportRowException(__('enrollment.MEMBER_RENEWAL_STILL_ACTIVE'), 0, null, $row);
}
$memberPolicy->fill([
'start' => $row['member_expiry_date'],
'end' => $row['member_expiry_date']
]);
if (!$memberPolicy->isDirty()) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_NO_CHANGE'), 0, null, $row);
}
return $memberPolicy->save();
break;
case "7": // Requesting to Change the Unique Data of the Member
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->with('member')
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
// TODO OPTION MODE FORMAT VALIDATION
if (empty($row["option_mode"])) {
throw new ImportRowException(__('enrollment.OPTION_MODE_INVALID_FORMAT', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if (
$row['record_type'] == 'P' &&
$memberPolicy->member->corporate->code == $row["corporate_id"] &&
$memberPolicy->policy_id == $row['policy_number'] &&
$memberPolicy->member_id == $row['member_id'] &&
$memberPolicy->member->record_type == $row['record_type']
) {
throw new ImportRowException(__('enrollment.UNIQUE_CHANGE_PRINCIPAL_INVALID', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if (
$row['record_type'] == 'D' &&
$memberPolicy->member->corporate->code == $row["corporate_id"] &&
$memberPolicy->policy_id == $row['policy_number'] &&
$memberPolicy->member->record_type == $row['record_type'] &&
$memberPolicy->member->payor_id == $row['payor_id']
) {
throw new ImportRowException(__('enrollment.UNIQUE_CHANGE_DEPENDANT_INVALID', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
throw new ImportRowException(__('MODE 7 NOT HANDLED PROPERLY'), 0, null, $row);
break;
case "8": // Member Information Update (With Replacement Card)
break;
case "9": // Member Reactivation and Personal information update (Without replacement Card)
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if (Carbon::parse($row['member_effective_date']) < now() || Carbon::parse($row['member_expiry_date']) < now()) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_MUST_BE_AFTER_TODAY'), 0, null, $row);
}
if (Carbon::parse($row['member_effective_date']) > Carbon::parse($row['member_expiry_date'])) {
throw new ImportRowException(__('enrollment.MEMBER_EXPIRY_DATE_INVALID'), 0, null, $row);
}
$memberPolicy->fill([
'start' => $row['member_effective_date'],
'end' => $row['member_expiry_date']
]);
return $memberPolicy->save();
break;
// case "10": // No Information Available
// break;
case "11": // Advance Renewal with OLD Card No. (PRINT)
throw new ImportRowException(__('MODE 11 NOT HANDLED PROPERLY'), 0, null, $row);
break;
case "12": // Advance Renewal iwh NEW Card No. (PRINT)
throw new ImportRowException(__('MODE 12 NOT HANDLED PROPERLY'), 0, null, $row);
break;
case "13": // Advance Renewal with OLD Card No. (NO PRINT)
throw new ImportRowException(__('MODE 13 NOT HANDLED PROPERLY'), 0, null, $row);
break;
// case "14": // No Information Available
// break;
case "15": // Lost Card / Change Card with new card number (Print) (Rarely Used)
throw new ImportRowException(__('MODE 15 NOT HANDLED PROPERLY'), 0, null, $row);
break;
case "16": // Endorsement Plan OLD Card No. (NO PRINT)
$plan = CorporatePlan::query()
->where('corporate_id', $corporate->id)
->where('code', $row['plan_id'])
->where('active', true)
->first();
if (!$plan) {
throw new ImportRowException(__('enrollment.PLAN_NOT_FOUND'), 0, null, $row);
}
$memberPolicy = MemberPolicy::query()
->where('policy_id', $row['policy_number'])
->where('member_id', $row['member_id'])
->first();
if (!$memberPolicy) {
throw new ImportRowException(__('enrollment.MEMBER_NOT_EXISTS', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
if ($memberPolicy->status != 'active') {
throw new ImportRowException(__('enrollment.MEMBER_INACTIVE', [
'member_id' => $row['member_id'],
'policy_id' => $row['policy_number']
]), 0, null, $row);
}
throw new ImportRowException(__('MODE 16 NOT HANDLED PROPERLY'), 0, null, $row);
break;
case "17": // Endorsement Plan OLD Card No. (PRINT)
throw new ImportRowException(__('MODE 17 NOT HANDLED PROPERLY'), 0, null, $row);
break;
default:
throw new ImportRowException(__("enrollment.MODE_UNAVAILABLE"), 0, null, $row);
}
} catch (\Exception $e) {
throw new ImportRowException($e->getMessage(), (int) $e->getCode(), $e, $row);
}
}
public function makeResultRow($row_data)
{
$cells = [];
foreach ($row_data as $cellValue) {
$cells[] = WriterEntityFactory::createCell($cellValue);
}
return $cells;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class ImportRowException extends Exception
{
protected $data = null;
public function __construct($message, $code = 0, Throwable $previous = null, $data)
{
parent::__construct($message, $code, $previous);
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}

20
app/Helpers/Helper.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
namespace App\Helpers;
class Helper{
public static function genderNormalization($anyGenderCode)
{
if ($anyGenderCode == 'M') {
return 'male';
} else if ($anyGenderCode == 'F') {
return 'female';
} else if ($anyGenderCode == 'O') {
return 'others';
} else if ($anyGenderCode == 'U') {
return 'unknown';
} else {
return null;
}
}
}

View File

@@ -42,4 +42,19 @@ class Corporate extends Model
->where('active', true)
->latestOfMany();
}
public function corporatePlans()
{
return $this->hasMany(CorporatePlan::class, 'corporate_id');
}
public function corporateBenefits()
{
return $this->hasMany(CorporateBenefit::class, 'corporate_id');
}
public function corporateDivisions()
{
return $this->hasMany(CorporateDivision::class, 'corporate_id');
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CorporateEmployee extends Model
{
use HasFactory;
protected $fillable = [
'corporate_id',
'member_id',
'branch_code',
'division_id',
'nik',
'status'
];
}

View File

@@ -1,11 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CorporateEmployees extends Model
{
use HasFactory;
}

View File

@@ -8,4 +8,118 @@ use Illuminate\Database\Eloquent\Model;
class Member extends Model
{
use HasFactory;
protected $fillable = [
"id",
"member_id",
"record_type",
"payor_id",
"user_id",
"name_prefix",
"name",
"name_suffix",
"birth_date",
"gender",
"language",
"race",
"marital_status",
"principal_id",
"relation_with_principal",
"bpjs_class",
"active",
"created_by",
"updated_by",
"deleted_by"
];
public static $doc_headers_to_field_map = [
"Record Mode" => "record_mode",
"Record Type" => "record_type",
"Payor ID" => "payor_id",
"Member ID" => "member_id",
"Mapping ID" => "principal_id",
"Halodoc Member ID" => "halodoc_member_id",
"Corporate ID" => "corporate_id",
"NIK" => "nik",
"Division" => "division_code",
"Branch Code" => "branch_code",
"Bank Info" => "banks_info",
"Language" => "language",
"Type of work" => "type_of_work",
"Race" => "race",
"Policy Number" => "policy_number",
"Marital Status" => "marital_status",
"Relationship" => "relationship_with_principal",
"Member's Effective Date" => "member_effective_date",
"Member's Expiry Date" => "member_expiry_date",
"Faskes FKTP (First Level Provider) or Individual preferred provider" => "faskes_fktp",
"Faskes FKRTL (Next Level Provider) or Individual group preferred provider" => "faskes_fkrtl",
"The Right Classes Room of BPJS Participants" => "bpjs_class",
"Name of Faskes" => "faskes_name",
"Rule_BPJSK ('Y' or 'N')" => "bpjsk",
"Agent Code / intermediary code" => "agent_code",
"Member Name" => "name",
"Address1" => "address1",
"Address2" => "address2",
"Address3" => "address3",
"Address4" => "address4",
"City" => "city",
"State" => "state",
"Post Code" => "post_code",
"Telephone Mobile" => "telephone_mobile",
"Telephone Res" => "telephone_res",
"Telephone Office" => "telephone_office",
"NRIC" => "nric",
"Passport No" => "passport_number",
"Passport Country" => "passport_country",
"Email" => "email",
"Identification Code" => "identification_code",
"Date of Birth" => "date_of_birth",
"Sex" => "sex",
"Internal Use" => "internal_use",
"Plan-ID" => "plan_id",
"Employment-Status" => "employment_status",
"Internal Use" => "internal_use_1",
"Internal Use" => "internal_use_2",
"Internal Use" => "internal_use_3",
"Date Terminated" => "date_terminated",
"Pre Existing" => "pre_existing",
"BPJS ID" => "bpjs_id",
"Endorsement Date" => "endorsement_date",
"Remarks" => "remarks",
"Internal Use" => "internal_use_4",
"Member Since" => "member_since",
"Internal Use" => "internal_use_5",
"Policy In Force" => "policy_in_force",
"Member Suspended" => "member_suspended",
"Activation Date" => "activation_date",
"Internal Use" => "internal_use_6",
"StartNoClaim" => "start_no_claim",
"EndNoClaim" => "end_no_claim",
"Option Mode" => "option_mode",
];
public function employeds()
{
return $this->hasMany(CorporateEmployee::class, 'member_id');
}
public function policies()
{
return $this->hasMany(MemberPolicy::class, 'member_id');
}
public function scopeFilter($query, array $filters)
{
$query->when($filters['search'] ?? false, function ($query, $search) {
return $query
->where('member_id', 'like', "%" . $search . "%")
->orWhere('payor_id', 'like', "%" . $search . "%")
->orWhere('name', 'like', "%" . $search . "%")
;
// ->orWhereHas('corporatePlan', function ($query) use ($search) {
// $query->where('code', 'like', "%" . $search . "%");
// });
});
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Traits\Blameable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class MemberPolicy extends Model
{
use HasFactory, SoftDeletes, Blameable;
protected $fillable = [
'policy_id',
'member_id',
'status',
'start',
'end'
];
public function policy()
{
return $this->belongsTo(Policy::class, 'policy_id', 'policy_id');
}
public function member()
{
return $this->belongsTo(Member::class, 'member_id', 'member_id');
}
}

View File

@@ -16,6 +16,8 @@ return new class extends Migration
Schema::create('members', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->index();
$table->string('member_id')->nullable()->unique();
$table->string('payor_id')->nullable();
$table->string('name_prefix')->nullable();
$table->string('name');
$table->string('name_suffix')->nullable();
@@ -25,9 +27,10 @@ return new class extends Migration
$table->string('language')->nullable();
$table->string('race')->nullable();
$table->string('marital_status')->nullable();
$table->foreignId('principle_id')->nullable()->index();
$table->string('relation_with_principle')->nullable();
$table->date('bpjs_class')->nullable();
$table->string('record_type', 1)->nullable();
$table->string('principal_id')->nullable()->index();
$table->string('relation_with_principal')->nullable();
$table->string('bpjs_class')->nullable();
$table->boolean('active')->default(true);
$table->timestamps();

View File

@@ -15,7 +15,19 @@ return new class extends Migration
{
Schema::create('corporate_employees', function (Blueprint $table) {
$table->id();
$table->foreignId('corporate_id')->index();
$table->foreignId('member_id')->index();
$table->string('branch_code')->nullable()->index();
$table->foreignId('division_id')->nullable()->index();
$table->string('nik')->nullable()->index();
$table->string('status')->nullable();
$table->timestamps();
$table->softDeletes();
$table->unsignedBigInteger('created_by')->nullable();
$table->unsignedBigInteger('updated_by')->nullable();
$table->unsignedBigInteger('deleted_by')->nullable();
});
}

View File

@@ -16,7 +16,7 @@ return new class extends Migration
Schema::create('corporate_policies', function (Blueprint $table) {
$table->id();
$table->string('corporate_id');
$table->string('code')->unique();
$table->string('policy_id')->unique();
$table->decimal('total_premi', 15, 2)->nullable();
$table->decimal('minimal_deposit_percentage', 15, 2)->nullable();
$table->decimal('minimal_deposit_net', 15, 2)->nullable();

View File

@@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('member_policies', function (Blueprint $table) {
$table->id();
$table->string('policy_id');
$table->string('member_id');
$table->string('status')->default('active');
$table->dateTime('start')->nullable();
$table->dateTime('end')->nullalbe();
$table->timestamps();
$table->softDeletes();
$table->unsignedBigInteger('created_by')->nullable();
$table->unsignedBigInteger('updated_by')->nullable();
$table->unsignedBigInteger('deleted_by')->nullable();
$table->index(['policy_id', 'member_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('member_policies');
}
};

View File

@@ -15,5 +15,10 @@ class DatabaseSeeder extends Seeder
public function run()
{
// \App\Models\User::factory(10)->create();
$this->call([
DummyMemberSeeder::class,
DummyCorprateSeeder::class
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Database\Seeders;
use App\Models\Corporate;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DummyCorporateSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Corporate::truncate();
$corporate = Corporate::create([
'code' => 'UNTR0001',
'name' => 'United Tractor',
'welcome_message' => 'Welcome to United Tractor',
'help_text' => 'You can blah blah blah blah',
'active' => true
]);
$corporate->corporatePlans()->create([
'code' => 'PLAN001',
'name' => 'PLAN Name',
'description' => 'Description of PLAN Name',
'active' => true,
]);
$corporate->corporateBenefits()->create([
'code' => 'BENEFIT001',
'name' => 'BENEFIT Name',
'description' => 'Description of BENEFIT Name',
'active' => true,
]);
$corporate->corporateDivisions()->create([
'code' => 'DIVISION001',
'name' => 'DIVISION Name',
'description' => 'Description of DIVISION Name',
'active' => true,
]);
}
}

View File

@@ -1,5 +1,21 @@
// ----------------------------------------------------------------------
export type Member = {
id: string,
member_id: string,
record_type: string,
payor_id: string,
user_id: string,
name_prefix: string,
name: string,
name_suffix: string,
birth_date: string,
gender: string,
language: string,
race: string,
marital_status: string,
principal_id: string,
relation_with_principal: string,
bpjs_class: string,
active: string,
};

View File

@@ -151,15 +151,15 @@ export default function PlanCreate() {
},
{
name: 'Corporate Name',
href: '/corporates/'+id,
href: '/corporates/'+corporate_id,
},
{
name: 'Plans',
href: '/corporates/'+id+'/plans',
href: '/corporates/'+corporate_id+'/plans',
},
{
name: 'Create',
href: '/corporates/'+id+'/plans/create',
href: '/corporates/'+corporate_id+'/plans/create',
},
]}
/>

View File

@@ -34,20 +34,10 @@ export default function Divisions() {
]}
/>
<Grid container spacing={2}>
<Grid item xs={8}>
<Card>
<CorporateTabNavigations position={'members'} />
<DivisionsList />
</Card>
</Grid>
<Grid item xs={4}>
<Card sx={{ p: 2 }}>
Corporate Detail Goes Here
&nbsp;
</Card>
</Grid>
</Grid>
<Card>
<CorporateTabNavigations position={'members'} />
<DivisionsList />
</Card>
</Page>
);
}

View File

@@ -13,11 +13,13 @@ import { useParams, useSearchParams } from 'react-router-dom';
import axios from '../../../utils/axios';
import { Plan } from '../../../@types/corporates';
import { LaravelPaginatedData } from '../../../@types/paginated-data';
import { Member } from '../../../@types/member';
export default function CorporatePlanList() {
const { themeStretch } = useSettings();
const { corporate_id } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [importResult, setImportResult] = useState(null)
function SearchInput(props: any) {
// SEARCH
@@ -90,13 +92,16 @@ export default function CorporatePlanList() {
formData.append("file", importPlan.current?.files[0])
axios.post(`corporates/${corporate_id}/members/import`, formData )
.then(response => {
setImportResult(response.data)
handleCancelImportButton();
loadDataTableData();
alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows');
})
.catch(response => {
alert('Looks like something went wrong. Please check your data and try again. ' + response.message)
})
.then(x => {
console.log('motherfucker', importResult)
})
} else {
alert('No File Selected')
}
@@ -150,6 +155,11 @@ export default function CorporatePlanList() {
</Button>
</Stack>
)}
{( importResult &&
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: "text.secondary" }}>Last Import Result : <Box sx={{ color: "success.main", display: "inline" }}>{ importResult.total_success_row ?? 0 }</Box> Row Processed, <Box sx={{ color: "error.main", display: "inline" }}>{ importResult.total_failed_row }</Box> Failed, Report : <a href={importResult.result_file?.url ?? "#"}>{importResult.result_file?.name ?? "-"}</a></Box>
</Stack>
)}
</div>
);
}
@@ -161,6 +171,20 @@ export default function CorporatePlanList() {
}
}
const [columns, setColumns] = React.useState([
{ id: 'member_id', label: 'MemberID', minWidth: 100, align: "left" },
{ id: 'principal_id', label: 'Mapping ID', minWidth: 100, align: "left" },
{ id: 'nik', label: 'NIK', minWidth: 100, align: "left" },
{ id: 'policy_number', label: 'Policy Number', minWidth: 100, align: "left" },
{ id: 'effective_date', label: 'Effective Date', minWidth: 100, align: "left" },
{ id: 'name', label: 'Name', minWidth: 100, align: "left" },
{ id: 'nric', label: 'NRIC', minWidth: 100, align: "left" },
{ id: 'email', label: 'E-mail', minWidth: 100, align: "left" },
{ id: 'plan_id', label: 'PlanID', minWidth: 100, align: "left" },
{ id: 'termination_date', label: 'Termination Date', minWidth: 100, align: 'right' },
{ id: 'activation_date', label: 'Activation Date', minWidth: 100, align: "right" },
]);
// Generate the every row of the table
function Row(props: { row: ReturnType<typeof createData> }) {
const { row } = props;
@@ -178,54 +202,9 @@ export default function CorporatePlanList() {
{open ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
</IconButton>
</TableCell>
<TableCell align="left">{row.service_code}</TableCell>
<TableCell align="left">{row.corporate_plan?.code}</TableCell>
<TableCell align="left">{row.code}</TableCell>
<TableCell align="left">{row.type}</TableCell>
<TableCell align="left">{row.start}</TableCell>
<TableCell align="left">{row.end}</TableCell>
<TableCell align="left">{row.require_referral}</TableCell>
<TableCell align="left">{row.referral_source}</TableCell>
<TableCell align="left">{row.referral_duration}</TableCell>
<TableCell align="left">{row.family_plan}</TableCell>
<TableCell align="left">{row.family_plan_share_rules}</TableCell>
<TableCell align="left">{row.limit_rules}</TableCell>
<TableCell align="left">{row.layer}</TableCell>
<TableCell align="left">{row.layer_conditions}</TableCell>
<TableCell align="left">{row.budget_type}</TableCell>
<TableCell align="left">{row.budget_code}</TableCell>
<TableCell align="left">{row.budget_conditions}</TableCell>
<TableCell align="left">{row.surgery_limit}</TableCell>
<TableCell align="left">{row.non_surgery_limit}</TableCell>
<TableCell align="left">{row.max_claim_limit}</TableCell>
<TableCell align="left">{row.max_claim_count}</TableCell>
<TableCell align="left">{row.area_limit}</TableCell>
<TableCell align="left">{row.limit_shared_plans}</TableCell>
<TableCell align="left">{row.limit_shared_plan_type}</TableCell>
<TableCell align="left">{row.cashless_percentage}</TableCell>
<TableCell align="left">{row.reimbursement_percentage}</TableCell>
<TableCell align="left">{row.digital_percentage}</TableCell>
<TableCell align="left">{row.co_share_m_percentage}</TableCell>
<TableCell align="left">{row.co_share_s_percentage}</TableCell>
<TableCell align="left">{row.co_share_c_percentage}</TableCell>
<TableCell align="left">{row.cashless_deductible}</TableCell>
<TableCell align="left">{row.reimbursement_deductible}</TableCell>
<TableCell align="left">{row.digital_deductible}</TableCell>
<TableCell align="left">{row.co_share_m_deductible}</TableCell>
<TableCell align="left">{row.co_share_s_deductible}</TableCell>
<TableCell align="left">{row.co_share_c_deductible}</TableCell>
<TableCell align="left">{row.co_share_deductible_condition}</TableCell>
<TableCell align="left">{row.msc}</TableCell>
<TableCell align="left">{row.genders}</TableCell>
<TableCell align="left">{row.min_age}</TableCell>
<TableCell align="left">{row.max_age}</TableCell>
<TableCell align="left">{row.rule_of_excess}</TableCell>
<TableCell align="left">{row.max_excess_covered}</TableCell>
<TableCell align="left">{row.prorate_type}</TableCell>
<TableCell align="left">{row.prorate_lookup}</TableCell>
<TableCell align="left">{row.currency}</TableCell>
<TableCell align="left">{row.max_surgery_reinstatement_days}</TableCell>
<TableCell align="left">{row.max_surgery_periode_days}</TableCell>
{ columns.map((column, index) =>
<TableCell key={ index } align={ column.align } minwidth={ column.minWidth }>{ row[column.id] ?? '-' }</TableCell>
) }
<TableCell align="right"><Button variant="outlined" color="success" size="small">Active</Button></TableCell>
<TableCell align="right"><Button variant="outlined" color="success" size="small">Edit</Button></TableCell>
</TableRow>
@@ -238,37 +217,7 @@ export default function CorporatePlanList() {
No Extra Data
</Typography>
</Box>
{false && <Box sx={{ margin: 1 }}>
<Typography variant="h6" gutterBottom component="div">
Rules
</Typography>
<Table size="small" aria-label="purchases">
<TableHead>
<TableRow>
<TableCell>Date</TableCell>
<TableCell>Customer</TableCell>
<TableCell align="right">Amount</TableCell>
<TableCell align="right">Total price ($)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* {row.history ? row.history.map((historyRow) => ( */}
<TableRow key={row.id}>
<TableCell component="th" scope="row">{row.start} - {row.end}</TableCell>
<TableCell>{row.start}</TableCell>
<TableCell align="right">{row.start}</TableCell>
<TableCell align="right">{row.start}</TableCell>
</TableRow>
{/* ))
: (
<TableRow>
<TableCell colSpan={8}>No Data</TableCell>
</TableRow>
)
} */}
</TableBody>
</Table>
</Box>}
{false && <Box sx={{ margin: 1 }} />}
</Collapse>
</TableCell>
</TableRow>
@@ -296,7 +245,7 @@ export default function CorporatePlanList() {
const loadDataTableData = async (appliedFilter = null) => {
setDataTableLoading(true);
const filter = appliedFilter ? appliedFilter : Object.fromEntries([...searchParams.entries()]);
const response = await axios.get('/corporates/'+corporate_id+'/plans', { params: filter });
const response = await axios.get('/corporates/'+corporate_id+'/members', { params: filter });
// console.log(response.data);
setDataTableLoading(false);
@@ -327,54 +276,9 @@ export default function CorporatePlanList() {
<TableBody>
<TableRow>
<TableCell style={headStyle} align="left" />
<TableCell style={headStyle} align="left">Service</TableCell>
<TableCell style={headStyle} align="left">Plan</TableCell>
<TableCell style={headStyle} align="left">Code</TableCell>
<TableCell style={headStyle} align="left">Type</TableCell>
<TableCell style={headStyle} align="left">Start</TableCell>
<TableCell style={headStyle} align="left">End</TableCell>
<TableCell style={headStyle} align="left">Referral</TableCell>
<TableCell style={headStyle} align="left">Referral Source</TableCell>
<TableCell style={headStyle} align="left">Referral Duration</TableCell>
<TableCell style={headStyle} align="left">Family Plan</TableCell>
<TableCell style={headStyle} align="left">Family Sharing Overflow</TableCell>
<TableCell style={headStyle} align="left">Plan Limit</TableCell>
<TableCell style={headStyle} align="left">Layer ID</TableCell>
<TableCell style={headStyle} align="left">Layer Condition</TableCell>
<TableCell style={headStyle} align="left">Budget Type</TableCell>
<TableCell style={headStyle} align="left">Budget Code</TableCell>
<TableCell style={headStyle} align="left">Budget Condition</TableCell>
<TableCell style={headStyle} align="left">Surgery</TableCell>
<TableCell style={headStyle} align="left">Non Surgery</TableCell>
<TableCell style={headStyle} align="left">Max/Claim</TableCell>
<TableCell style={headStyle} align="left">Max Count of Claim</TableCell>
<TableCell style={headStyle} align="left">Area</TableCell>
<TableCell style={headStyle} align="left">Shared Plan</TableCell>
<TableCell style={headStyle} align="left">Limit Shared Type</TableCell>
<TableCell style={headStyle} align="left">Cashless(%)</TableCell>
<TableCell style={headStyle} align="left">Reimbursement(%)</TableCell>
<TableCell style={headStyle} align="left">Digital(%)</TableCell>
<TableCell style={headStyle} align="left">CoShare M(%)</TableCell>
<TableCell style={headStyle} align="left">CoShare S(%)</TableCell>
<TableCell style={headStyle} align="left">CoShare C(%)</TableCell>
<TableCell style={headStyle} align="left">Cashless Deductible</TableCell>
<TableCell style={headStyle} align="left">Reimbursement Deductible</TableCell>
<TableCell style={headStyle} align="left">Digital Deductible</TableCell>
<TableCell style={headStyle} align="left">DeductibleM</TableCell>
<TableCell style={headStyle} align="left">DeductibleS</TableCell>
<TableCell style={headStyle} align="left">DeductibleC</TableCell>
<TableCell style={headStyle} align="left">CoShare & Deductible Condition</TableCell>
<TableCell style={headStyle} align="left">MSC</TableCell>
<TableCell style={headStyle} align="left">Gender</TableCell>
<TableCell style={headStyle} align="left">Min Age</TableCell>
<TableCell style={headStyle} align="left">Max Age</TableCell>
<TableCell style={headStyle} align="left">Rule of Excess</TableCell>
<TableCell style={headStyle} align="left">Max Excess Covered</TableCell>
<TableCell style={headStyle} align="left">Prorate Type</TableCell>
<TableCell style={headStyle} align="left">Prorate Lookup</TableCell>
<TableCell style={headStyle} align="left">Currency</TableCell>
<TableCell style={headStyle} align="left">Reinstatement Surgery</TableCell>
<TableCell style={headStyle} align="left">Period of Surgery</TableCell>
{ columns.map((column, index) => (
<TableCell style={headStyle} key={index} align={column.align}>{column.label}</TableCell>
)) }
<TableCell style={headStyle} align="right">Status</TableCell>
<TableCell style={headStyle} align="right">Action</TableCell>
</TableRow>
@@ -396,8 +300,8 @@ export default function CorporatePlanList() {
</TableBody>
) : (
<TableBody>
{dataTableData.data.map(row => (
<Row key={row.code} row={row} />
{dataTableData.data.map((row, index) => (
<Row key={index} row={row} />
))}
</TableBody>
)

56
lang/en/enrollment.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Enrollment Import Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during import enrollment for various
|
*/
"RECORD_MODE_REQUIRED" => "Record mode must be filled",
"MODE_UNAVAILABLE" => "Record mode for member is not available",
"RECORD_TYPE_REQUIRED" => "Record Type must be filled for member (Member ID)",
"MEMBER_EXISTS" => "Member (:member_id) for policy (:policy_id) already exist in database",
"MEMBER_NOT_EXISTS" => "Member (Member ID) for policy (Policy No) not found",
"MEMBER_INACTIVE" => "Member (Member ID) for policy (Policy No) is inactive",
"MEMBER_NO_CHANGE" => "No changes in plan/ personal info found",
"MEMBER_EXPIRY_DATE_NO_CHANGE" => "No changes in member effective/ expiry date found",
"MEMBER_EXPIRY_DATE_INVALID" => "Member Effective Date must be before or equal to Member Expiry Date",
"MEMBER_RENEWAL_STILL_ACTIVE" => "Policy period is still active, please use mode 11/12/13",
"OPTION_MODE_INVALID_FORMAT" => "Option Mode must follow delimited format",
"UNIQUE_CHANGE_PRINCIPAL_INVALID" => "No changes in the following info found:
> Corporate Code (Field 7)
> Policy No (Field 15)
> Member ID (Field 4)
> Record Type (Field 2)",
"UNIQUE_CHANGE_DEPENDANT_INVALID" => "No changes in the following info found:
> Corporate Code (Field 7)
> Policy No (Field 15)
> Record Type (Field 2)
> Payor ID (Field 3)",
"MEMBER_EXPIRY_MUST_BE_AFTER_TODAY" => "Valid if Activation Date is later than member effective date, not empty and in YYYYMMDD format",
"PLAN_NOT_FOUND" => "Plan ID inactive / not found in the system",
"PAYOR_ID_REQUIRED" => "Payor ID must be filled for member (Member ID)",
"MEMBER_ID_REQUIRED" => "Member ID must be filled",
"PRINCIPAL_ID_NOT_REQUIRED" => "Mapping ID should only be filled for dependents",
"CORPORATE_ID_REQUIRED" => "Corporate ID must be filled",
"PRINCIPAL_ID_REQUIRED" => "Mapping ID must be filled",
"BRANCH_CODE_NOT_REQUIRED" => "Dependents don't need to fill in Branch Code ",
"INVALID_LANGUAGE" => "Language (field 12) is invalid",
"INVALID_TYPE_OF_WORK" => "Type of work (field 13) is invalid",
"INVALID_RACE" => "Race (field 14) is invalid",
"POLICY_NUMBER_REQUIRED" => "Policy Number must be filled for member (Member ID)",
"MEMBER_EFFECTIVE_REQUIRED" => "Member's Effective Date must be filled for member (Member ID)",
"MEMBER_EXPIRY_REQUIRED" => "Member's Expiry Date must be filled for member (Member ID)",
"INVALID_MARITAL_STATUS" => "Marital Status (field 16) is invalid",
"NAME_REQUIRED" => "Member Name must be filled",
"PHONE_INVALID" => "Telephone - Mobile must follow +628 format",
"EMAIL_INVALID" => "Email must folllow email format e.g. xx@gmail.com",
"DATE_OF_BIRTH_REQUIRED" => "Date of Birth must be filled",
"SEX_REQUIRED" => "Sex must be filled",
];