quoteModel = new MotorQuoteModel(); $this->executor = new DigitExecutorService(); $this->vehicleMaster = new MotorMasterVehicleModel(); $this->productMaster = new MotorMasterProductModel(); $this->insurerMaster = new MotorMasterPreviousInsurerModel(); $this->ncbMaster = new MotorMasterNcbModel(); $this->prevPolicyTypeMaster = new MotorMasterPreviousPolicyTypeModel(); $this->deductibleMaster = new MotorMasterVoluntaryDeductibleModel(); $this->pincodeMaster = new MotorMasterPincodeModel(); } // ===================== LIST ===================== public function list() { if (strtolower($this->request->getMethod()) === 'post') { $filters = [ 'status' => $this->request->getPost('status'), 'enquiry_id' => $this->request->getPost('enquiry_id'), 'quote_number' => $this->request->getPost('quote_number'), 'license_plate' => $this->request->getPost('license_plate'), ]; $data['quotes'] = $this->quoteModel->getListWithVehicle($filters); return view('digit_motor/list_table', $data); } $data['quotes'] = $this->quoteModel->getListWithVehicle(); $data['statuses'] = [ 'DRAFT' => 'Draft', 'QUOTED' => 'Quoted', 'CREATED' => 'Created', 'KYC_DONE' => 'KYC Done', 'PAID' => 'Paid', 'EFFECTIVE' => 'Effective', 'FAILED' => 'Failed', ]; $data['title'] = 'Digit Motor Quotes'; return $this->loadLayout('digit_motor/list', $data); } // ===================== JOURNEY UI ===================== public function journey($quoteId = null) { $data['title'] = 'Digit Motor Journey'; $data['quote'] = null; $data['quote_id'] = null; if ($quoteId) { $data['quote'] = $this->quoteModel->getDetail((int) $quoteId); $data['quote_id'] = (int) $quoteId; if (!$data['quote']) { return redirect()->to(base_url('digit-motor/list'))->with('error', 'Quote not found.'); } } return $this->loadLayout('digit_motor/journey', $data); } public function detail($quoteId) { $detail = $this->quoteModel->getDetail((int) $quoteId); if (!$detail) { return $this->respond(['status' => false, 'message' => 'Quote not found.'], 404); } return $this->respond(['status' => true, 'data' => $detail]); } // ===================== MASTER LOOKUPS ===================== public function mastersBootstrap() { try { return $this->respond([ 'status' => true, 'data' => [ 'products' => $this->productMaster->activeList(), 'previous_insurers' => $this->insurerMaster->activeList(), 'ncb' => $this->ncbMaster->activeList(), 'previous_policy_types' => $this->prevPolicyTypeMaster->activeList(), 'voluntary_deductibles' => $this->deductibleMaster->activeList(), ], ]); } catch (\Throwable $e) { log_message('error', 'DigitMotor mastersBootstrap: ' . $e->getMessage()); return $this->respond([ 'status' => false, 'message' => 'Master tables not ready. Run: php spark digit-motor:import-masters --create-tables', ], 503); } } public function masterVehicleSearch() { $q = trim((string) ($this->request->getGet('q') ?? '')); if (strlen($q) < 2) { return $this->respond(['status' => true, 'data' => []]); } return $this->respond([ 'status' => true, 'data' => $this->vehicleMaster->search($q, 40), ]); } public function masterVehicleMakes() { $q = trim((string) ($this->request->getGet('q') ?? '')); return $this->respond([ 'status' => true, 'data' => $this->vehicleMaster->distinctMakes($q ?: null, 120), ]); } public function masterVehicleModels() { $make = trim((string) ($this->request->getGet('make') ?? '')); if ($make === '') { return $this->respond(['status' => false, 'message' => 'make is required.'], 422); } $q = trim((string) ($this->request->getGet('q') ?? '')); return $this->respond([ 'status' => true, 'data' => $this->vehicleMaster->distinctModels($make, $q ?: null), ]); } public function masterVehicleVariants() { $make = trim((string) ($this->request->getGet('make') ?? '')); $model = trim((string) ($this->request->getGet('model') ?? '')); if ($make === '' || $model === '') { return $this->respond(['status' => false, 'message' => 'make and model are required.'], 422); } return $this->respond([ 'status' => true, 'data' => $this->vehicleMaster->variants($make, $model), ]); } public function masterPincode($pincode = null) { $pincode = preg_replace('/\D+/', '', (string) ($pincode ?: $this->request->getGet('pincode'))); if (strlen($pincode) !== 6) { return $this->respond(['status' => false, 'message' => 'Valid 6-digit pincode required.'], 422); } $row = $this->pincodeMaster->findActive($pincode); return $this->respond([ 'status' => (bool) $row, 'data' => $row, 'message'=> $row ? null : 'Pincode not found in Digit master.', ], $row ? 200 : 404); } // ===================== API ACTIONS ===================== public function quickQuote() { return $this->runAction(function () { $input = $this->request->getJSON(true) ?: $this->request->getPost(); $rules = [ 'license_plate_number' => 'required|min_length[4]', 'vehicle_maincode' => 'required', 'registration_date' => 'required', 'manufacture_date' => 'required', 'vehicle_identification_number' => 'required|min_length[5]', 'engine_number' => 'required|min_length[5]', 'pincode' => 'required|exact_length[6]', 'insurance_product_code' => 'required', ]; if (!$this->validateDigitInput($input, $rules)) { $errors = $this->validator->getErrors(); $first = reset($errors); return $this->respond([ 'status' => false, 'message' => is_string($first) ? $first : 'Validation failed.', 'errors' => $errors, ], 422); } $masterError = $this->validateQuickQuoteMasters($input); if ($masterError !== null) { return $this->respond([ 'status' => false, 'message' => $masterError, ], 422); } $result = $this->executor->quickQuote($input); return $this->respond([ 'status' => true, 'message' => 'Quick quote generated.', 'data' => $result, ]); }); } public function createQuote($quoteId = null) { return $this->runAction(function () use ($quoteId) { $input = $this->request->getJSON(true) ?: $this->request->getPost(); $quoteId = (int) ($quoteId ?: ($input['quote_id'] ?? 0)); if ($quoteId <= 0) { return $this->respond(['status' => false, 'message' => 'quote_id is required.'], 422); } $rules = [ 'first_name' => 'required', 'mobile' => 'required|exact_length[10]', 'email' => 'required|valid_email', 'pan' => 'required', 'dob' => 'required', 'address' => 'required', 'vehicle_identification_number' => 'required|min_length[5]', 'engine_number' => 'required|min_length[5]', ]; if (!$this->validateDigitInput($input, $rules)) { $errors = $this->validator->getErrors(); $first = reset($errors); return $this->respond([ 'status' => false, 'message' => is_string($first) ? $first : 'Validation failed.', 'errors' => $errors, ], 422); } $result = $this->executor->createQuote($quoteId, $input); return $this->respond([ 'status' => true, 'message' => 'Quote created.', 'data' => $result, ]); }); } public function kycStatus($quoteId) { return $this->runAction(function () use ($quoteId) { $result = $this->executor->kycStatus((int) $quoteId); return $this->respond([ 'status' => true, 'message' => 'KYC status fetched.', 'data' => $result, ]); }); } public function paymentLink($quoteId) { return $this->runAction(function () use ($quoteId) { $input = $this->request->getJSON(true) ?: $this->request->getPost(); $result = $this->executor->paymentLink((int) $quoteId, $input ?: []); return $this->respond([ 'status' => true, 'message' => 'Payment link generated.', 'data' => $result, ]); }); } public function policyStatus($quoteId) { return $this->runAction(function () use ($quoteId) { $result = $this->executor->policyStatus((int) $quoteId); return $this->respond([ 'status' => true, 'message' => 'Policy status fetched.', 'data' => $result, ]); }); } public function policyPdf($quoteId) { return $this->runAction(function () use ($quoteId) { $result = $this->executor->policyPdf((int) $quoteId); return $this->respond([ 'status' => true, 'message' => 'Policy PDF fetched.', 'data' => $result, ]); }); } public function paymentCallback($outcome, $quoteId) { $quoteId = (int) $quoteId; if ($outcome === 'success' && $quoteId > 0) { try { $this->executor->policyStatus($quoteId); } catch (\Throwable $e) { log_message('error', 'DigitMotor payment callback policyStatus: ' . $e->getMessage()); } } return redirect()->to(base_url('digit-motor/journey/' . $quoteId)) ->with($outcome === 'success' ? 'success' : 'error', $outcome === 'success' ? 'Payment return received. Refreshing policy status.' : 'Payment was cancelled or failed.'); } protected function runAction(callable $fn) { try { return $fn(); } catch (DigitApiException $e) { log_message('error', $e->toLogMessage()); $http = $e->getHttpStatus() >= 400 ? $e->getHttpStatus() : 502; if ($e->isInfraError()) { $http = 502; } return $this->respond([ 'status' => false, 'message' => $e->getMessage(), 'digit_code' => $e->getDigitCode(), 'infra' => $e->isInfraError(), ], min($http, 599)); } catch (\Throwable $e) { log_message('error', 'DigitMotor unexpected error: ' . $e->getMessage()); return $this->respond([ 'status' => false, 'message' => 'Something went wrong. Please try again.', ], 500); } } protected function validateDigitInput(array $data, array $rules): bool { $this->validator = \Config\Services::validation(); $this->validator->setRules($rules); return $this->validator->run($data); } /** * Ensure vehicleMaincode / pincode / product exist in Digit masters before API call. */ protected function validateQuickQuoteMasters(array $input): ?string { try { $code = trim((string) ($input['vehicle_maincode'] ?? '')); if ($code === '' || !$this->vehicleMaster->findActive($code)) { return 'Vehicle code "' . $code . '" not found in Digit vehicle master. Select Make → Model → Variant.'; } $pin = preg_replace('/\D+/', '', (string) ($input['pincode'] ?? '')); if (strlen($pin) !== 6 || !$this->pincodeMaster->findActive($pin)) { return 'Pincode "' . ($input['pincode'] ?? '') . '" not found in Digit pin master (may not be serviceable).'; } $product = trim((string) ($input['insurance_product_code'] ?? '')); if ($product !== '') { $prod = $this->productMaster->where('product_code', $product)->where('is_active', 1)->first(); if (!$prod) { return 'Product code "' . $product . '" is not a valid Digit motor product.'; } } $insurer = trim((string) ($input['previous_insurer_code'] ?? '')); if ($insurer !== '') { $row = $this->insurerMaster->where('insurer_code', str_pad($insurer, 3, '0', STR_PAD_LEFT)) ->where('is_active', 1)->first(); if (!$row) { // also try raw code $row = $this->insurerMaster->where('insurer_code', $insurer)->where('is_active', 1)->first(); } if (!$row) { return 'Previous insurer code "' . $insurer . '" not found in Digit previous-insurer master.'; } } } catch (\Throwable $e) { log_message('error', 'DigitMotor master validation skipped: ' . $e->getMessage()); // If masters table missing, don't block — Digit will still validate } return null; } }