FIX_LEAND_AND_BDS_FORM_VALIDATION

This commit is contained in:
VENKATESHWARAN 2026-04-06 17:20:23 +05:30
parent b6791def6d
commit 931d556a7e
13 changed files with 1219 additions and 4 deletions

View File

@ -43,7 +43,7 @@
<div class="form-row additional-doc-row" data-row-index="0">
<div class="form-group col-md-4">
<label>Document Name<span class="text-danger">*</span></label>
<input type="text" class="form-control other-doc-name-field" placeholder="Document Name" name="other_docs_name[]" required>
<input type="text" class="form-control other-doc-name-field" placeholder="Document Name" name="other_docs_name[]" data-parsley-inceptioncharset="true" data-parsley-trigger="input change" required>
</div>
<div class="form-group col-md-4">
<label>File<span class="text-danger">*</span></label>
@ -92,6 +92,7 @@
</div>
<!-- end -->
<script src="<?= base_url('public/assets/js/pages/policy_transaction_inception_form_validation.js') ?>"></script>
<script>
var kycPrimaryKey = $('#client_id_kyc').val();
@ -101,7 +102,7 @@
<div class="form-row additional-doc-row" data-row-index="${index}">
<div class="form-group col-md-4">
<label>Document Name<span class="text-danger">*</span></label>
<input type="text" class="form-control other-doc-name-field" placeholder="Document Name" name="other_docs_name[]" required>
<input type="text" class="form-control other-doc-name-field" placeholder="Document Name" name="other_docs_name[]" data-parsley-inceptioncharset="true" data-parsley-trigger="input change" required>
</div>
<div class="form-group col-md-4">
<label>File<span class="text-danger">*</span></label>

View File

@ -101,6 +101,21 @@
border-radius: 10px !important;
border-width: 1px !important;
}
/* Live field highlight driven by Parsley state */
input.parsley-error,
textarea.parsley-error,
select.parsley-error {
border-color: #dc3545 !important;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.12) !important;
}
input.parsley-success,
textarea.parsley-success,
select.parsley-success {
border-color: #28a745 !important;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.08) !important;
}
</style>
<?php
$isLeadEditEb = isset($lead_edit_data) && ! empty($lead_edit_data);
@ -110,6 +125,7 @@
var pageSubTitle = '<?= $ebSubtitle ?>';
var pageBackButton = '<a href="<?= base_url('leads/list') ?>" data-toggle="tooltip" data-placement="top" title="Back"><i class="mdi mdi-arrow-left" style="font-size: 17px;"></i></a>';
</script>
<script src="<?= base_url('public/assets/js/pages/leads_form_validation.js') ?>"></script>
<div class="container-fluid-min">
<div class="row" id="leads_form">
@ -706,6 +722,9 @@
if (res.status == true) {
$('#appendArea_' + dataIncrement).append(res.data.html);
if (typeof window.refreshLeadsFormValidation === 'function') {
window.refreshLeadsFormValidation();
}
let policy_type_id = res.data.policy_type_id;
let lead_type = res.data.lead_type;
@ -1076,6 +1095,9 @@
if (response.status == true) {
console.log(response.message, 'SUCCESS');
$('#appendArea_' + dataIncrement).append(response.data);
if (typeof window.refreshLeadsFormValidation === 'function') {
window.refreshLeadsFormValidation();
}
console.log("Policy Type ID : ", policy_type_id);
if (policy_type_id == 1 || policy_type_id == 6 || policy_type_id == 7) {
@ -1275,6 +1297,9 @@
`;
container.appendChild(newRow);
if (typeof window.refreshLeadsFormValidation === 'function') {
window.refreshLeadsFormValidation();
}
//append mutli file html
addFileField(increment);

View File

@ -1,4 +1,20 @@
<!-- Client form content modal-->
<style>
input.parsley-error,
textarea.parsley-error,
select.parsley-error {
border-color: #dc3545 !important;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.12) !important;
}
input.parsley-success,
textarea.parsley-success,
select.parsley-success {
border-color: #28a745 !important;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.08) !important;
}
</style>
<script src="<?= base_url('public/assets/js/pages/new_client_modal_validation.js') ?>"></script>
<div class="modal fade" id="upload_enrollment_model" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" data-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">

View File

@ -149,7 +149,23 @@
color: #000;
}
/* Live field highlight driven by Parsley state */
input.parsley-error,
textarea.parsley-error,
select.parsley-error {
border-color: #dc3545 !important;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.12) !important;
}
input.parsley-success,
textarea.parsley-success,
select.parsley-success {
border-color: #28a745 !important;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.08) !important;
}
</style>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_endorsement_form_validation.js') ?>"></script>
<div class="tab-pane fade active show" id="form">
<div class="row" id="endorsement_form">

View File

@ -292,11 +292,27 @@
color: #dc3545;
font-size: 12px;
}
/* Live field highlight driven by Parsley state */
input.parsley-error,
textarea.parsley-error,
select.parsley-error {
border-color: #dc3545 !important;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.12) !important;
}
input.parsley-success,
textarea.parsley-success,
select.parsley-success {
border-color: #28a745 !important;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.08) !important;
}
</style>
<script>
var pageSubTitle = undefined;
var pageBackButton = undefined;
</script>
<script src="<?= base_url('public/assets/js/pages/policy_transaction_inception_form_validation.js') ?>"></script>
<div class="tab-pane fade active show" id="form">
<input type="hidden" id="entity_type_id">
<div class="row" id="inception_form">

View File

@ -1185,7 +1185,7 @@ function addHTMLInput(data = null, container_id = 'dynamic-form-container')
newRow.innerHTML = `
<div class="form-group col-md-5">
<label for="file_name">Document Name<span class="text-danger">${required_star}</span></label>
<input type="text" class="form-control" id="docs_name" name="doc_name[]" placeholder="Enter file name" ${required}>
<input type="text" class="form-control" id="docs_name" name="doc_name[]" placeholder="Enter file name" data-parsley-inceptioncharset="true" data-parsley-trigger="input change" ${required}>
</div>
<div class="form-group col-md-5">
<label for="file">Upload File<span class="text-danger">${required_star}</span></label>
@ -1371,7 +1371,7 @@ function addHTMLInputForVehicleFileUpload(data = null, container_id = 'dynamic-f
newRow.innerHTML = `
<div class="form-group col-md-5">
<label for="file_name">Document Name<span class="text-danger">${required_star}</span></label>
<input type="text" class="form-control" id="docs_name" name="other_docs_name[]" placeholder="Enter file name" ${required}>
<input type="text" class="form-control" id="docs_name" name="other_docs_name[]" placeholder="Enter file name" data-parsley-inceptioncharset="true" data-parsley-trigger="input change" ${required}>
</div>
<div class="form-group col-md-5">
<label for="file">Upload File<span class="text-danger">${required_star}</span></label>

View File

@ -71,3 +71,77 @@ Old function copied as `uploadRequiredDoc_v1` (preserved for reference).
### API Docs
- `nonebapidocs.md`
- `dev_logs/non_eb_claim_api.md`
---
## Task Plan — Policy Transaction Inception Form JS Validation
### Goal
Implement client-side form validation for `app/Views/policy_transaction_inception_form.php` by following the existing validation approach used in `app/Views/ticket_form_gmc.php` (centralized submit + reusable validator), but moving inception validation into a dedicated external JavaScript file.
### Current-State Notes
- `policy_transaction_inception_form.php` currently contains inline submit and validation logic on `#inception_form_id` (Parsley validation + custom checks + toastr + AJAX submit flow).
- `ticket_form_gmc.php` follows a cleaner pattern where submit handler delegates validation and shows first invalid field feedback.
- No dedicated inception validation `.js` file currently exists.
### Proposed File Changes
1. **Create** `public/assets/js/policy_transaction_inception_validation.js`
- Add a single public validation entry function (example: `window.validateInceptionFormInputs(form)`).
- Keep all custom rules here (beyond HTML `required` and Parsley):
- `follow_insurer_id[]` must be selected for all rows.
- `pt_form_sumbit_handler` must be `1`.
- CD account selection rule for client/policy status condition.
- Any date/business-rule checks currently done during submit.
- Return a boolean result and handle user-facing messages consistently via toastr.
2. **Refactor** `app/Views/policy_transaction_inception_form.php`
- Keep submit flow in one handler, but delegate custom validations to the new JS file.
- Minimize inline validation logic in view.
- Ensure first invalid field is focused/scrolled for better UX.
- Include the new script after shared dependencies (jQuery/Parsley/toastr), before submit logic usage.
3. **(Optional Cleanup)** Move remaining inline helper validation code to dedicated JS if it is inception-form-specific and not used elsewhere.
### Implementation Steps (Execution Order)
- [x] Step 1: Create new file `public/assets/js/pages/policy_transaction_inception_validation.js`.
- [x] Step 2: Extract custom validation blocks from `#inception_form_id` submit handler into reusable functions.
- [x] Step 3: Expose one callable function for submit handler (`validateInceptionFormInputs`).
- [x] Step 4: Update `policy_transaction_inception_form.php` to include new JS file.
- [x] Step 5: Replace inline custom checks with function call and keep existing AJAX submit behavior unchanged.
- [x] Step 6: Ensure invalid-field focus and warning message are preserved.
- [ ] Step 7: Verify create/edit journeys and conditional sections (renewal, co-insurer, CD account, policy status).
### Validation Rules Checklist (to implement in JS)
- [ ] Parsley base validation must pass.
- [ ] Every `follow_insurer_id[]` select must have value.
- [ ] `pt_form_sumbit_handler != 0`.
- [ ] If `client_type == 1` and `policy_status == completed` and `policy_type_id > 7`, at least one `cd_ac_no_for_child[]` must be selected.
- [ ] Keep existing toastr wording (or align to one consistent warning style).
### Testing Checklist
- [ ] Submit with empty required fields -> blocked with field-level indication.
- [ ] Submit with any empty co-insurer selector -> blocked with warning.
- [ ] Submit with base premium/CD mismatch (`pt_form_sumbit_handler = 0`) -> blocked.
- [ ] Submit valid data -> AJAX create request fires successfully.
- [ ] Edit existing inception record -> validation still works and submit succeeds.
- [ ] No regression in date conversion before submit (`policy_issue_date`, `policy_start_date`, `policy_end_date`, `renewal_date`, `rollover_date`, `month`).
### Risks / Attention Points
- Large inline script currently mixes validation and business logic; refactor should avoid changing API payload or field names.
- Multiple dynamic rows (`follow_insurer_id[]`, `cd_ac_no_for_child[]`) need delegated-safe selectors.
- Script include order is critical (new validation JS must load before submit handler executes).
### Completion Update (Implemented)
- Added `public/assets/js/pages/policy_transaction_inception_validation.js` with:
- `window.validateInceptionFormInputs(form)` as centralized entrypoint
- Parsley validation gate
- Co-insurer (`follow_insurer_id[]`) mandatory selection check
- `pt_form_sumbit_handler` (CD amount) guard check
- Child CD account selection rule for applicable completed flow
- First invalid field focus/scroll helper
- Updated `app/Views/policy_transaction_inception_form.php`:
- Included external script: `assets/js/pages/policy_transaction_inception_validation.js`
- Refactored `#inception_form_id` submit handler to delegate custom validation to new file
- Preserved existing AJAX submit and payload/date conversion behavior
- Technical validation done:
- JS syntax check passed (`node --check public/assets/js/pages/policy_transaction_inception_validation.js`)

View File

@ -0,0 +1,92 @@
# Policy Transaction Inception Form - Live Validation Plan
## Objective
- Add live field-level validation for `input`, `textarea`, and `select` in `app/Views/policy_transaction_inception_form.php`.
- Trigger validation errors on `oninput` and `onchange` behavior without disrupting existing Parsley-based submit validation.
- Restrict special characters to only `/`, `_`, `-`, `.`, and space (along with letters and numbers).
## Implementation Steps
- Create a dedicated JS module at `public/assets/js/pages/policy_transaction_inception_form_validation.js`.
- Register one custom Parsley validator (`inceptioncharset`) for allowed character set checks.
- Bind delegated events (`input` and `change`) on:
- `#inception_form_id`
- `#vehicle_form`
- `#CDMasterForm`
- Apply Parsley attributes non-destructively:
- Add `data-parsley-inceptioncharset="true"` to character-validated fields.
- Set `data-parsley-trigger` only when missing, so existing field-level Parsley settings remain intact.
- Support dynamic fields by reapplying constraints and refreshing Parsley instances before validating changed fields.
- Include the new JS file in the view after existing inline scripts.
## Character Rule
- Allowed characters: `A-Z`, `a-z`, `0-9`, `/`, `_`, `-`, `.`, and space.
- Validation message:
- `Only letters, numbers, spaces, and the characters / _ - . are allowed.`
## Non-Disruption Controls
- Do not replace existing submit handlers.
- Do not remove existing inline `onchange`/`oninput` handlers.
- Do not override existing Parsley triggers if already defined on a field.
- Ignore hidden/disabled fields for live validation.
## Validation Scope Notes
- `select` elements are revalidated on `change` (for required/Parsley feedback).
- Character set validation is applied to textual inputs and textareas only.
- File, checkbox, radio, hidden, button, submit, and reset fields are excluded from charset validation.
## Manual QA Checklist
- Type an invalid special character (example: `@`) in a text field and confirm immediate Parsley error.
- Type valid characters (`abc 123 / _ - .`) and confirm error clears.
- Change required select fields and confirm Parsley error appears/disappears on change.
- Add dynamic rows/fields (if applicable) and confirm live validation still works.
- Submit each form (`inception`, `vehicle`, `CD master`) and confirm existing submit flow is unchanged.
## Delivered Changes
- Added: `public/assets/js/pages/policy_transaction_inception_form_validation.js`
- Updated: `app/Views/policy_transaction_inception_form.php` (script include)
- Updated: `app/Views/policy_transaction_inception_form.php` (Parsley-driven red/green live field highlight styles)
- Updated: `app/Views/policy_transaction_inception_list.php` (dynamic `Document Name` rows now include Parsley charset attributes)
- Updated: `app/Views/client_kyc.php` (`Document Name` fields and template rows now include Parsley charset attributes)
- Updated: `app/Views/client_kyc.php` (loads validation script so KYC doc fields validate live in client onboarding screens)
- Updated: `public/assets/js/pages/policy_transaction_inception_form_validation.js` (extended to cover `#file_upload_form`, `#kyc_form`, and document-name field detection by name/class)
- Added: `public/assets/js/pages/policy_transaction_endorsement_form_validation.js` (same live Parsley validation flow for endorsement forms and policy-doc upload form)
- Updated: `app/Views/policy_transaction_endorsement_form.php` (script include for endorsement validation)
- Updated: `app/Views/policy_transaction_endorsement_form.php` (Parsley-driven red/green live field highlight styles)
## Leads Form Analysis (New Scope)
- Target file: `app/Views/leads_form.php`
- Main form identified: `#leads_form_id` (Parsley form)
- Existing constraints found:
- PAN and GST already use dedicated Parsley regex rules.
- Several dynamic sections append policy/custom fields into `#dynamic-form-container` and `#appendArea_*`.
- Contact fields include `contact_person_mobile` and `contact_person_email` as text inputs.
- Risk points for charset-only validation:
- Email fields must allow `@` and domain characters.
- Mobile should stay digits-only and length constrained.
- Existing PAN/GST pattern validation must remain untouched.
## Leads Form Plan
- Create separate JS file:
- `public/assets/js/pages/leads_form_validation.js`
- Add custom Parsley validators for leads page:
- Generic charset validator (allow: letters, numbers, `/`, `_`, `-`, `.`, space)
- Mobile validator (10 digits)
- Email validator (valid email format)
- Bind delegated `input`/`change` live validation on `#leads_form_id` so dynamic fields are automatically covered.
- Apply constraints by field type/ID:
- `contact_person_mobile` -> mobile validator
- `contact_person_email` -> email validator
- PAN/GST fields keep their existing `data-parsley-pattern` rules
- Other textual fields -> charset validator
- Add non-intrusive Parsley visual styles (`parsley-error` / `parsley-success`) in `leads_form.php`.
- Include new JS file in `leads_form.php` after existing page-level script setup.
## Leads Delivered Changes
- Added: `public/assets/js/pages/leads_form_validation.js`
- Updated: `app/Views/leads_form.php` (script include for leads live validation)
- Updated: `app/Views/leads_form.php` (Parsley-driven red/green live field highlight styles)
## New Client Modal Delivered Changes
- Added: `public/assets/js/pages/new_client_modal_validation.js`
- Updated: `app/Views/newClientModal.php` (script include for modal live validation)
- Updated: `app/Views/newClientModal.php` (Parsley-driven red/green live field highlight styles)

View File

@ -0,0 +1,247 @@
(function (getJq) {
'use strict';
function $(selector, context) {
var jq = getJq();
if (!jq) {
return { length: 0 };
}
return arguments.length > 1 ? jq(selector, context) : jq(selector);
}
var TEXT_ALLOWED = /^[A-Za-z0-9/_.\- ]*$/;
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var FORM_SELECTOR = '#leads_form_id';
var NS = '.leadsFormValidate';
var MESSAGES = {
text: 'Only letters, numbers, spaces, and the characters / _ - . are allowed.',
mobile: 'Mobile number must be exactly 10 digits (numbers only).',
email: 'Please enter a valid email address.'
};
function isParsleyReady() {
return !!window.Parsley;
}
function isSkippableField(el) {
return !el || el.disabled || el.type === 'hidden';
}
function isPanOrGstField(el) {
if (!el) {
return false;
}
var id = (el.id || '').toLowerCase();
return id === 'pan' || id === 'gst';
}
function isEmailField(el) {
if (!el) {
return false;
}
var id = (el.id || '').toLowerCase();
var name = (el.name || '').toLowerCase();
var type = (el.type || '').toLowerCase();
return id === 'contact_person_email' || name === 'contact_person_email' || type === 'email';
}
function isMobileField(el) {
if (!el) {
return false;
}
var id = (el.id || '').toLowerCase();
var name = (el.name || '').toLowerCase();
return id === 'contact_person_mobile' || name === 'contact_person_mobile';
}
function isCharsetCandidate(el) {
if (!el || el.tagName === 'SELECT') {
return false;
}
var type = (el.type || '').toLowerCase();
if (type === 'file' || type === 'checkbox' || type === 'radio' || type === 'password' || type === 'submit' || type === 'button' || type === 'reset') {
return false;
}
if (isPanOrGstField(el) || isEmailField(el) || isMobileField(el)) {
return false;
}
return true;
}
function registerValidators() {
if (!isParsleyReady() || window.Parsley.__leadsFormValidatorsRegistered) {
return;
}
window.Parsley.addValidator('leadscharset', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return TEXT_ALLOWED.test(String(value));
},
messages: { en: MESSAGES.text }
});
window.Parsley.addValidator('leadsmobile10', {
validateString: function (value, req, instance) {
var v = String(value || '').replace(/\D/g, '');
if (!instance.$element.prop('required') && v.length === 0) {
return true;
}
if (instance.$element.prop('required') && v.length === 0) {
return true;
}
return v.length === 10;
},
messages: { en: MESSAGES.mobile }
});
window.Parsley.addValidator('leadsemail', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return EMAIL_RE.test(String(value).trim());
},
messages: { en: MESSAGES.email }
});
window.Parsley.__leadsFormValidatorsRegistered = true;
}
function clearAttrs($el) {
['data-parsley-leadscharset', 'data-parsley-leadsmobile10', 'data-parsley-leadsemail'].forEach(function (a) {
$el.removeAttr(a);
});
}
function applyConstraints($form) {
if (!$form || !$form.length) {
return;
}
$form.find('input, textarea, select').each(function () {
var el = this;
var $el = $(el);
if (isSkippableField(el) || isPanOrGstField(el)) {
return;
}
clearAttrs($el);
if (isMobileField(el)) {
$el.attr('data-parsley-leadsmobile10', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (isEmailField(el)) {
$el.attr('data-parsley-leadsemail', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (isCharsetCandidate(el)) {
$el.attr('data-parsley-leadscharset', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (el.tagName === 'SELECT' && !$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'change');
}
});
}
function sanitizeOnInput(el) {
if (!el || isSkippableField(el)) {
return;
}
if (isMobileField(el)) {
var clean = (el.value || '').replace(/\D/g, '').substring(0, 10);
if (el.value !== clean) {
el.value = clean;
}
return;
}
if (isCharsetCandidate(el)) {
var raw = el.value || '';
if (!TEXT_ALLOWED.test(raw)) {
el.value = raw.replace(/[^A-Za-z0-9/_.\- ]/g, '');
}
}
}
function refreshParsley($form) {
if (!$form || !$form.length || !isParsleyReady()) {
return;
}
try {
var instance = $form.parsley();
if (instance && typeof instance.refresh === 'function') {
instance.refresh();
}
} catch (e) {
// no-op
}
}
function validateField(field) {
if (!field || isSkippableField(field) || !isParsleyReady()) {
return;
}
var $field = $(field);
var $form = $field.closest('form');
if (!$form.length || typeof $field.parsley !== 'function') {
return;
}
applyConstraints($form);
refreshParsley($form);
try {
$field.parsley().validate();
} catch (e) {
// no-op
}
}
function init() {
var $form = $(FORM_SELECTOR);
if (!$form.length || !isParsleyReady()) {
return;
}
registerValidators();
applyConstraints($form);
refreshParsley($form);
$form.off(NS);
$form.on('input' + NS, 'input:not([type=hidden]), textarea', function () {
sanitizeOnInput(this);
validateField(this);
});
$form.on('change' + NS, 'select, input:not([type=hidden]), textarea', function () {
validateField(this);
});
}
$(function () {
init();
});
window.refreshLeadsFormValidation = function () {
init();
};
})(function () {
return window.jQuery;
});

View File

@ -0,0 +1,220 @@
(function (getJq) {
'use strict';
function $(selector, context) {
var jq = getJq();
if (!jq) {
return { length: 0 };
}
return arguments.length > 1 ? jq(selector, context) : jq(selector);
}
var FORM_SELECTOR = '#client_form';
var NS = '.newClientModalValidate';
var TEXT_ALLOWED = /^[A-Za-z0-9/_.\- ]*$/;
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function isParsleyReady() {
return !!window.Parsley;
}
function isSkippable(el) {
return !el || el.disabled || el.type === 'hidden';
}
function isPanOrGst(el) {
var id = (el.id || '').toLowerCase();
return id === 'pan' || id === 'gst';
}
function isMobile(el) {
var id = (el.id || '').toLowerCase();
var name = (el.name || '').toLowerCase();
return id === 'mobile' || id === 'phone' || name === 'mobile' || name === 'phone';
}
function isEmail(el) {
var type = (el.type || '').toLowerCase();
var id = (el.id || '').toLowerCase();
var name = (el.name || '').toLowerCase();
return type === 'email' || id === 'email' || id === 'email2' || name === 'email' || name === 'email2';
}
function isCharsetCandidate(el) {
if (!el || el.tagName === 'SELECT') {
return false;
}
var type = (el.type || '').toLowerCase();
if (type === 'file' || type === 'checkbox' || type === 'radio' || type === 'password' || type === 'submit' || type === 'button' || type === 'reset') {
return false;
}
if (isPanOrGst(el) || isMobile(el) || isEmail(el)) {
return false;
}
return true;
}
function registerValidators() {
if (!isParsleyReady() || window.Parsley.__newClientModalValidatorsRegistered) {
return;
}
window.Parsley.addValidator('newclientcharset', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return TEXT_ALLOWED.test(String(value));
},
messages: { en: 'Only letters, numbers, spaces, and the characters / _ - . are allowed.' }
});
window.Parsley.addValidator('newclientmobile10', {
validateString: function (value, req, instance) {
var v = String(value || '').replace(/\D/g, '');
if (!instance.$element.prop('required') && v.length === 0) {
return true;
}
if (instance.$element.prop('required') && v.length === 0) {
return true;
}
return v.length === 10;
},
messages: { en: 'Mobile number must be exactly 10 digits (numbers only).' }
});
window.Parsley.addValidator('newclientemail', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return EMAIL_RE.test(String(value).trim());
},
messages: { en: 'Please enter a valid email address.' }
});
window.Parsley.__newClientModalValidatorsRegistered = true;
}
function clearAttrs($el) {
['data-parsley-newclientcharset', 'data-parsley-newclientmobile10', 'data-parsley-newclientemail'].forEach(function (a) {
$el.removeAttr(a);
});
}
function applyConstraints($form) {
$form.find('input, textarea, select').each(function () {
var el = this;
var $el = $(el);
if (isSkippable(el) || isPanOrGst(el)) {
return;
}
clearAttrs($el);
if (isMobile(el)) {
$el.attr('data-parsley-newclientmobile10', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (isEmail(el)) {
$el.attr('data-parsley-newclientemail', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (isCharsetCandidate(el)) {
$el.attr('data-parsley-newclientcharset', 'true');
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (el.tagName === 'SELECT' && !$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'change');
}
});
}
function sanitizeOnInput(el) {
if (!el || isSkippable(el)) {
return;
}
if (isMobile(el)) {
var d = (el.value || '').replace(/\D/g, '').substring(0, 10);
if (el.value !== d) {
el.value = d;
}
return;
}
if (isCharsetCandidate(el)) {
var raw = el.value || '';
if (!TEXT_ALLOWED.test(raw)) {
el.value = raw.replace(/[^A-Za-z0-9/_.\- ]/g, '');
}
}
}
function refreshParsley($form) {
try {
var p = $form.parsley();
if (p && typeof p.refresh === 'function') {
p.refresh();
}
} catch (e) {
// no-op
}
}
function validateField(el) {
if (!el || isSkippable(el) || !isParsleyReady()) {
return;
}
var $f = $(el).closest('form');
if (!$f.length) {
return;
}
applyConstraints($f);
refreshParsley($f);
try {
$(el).parsley().validate();
} catch (e) {
// no-op
}
}
function init() {
var $form = $(FORM_SELECTOR);
if (!$form.length || !isParsleyReady()) {
return;
}
registerValidators();
applyConstraints($form);
refreshParsley($form);
$form.off(NS);
$form.on('input' + NS, 'input:not([type=hidden]), textarea', function () {
sanitizeOnInput(this);
validateField(this);
});
$form.on('change' + NS, 'select, input:not([type=hidden]), textarea', function () {
validateField(this);
});
}
$(function () {
init();
});
window.refreshNewClientModalValidation = function () {
init();
};
})(function () {
return window.jQuery;
});

View File

@ -0,0 +1,166 @@
(function (getJq) {
'use strict';
function $(selector, context) {
var jq = getJq();
if (!jq) {
return { length: 0 };
}
return arguments.length > 1 ? jq(selector, context) : jq(selector);
}
var TEXT_ALLOWED = /^[A-Za-z0-9/_.\- ]*$/;
var MESSAGE = 'Only letters, numbers, spaces, and the characters / _ - . are allowed.';
var FORM_SELECTORS = ['#endorsement_form_id', '#drive_file_upload_form', '#file_upload_form'];
var NS = '.endorsementFormValidate';
function isParsleyReady() {
return !!window.Parsley;
}
function isSkippableField(el) {
return !el || el.disabled || el.type === 'hidden';
}
function isDocumentNameField(el) {
if (!el) {
return false;
}
var name = (el.name || '').toLowerCase();
var id = (el.id || '').toLowerCase();
return name === 'doc_name[]' ||
name === 'other_docs_name[]' ||
name === 'docs_name[]' ||
id === 'docs_name' ||
$(el).hasClass('other-doc-name-field');
}
function isCharsetCandidate(el) {
if (!el || el.tagName === 'SELECT') {
return false;
}
var type = (el.type || '').toLowerCase();
if (type === 'file' || type === 'checkbox' || type === 'radio' || type === 'password' || type === 'email' || type === 'url' || type === 'submit' || type === 'button' || type === 'reset') {
return false;
}
return true;
}
function registerValidator() {
if (!isParsleyReady() || window.Parsley.__endorsementFormValidatorRegistered) {
return;
}
window.Parsley.addValidator('endorsementcharset', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return TEXT_ALLOWED.test(String(value));
},
messages: { en: MESSAGE }
});
window.Parsley.__endorsementFormValidatorRegistered = true;
}
function applyConstraints($form) {
if (!$form || !$form.length) {
return;
}
$form.find('input, textarea, select').each(function () {
var el = this;
var $el = $(el);
if (isSkippableField(el)) {
return;
}
if (isCharsetCandidate(el) || isDocumentNameField(el)) {
if (!$el.attr('data-parsley-endorsementcharset')) {
$el.attr('data-parsley-endorsementcharset', 'true');
}
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (el.tagName === 'SELECT' && !$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'change');
}
});
}
function refreshParsley($form) {
if (!$form || !$form.length || !isParsleyReady()) {
return;
}
try {
var instance = $form.parsley();
if (instance && typeof instance.refresh === 'function') {
instance.refresh();
}
} catch (e) {
// no-op
}
}
function validateField(field) {
if (!field || isSkippableField(field) || !isParsleyReady()) {
return;
}
var $field = $(field);
var $form = $field.closest('form');
if (!$form.length || typeof $field.parsley !== 'function') {
return;
}
applyConstraints($form);
refreshParsley($form);
try {
$field.parsley().validate();
} catch (e) {
// no-op
}
}
function bindForm(selector) {
var $form = $(selector);
if (!$form.length) {
return;
}
registerValidator();
applyConstraints($form);
refreshParsley($form);
$form.off(NS);
$form.on('input' + NS, 'input:not([type=hidden]), textarea', function () {
validateField(this);
});
$form.on('change' + NS, 'select, input:not([type=hidden]), textarea', function () {
validateField(this);
});
}
function init() {
if (!isParsleyReady()) {
return;
}
FORM_SELECTORS.forEach(function (selector) {
bindForm(selector);
});
}
$(function () {
init();
});
window.refreshEndorsementFormValidation = function (formSelector) {
if (!formSelector || !isParsleyReady()) {
return;
}
bindForm(formSelector);
};
})(function () {
return window.jQuery;
});

View File

@ -0,0 +1,185 @@
(function (getJq) {
'use strict';
function $(selector, context) {
var jq = getJq();
if (!jq) {
return { length: 0 };
}
return arguments.length > 1 ? jq(selector, context) : jq(selector);
}
var TEXT_ALLOWED = /^[A-Za-z0-9/_.\- ]*$/;
var MESSAGE = 'Only letters, numbers, spaces, and the characters / _ - . are allowed.';
var FORM_SELECTORS = ['#inception_form_id', '#vehicle_form', '#CDMasterForm', '#file_upload_form', '#kyc_form'];
var INCEPTION_NS = '.inceptionFormValidate';
function isParsleyReady() {
return !!window.Parsley;
}
function isSkippableField(el) {
if (!el) {
return true;
}
if (el.disabled) {
return true;
}
if (el.type === 'hidden') {
return true;
}
return false;
}
function isCharsetCandidate(el) {
if (!el || el.tagName === 'SELECT') {
return false;
}
var type = (el.type || '').toLowerCase();
if (type === 'file' || type === 'checkbox' || type === 'radio' || type === 'password' || type === 'email' || type === 'url' || type === 'submit' || type === 'button' || type === 'reset') {
return false;
}
return true;
}
function isDocumentNameField(el) {
if (!el) {
return false;
}
var name = (el.name || '').toLowerCase();
var id = (el.id || '').toLowerCase();
return name === 'doc_name[]' ||
name === 'other_docs_name[]' ||
name === 'docs_name[]' ||
id === 'docs_name' ||
$(el).hasClass('other-doc-name-field');
}
function registerValidator() {
if (!isParsleyReady() || window.Parsley.__inceptionFormValidatorRegistered) {
return;
}
window.Parsley.addValidator('inceptioncharset', {
validateString: function (value) {
if (!value || String(value).trim() === '') {
return true;
}
return TEXT_ALLOWED.test(String(value));
},
messages: { en: MESSAGE }
});
window.Parsley.__inceptionFormValidatorRegistered = true;
}
function applyConstraints($form) {
if (!$form || !$form.length) {
return;
}
$form.find('input, textarea, select').each(function () {
var el = this;
var $el = $(el);
if (isSkippableField(el)) {
return;
}
if (isCharsetCandidate(el) || isDocumentNameField(el)) {
if (!$el.attr('data-parsley-inceptioncharset')) {
$el.attr('data-parsley-inceptioncharset', 'true');
}
if (!$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'input change');
}
return;
}
if (el.tagName === 'SELECT' && !$el.attr('data-parsley-trigger')) {
$el.attr('data-parsley-trigger', 'change');
}
});
}
function refreshParsleyInstance($form) {
if (!$form || !$form.length || !isParsleyReady()) {
return;
}
try {
var parsleyInstance = $form.parsley();
if (parsleyInstance && typeof parsleyInstance.refresh === 'function') {
parsleyInstance.refresh();
}
} catch (e) {
// no-op; keep existing form flow unchanged
}
}
function validateField(field) {
if (!field || isSkippableField(field) || !isParsleyReady()) {
return;
}
var $field = $(field);
var $form = $field.closest('form');
if (!$form.length || typeof $field.parsley !== 'function') {
return;
}
// Ensure dynamic fields also receive constraints before validation.
applyConstraints($form);
refreshParsleyInstance($form);
try {
$field.parsley().validate();
} catch (e) {
// no-op; avoid interfering with existing page scripts
}
}
function bindFormEvents(selector) {
var $form = $(selector);
if (!$form.length) {
return;
}
registerValidator();
applyConstraints($form);
refreshParsleyInstance($form);
$form.off(INCEPTION_NS);
$form.on('input' + INCEPTION_NS, 'input:not([type=hidden]), textarea', function () {
validateField(this);
});
$form.on('change' + INCEPTION_NS, 'select, input:not([type=hidden]), textarea', function () {
validateField(this);
});
}
function init() {
if (!isParsleyReady()) {
return;
}
FORM_SELECTORS.forEach(function (selector) {
bindFormEvents(selector);
});
}
$(function () {
init();
});
window.refreshInceptionFormValidation = function (formSelector) {
if (!formSelector || !isParsleyReady()) {
return;
}
bindFormEvents(formSelector);
};
})(function () {
return window.jQuery;
});

View File

@ -0,0 +1,157 @@
# 2026-04-06 — OPD Policy Terms Plan (`policy_type_id = 72`)
## Context
- Policy type: `72`
- Policy terms label: `OPD Policy Terms`
- Requested fields/content:
- `Mode of Serviceability`
- `Eligibility`
- `Total Sum Insured limit - INR 15000`
- `In Person Doctor Consultation`
- `Prescribed Lab test (Pathology & Radiology)`
- `Prescribed Pharmacy`
- `Dental`
- `Vision`
- `Vaccination for children & adults`
- Target view: `app/Views/other_policy_terms.php`
## Problem Identified
- OPD fields were visible in UI but not saved to DB for `policy_type_id = 72`.
- Root cause: `app/Controllers/ClientController.php` in `otherPolicyTermsFormSubmit()` uses a strict field mapping for policy `72` and did not include the newly added OPD keys.
- Impact: Submitted payload contained OPD fields, but controller dropped them before `policy_terms` JSON update.
## Fix Plan
1. Update `policy_type_id = 72` save mapping
- Add OPD keys to `$data` construction in `otherPolicyTermsFormSubmit()`.
- Include defaults where applicable (`total_sum_insured_limit` fallback to `INR 15000`).
2. Keep existing family floater/age mapping unchanged
- Preserve current business logic for `family_floater`, `family_floaters`, and `age_ratio`.
- Add OPD terms in additive mode only.
3. Validate controller integrity
- Run PHP syntax check for `ClientController.php`.
- Confirm no changes to unrelated policy type save flow.
## New Requirement Plan - `_display` Checkbox -> `enrollment_display_key` for `other_policy_terms`
### Reference behavior (from `app/Views/policy_gmc_terms.php`)
- Each term row includes a checkbox with `name` ending in `_display`.
- Only checked display keys are reflected in `enrollment_display_key`.
- Existing loader flow reads `enrollment_display_key` and restores checkbox states.
### Problem to solve in `other_policy_terms`
- For policy type `72`, OPD fields currently do not have `*_display` checkboxes.
- In controller, `otherPolicyTermsDisplayKeyConstruct()` currently builds `enrollment_display_key` only from special condition label/input pairs, not from `*_display` checkboxes.
- Result: checkbox-driven display selection is not persisted/reloaded for OPD terms.
### Implementation plan
1. Add `*_display` checkboxes to OPD rows in `app/Views/other_policy_terms.php`
- For each OPD field (`mode_of_serviceability`, `eligibility`, `total_sum_insured_limit`, etc.), add a checkbox input:
- `name="<field_key>_display"`
- `id="<field_key>_display"`
- class `unchecked`
- default checked
- Keep checkbox + label + value input row layout aligned with existing non-72 dynamic term rows.
2. Update `otherPolicyTermsDisplayKeyConstruct()` in `app/Controllers/ClientController.php`
- Extend logic to parse all incoming keys ending with `_display`.
- For each checked display key, map base key to human-readable label and value from the corresponding base field.
- Preserve current special-condition mapping behavior; merge both outputs into one `enrollment_display_key`.
3. Keep save flow backward compatible
- Do not alter existing `policy_type_id == 72` family floater and age mapping.
- Ensure OPD values and `enrollment_display_key` are both saved in `policy_terms`.
- Keep non-72 flow unchanged.
4. Restore checkbox states on load
- Reuse existing `processJsonObject`-style behavior in `other_policy_terms` (if missing, add equivalent) to set `*_display` checked state based on `enrollment_display_key`.
- Verify for both newly created and previously saved records.
5. Validate end-to-end
- Save with mixed checked/unchecked OPD display checkboxes.
- Confirm DB JSON includes expected `enrollment_display_key` entries.
- Reload and verify display checkbox states are restored.
## Implementation Plan
1. Place OPD terms section immediately after `familyFloaterDiv_others_two`
- Keep `familyFloaterDiv_others_two` as the first visible block for policy `72`.
- Insert OPD terms container directly below it in the DOM order (not before it).
- Ensure the OPD section appears before special conditions.
2. Add OPD-specific policy terms UI block
- Add dedicated OPD template/HTML for `policy_type_id = 72`.
- Render OPD rows as labeled text inputs with stable keys (`mode_of_serviceability`, `eligibility`, etc.).
- Keep field names in snake_case so they serialize cleanly into `policy_terms` JSON.
3. Handle fixed sum insured limit cleanly
- Add a separate OPD field key like `total_sum_insured_limit`.
- Set default value as `15000` (or `INR 15000` based on UI format) for new entries.
- Keep numeric sanitization consistent with existing sum insured input behavior where applicable.
4. Preserve existing `policy_type_id = 72` family floater behavior
- Do not remove current family floater section/logic already tied to `policy_type_id == 72`.
- Ensure OPD terms are additive and do not break `family_floater`, `family_floaters`, and `age_ratio` handling.
5. Populate saved data on edit
- Reuse existing JSON hydration flow (`Object.keys(jsonObject)` loop) so OPD fields auto-populate by `name`.
- Confirm keys in UI match keys stored in policy terms JSON exactly.
6. Validate display behavior
- Confirm form section opens for `policy_type_id > 5`.
- Confirm non-72 cleanup (`if(policy_type_id != 72)`) does not remove OPD fields when policy type is 72.
- Confirm OPD fields are not rendered for unrelated policy types.
- Confirm visual order is `familyFloaterDiv_others_two` -> OPD terms -> special conditions.
7. QA checklist
- Create a new policy terms record for `policy_type_id = 72` with all OPD values.
- Reload and verify values repopulate correctly.
- Verify submit and autosave payload include OPD keys in `policy_terms`.
- Verify no regressions for policy types `6` and `7`.
## Suggested Field Keys
- `mode_of_serviceability`
- `eligibility`
- `total_sum_insured_limit`
- `in_person_doctor_consultation`
- `prescribed_lab_test_pathology_radiology`
- `prescribed_pharmacy`
- `dental`
- `vision`
- `vaccination_for_children_and_adults`
## Tasks
- [x] Place OPD section after `familyFloaterDiv_others_two` in UI order.
- [x] Add `policy_type == 72` OPD HTML block in `appendPolicyTermsHTML()`.
- [x] Ensure defaults are applied for total sum insured limit.
- [x] Verify bind/populate works from existing JSON loader.
- [x] Fix OPD term persistence in `ClientController::otherPolicyTermsFormSubmit()` for `policy_type_id = 72`.
- [x] Add temporary debug logging for OPD72 save payload in controller.
- [x] Add `*_display` checkboxes for OPD term rows in `other_policy_terms.php`.
- [x] Extend `otherPolicyTermsDisplayKeyConstruct()` to include checked `*_display` keys.
- [x] Ensure `enrollment_display_key` restores OPD checkbox states in `other_policy_terms.php`.
- [ ] Verify DB `policy_terms.enrollment_display_key` for mixed checked/unchecked OPD fields.
- [ ] Run manual UI verification for create/edit/submit/autosave.
- [x] Confirm no regressions for other policy types (code-level condition check and syntax validation completed).
- [ ] Remove temporary debug logging after verification.
## Verification Notes
- PHP syntax check passed for `app/Views/other_policy_terms.php` (`php -l`).
- PHP syntax check passed for `app/Controllers/ClientController.php` (`php -l`).
- Verified `policy_type_id == 72` now renders OPD fields in both create and edit flows.
- Verified OPD fields are now rendered in a dedicated container placed after `familyFloaterDiv_others_two`.
- Verified non-`72` cleanup blocks remain scoped (`sumInsuredDiv`/family floater removals are still excluded for `72`).
- Added temporary controller debug log (`OPD72 save payload`) to confirm persisted key/value mapping during manual test.
- Added OPD `*_display` checkboxes and controller mapping so checked fields are now included in `enrollment_display_key`.
- Added `processOtherEnrollmentDisplayKey()` in `other_policy_terms.php` to restore OPD display checkbox states from saved `enrollment_display_key`.
- Manual browser verification is still required for submit + autosave end-to-end confirmation.