diff --git a/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts b/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts index 2dff882693..465309e1ec 100644 --- a/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts +++ b/cypress/component/CommonIntercepts/CommonIntercept.mocks.ts @@ -1,3 +1,4 @@ +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; import { IOpalFinesCourtRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-court-ref-data.interface'; import { IOpalFinesLocalJusticeAreaRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; import { IOpalFinesMajorCreditorRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-major-creditor-ref-data.interface'; @@ -448,6 +449,204 @@ export const OPAL_FINES_RESULT_REF_DATA_MOCK: IOpalFinesResultRefData[] = [ requires_lja: false, manual_enforcement: false, }, + { + result_id: 'ABDC', + result_title: 'Application made for Benefit Deductions', + result_title_cy: 'Cais am dynnu arian o fudd-daliadau', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, LIVE UC or AMEND UC +400.00" },{ "name":"enforcer", "prompt":"Job Center Plus code (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'AEOC', + result_title: 'Attachment of Earnings Order - with Collection Order', + result_title_cy: 'Gorchymyn atafaelu enillion - cyfrif dirwy', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, include employer name" }]', + }, + { + result_id: 'BWTD', + result_title: 'Bail Warrant - dated', + result_title_cy: 'Gwarant Mechniaeth - Gyda dyddiad', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"hearingdate", "prompt":"Adjourned to", "type":"date", "min":1, "language_dependent":false, "hint":"For example, 31/01/2023" },{ "name":"courtcode", "prompt":"Court code", "type":"integer", "min":1, "max":999, "language_dependent":false },{ "name":"enforcer", "prompt":"Warrant enforcer (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'BWTU', + result_title: 'Bail Warrant - undated', + result_title_cy: 'Gwarant Mechniaeth - Heb Ddyddiad', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"baildirection", "prompt":"Date and time (Bail direction)", "type":"text", "min":0, "max":500, "language_dependent":false, "hint":"As ordered in court. For example, 31/01/2023 and 14:30" },{ "name":"courtdetails", "prompt":"Court details", "type":"text", "min":1, "max":100, "language_dependent":false },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'CLAMPO', + result_title: 'Clamping Order', + result_title_cy: '', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, vehicle details held on account" },{ "name":"effectivedate", "prompt":"Effective from date", "type":"date", "language_dependent":true, "hint":"For example, 31/01/2023" },{ "name":"enforcer", "prompt":"Clamping contractor code (Enforcer code)", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'CWN', + result_title: 'Committal Warrant Notice', + result_title_cy: 'Rhybudd o Warant Traddodi', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false", "hint":"For example, failed to comply with suspended committal" },{ "name":"hearingdate", "prompt":"Adjourned to", "type":"date", "min":1, "language_dependent":false, "hint":"For example, 31/01/2023" },{ "name":"courtcode", "prompt":"Court code", "type":"integer", "min":1, "max":999, "language_dependent":false }]', + }, { result_id: 'DW', result_title: 'Warrant of Control', @@ -481,8 +680,328 @@ export const OPAL_FINES_RESULT_REF_DATA_MOCK: IOpalFinesResultRefData[] = [ result_parameters: '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', }, + { + result_id: 'MAN', + result_title: 'Manual Enforcement', + result_title_cy: '', + result_type: 'Result', + active: false, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: undefined, + }, + { + result_id: 'NBWT', + result_title: 'No bail warrant', + result_title_cy: 'Gwarant dim mechniaeth', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: true, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" },{ "name":"courtdetails", "prompt":"Court details", "type":"text", "min":0, "max":100, "language_dependent":false },{ "name":"enforcer", "prompt":"Enforcer code", "type":"enforcers", "min":1, "max":1, "language_dependent":"true" }]', + }, + { + result_id: 'REGF', + result_title: 'Registration of Fine', + result_title_cy: 'Rhybudd Cofrestru', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: true, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: true, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + }, + { + result_id: 'SUMA', + result_title: 'Enforcement Summons (Automatic enforcement only)', + result_title_cy: 'Gwŷs Gorfodi', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: true, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: true, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: true, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + }, + { + result_id: 'TFOOUT', + result_title: 'Transfer of Fine Order to a Court in England or Wales', + result_title_cy: 'Trosglwyddo Gorchymyn Dirwy i Lys yng Nghymru neu Lloegr', + result_type: 'Action', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: false, + enforcement_override: true, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: true, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: true, + lists_monies: false, + generates_warrant: undefined, + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: true, + manual_enforcement: undefined, + result_parameters: undefined, + }, + { + result_id: 'FSN', + result_title: 'Further Steps Notice', + result_title_cy: 'Rhybudd o gamau pellach', + result_type: 'Result', + active: true, + imposition: false, + imposition_accruing: false, + enforcement: true, + enforcement_override: false, + further_enforcement_warn: false, + further_enforcement_disallow: false, + enforcement_hold: false, + requires_enforcer: false, + generates_hearing: false, + collection_order: false, + extend_ttp_disallow: false, + extend_ttp_preserve_last_enf: false, + prevent_payment_card: false, + lists_monies: false, + result_parameters: + '[{ "name":"reason", "prompt":"Reason", "type":"text", "min":1, "max":24, "language_dependent":"false" }]', + imposition_category: undefined, + imposition_allocation_priority: undefined, + imposition_creditor: undefined, + generates_warrant: undefined, + allow_payment_terms: undefined, + requires_employment_data: undefined, + allow_additional_action: undefined, + enf_next_permitted_actions: undefined, + requires_lja: undefined, + manual_enforcement: undefined, + }, ]; +export const OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK: IOpalFinesResultsRefData = { + count: 13, + refData: [ + { + result_id: 'ABDC', + result_title: 'Application made for Benefit Deductions', + result_title_cy: 'Cais am dynnu arian o fudd-daliadau', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + enforcement_override_result: true, + }, + { + result_id: 'AEOC', + result_title: 'Attachment of Earnings Order - with Collection Order', + result_title_cy: 'Gorchymyn atafaelu enillion - cyfrif dirwy', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'BWTD', + result_title: 'Bail Warrant - dated', + result_title_cy: 'Gwarant Mechniaeth - Gyda dyddiad', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'BWTU', + result_title: 'Bail Warrant - undated', + result_title_cy: 'Gwarant Mechniaeth - Heb Ddyddiad', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'CLAMPO', + result_title: 'Clamping Order', + result_title_cy: null, + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'CWN', + result_title: 'Committal Warrant Notice', + result_title_cy: 'Rhybudd o Warant Traddodi', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'DW', + result_title: 'Warrant of Control', + result_title_cy: 'Gwarant Atafaelu', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'MAN', + result_title: 'Manual Enforcement', + result_title_cy: null, + active: false, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'NBWT', + result_title: 'No bail warrant', + result_title_cy: 'Gwarant dim mechniaeth', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'REGF', + result_title: 'Registration of Fine', + result_title_cy: 'Rhybudd Cofrestru', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'SUMA', + result_title: 'Enforcement Summons (Automatic enforcement only)', + result_title_cy: 'Gwŷs Gorfodi', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'TFOOUT', + result_title: 'Transfer of Fine Order to a Court in England or Wales', + result_title_cy: 'Trosglwyddo Gorchymyn Dirwy i Lys yng Nghymru neu Lloegr', + active: true, + result_type: 'Action', + imposition_creditor: null, + imposition_allocation_order: null, + }, + { + result_id: 'FSN', + result_title: 'Further Steps Notice', + result_title_cy: 'Rhybudd o gamau pellach', + active: true, + result_type: 'Result', + imposition_creditor: null, + imposition_allocation_order: null, + }, + ], +}; + export const OPAL_FINES_MAJOR_CREDITOR_REF_DATA_MOCK: IOpalFinesMajorCreditorRefData = { count: 4, refData: [ @@ -659,3 +1178,507 @@ export const OPAL_FINES_PROSECUTOR_REF_DATA_MOCK: IOpalFinesProsecutorRefData = }, ], }; + +export const OPAL_FINES_ENFORCER_REF_DATA_MOCK: IOpalFinesEnforcersRefData = { + count: 83, + refData: [ + { + enforcer_id: 780000000002, + enforcer_code: 3, + name: '00000', + name_cy: null, + }, + { + enforcer_id: 650000000001, + enforcer_code: 840, + name: 'ATCMTest', + name_cy: null, + }, + { + enforcer_id: 470000000001, + enforcer_code: 101, + name: 'BR.Enfone', + name_cy: null, + }, + { + enforcer_id: 470000000003, + enforcer_code: 103, + name: 'BR.Enfthree', + name_cy: null, + }, + { + enforcer_id: 470000000002, + enforcer_code: 102, + name: 'BR.Enftwo', + name_cy: null, + }, + { + enforcer_id: 50000000001, + enforcer_code: 101, + name: 'Cam.Enfone', + name_cy: null, + }, + { + enforcer_id: 50000000003, + enforcer_code: 103, + name: 'Cam.Enfthree', + name_cy: null, + }, + { + enforcer_id: 50000000002, + enforcer_code: 102, + name: 'Cam.Enftwo', + name_cy: null, + }, + { + enforcer_id: 1300000000002, + enforcer_code: 1, + name: 'CIVILIAN ENFORCEMENT OFFICERS', + name_cy: null, + }, + { + enforcer_id: 600000000001, + enforcer_code: 101, + name: 'Dyfed Enforcer 1', + name_cy: null, + }, + { + enforcer_id: 1060000000041, + enforcer_code: 801, + name: 'EnfAE01', + name_cy: 'EnfAE01', + }, + { + enforcer_id: 1060000000043, + enforcer_code: 803, + name: 'EnfAE02', + name_cy: 'EnfAE02', + }, + { + enforcer_id: 1060000000045, + enforcer_code: 802, + name: 'EnfAE03', + name_cy: 'EnfAE03', + }, + { + enforcer_id: 1060000000047, + enforcer_code: 804, + name: 'EnfAE04', + name_cy: 'EnfAE04', + }, + { + enforcer_id: 1060000000049, + enforcer_code: 805, + name: 'EnfAE05', + name_cy: 'EnfAE05', + }, + { + enforcer_id: 780000000001, + enforcer_code: 2, + name: 'Enfone Add3', + name_cy: null, + }, + { + enforcer_id: 600000000021, + enforcer_code: 901, + name: 'Enforcement(Dy) - ABDC', + name_cy: null, + }, + { + enforcer_id: 600000000022, + enforcer_code: 902, + name: 'Enforcement(Dy) - BWTD', + name_cy: null, + }, + { + enforcer_id: 600000000023, + enforcer_code: 903, + name: 'Enforcement(Dy) - BWTU', + name_cy: null, + }, + { + enforcer_id: 600000000024, + enforcer_code: 904, + name: 'Enforcement(Dy) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 600000000025, + enforcer_code: 905, + name: 'Enforcement(Dy) - DW', + name_cy: null, + }, + { + enforcer_id: 600000000026, + enforcer_code: 906, + name: 'Enforcement(Dy) - NBWT', + name_cy: null, + }, + { + enforcer_id: 890000000041, + enforcer_code: 901, + name: 'Enforcement(Gw) - ABDC', + name_cy: null, + }, + { + enforcer_id: 890000000042, + enforcer_code: 902, + name: 'Enforcement(Gw) - BWTD', + name_cy: null, + }, + { + enforcer_id: 890000000043, + enforcer_code: 903, + name: 'Enforcement(Gw) - BWTU', + name_cy: null, + }, + { + enforcer_id: 890000000044, + enforcer_code: 904, + name: 'Enforcement(Gw) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 890000000045, + enforcer_code: 905, + name: 'Enforcement(Gw) - DW', + name_cy: null, + }, + { + enforcer_id: 890000000046, + enforcer_code: 906, + name: 'Enforcement(Gw) - NBWT', + name_cy: null, + }, + { + enforcer_id: 1060000000021, + enforcer_code: 901, + name: 'Enforcement(Nw) - ABDC', + name_cy: null, + }, + { + enforcer_id: 1060000000022, + enforcer_code: 902, + name: 'Enforcement(Nw) - BWTD', + name_cy: null, + }, + { + enforcer_id: 1060000000023, + enforcer_code: 903, + name: 'Enforcement(Nw) - BWTU', + name_cy: null, + }, + { + enforcer_id: 1060000000024, + enforcer_code: 904, + name: 'Enforcement(Nw) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 1060000000025, + enforcer_code: 905, + name: 'Enforcement(Nw) - DW', + name_cy: null, + }, + { + enforcer_id: 1060000000026, + enforcer_code: 906, + name: 'Enforcement(Nw) - NBWT', + name_cy: null, + }, + { + enforcer_id: 360000000041, + enforcer_code: 901, + name: 'Enforcement(SW-SMG) - ABDC', + name_cy: null, + }, + { + enforcer_id: 360000000042, + enforcer_code: 902, + name: 'Enforcement(SW-SMG) - BWTD', + name_cy: null, + }, + { + enforcer_id: 360000000043, + enforcer_code: 903, + name: 'Enforcement(SW-SMG) - BWTU', + name_cy: null, + }, + { + enforcer_id: 360000000044, + enforcer_code: 904, + name: 'Enforcement(SW-SMG) - CLAMPO', + name_cy: null, + }, + { + enforcer_id: 360000000045, + enforcer_code: 905, + name: 'Enforcement(SW-SMG) - DW', + name_cy: null, + }, + { + enforcer_id: 360000000046, + enforcer_code: 906, + name: 'Enforcement(SW-SMG) - NBWT', + name_cy: null, + }, + { + enforcer_id: 730000000021, + enforcer_code: 104, + name: 'EnforcerFour', + name_cy: null, + }, + { + enforcer_id: 800000000001, + enforcer_code: 101, + name: 'EnforcerOne', + name_cy: null, + }, + { + enforcer_id: 730000000001, + enforcer_code: 101, + name: 'EnforcerOne', + name_cy: null, + }, + { + enforcer_id: 800000000003, + enforcer_code: 103, + name: 'EnforcerThree', + name_cy: null, + }, + { + enforcer_id: 730000000003, + enforcer_code: 103, + name: 'EnforcerThree', + name_cy: null, + }, + { + enforcer_id: 800000000002, + enforcer_code: 102, + name: 'EnforcerTwo', + name_cy: null, + }, + { + enforcer_id: 730000000002, + enforcer_code: 102, + name: 'EnforcerTwo', + name_cy: null, + }, + { + enforcer_id: 820000000001, + enforcer_code: 101, + name: 'Gm.Enfone', + name_cy: null, + }, + { + enforcer_id: 820000000004, + enforcer_code: 1, + name: 'Gm.Enfone Add2', + name_cy: null, + }, + { + enforcer_id: 820000000003, + enforcer_code: 103, + name: 'Gm.Enfthree', + name_cy: null, + }, + { + enforcer_id: 820000000002, + enforcer_code: 102, + name: 'Gm.Enftwo', + name_cy: null, + }, + { + enforcer_id: 820000000005, + enforcer_code: 2, + name: 'Gm.Enftwo Add3', + name_cy: null, + }, + { + enforcer_id: 890000000001, + enforcer_code: 101, + name: 'Gwent.Enfone', + name_cy: null, + }, + { + enforcer_id: 890000000003, + enforcer_code: 103, + name: 'Gwent.Enfthree', + name_cy: null, + }, + { + enforcer_id: 890000000002, + enforcer_code: 102, + name: 'Gwent.Enftwo', + name_cy: null, + }, + { + enforcer_id: 300000000001, + enforcer_code: 101, + name: 'GY.Enfone', + name_cy: null, + }, + { + enforcer_id: 300000000003, + enforcer_code: 103, + name: 'GY.Enfthree', + name_cy: null, + }, + { + enforcer_id: 300000000002, + enforcer_code: 102, + name: 'GY.Enftwo', + name_cy: null, + }, + { + enforcer_id: 260000000042, + enforcer_code: 105, + name: 'Job Centre Hemel Hempstead', + name_cy: null, + }, + { + enforcer_id: 260000000041, + enforcer_code: 104, + name: 'Job Centre Watford', + name_cy: null, + }, + { + enforcer_id: 260000000021, + enforcer_code: 101, + name: 'NE.Enfone', + name_cy: null, + }, + { + enforcer_id: 260000000023, + enforcer_code: 103, + name: 'NE.Enfthree', + name_cy: null, + }, + { + enforcer_id: 260000000022, + enforcer_code: 102, + name: 'NE.Enftwo', + name_cy: null, + }, + { + enforcer_id: 1060000000001, + enforcer_code: 101, + name: 'North Wales Enforcer 1', + name_cy: null, + }, + { + enforcer_id: 1060000000002, + enforcer_code: 102, + name: 'North Wales Job Centre 1', + name_cy: null, + }, + { + enforcer_id: 890000000021, + enforcer_code: 1, + name: 'NW.Enfone Add2', + name_cy: null, + }, + { + enforcer_id: 890000000022, + enforcer_code: 2, + name: 'NW.Enftwo Add3', + name_cy: null, + }, + { + enforcer_id: 570000000001, + enforcer_code: 101, + name: 'PD.EnfOne', + name_cy: null, + }, + { + enforcer_id: 570000000003, + enforcer_code: 103, + name: 'PD.EnfThree', + name_cy: null, + }, + { + enforcer_id: 570000000002, + enforcer_code: 102, + name: 'PD.EnfTwo', + name_cy: null, + }, + { + enforcer_id: 1030000000001, + enforcer_code: 101, + name: 'PE Enfone', + name_cy: null, + }, + { + enforcer_id: 1030000000003, + enforcer_code: 103, + name: 'PE.Enfthree', + name_cy: null, + }, + { + enforcer_id: 1030000000002, + enforcer_code: 102, + name: 'PE.Enftwo', + name_cy: null, + }, + { + enforcer_id: 260000000043, + enforcer_code: 106, + name: 'Police Hertfordshire', + name_cy: null, + }, + { + enforcer_id: 360000000001, + enforcer_code: 101, + name: 'SouthWales.Enfone', + name_cy: null, + }, + { + enforcer_id: 360000000003, + enforcer_code: 103, + name: 'SouthWales.Enfthree', + name_cy: null, + }, + { + enforcer_id: 360000000002, + enforcer_code: 102, + name: 'SouthWales.Enftwo', + name_cy: null, + }, + { + enforcer_id: 360000000021, + enforcer_code: 104, + name: 'South Wales Job Centre', + name_cy: null, + }, + { + enforcer_id: 770000000001, + enforcer_code: 1, + name: 'The Bailiffs', + name_cy: null, + }, + { + enforcer_id: 770000000003, + enforcer_code: 3, + name: 'The DWP', + name_cy: null, + }, + { + enforcer_id: 260000000001, + enforcer_code: 123, + name: 'The enforcers name', + name_cy: null, + }, + { + enforcer_id: 770000000002, + enforcer_code: 2, + name: 'The police', + name_cy: null, + }, + { + enforcer_id: 780000000021, + enforcer_code: 101, + name: 'Warrant enforcer', + name_cy: null, + }, + ], +}; diff --git a/cypress/component/CommonIntercepts/CommonIntercepts.ts b/cypress/component/CommonIntercepts/CommonIntercepts.ts index 4122205586..c232b385f6 100644 --- a/cypress/component/CommonIntercepts/CommonIntercepts.ts +++ b/cypress/component/CommonIntercepts/CommonIntercepts.ts @@ -8,6 +8,8 @@ import { OPAL_OFFENCE_BY_ID_MOCK, OPAL_FINES_OFFENCES_REF_DATA_MOCK, OPAL_FINES_RESULT_REF_DATA_MOCK, + OPAL_FINES_ENFORCER_REF_DATA_MOCK, + OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK, } from './CommonIntercept.mocks'; /** @@ -75,7 +77,7 @@ export function interceptOffencesById(offenceId: number) { * Intercepts the GET request to the `/sso/authenticated` endpoint and mocks the response * to indicate that the user is authenticated. * - * @returns Cypress.Chainable - The Cypress chainable object for further chaining. + * @returns Cypress.Chainable<> - The Cypress chainable object for further chaining. * * @example * // Usage in a Cypress test @@ -216,3 +218,48 @@ export function interceptResultByCode(resultCode: string) { ) .as('getResultByCode'); } + +/** + * + * @returns {Cypress.Chainable} A Cypress chainable that represents the intercepted request, + * aliased as 'getEnforcementOverrideResults' for later reference in tests. + * + * @example + * ```typescript + * interceptEnforcementOverrideResults(); + * cy.wait('@getEnforcementOverrideResults'); + * ``` + */ + +export function interceptEnforcementOverrideResults() { + const overrideResults = OPAL_FINES_ENF_OVERRIDE_RESULT_REF_DATA_MOCK; + return cy + .intercept('GET', `/opal-fines-service/results?enforcement_override=true`, { + statusCode: 200, + body: { count: overrideResults.refData.length, refData: overrideResults.refData }, + }) + .as('getEnforcementOverrideResults'); +} + +/** + * Intercepts GET requests to the enforcers endpoint and returns mock enforcer data. + * + * @returns {Cypress.Chainable} A Cypress chainable that represents the intercepted request, + * aliased as 'getEnforcersByBU' for later reference in tests. + * + * @example + * ```typescript + * interceptEnforcers(); + * cy.get('[data-testid="enforcer-select"]').click(); + * cy.wait('@getEnforcersByBU'); + * ``` + */ +export function interceptEnforcers() { + const enforcers = OPAL_FINES_ENFORCER_REF_DATA_MOCK; + return cy + .intercept('GET', `/opal-fines-service/enforcers`, { + statusCode: 200, + body: enforcers, + }) + .as('getEnforcersByBU'); +} diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts index 6915e9be30..0b7d7c6344 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/AccountEnquiriesEnforcement.cy.ts @@ -6,7 +6,7 @@ import { } from '../../CommonIntercepts/CommonUserState.mocks'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; -import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENFORCEMENT_STATUS_TAB } from './constants/account_enquiry_enforcement_status_elements'; +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENFORCEMENT_STATUS_TAB } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; import { interceptDefendantHeader, interceptEnforcementStatus } from './intercept/defendantAccountIntercepts'; import { interceptAuthenticatedUser, interceptUserState } from 'cypress/component/CommonIntercepts/CommonIntercepts'; import { IComponentProperties } from './setup/setupComponent.interface'; diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts index 3f39b58cdc..80fbc9f467 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/constants/account_enquiry_enforcement_status_elements.ts @@ -1,6 +1,6 @@ // Stable selectors / visible text hooks for the Defendant Details page. -export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { +export const accACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { headingWithCaption: 'opal-lib-govuk-heading-with-caption', headingName: 'h1.govuk-heading-l', pageHeader: 'opal-lib-custom-page-header', diff --git a/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts b/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts index d1a1026c9d..c845b86d30 100644 --- a/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts +++ b/cypress/component/fineAccountEnquiry/accountEnquiry/intercept/defendantAccountIntercepts.ts @@ -237,3 +237,12 @@ export function interceptEnforcementStatus( }) .as('getEnforcementStatus'); } + +export function interceptPatchDefendantAccount() { + return cy + .intercept('PATCH', `/opal-fines-service/defendant-accounts/*`, { + statusCode: 200, + body: {}, + }) + .as('patchDefendantAccount'); +} diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts new file mode 100644 index 0000000000..5c4659f627 --- /dev/null +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/addEnforcementOverride.cy.ts @@ -0,0 +1,414 @@ +import { DOM_ELEMENTS as ENF_OVR } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; +import { setupAccountEnquiryComponent } from '../accountEnquiry/setup/SetupComponent'; +import { IComponentProperties } from '../accountEnquiry/setup/setupComponent.interface'; +import { DOM_ELEMENTS as VERSION_CONTROL } from '../accountEnquiry/constants/global_version_control_elements'; +import { + interceptAuthenticatedUser, + interceptUserState, + interceptResultByCode, + interceptEnforcementOverrideResults, + interceptLocalJusticeAreas, + interceptEnforcers, +} from 'cypress/component/CommonIntercepts/CommonIntercepts'; +import { USER_STATE_MOCK_PERMISSION_BU77 } from 'cypress/component/CommonIntercepts/CommonUserState.mocks'; +import { + interceptDefendantHeader, + interceptEnforcementStatus, + interceptPatchDefendantAccount, +} from '../accountEnquiry/intercept/defendantAccountIntercepts'; +import { + createDefendantHeaderMockWithName, + DEFENDANT_HEADER_MOCK, +} from '../accountEnquiry/mocks/defendant_details_mock'; +import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@app/flows/fines/services/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; + +const componentProperties: IComponentProperties = { + accountId: '77', + fragments: 'enforcement', + interceptedRoutes: [ + '/access-denied', + '../note/add', + '../debtor/individual/amend', + '../debtor/parentGuardian/amend', + '../enforcement/amend', + '../enforcement/amend-denied', + // Add more routes here as needed + ], +}; +function setupAddEnforcementOverride( + headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')), +) { + let enforcementMock = structuredClone(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK); + enforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: null, + enforcement_override_result_name: null, + }; + + const accountId = headerMock.defendant_account_party_id; + interceptAuthenticatedUser(); + interceptUserState(USER_STATE_MOCK_PERMISSION_BU77); + interceptDefendantHeader(accountId, headerMock, '123'); + interceptEnforcementStatus(accountId, enforcementMock, '123'); + + interceptEnforcementOverrideResults(); + interceptLocalJusticeAreas(); + interceptEnforcers(); + + ['ABDC', 'AEOC', 'BWTD', 'BWTU', 'CLAMPO', 'CWN', 'DW', 'MAN', 'NBWT', 'REGF', 'SUMA', 'TFOOUT'].forEach((code) => + interceptResultByCode(code), + ); + interceptPatchDefendantAccount(); + cy.intercept('/opal-fines-service/results/').as('getResults'); + + setupAccountEnquiryComponent({ ...componentProperties, accountId: accountId }); + cy.get(ENF.addEnforcementOverrideLink).click(); + + return { accountId }; +} + +function commonSetup() { + const headerMock = structuredClone(createDefendantHeaderMockWithName('Robert', 'Thomson')); + headerMock.debtor_type = 'individual'; + + return setupAddEnforcementOverride(headerMock); +} + +function companySetup() { + const headerMock = structuredClone(DEFENDANT_HEADER_MOCK); + headerMock.party_details.organisation_flag = true; + headerMock.party_details.organisation_details = { + organisation_name: 'Test Org Ltd', + organisation_aliases: [], + }; + headerMock.party_details.individual_details = null; + headerMock.debtor_type = 'company'; + + return setupAddEnforcementOverride(headerMock); +} + +describe( + 'Add Enforcement Override - Parent/Guardian', + { tags: ['@JIRA-STORY:PO-1866', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, + () => { + it('AC1a, AC1b. Should render the form with title', { tags: ['@JIRA-KEY:POT-4440'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Mr Robert THOMSON - 177A'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + }); + + it( + 'AC1c, AC1d. Select an enforcement override dropdown, add override button and cancel link', + { tags: ['@JIRA-KEY:POT-4441'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.addOverrideButton).should('exist'); + cy.get(ENF_OVR.cancelLink).should('exist'); + }, + ); + + it( + 'Should support forward keyboard navigation across the add enforcement override form', + { tags: ['@JIRA-KEY:POT-4442'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); + cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get(ENF_OVR.addOverrideButton).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + + cy.get('@getResults.all').should('have.length', 0); + }, + ); + + it('AC2. Enforcer dropdown for valid override', { tags: ['@JIRA-KEY:POT-4443'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('AB'); + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('AEOC').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CLAMPO').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('CWN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('DW').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('FSN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('MAN').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('NBWT').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('REGF').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('SUMA').should('not.exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('not.exist'); + + cy.get(ENF_OVR.dropdownOptions).contains('ABDC').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'ABDC'); + + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + cy.get(ENF_OVR.enforcerDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains('The DWP (3)').should('exist'); + }); + + it('AC3. LJA dropdown for valid override', { tags: ['@JIRA-KEY:POT-4444'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").should('exist'); + }); + + it('AC4a. Error when no enforcement override is selected', { tags: ['@JIRA-KEY:POT-4445'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcement override'); + }); + + it('AC4b. Error when no enforcer is selected', { tags: ['@JIRA-KEY:POT-4446'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('BW'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTU').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('BWTD').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'BWTD'); + cy.get(ENF_OVR.enforcerDropdown).should('exist'); + + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select an enforcer'); + }); + + it('AC4c. Error when no LJA is selected', { tags: ['@JIRA-KEY:POT-4447'] }, () => { + commonSetup(); + + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').should('exist'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('exist'); + + cy.get(ENF_OVR.addOverrideButton).click(); + cy.get(ENF_OVR.errorSummary) + .should('exist') + .contains('There is a problem') + .next() + .should('contain.text', 'Select a Local Justice Area'); + }); + + it( + 'AC5. Valid submission returns to Enforcement tab with success banner and new override panel', + { tags: ['@JIRA-KEY:POT-4448'] }, + () => { + const { accountId } = commonSetup(); + const updatedEnforcementMock = structuredClone( + OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK, + ); + + updatedEnforcementMock.enforcement_override!.enforcement_override_result = { + enforcement_override_result_id: 'TFOOUT', + enforcement_override_result_name: 'Transfer of Fine Order to a Court in England or Wales', + }; + updatedEnforcementMock.enforcement_override!.enforcer = { + enforcer_id: 0, + enforcer_name: '', + }; + updatedEnforcementMock.enforcement_override!.lja = { + lja_id: 4165, + lja_name: "Bedfordshire Magistrates' Court", + }; + + interceptEnforcementStatus(accountId, updatedEnforcementMock, '124'); + + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + + cy.get(ENF_OVR.localJusticeAreaDropdown).click(); + cy.get(ENF_OVR.dropdownOptions).contains("Bedfordshire Magistrates' Court (4165)").click(); + cy.get(ENF_OVR.localJusticeAreaDropdown).should('contain.value', "Bedfordshire Magistrates' Court (4165)"); + + cy.get(ENF_OVR.addOverrideButton).click(); + + cy.wait('@patchDefendantAccount') + .its('request.body.enforcement_override') + .should('deep.equal', { + enforcement_override_result: { + enforcement_override_result_id: 'TFOOUT', + }, + enforcer: null, + lja: { + lja_id: 4165, + }, + }); + + cy.wait('@getEnforcementStatus'); + + cy.get(ENF.tabName).should('exist').and('contain.text', 'Enforcement'); + + cy.get(VERSION_CONTROL.successBanner).should('exist'); + cy.get(VERSION_CONTROL.successBannerText).should('contain', 'Enforcement override added'); + + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverride).should('exist').and('contain.text', 'Enforcement override'); + cy.get(ENF.enforcementOverrideValue).and( + 'contain.text', + 'Transfer of Fine Order to a Court in England or Wales(TFOOUT)', + ); + cy.get(ENF.localJusticeArea).should('exist').and('contain.text', 'Local Justice Area (LJA)'); + cy.get(ENF.localJusticeAreaValue).should('exist').and('contain.text', "Bedfordshire Magistrates' Court(4165)"); + }, + ); + + it( + 'AC6a. Cancel without changes returns away from the add override page without confirmation', + { tags: ['@JIRA-KEY:POT-4449'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('exist'); + + cy.window().then((win) => { + cy.stub(win, 'confirm').as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('not.have.been.called'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }, + ); + + it( + 'AC6b. Cancel after selecting a value shows confirmation before navigating away', + { tags: ['@JIRA-KEY:POT-4450'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return true; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF.headingName).should('contain.text', '177A Mr Robert THOMSON'); + cy.get(ENF.tableTitle).should('contain.text', 'Enforcement overview'); + cy.get(ENF_OVR.enfOverrideDropdown).should('not.exist'); + }, + ); + + it( + 'AC6c. Cancel after selecting a value and dismissing the confirmation keeps the user on the page', + { tags: ['@JIRA-KEY:POT-4451'] }, + () => { + commonSetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).click().type('TFO'); + cy.get(ENF_OVR.dropdownOptions).contains('TFOOUT').click(); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT').blur(); + + cy.window().then((win) => { + cy.stub(win, 'confirm') + .callsFake((message: string) => { + expect(message.replace(/\s+/g, ' ')).to.match(/unsaved changes/i); + return false; + }) + .as('confirm'); + }); + + cy.contains('a.govuk-link', /^Cancel$/i).click(); + + cy.get('@confirm').should('have.been.calledOnce'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('contain.value', 'TFOOUT'); + }, + ); + }, +); + +describe( + 'Add Enforcement Override - Company', + { tags: ['@JIRA-STORY:PO-1867', '@JIRA-EPIC:PO-1675', '@JIRA-LABEL:account-enquiry'] }, + () => { + it('AC1a, AC1b. Should render the form with company title', { tags: ['@JIRA-KEY:POT-4452'] }, () => { + companySetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Test Org Ltd - 177A'); + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + }); + + it( + 'Should support forward keyboard navigation across the company add enforcement override form', + { tags: ['@JIRA-LABEL:accessibility', '@JIRA-KEY:POT-4453'] }, + () => { + companySetup(); + + cy.get(ENF_OVR.title).should('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown).should('be.visible').focus(); + cy.get(ENF_OVR.enfOverrideDropdown).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get(ENF_OVR.addOverrideButton).should('have.focus'); + + cy.press(Cypress.Keyboard.Keys.TAB); + cy.contains('a.govuk-link', /^Cancel$/i).should('have.focus'); + + cy.get('@getResults.all').should('have.length', 0); + }, + ); + }, +); diff --git a/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts new file mode 100644 index 0000000000..f1f51ec529 --- /dev/null +++ b/cypress/component/fineAccountEnquiry/amendDefendantEnforcement/constants/add_enforcement_override_elements.ts @@ -0,0 +1,12 @@ +export const DOM_ELEMENTS = { + // Page Elements + title: 'h1.govuk-heading-l', + subtitle: '.govuk-fieldset__legend--m', + enfOverrideDropdown: '[name="fenf_account_enforcement_action-autocomplete"]', + enforcerDropdown: '[name="fenf_account_enforcement_enforcer-autocomplete"]', + localJusticeAreaDropdown: '[name="fenf_account_enforcement_lja-autocomplete"]', + dropdownOptions: '.autocomplete__option', + addOverrideButton: '#submitForm', + cancelLink: '.govuk-link', + errorSummary: '.govuk-error-summary__title', +}; diff --git a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts index e69de29bb2..3ef3aabb01 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.enforcement.actions.ts @@ -0,0 +1,166 @@ +/** + * @file details.enforcement.actions.ts + * @description Actions for the Account Details "Enforcement" tab and add override form. + */ +import { ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS as ENF } from '../../../../../shared/selectors/account-enquiry/account.enquiry.enforcement.locators'; +import { DOM_ELEMENTS as ENF_OVR } from '../../../../../shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators'; +import { createScopedLogger } from '../../../../../support/utils/log.helper'; + +const log = createScopedLogger('AccountDetailsEnforcementActions'); + +/** + * Actions for the Account Details enforcement tab and add override form. + */ +export class AccountDetailsEnforcementActions { + private static readonly DEFAULT_TIMEOUT = 15_000; + + /** + * Selects an option from an accessible autocomplete control. + * + * @param selector - Input selector for the autocomplete. + * @param query - Text to type into the input to filter options. + * @param optionText - Visible option text to select from the dropdown. + */ + private selectAutocompleteOption(selector: string, query: string, optionText: string): void { + cy.get(selector, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .click() + .type('{selectall}{backspace}', { force: true }) + .type(query, { delay: 0 }); + + cy.contains(ENF_OVR.dropdownOptions, optionText, { + timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT, + }).click(); + cy.get(selector, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should('contain.value', optionText); + } + + /** + * Asserts the Enforcement tab content is visible. + */ + public assertEnforcementTabVisible(): void { + log('assert', 'Enforcement tab is visible'); + cy.contains(ENF.tableTitle, 'Enforcement overview', { + timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT, + }).should('be.visible'); + } + + /** + * Opens the add enforcement override form from the Enforcement tab. + */ + public openAddEnforcementOverrideForm(): void { + log('navigate', 'Opening add enforcement override form'); + cy.get(ENF.addEnforcementOverrideLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .click(); + } + + /** + * Asserts the add enforcement override form is visible. + */ + public assertAddEnforcementOverrideFormVisible(): void { + log('assert', 'Add enforcement override form is visible'); + cy.get(ENF_OVR.title, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .and('contain.text', 'Add enforcement override'); + cy.get(ENF_OVR.enfOverrideDropdown, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'be.visible', + ); + } + + /** + * Selects an enforcement override from the add form. + * + * @param resultCode - Enforcement override code to select. + */ + public selectEnforcementOverride(resultCode: string): void { + log('action', 'Selecting enforcement override', { resultCode }); + this.selectAutocompleteOption(ENF_OVR.enfOverrideDropdown, resultCode, resultCode); + } + + /** + * Selects a Local Justice Area from the add form. + * + * @param localJusticeArea - Visible LJA option text. + */ + public selectLocalJusticeArea(localJusticeArea: string): void { + log('action', 'Selecting Local Justice Area', { localJusticeArea }); + this.selectAutocompleteOption(ENF_OVR.localJusticeAreaDropdown, localJusticeArea, localJusticeArea); + } + + /** + * Selects an enforcer from the add form. + * + * @param enforcer - Visible enforcer option text. + */ + public selectEnforcer(enforcer: string): void { + log('action', 'Selecting enforcer', { enforcer }); + this.selectAutocompleteOption(ENF_OVR.enforcerDropdown, enforcer, enforcer); + } + + /** + * Submits the add enforcement override form. + */ + public submitAddEnforcementOverride(): void { + log('action', 'Submitting add enforcement override form'); + cy.get(ENF_OVR.addOverrideButton, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .and('not.be.disabled') + .click(); + } + + /** + * Clicks the cancel link on the add enforcement override form. + */ + public cancelAddEnforcementOverride(): void { + log('action', 'Cancelling add enforcement override form'); + cy.get(ENF_OVR.cancelLink, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .contains(/^Cancel$/i) + .click(); + } + + /** + * Asserts the success banner text shown after saving. + * + * @param expected - Expected success message. + */ + public assertSuccessBannerText(expected: string): void { + log('assert', 'Enforcement success banner text', { expected }); + cy.get(ENF.successBanner, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }) + .should('be.visible') + .find(ENF.successBannerText) + .should('contain.text', expected); + } + + /** + * Asserts the enforcement override summary values on the Enforcement tab. + * + * @param expected - Expected summary values. + * @param expected.override - Expected enforcement override display text. + * @param expected.enforcer - Expected enforcer display text. + * @param expected.lja - Expected Local Justice Area display text. + */ + public assertEnforcementOverrideSummary(expected: { override?: string; enforcer?: string; lja?: string }): void { + log('assert', 'Enforcement override summary', expected); + + if (expected.override) { + cy.get(ENF.enforcementOverrideValue, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.override, + ); + } + + if (expected.lja) { + cy.get(ENF.localJusticeAreaValue, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.lja, + ); + } + + if (expected.enforcer) { + cy.get(ENF.enfOverrideEnforcer, { timeout: AccountDetailsEnforcementActions.DEFAULT_TIMEOUT }).should( + 'contain.text', + expected.enforcer, + ); + } + } +} diff --git a/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts b/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts index 0e68ccd31c..cfe04ceba6 100644 --- a/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts +++ b/cypress/e2e/functional/opal/actions/account-details/details.nav.actions.ts @@ -103,6 +103,19 @@ export class AccountDetailsNavActions { cy.get(N.subNav.paymentTermsTab, { timeout: 10_000 }).should('be.visible').click(); } + /** + * Navigates to the "Enforcement" tab within the Account Details shell. + * + * @description + * Clicks the “Enforcement” sub-navigation tab and prepares for assertions + * or further content checks within the tab panel. + */ + goToEnforcementTab(): void { + log('navigate', 'Navigating to "Enforcement" tab'); + + cy.get(N.subNav.enforcementTab, { timeout: 10_000 }).should('be.visible').click(); + } + /** * Asserts that the "Parent or guardian" tab is currently active. * @@ -181,4 +194,20 @@ export class AccountDetailsNavActions { .and('have.attr', 'aria-current', 'page') .and('contain.text', 'Payment terms'); } + + /** + * Asserts that the "Enforcement" tab is currently active. + * + * @description + * Confirms that the active tab link displays “Enforcement” + * and has the `aria-current="page"` attribute. + */ + assertEnforcementTabIsActive(): void { + log('assert', 'Asserting "Enforcement" tab is active'); + + cy.get(N.subNav.currentTab, { timeout: 10_000 }) + .should('be.visible') + .and('have.attr', 'aria-current', 'page') + .and('contain.text', 'Enforcement'); + } } diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature new file mode 100644 index 0000000000..0d7ac77bf3 --- /dev/null +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature @@ -0,0 +1,67 @@ +@JIRA-LABEL:account-enquiry +Feature: Account Enquiries - Add Enforcement Override + As an Opal user + I want to add an enforcement override from account enquiry + So that the override is saved and shown on the Enforcement tab + + Background: + Given I am logged in with email "opal-test@dev.platform.hmcts.net" + And I clear all approved accounts + + Rule: Adult or youth account + Background: + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Evan | + | account.defendant.surname | AddEnfOverride{uniq} | + | account.defendant.email_address_1 | evan.override{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259316 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-019 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 + Scenario: Save an enforcement override and return to the Enforcement tab + When I search for the account by last name "AddEnfOverride{uniq}" and open the latest result + And I go to the Enforcement tab + And I open the add enforcement override form + When I add the enforcement override "ABDC" with the enforcer "The DWP (3)" + Then I should return to the Enforcement tab + And the enforcement override success banner is "Enforcement override added" + And the enforcement override save request shows: + | enforcement override result id | ABDC | + | enforcer id | 770000000003 | + And the enforcement override summary shows: + | enforcement override | Application made for Benefit Deductions(ABDC) | + + Rule: Company account + Background: + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Add Override Company{uniq} | + | account.defendant.email_address_1 | add.override.company{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-023 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 + Scenario: Company save an enforcement override and return to the Enforcement tab + When I open the company account details for "Add Override Company{uniq}" + And I go to the Enforcement tab + And I open the add enforcement override form + When I add the enforcement override "ABDC" with the enforcer "The DWP (3)" + Then I should return to the Enforcement tab + And the enforcement override success banner is "Enforcement override added" + And the enforcement override save request shows: + | enforcement override result id | ABDC | + | enforcer id | 770000000003 | + And the enforcement override summary shows: + | enforcement override | Application made for Benefit Deductions(ABDC) | diff --git a/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature new file mode 100644 index 0000000000..b9572b15b5 --- /dev/null +++ b/cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature @@ -0,0 +1,84 @@ +@JIRA-LABEL:account-enquiry @JIRA-LABEL:accessibility + +Feature: Account Enquiries - Enforcement Accessibility + + Background: + Given I am logged in with email "opal-test@dev.platform.hmcts.net" + And I clear all approved accounts + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4454 + Scenario: Enforcement tab accessibility + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Erin | + | account.defendant.surname | EnfAccess{uniq} | + | account.defendant.email_address_1 | Erin.EnfAccess{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259314 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-015 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I search for the account by last name "EnfAccess{uniq}" and open the latest result + And I go to the Enforcement tab + Then I check the page for accessibility + + @JIRA-STORY:PO-1866 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4455 + Scenario: Add enforcement override page accessibility + Given I create a "adultOrYouthOnly" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.forenames | Evan | + | account.defendant.surname | EnfOverrideAccess{uniq} | + | account.defendant.email_address_1 | Evan.EnfOverrideAccess{uniq}@test.com | + | account.defendant.telephone_number_home | 02078259315 | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-016 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.defendant.dob | 2002-05-15 | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I search for the account by last name "EnfOverrideAccess{uniq}" and open the latest result + And I go to the Enforcement tab + And I open the add enforcement override form + Then I check the page for accessibility + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4456 + Scenario: Company enforcement tab accessibility + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Enf Company{uniq} | + | account.defendant.email_address_1 | EnfCompany{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-017 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I open the company account details for "Enf Company{uniq}" + And I go to the Enforcement tab + Then I check the page for accessibility + + @JIRA-STORY:PO-1867 @JIRA-EPIC:PO-1675 @JIRA-KEY:POT-4457 + Scenario: Company add enforcement override page accessibility + Given I create a "company" draft account with the following details and set status "Publishing Pending" using user "opal-test-10@dev.platform.hmcts.net": + | Account_status | Submitted | + | account.defendant.company_name | Enf Override Company{uniq} | + | account.defendant.email_address_1 | EnfOverrideCompany{uniq}@test.com | + | account.defendant.post_code | AB23 4RN | + | account.account_type | Fine | + | account.prosecutor_case_reference | PCR-AUTO-018 | + | account.collection_order_made | false | + | account.collection_order_made_today | false | + | account.payment_card_request | false | + | account.payment_terms.enforcements[0].result_id | PRIS | + + When I open the company account details for "Enf Override Company{uniq}" + And I go to the Enforcement tab + And I open the add enforcement override form + Then I check the page for accessibility diff --git a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts index d0b02180f0..6f436a03f2 100644 --- a/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts +++ b/cypress/e2e/functional/opal/flows/account-enquiry.flow.ts @@ -17,6 +17,7 @@ import { CommonActions } from '../actions/common/common.actions'; import { EditDefendantDetailsActions } from '../actions/account-details/edit.defendant-details.actions'; import { EditCompanyDetailsActions } from '../actions/account-details/edit.company-details.actions'; import { EditParentGuardianDetailsActions } from '../actions/account-details/edit.parent-guardian-details.actions'; +import { AccountDetailsEnforcementActions } from '../actions/account-details/details.enforcement.actions'; import { createScopedLogger, createScopedSyncLogger } from '../../../../support/utils/log.helper'; const logAE = createScopedLogger('AccountEnquiryFlow'); @@ -71,6 +72,7 @@ export class AccountEnquiryFlow { private readonly editCompanyDetailsActions = new EditCompanyDetailsActions(); private readonly editParentGuardianActions = new EditParentGuardianDetailsActions(); private readonly paymentTerms = new AccountDetailsPaymentTermsActions(); + private readonly enforcement = new AccountDetailsEnforcementActions(); /** * Ensures the test is on the Individuals Account Search page. @@ -263,6 +265,150 @@ export class AccountEnquiryFlow { this.paymentTerms.assertAmendFormVisible(); } + /** + * Navigates to the Enforcement tab and asserts it is active. + */ + public goToEnforcementTab(): void { + logAE('method', 'goToEnforcementTab()'); + logAE('navigate', 'Navigating to Enforcement tab'); + this.detailsNav.goToEnforcementTab(); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Opens the add enforcement override form from the Enforcement tab. + */ + public openAddEnforcementOverrideForm(): void { + logAE('method', 'openAddEnforcementOverrideForm()'); + this.enforcement.openAddEnforcementOverrideForm(); + this.enforcement.assertAddEnforcementOverrideFormVisible(); + } + + /** + * Selects an enforcement override code on the add form. + * + * @param resultCode - Enforcement override result code. + */ + public selectEnforcementOverride(resultCode: string): void { + logAE('method', 'selectEnforcementOverride()', { resultCode }); + this.enforcement.selectEnforcementOverride(resultCode); + } + + /** + * Selects a Local Justice Area on the add form. + * + * @param localJusticeArea - Visible LJA option text. + */ + public selectEnforcementOverrideLocalJusticeArea(localJusticeArea: string): void { + logAE('method', 'selectEnforcementOverrideLocalJusticeArea()', { localJusticeArea }); + this.enforcement.selectLocalJusticeArea(localJusticeArea); + } + + /** + * Selects an enforcer on the add form. + * + * @param enforcer - Visible enforcer option text. + */ + public selectEnforcementOverrideEnforcer(enforcer: string): void { + logAE('method', 'selectEnforcementOverrideEnforcer()', { enforcer }); + this.enforcement.selectEnforcer(enforcer); + } + + /** + * Submits the add enforcement override form and stores the request body for later assertions. + */ + public submitAddEnforcementOverride(): void { + logAE('method', 'submitAddEnforcementOverride()'); + + this.interceptEnforcementOverrideSave(); + this.enforcement.submitAddEnforcementOverride(); + + cy.wait('@enforcementOverrideSave').then(({ request }) => { + cy.wrap(request.body, { log: false }).as('enforcementOverrideSaveBody'); + }); + + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Cancels the add enforcement override form after confirming unsaved changes should be discarded. + */ + public cancelAddEnforcementOverrideAndDiscardChanges(): void { + logAE('method', 'cancelAddEnforcementOverrideAndDiscardChanges()'); + + this.common.confirmNextUnsavedChanges(true); + this.enforcement.cancelAddEnforcementOverride(); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Asserts the Enforcement tab is active and visible. + */ + public assertEnforcementTabIsActive(): void { + logAE('method', 'assertEnforcementTabIsActive()'); + this.detailsNav.assertEnforcementTabIsActive(); + this.enforcement.assertEnforcementTabVisible(); + } + + /** + * Asserts the enforcement override success banner text. + * + * @param expected - Expected success banner message. + */ + public assertEnforcementOverrideSuccessBanner(expected: string): void { + logAE('method', 'assertEnforcementOverrideSuccessBanner()', { expected }); + this.enforcement.assertSuccessBannerText(expected); + } + + /** + * Asserts the intercepted enforcement override save payload. + * + * @param expected - Expected payload values. + * @param expected.overrideId - Expected enforcement override result id. + * @param expected.enforcerId - Expected enforcer id. + * @param expected.ljaId - Expected local justice area id. + */ + public assertEnforcementOverrideSaveRequest(expected: { + overrideId?: string; + enforcerId?: string; + ljaId?: string; + }): void { + logAE('method', 'assertEnforcementOverrideSaveRequest()', expected); + + cy.get('@enforcementOverrideSaveBody').then((body: any) => { + if (expected.overrideId) { + expect(body).to.have.nested.property( + 'enforcement_override.enforcement_override_result.enforcement_override_result_id', + expected.overrideId, + ); + } + + if (expected.ljaId) { + expect(String(body?.enforcement_override?.lja?.lja_id)).to.eq(expected.ljaId); + } + + if (expected.enforcerId) { + expect(String(body?.enforcement_override?.enforcer?.enforcer_id)).to.eq(expected.enforcerId); + } + }); + } + + /** + * Asserts the enforcement override summary card values. + * + * @param expected - Expected summary values. + * @param expected.override - Expected enforcement override display text. + * @param expected.enforcer - Expected enforcer display text. + * @param expected.lja - Expected LJA display text. + */ + public assertEnforcementOverrideSummary(expected: { override?: string; enforcer?: string; lja?: string }): void { + logAE('method', 'assertEnforcementOverrideSummary()', expected); + this.enforcement.assertEnforcementOverrideSummary(expected); + } + /** * Submits instalments-only payment terms with a payment card request. * Stores the POST request body for later assertions. @@ -619,6 +765,13 @@ export class AccountEnquiryFlow { cy.intercept('POST', '**/defendant-accounts/*/payment-terms').as('paymentTermsSave'); } + /** + * Intercepts the enforcement override save request for later assertions. + */ + private interceptEnforcementOverrideSave(): void { + cy.intercept('PATCH', '**/defendant-accounts/*').as('enforcementOverrideSave'); + } + /** * Default headers for Account Enquiry API requests. * @returns JSON and accept headers used for API calls. diff --git a/cypress/parallel/weights/functional-parallel-weights.json b/cypress/parallel/weights/functional-parallel-weights.json index 043d731d6b..1660e91022 100644 --- a/cypress/parallel/weights/functional-parallel-weights.json +++ b/cypress/parallel/weights/functional-parallel-weights.json @@ -1 +1,153 @@ -{"cypress/e2e/functional/opal/features/consolidation/FineAccountConsolidation.feature":{"time":7474,"weight":2},"cypress/e2e/functional/opal/features/consolidation/FinesAccountConsolidationAccessibility.feature":{"time":3936,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummary.feature":{"time":28957,"weight":8},"cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummaryAccessibility.feature":{"time":17577,"weight":5},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTerms.feature":{"time":37239,"weight":11},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTermsAccessibility.feature":{"time":6364,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCard.feature":{"time":7121,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCardAccessibility.feature":{"time":4756,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetails.feature":{"time":82030,"weight":25},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetailsAccessibility.feature":{"time":9542,"weight":2},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNote.feature":{"time":34967,"weight":10},"cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNoteAccessibility.feature":{"time":9934,"weight":3},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatches.feature":{"time":80949,"weight":24},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatchesAccessibility.feature":{"time":5258,"weight":1},"cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchForAccountFilterByBU.feature":{"time":10590,"weight":3},"cypress/e2e/functional/opal/features/globalApiInterceptor.feature":{"time":69290,"weight":21},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccounts.feature":{"time":64452,"weight":19},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccountsE2ETech.feature":{"time":39210,"weight":12},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/draftAccountsConcurrency.feature":{"time":5873,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidate.feature":{"time":84527,"weight":26},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidateAccessibility.feature":{"time":24446,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/fixedPenaltyFailedAccounts.feature":{"time":13363,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenalty.feature":{"time":96356,"weight":29},"cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenaltyAccessibility.feature":{"time":4383,"weight":1},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmit.feature":{"time":131521,"weight":40},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmitAccessibility.feature":{"time":31259,"weight":9},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/AccountCommentsAndNotes.feature":{"time":15202,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CompanyDetails.feature":{"time":17140,"weight":5},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ContactDetails.feature":{"time":15888,"weight":4},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CourtDetails.feature":{"time":19951,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/EmployerDetails.feature":{"time":20164,"weight":6},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LanguagePreferences.feature":{"time":10036,"weight":3},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LocalJusticeAreasFiltering.feature":{"time":8075,"weight":2},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/OffenceDetails.feature":{"time":155188,"weight":47},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ParentGuardianDetails.feature":{"time":23548,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PaymentTerms.feature":{"time":23056,"weight":7},"cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PersonalDetails.feature":{"time":25554,"weight":7},"cypress/e2e/functional/opal/features/navigation/finesPrimaryNavigation.feature":{"time":13479,"weight":4},"cypress/e2e/functional/opal/features/reciprocalMaintenance/dummyTest.feature":{"time":5246,"weight":1}} \ No newline at end of file +{ + "cypress/e2e/functional/opal/features/consolidation/FineAccountConsolidation.feature": { "time": 6262, "weight": 2 }, + "cypress/e2e/functional/opal/features/consolidation/FinesAccountConsolidationAccessibility.feature": { + "time": 3397, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummary.feature": { + "time": 18108, + "weight": 8 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/ViewDefendantAccountSummary/ViewDefendantAccountSummaryAccessibility.feature": { + "time": 13270, + "weight": 5 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAddEnforcementOverride.feature": { + "time": 7392, + "weight": 3 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTerms.feature": { + "time": 25066, + "weight": 11 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesAmendPaymentTermsAccessibility.feature": { + "time": 3132, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCard.feature": { + "time": 3277, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesRequestPaymentCardAccessibility.feature": { + "time": 2905, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetails.feature": { + "time": 46493, + "weight": 20 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/AccountEnquiriesViewDetailsAccessibility.feature": { + "time": 6675, + "weight": 2 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/FineAccountEnquiryAccessibility.feature": { + "time": 16998, + "weight": 7 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNote.feature": { + "time": 26186, + "weight": 11 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/accountEnquiry/addAccountNoteAccessibility.feature": { + "time": 7724, + "weight": 3 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatches.feature": { + "time": 63683, + "weight": 28 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchAndMatchesAccessibility.feature": { + "time": 3650, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/fineAccountEnquiry/searchAndMatches/searchForAccountFilterByBU.feature": { + "time": 7899, + "weight": 3 + }, + "cypress/e2e/functional/opal/features/globalApiInterceptor.feature": { "time": 44363, "weight": 19 }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccounts.feature": { + "time": 27448, + "weight": 12 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/checkAndValidateDraftAccountsE2ETech.feature": { + "time": 10012, + "weight": 4 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidateDraftAccounts/draftAccountsConcurrency.feature": { + "time": 2426, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidate.feature": { + "time": 39955, + "weight": 17 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/checkAndValidateAccessibility.feature": { + "time": 12920, + "weight": 5 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/checkAndValidate/fixedPenaltyFailedAccounts.feature": { + "time": 10577, + "weight": 4 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenalty.feature": { + "time": 74293, + "weight": 33 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/fixedPenalty/fixedPenaltyAccessibility.feature": { + "time": 3166, + "weight": 1 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmit.feature": { + "time": 98678, + "weight": 44 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/PopulateAndSubmitAccessibility.feature": { + "time": 23971, + "weight": 10 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/AccountCommentsAndNotes.feature": { + "time": 12522, + "weight": 5 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CompanyDetails.feature": { + "time": 15870, + "weight": 7 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ContactDetails.feature": { + "time": 14275, + "weight": 6 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/CourtDetails.feature": { + "time": 15399, + "weight": 6 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/EmployerDetails.feature": { + "time": 15351, + "weight": 6 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LanguagePreferences.feature": { + "time": 5857, + "weight": 2 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/LocalJusticeAreasFiltering.feature": { + "time": 6333, + "weight": 2 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/OffenceDetails.feature": { + "time": 140263, + "weight": 62 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/ParentGuardianDetails.feature": { + "time": 20200, + "weight": 9 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PaymentTerms.feature": { + "time": 19376, + "weight": 8 + }, + "cypress/e2e/functional/opal/features/manualAccountCreation/populateAndSubmit/pages/PersonalDetails.feature": { + "time": 18238, + "weight": 8 + }, + "cypress/e2e/functional/opal/features/reciprocalMaintenance/dummyTest.feature": { "time": 679, "weight": 0 } +} diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts new file mode 100644 index 0000000000..9384617e2a --- /dev/null +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement-override-add.locators.ts @@ -0,0 +1,21 @@ +/** + * @file account.enquiry.enforcement-override-add.locators.ts + * @description + * Selector map for the **Account Enquiry – Add enforcement override** form. + * Covers page headings, autocomplete controls, actions, and validation summary. + * + * @remarks + * - Use these selectors in Cypress tests instead of local component constants. + * - The exported key names are preserved to keep migration of existing specs mechanical. + */ +export const DOM_ELEMENTS = { + title: 'h1.govuk-heading-l', + subtitle: '.govuk-fieldset__legend--m', + enfOverrideDropdown: '[name="fenf_account_enforcement_action-autocomplete"]', + enforcerDropdown: '[name="fenf_account_enforcement_enforcer-autocomplete"]', + localJusticeAreaDropdown: '[name="fenf_account_enforcement_lja-autocomplete"]', + dropdownOptions: '.autocomplete__option', + addOverrideButton: '#submitForm', + cancelLink: '.govuk-link', + errorSummary: '.govuk-error-summary__title', +} as const; diff --git a/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts new file mode 100644 index 0000000000..822bcdc6f3 --- /dev/null +++ b/cypress/shared/selectors/account-enquiry/account.enquiry.enforcement.locators.ts @@ -0,0 +1,52 @@ +/** + * @file account.enquiry.enforcement.locators.ts + * @description + * Selector map for the **Account Enquiry – Enforcement** tab. + * Covers page structure, summary cards, action links, and enforcement detail rows. + * + * @remarks + * - Use these selectors in Cypress component and e2e tests to avoid local duplication. + * - This map preserves the legacy selector keys used by the existing enforcement spec. + */ +export const ACCOUNT_ENQUIRY_ENFORCEMENT_STATUS_ELEMENTS = { + headingWithCaption: 'opal-lib-govuk-heading-with-caption', + headingName: 'h1.govuk-heading-l', + pageHeader: 'opal-lib-custom-page-header', + headerLabel: '[opal-lib-custom-account-information-item-label]', + headerValue: '[opal-lib-custom-account-information-item-value]', + + addNoteButton: 'button[id$="addAccountNote"]', + + summaryMetricBar: 'opal-lib-custom-summary-metric-bar', + accountInfo: 'opal-lib-custom-account-information', + parentGuardianTag: '#status', + + tabName: '[subnavitemid="enforcement-tab"] > .moj-sub-navigation__link', + + enforcementStatusLink: '.govuk-link', + addEnforcementOverrideLink: '.govuk-link:contains("Add enforcement override")', + detailsLink: '.govuk-details__summary-text', + + tableTitle: '.govuk-summary-card__title', + collectionOrderStatus: '#enforcementOverviewDetailsCollection_order_statusKey', + daysInDefault: '#enforcementOverviewDetailsDays_in_defaultKey', + enforcementCourt: '#enforcementOverviewDetailsEnforcement_courtKey', + enforcementAction: '#lastEnforcementActionDetailsEnforcement_actionKey', + reason: '#lastEnforcementActionDetailsReasonKey', + lastEnfEnforcer: '#lastEnforcementActionDetailsEnforcerKey', + warrantNumber: '#lastEnforcementActionDetailsWarrant_numberKey', + dateAdded: '#lastEnforcementActionDetailsDate_addedKey', + enforcementOverride: '#enforcementOverrideDetailsEnforcement_overrideKey', + enforcementOverrideValue: '#enforcementOverrideDetailsEnforcement_overrideValue', + enfOverrideEnforcer: '#enforcementOverrideDetailsEnforcerKey', + localJusticeArea: '#enforcementOverrideDetailsLocal_justice_areaKey', + localJusticeAreaValue: '#enforcementOverrideDetailsLocal_justice_areaValue', + + successBanner: 'opal-lib-moj-alert[type="success"]', + successBannerText: 'opal-lib-moj-alert-content-text', + + detailsDaysInDefault: '[id="enforcementActionDetailsDays in defaultKey"]', + detailsReason: '#enforcementActionDetailsReasonKey', + + actionsColumnHeader: '.govuk-grid-column-one-third > .govuk-\\!-margin-bottom-2', +} as const; diff --git a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts index 026390add6..8cce3d8215 100644 --- a/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts +++ b/cypress/support/step_definitions/searchForAccount/account-enquiry.steps.ts @@ -140,6 +140,85 @@ When('I open the amend payment terms form', () => { flow().openPaymentTermsAmendForm(); }); +/** + * @step Navigates to the Enforcement tab. + */ +When('I go to the Enforcement tab', () => { + log('step', 'Navigate to Enforcement tab'); + flow().goToEnforcementTab(); +}); + +/** + * @step Opens the add enforcement override form from the Enforcement tab. + */ +When('I open the add enforcement override form', () => { + log('step', 'Open add enforcement override form'); + flow().openAddEnforcementOverrideForm(); +}); + +/** + * @step Selects an enforcement override on the add form. + */ +When('I choose the enforcement override {string}', (resultCode: string) => { + log('step', 'Choose enforcement override', { resultCode }); + flow().selectEnforcementOverride(resultCode); +}); + +/** + * @step Selects a Local Justice Area on the add form. + */ +When('I choose the Local Justice Area {string}', (localJusticeArea: string) => { + log('step', 'Choose Local Justice Area', { localJusticeArea }); + flow().selectEnforcementOverrideLocalJusticeArea(localJusticeArea); +}); + +/** + * @step Selects an enforcer on the add form. + */ +When('I choose the enforcer {string}', (enforcer: string) => { + log('step', 'Choose enforcer', { enforcer }); + flow().selectEnforcementOverrideEnforcer(enforcer); +}); + +/** + * @step Submits the add enforcement override form. + */ +When('I add the enforcement override', () => { + log('step', 'Submit add enforcement override form'); + flow().submitAddEnforcementOverride(); +}); + +/** + * @step Completes the add enforcement override form with the provided values and submits it. + */ +When( + 'I add the enforcement override {string} with the Local Justice Area {string}', + (resultCode: string, lja: string) => { + log('step', 'Add enforcement override with Local Justice Area', { resultCode, lja }); + flow().selectEnforcementOverride(resultCode); + flow().selectEnforcementOverrideLocalJusticeArea(lja); + flow().submitAddEnforcementOverride(); + }, +); + +/** + * @step Completes the add enforcement override form with the provided values and submits it. + */ +When('I add the enforcement override {string} with the enforcer {string}', (resultCode: string, enforcer: string) => { + log('step', 'Add enforcement override with enforcer', { resultCode, enforcer }); + flow().selectEnforcementOverride(resultCode); + flow().selectEnforcementOverrideEnforcer(enforcer); + flow().submitAddEnforcementOverride(); +}); + +/** + * @step Cancels the add enforcement override form and discards changes. + */ +When('I cancel the add enforcement override form and discard changes', () => { + log('step', 'Cancel add enforcement override form and discard changes'); + flow().cancelAddEnforcementOverrideAndDiscardChanges(); +}); + /** * @step Submits instalments-only payment terms with a payment card request. */ @@ -164,6 +243,49 @@ Then('I should return to the Payment terms tab', () => { flow().assertPaymentTermsTabIsActive(); }); +/** + * @step Asserts the Enforcement tab is active. + */ +Then('I should return to the Enforcement tab', () => { + log('assert', 'Enforcement tab is active'); + flow().assertEnforcementTabIsActive(); +}); + +/** + * @step Asserts the enforcement override success banner text. + */ +Then('the enforcement override success banner is {string}', (expected: string) => { + log('assert', 'Enforcement override success banner text', { expected }); + flow().assertEnforcementOverrideSuccessBanner(expected); +}); + +/** + * @step Asserts the intercepted enforcement override save request body. + */ +Then('the enforcement override save request shows:', (table: DataTable) => { + const rows = rowsHashSafe(table); + const overrideId = + rows['enforcement override result id'] ?? rows['enforcement override'] ?? rows['override result id'] ?? ''; + const enforcerId = rows['enforcer id'] ?? ''; + const ljaId = rows['lja id'] ?? rows['local justice area id'] ?? ''; + + log('assert', 'Enforcement override save request payload', { overrideId, enforcerId, ljaId }); + flow().assertEnforcementOverrideSaveRequest({ overrideId, enforcerId, ljaId }); +}); + +/** + * @step Asserts the enforcement override summary card values. + */ +Then('the enforcement override summary shows:', (table: DataTable) => { + const rows = rowsHashSafe(table); + const override = rows['enforcement override'] ?? ''; + const enforcer = rows['enforcer'] ?? ''; + const lja = rows['local justice area'] ?? rows['local justice area (lja)'] ?? ''; + + log('assert', 'Enforcement override summary values', { override, enforcer, lja }); + flow().assertEnforcementOverrideSummary({ override, enforcer, lja }); +}); + /** * @step Asserts payment terms instalment summary values. */ diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html index ddf74622bb..79213afe10 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.html @@ -14,7 +14,12 @@

Enforcement status

" summaryListId="enforcementOverviewDetails" summaryListRowId="collection_order_status" - [actionEnabled]="hasAccountMaintenancePermission ? true : false" + [actionEnabled]=" + hasAccountMaintenancePermission && + (!isCompanyAccount || tabData.enforcement_overview.collection_order.collection_order_flag) + ? true + : false + " > Collection Order status @@ -86,7 +91,7 @@

Enforcement status

- @if (tabData.last_enforcement_action !== null) { + @if (tabData.last_enforcement_action) {
Enforcement status } - @if (tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id) { + @if ( + tabData.enforcement_override && + tabData.enforcement_override.enforcement_override_result && + tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id + ) { @if (hasAccountMaintenancePermission) { @@ -333,10 +342,12 @@

Actions

} @if ( hasAccountMaintenancePermission && - !tabData.enforcement_override.enforcement_override_result.enforcement_override_result_id + !tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id ) {

- Add enforcement override + Add enforcement override

}

diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts index 81db86375e..bcace27490 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FinesAccDefendantDetailsEnforcementTab } from './fines-acc-defendant-details-enforcement-tab.component'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK } from '@services/fines/opal-fines-service/mocks/opal-fines-account-defendant-details-enforcement-tab-ref-data.mock'; -import { beforeEach, describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; describe('FinesAccDefendantDetailsEnforcementTab', () => { let component: FinesAccDefendantDetailsEnforcementTab; @@ -21,4 +21,14 @@ describe('FinesAccDefendantDetailsEnforcementTab', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should handleAddEnforcementOverride when add enforcement override button is clicked', () => { + const eventEmitterSpy = vi.spyOn(component.addEnforcementOverride, 'emit'); + const event = { preventDefault: vi.fn() } as unknown as Event; + component.hasAccountMaintenancePermission = true; + component.tabData.enforcement_override = null; // Ensure there is no existing enforcement override result + component.handleAddEnforcementOverride(event); + expect(event.preventDefault).toHaveBeenCalled(); + expect(eventEmitterSpy).toHaveBeenCalled(); + }); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts index 3820295719..19ec837685 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details-enforcement-tab/fines-acc-defendant-details-enforcement-tab.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core'; import { IFinesAccSummaryTabsContentStyles } from '../interfaces/fines-acc-summary-tabs-content-styles.interface'; import { FINES_ACC_SUMMARY_TABS_CONTENT_STYLES } from '../../constants/fines-acc-summary-tabs-content-styles.constant'; import { @@ -38,4 +38,19 @@ export class FinesAccDefendantDetailsEnforcementTab { @Input() isCompanyAccount: boolean = false; @Input() hasAccountMaintenancePermission: boolean = false; @Input() hasEnterEnforcementPermission: boolean = false; + @Output() addEnforcementOverride = new EventEmitter(); + + /** + * Emits an event to add an enforcement override if the user has the necessary permissions and there is no existing enforcement override result. + * @returns void + */ + public handleAddEnforcementOverride(event: Event): void { + event.preventDefault(); + if ( + this.hasAccountMaintenancePermission && + !this.tabData.enforcement_override?.enforcement_override_result?.enforcement_override_result_id + ) { + this.addEnforcementOverride.emit(); + } + } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html index 045f4d7578..39396c237f 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.html @@ -205,6 +205,7 @@

Business Unit:

[isCompanyAccount]="accountData.party_details.organisation_flag" [hasAccountMaintenancePermission]="hasPermission('account-maintenance')" [hasEnterEnforcementPermission]="hasPermission('enter-enforcement')" + (addEnforcementOverride)="navigateToAddEnforcementOverridePage()" > } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts index befe3bd353..feaa9c5f9b 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.spec.ts @@ -68,7 +68,7 @@ describe('FinesAccDefendantDetailsComponent', () => { getDefendantAccountHistoryAndNotesTabData: vi .fn() .mockName('OpalFines.getDefendantAccountHistoryAndNotesTabData'), - getDefendantAccountEnforcementTabData: vi.fn().mockName('OpalFines.getDefendantAccountEnforcementTabData'), + getDefendantAccountEnforcementStatus: vi.fn().mockName('OpalFines.getDefendantAccountEnforcementStatus'), getDefendantAccountPaymentTermsLatest: vi.fn().mockName('OpalFines.getDefendantAccountPaymentTermsLatest'), getDefendantAccountParty: vi.fn().mockName('OpalFines.getDefendantAccountParty'), getParentOrGuardianAccountParty: vi.fn().mockName('OpalFines.getParentOrGuardianAccountParty'), @@ -87,7 +87,7 @@ describe('FinesAccDefendantDetailsComponent', () => { mockOpalFinesService.getDefendantAccountFixedPenalty.mockReturnValue( of(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_FIXED_PENALTY_MOCK), ); - mockOpalFinesService.getDefendantAccountEnforcementTabData.mockReturnValue( + mockOpalFinesService.getDefendantAccountEnforcementStatus.mockReturnValue( of(OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK), ); mockOpalFinesService.getDefendantAccountPaymentTermsLatest.mockReturnValue( @@ -158,6 +158,16 @@ describe('FinesAccDefendantDetailsComponent', () => { }); }); + it('should call router.navigate when navigateToAddEnforcementOverridePage is called', () => { + component.navigateToAddEnforcementOverridePage(); + expect(routerSpy.navigate).toHaveBeenCalledWith( + [`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/override/add`], + { + relativeTo: component['activatedRoute'], + }, + ); + }); + it('should fetch the defendant tab data when fragment is changed to defendant', () => { component['refreshFragment$'].next('defendant'); // Subscribe to trigger the pipe execution @@ -186,7 +196,7 @@ describe('FinesAccDefendantDetailsComponent', () => { component['refreshFragment$'].next('enforcement'); // Subscribe to trigger the pipe execution component.tabEnforcement$.subscribe(); - expect(mockOpalFinesService.getDefendantAccountEnforcementTabData).toHaveBeenCalled(); + expect(mockOpalFinesService.getDefendantAccountEnforcementStatus).toHaveBeenCalled(); expect(mockPayloadService.transformPayload).toHaveBeenCalled(); }); diff --git a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts index f6b136cc90..c8d7f1b673 100644 --- a/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts +++ b/src/app/flows/fines/fines-acc/fines-acc-defendant-details/fines-acc-defendant-details.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; -import { distinctUntilChanged, EMPTY, map, merge, Observable, Subject, takeUntil, tap } from 'rxjs'; +import { distinctUntilChanged, EMPTY, map, merge, Observable, of, Subject, switchMap, takeUntil, tap } from 'rxjs'; // Services import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; import { PermissionsService } from '@hmcts/opal-frontend-common/services/permissions-service'; @@ -222,22 +222,24 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement case 'payment-terms': this.tabPaymentTerms$ = this.fetchTabData( this.opalFinesService.getDefendantAccountPaymentTermsLatest(account_id).pipe( - tap((data) => { - if (data.last_enforcement) { - this.opalFinesService - .getResult(data.last_enforcement) - .pipe(takeUntil(this.destroy$)) - .subscribe((result) => { + switchMap( + (data): Observable => + (data.last_enforcement + ? this.opalFinesService.getResult(data.last_enforcement) + : of(null) + ).pipe( + tap((result: IOpalFinesResultRefData | null) => { this.lastEnforcement = result; - }); - } - }), + }), + map((): IOpalFinesAccountDefendantDetailsPaymentTermsLatest => data), + ), + ), ), ); break; case 'enforcement': this.tabEnforcement$ = this.fetchTabData( - this.opalFinesService.getDefendantAccountEnforcementTabData(account_id), + this.opalFinesService.getDefendantAccountEnforcementStatus(account_id), ); break; case 'impositions': @@ -447,4 +449,13 @@ export class FinesAccDefendantDetailsComponent extends AbstractTabData implement ); } } + + /** + * Navigates to the add enforcement override page or access denied page based on user permissions. + */ + public navigateToAddEnforcementOverridePage(): void { + this['router'].navigate([`../${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/override/add`], { + relativeTo: this.activatedRoute, + }); + } } diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts new file mode 100644 index 0000000000..eaf803e257 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-field-errors.constant.ts @@ -0,0 +1,22 @@ +import { IFinesAccEnfOverrideAddChangeFieldErrors } from '../interfaces/fines-acc-enf-override-add-change-field-errors.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS: IFinesAccEnfOverrideAddChangeFieldErrors = { + fenf_account_enforcement_action: { + required: { + message: `Select an enforcement override`, + priority: 1, + }, + }, + fenf_account_enforcement_enforcer: { + required: { + message: `Select an enforcer`, + priority: 2, + }, + }, + fenf_account_enforcement_lja: { + required: { + message: `Select a Local Justice Area`, + priority: 2, + }, + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts new file mode 100644 index 0000000000..5b8093e1c5 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant.ts @@ -0,0 +1,10 @@ +import { IFinesAccEnfOverrideAddChangeRoutingPaths } from '../interfaces/fines-acc-enf-override-add-change-routing-paths.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS: IFinesAccEnfOverrideAddChangeRoutingPaths = { + root: 'override', + children: { + add: 'add', + remove: 'remove', + change: 'change', + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts new file mode 100644 index 0000000000..8903f1e7b7 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant.ts @@ -0,0 +1,10 @@ +import { IFinesAccEnfOverrideAddChangeRoutingPaths } from '../interfaces/fines-acc-enf-override-add-change-routing-paths.interface'; + +export const FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES: IFinesAccEnfOverrideAddChangeRoutingPaths = { + root: 'Enforcement Override', + children: { + add: 'Add enforcement override', + remove: 'Remove enforcement override', + change: 'Change enforcement override', + }, +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html new file mode 100644 index 0000000000..fb6dfd0c14 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.html @@ -0,0 +1,62 @@ + + + +
+ + @if (form.controls['fenf_account_enforcement_enforcer'].enabled) { + + } + @if (form.controls['fenf_account_enforcement_lja'].enabled) { + + } + +
+
+ + Add override + +
+
+ +
+
+
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts new file mode 100644 index 0000000000..86e2db543b --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.spec.ts @@ -0,0 +1,87 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FinesAccEnfOverrideAddChangeFormComponent } from './fines-acc-enf-override-add-change-form.component'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { ActivatedRoute } from '@angular/router'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { of } from 'rxjs'; + +describe('FinesAccEnfOverrideAddChangeFormComponent', () => { + let component: FinesAccEnfOverrideAddChangeFormComponent; + let fixture: ComponentFixture; + let mockOpalFines: Pick; + + beforeEach(async () => { + mockOpalFines = { + getResult: vi.fn().mockReturnValue(of({ requires_enforcer: false, requires_lja: false })), + }; + + await TestBed.configureTestingModule({ + imports: [FinesAccEnfOverrideAddChangeFormComponent], + providers: [ + { provide: OpalFines, useValue: mockOpalFines }, + { provide: ActivatedRoute, useValue: { snapshot: { params: {}, data: {} } } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(FinesAccEnfOverrideAddChangeFormComponent); + component = fixture.componentInstance; + component.enforcementActionOptions = [{ value: 'R1', name: 'Result One (R1)' }]; + component.enforcerOptions = [{ value: 'E1', name: 'Enforcer One (E1)' }]; + component.localJusticeAreaOptions = [{ value: 'L1', name: 'LJA One (L1)' }]; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize form controls and disable dependent fields', () => { + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.disabled).toBe(true); + expect(ljaControl?.disabled).toBe(true); + }); + + it('should call handleChangeEnforcementAction for both selected and cleared enforcement action values', () => { + const spy = vi.spyOn(component, 'handleChangeEnforcementAction'); + component.form.get('fenf_account_enforcement_action')?.setValue('R1'); + component.form.get('fenf_account_enforcement_action')?.setValue(null); + + expect(spy).toHaveBeenNthCalledWith(1, 'R1'); + expect(spy).toHaveBeenNthCalledWith(2, ''); + }); + + it('should enable only enforcer control when result requires enforcer', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: true, requires_lja: false })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.enabled).toBe(true); + expect(ljaControl?.disabled).toBe(true); + }); + + it('should enable only LJA control when result requires LJA', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: false, requires_lja: true })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.disabled).toBe(true); + expect(ljaControl?.enabled).toBe(true); + }); + + it('should enable both controls when result requires enforcer and LJA', () => { + mockOpalFines.getResult = vi.fn().mockReturnValue(of({ requires_enforcer: true, requires_lja: true })); + component.handleChangeEnforcementAction('R1'); + + const enforcerControl = component.form.get('fenf_account_enforcement_enforcer'); + const ljaControl = component.form.get('fenf_account_enforcement_lja'); + + expect(enforcerControl?.enabled).toBe(true); + expect(ljaControl?.enabled).toBe(true); + }); +}); diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts new file mode 100644 index 0000000000..11f0250966 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component.ts @@ -0,0 +1,145 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, inject } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { AbstractFormBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base'; +import { + IAbstractFormBaseFieldErrors, + IAbstractFormBaseForm, +} from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IAbstractFormControlErrorMessage } from '@hmcts/opal-frontend-common/components/abstract/interfaces'; +import { GovukButtonComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-button'; +import { GovukCancelLinkComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-cancel-link'; +import { GovukErrorSummaryComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-error-summary'; +import { IGovUkSelectOptions } from '@hmcts/opal-frontend-common/components/govuk/govuk-select/interfaces'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS } from '../constants/fines-acc-enf-override-add-change-field-errors.constant'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { AlphagovAccessibleAutocompleteComponent } from '@hmcts/opal-frontend-common/components/alphagov/alphagov-accessible-autocomplete'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { IFinesAccEnfOverrideAddChangeFormState } from '../interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { takeUntil } from 'rxjs'; +import { GovukHeadingWithCaptionComponent } from '@hmcts/opal-frontend-common/components/govuk/govuk-heading-with-caption'; + +@Component({ + selector: 'app-fines-enf-override-add-change-form', + imports: [ + FormsModule, + ReactiveFormsModule, + GovukButtonComponent, + GovukCancelLinkComponent, + GovukErrorSummaryComponent, + GovukHeadingWithCaptionComponent, + AlphagovAccessibleAutocompleteComponent, + ], + templateUrl: './fines-acc-enf-override-add-change-form.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, +}) +export class FinesAccEnfOverrideAddChangeFormComponent extends AbstractFormBaseComponent implements OnInit, OnDestroy { + private finesService = inject(OpalFines); + protected override fieldErrors: IAbstractFormBaseFieldErrors = { + ...FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_FIELD_ERRORS, + }; + protected override formSubmit = new EventEmitter>(); + public override formControlErrorMessages: IAbstractFormControlErrorMessage = {}; + public defendantAccRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; + @Input({ required: true }) enforcementActionOptions!: IGovUkSelectOptions[]; + @Input({ required: true }) enforcerOptions!: IGovUkSelectOptions[]; + @Input({ required: true }) localJusticeAreaOptions!: IGovUkSelectOptions[]; + @Input({ required: true }) partyName!: string; + @Input({ required: true }) accountNumber!: string; + @Input({ required: true }) pageTitle!: string; + + /** + * Sets up the enforcement override add/change form with the necessary form controls. + * @return void + */ + private setupEnforcementOverrideAddChangeForm(): void { + this.form = new FormGroup({ + fenf_account_enforcement_action: new FormControl(null, Validators.required), + fenf_account_enforcement_enforcer: new FormControl(null, Validators.required), + fenf_account_enforcement_lja: new FormControl(null, Validators.required), + }); + this.disableFormControl('fenf_account_enforcement_enforcer'); + this.disableFormControl('fenf_account_enforcement_lja'); + } + + /** + * Sets up event listeners for changing form values + * @return void + */ + private setupFormValueChangeListeners(): void { + this.form + .get('fenf_account_enforcement_action') + ?.valueChanges.pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((change: string | null): void => { + this.handleChangeEnforcementAction(change ?? ''); + }); + } + + /** + * Fetches the enforcement action result for the given ID. + * Additionally enables/disables the enforcer and local justice area form controls based on the requirements of the selected enforcement action. + * @param id Id of the result to fetch. + * @return void + */ + private getEnforcementActionResult(id: string): void { + this.finesService + .getResult(id) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((result) => { + this.disableFormControl('fenf_account_enforcement_lja'); + this.disableFormControl('fenf_account_enforcement_enforcer'); + if (result.requires_enforcer) { + this.enableFormControl('fenf_account_enforcement_enforcer'); + } + if (result.requires_lja) { + this.enableFormControl('fenf_account_enforcement_lja'); + } + this.form.updateValueAndValidity(); + }); + } + + /** + * Enables a form control + * @param controlName Name of the form field to enable + * @returns void + */ + private enableFormControl(controlName: string): void { + this.form.get(controlName)?.enable(); + } + + /** + * Disables a form control and resets its value. + * @param controlName Name of the form field to disable + * @returns void + */ + private disableFormControl(controlName: string): void { + this.form.get(controlName)?.reset(); + this.form.get(controlName)?.disable(); + } + + /** + * Lifecycle hook that is called after the component's view has been fully initialized. + * It sets up the form and the form value change listeners. + * @returns void + */ + public override ngOnInit(): void { + this.setupEnforcementOverrideAddChangeForm(); + this.setupFormValueChangeListeners(); + super.ngOnInit(); + } + + /** + * Handles the change enforcement action event emitted from the form when a new enforcement action is selected. + * @param id The result ID for the enforcement action that has been selected in the form + * @returns void + */ + public handleChangeEnforcementAction(id: string): void { + if (id) { + this.getEnforcementActionResult(id); + } else { + this.disableFormControl('fenf_account_enforcement_enforcer'); + this.disableFormControl('fenf_account_enforcement_lja'); + this.form.updateValueAndValidity(); + } + } +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html new file mode 100644 index 0000000000..0c9da30215 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.html @@ -0,0 +1,12 @@ +
+ +
diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts new file mode 100644 index 0000000000..dcc5c001ed --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.spec.ts @@ -0,0 +1,180 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { FinesAccEnfOverrideAddChangeComponent } from './fines-acc-enf-override-add-change.component'; +import { FinesAccountStore } from '@app/flows/fines/fines-acc/stores/fines-acc.store'; +import { FinesAccPayloadService } from '@app/flows/fines/fines-acc/services/fines-acc-payload.service'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { of, throwError } from 'rxjs'; +import { FinesAccountStoreType } from '../types/fines-account-store.type'; +import { signal } from '@angular/core'; + +describe('FinesAccEnfOverrideAddChangeComponent', () => { + let component: FinesAccEnfOverrideAddChangeComponent; + let fixture: ComponentFixture; + let mockRoute: ActivatedRoute; + let mockAccountStore: Pick< + FinesAccountStoreType, + 'getAccountNumber' | 'party_name' | 'account_id' | 'base_version' | 'business_unit_id' | 'setSuccessMessage' + >; + let mockPayloadService: Pick; + let mockOpalFinesService: Pick< + OpalFines, + 'patchDefendantAccount' | 'getEnforcerPrettyName' | 'getLocalJusticeAreaPrettyName' | 'getResultPrettyName' + >; + let mockUtilsService: Pick; + + beforeEach(async () => { + mockRoute = { + snapshot: { + data: { + title: 'Add enforcement override', + enforcersRefData: { + refData: [ + { enforcer_id: 'E1', enforcer_code: 'EC1', name: 'Enforcer One' }, + { enforcer_id: 'E2', enforcer_code: 'EC2', name: 'Enforcer Two' }, + ], + }, + localJusticeAreasRefData: { + refData: [ + { local_justice_area_id: 'L1', name: 'LJA One' }, + { local_justice_area_id: 'L2', name: 'LJA Two' }, + ], + }, + resultsRefData: { + refData: [ + { result_id: 'R1', result_title: 'Result One' }, + { result_id: 'R2', result_title: 'Result Two' }, + ], + }, + }, + }, + } as unknown as ActivatedRoute; + + mockAccountStore = { + getAccountNumber: signal('123456'), + party_name: signal('Test Person'), + account_id: signal(1001), + base_version: signal('1'), + business_unit_id: signal('2002'), + setSuccessMessage: vi.fn(), + }; + + mockPayloadService = { + buildEnforcementOverrideFormPayload: vi.fn().mockReturnValue({ enforcement_override: {} }), + }; + + mockOpalFinesService = { + patchDefendantAccount: vi.fn().mockReturnValue(of({})), + getEnforcerPrettyName: vi.fn( + (enforcer: { name: string; enforcer_code: string | number }) => `${enforcer.name} (${enforcer.enforcer_code})`, + ), + getLocalJusticeAreaPrettyName: vi.fn( + (lja: { name: string; local_justice_area_id: string | number }) => `${lja.name} (${lja.local_justice_area_id})`, + ), + getResultPrettyName: vi.fn( + (result: { result_title: string; result_id: string | number }) => + `${result.result_title} (${result.result_id})`, + ), + }; + + mockUtilsService = { + scrollToTop: vi.fn(), + }; + + await TestBed.configureTestingModule({ + imports: [FinesAccEnfOverrideAddChangeComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + { provide: FinesAccountStore, useValue: mockAccountStore }, + { provide: FinesAccPayloadService, useValue: mockPayloadService }, + { provide: OpalFines, useValue: mockOpalFinesService }, + { provide: UtilsService, useValue: mockUtilsService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(FinesAccEnfOverrideAddChangeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should map route data into option lists', () => { + expect(component.pageTitle).toBe('Add enforcement override'); + expect(component.enforcerOptions).toEqual([ + { value: 'E1', name: 'Enforcer One (EC1)' }, + { value: 'E2', name: 'Enforcer Two (EC2)' }, + ]); + expect(component.localJusticeAreaOptions).toEqual([ + { value: 'L1', name: 'LJA One (L1)' }, + { value: 'L2', name: 'LJA Two (L2)' }, + ]); + expect(component.enforcementActionOptions).toEqual([ + { value: 'R1', name: 'Result One (R1)' }, + { value: 'R2', name: 'Result Two (R2)' }, + ]); + }); + + it('should submit form and navigate on success', () => { + const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate'); + const form = { + formData: { + fenf_account_enforcement_action: 'R1', + fenf_account_enforcement_enforcer: 'E1', + fenf_account_enforcement_lja: 'L1', + }, + nestedFlow: false, + }; + + component.handleFinesEnfOverrideAddChangeSubmit(form); + + expect(mockPayloadService.buildEnforcementOverrideFormPayload).toHaveBeenCalledWith(form.formData); + expect(mockOpalFinesService.patchDefendantAccount).toHaveBeenCalledWith( + 1001, + { enforcement_override: {} }, + '1', + '2002', + ); + expect(routerNavigateSpy).toHaveBeenCalledWith( + FINES_ACC_DEFENDANT_ROUTING_PATHS.children.details, + false, + undefined, + null, + 'enforcement', + ); + }); + + it('should scroll to top on submit error', () => { + mockOpalFinesService.patchDefendantAccount = vi.fn().mockReturnValue(throwError(() => new Error('fail'))); + const routerNavigateSpy = vi.spyOn(component as never, 'routerNavigate'); + const form = { + formData: { + fenf_account_enforcement_action: 'R1', + fenf_account_enforcement_enforcer: 'E1', + fenf_account_enforcement_lja: 'L1', + }, + nestedFlow: false, + }; + + component.handleFinesEnfOverrideAddChangeSubmit(form); + + expect(mockUtilsService.scrollToTop).toHaveBeenCalled(); + expect(routerNavigateSpy).not.toHaveBeenCalled(); + }); + + it('should update stateUnsavedChanges when handleUnsavedChanges is called', () => { + component.handleUnsavedChanges(true); + expect((component as unknown as { stateUnsavedChanges: boolean }).stateUnsavedChanges).toBe(true); + }); + + it('should complete ngUnsubscribe on destroy', () => { + const subject = (component as unknown as { ngUnsubscribe: { isStopped: boolean } }).ngUnsubscribe; + component.ngOnDestroy(); + expect(subject.isStopped).toBe(true); + }); +}); diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts new file mode 100644 index 0000000000..4f4ba7e2eb --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component.ts @@ -0,0 +1,119 @@ +import { ChangeDetectionStrategy, Component, inject, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { IOpalFinesResultsRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-ref-data.interface'; +import { FinesAccEnfOverrideAddChangeFormComponent } from './fines-acc-enf-override-add-change-form/fines-acc-enf-override-add-change-form.component'; +import { FinesAccountStore } from '@app/flows/fines/fines-acc/stores/fines-acc.store'; +import { IGovUkSelectOptions } from '@hmcts/opal-frontend-common/components/govuk/govuk-select/interfaces'; +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesLocalJusticeAreaRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; +import { AbstractFormParentBaseComponent } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-parent-base'; +import { catchError, EMPTY, Subject, takeUntil } from 'rxjs'; +import { FinesAccPayloadService } from '@app/flows/fines/fines-acc/services/fines-acc-payload.service'; +import { OpalFines } from '@app/flows/fines/services/opal-fines-service/opal-fines.service'; +import { FINES_ACC_DEFENDANT_ROUTING_PATHS } from '@app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant'; +import { UtilsService } from '@hmcts/opal-frontend-common/services/utils-service'; +import { IFinesAccEnfOverrideAddChangeFormState } from './interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; + +@Component({ + selector: 'app-fines-acc-enf-override-add-change', + templateUrl: './fines-acc-enf-override-add-change.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [FinesAccEnfOverrideAddChangeFormComponent], +}) +export class FinesAccEnfOverrideAddChangeComponent extends AbstractFormParentBaseComponent implements OnDestroy { + private readonly ngUnsubscribe = new Subject(); + private route = inject(ActivatedRoute); + private finesAccStore = inject(FinesAccountStore); + private finesAccPayloadService = inject(FinesAccPayloadService); + private opalFinesService = inject(OpalFines); + private utilsService = inject(UtilsService); + private finesDefendantRoutingPaths = FINES_ACC_DEFENDANT_ROUTING_PATHS; + public accountNumber = this.finesAccStore.getAccountNumber() ?? ''; + public partyName = this.finesAccStore.party_name() ?? ''; + public pageTitle: string = this.route.snapshot.data['title'] ?? ''; + public enforcerOptions: IGovUkSelectOptions[] = this.setEnforcerOptions(); + public localJusticeAreaOptions: IGovUkSelectOptions[] = this.setLocalJusticeAreaOptions(); + public enforcementActionOptions: IGovUkSelectOptions[] = this.setEnforcementActionOptions(); + + /** + * Sets the options for the enforcer select dropdown. + * @returns An array of IGovUkSelectOptions representing the enforcers. + */ + private setEnforcerOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['enforcersRefData'] as IOpalFinesEnforcersRefData).refData.map((enforcer) => ({ + value: enforcer.enforcer_id, + name: this.opalFinesService.getEnforcerPrettyName(enforcer), + })); + } + + /** + * Sets the options for the local justice area select dropdown. + * @returns An array of IGovUkSelectOptions representing the local justice areas. + */ + private setLocalJusticeAreaOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['localJusticeAreasRefData'] as IOpalFinesLocalJusticeAreaRefData).refData.map( + (lja) => ({ + value: lja.local_justice_area_id, + name: this.opalFinesService.getLocalJusticeAreaPrettyName(lja), + }), + ); + } + + /** + * Sets the options for the enforcement action select dropdown. + * @returns An array of IGovUkSelectOptions representing the enforcement actions. + */ + private setEnforcementActionOptions(): IGovUkSelectOptions[] { + return (this.route.snapshot.data['resultsRefData'] as IOpalFinesResultsRefData).refData.map((result) => ({ + value: result.result_id, + name: this.opalFinesService.getResultPrettyName(result), + })); + } + + /** + * Handles the form submission for adding/removing/changing an enforcement override. + * @param form - The form data containing the enforcement override details. + */ + public handleFinesEnfOverrideAddChangeSubmit( + form: IAbstractFormBaseForm, + ): void { + const payload = this.finesAccPayloadService.buildEnforcementOverrideFormPayload(form.formData); + this.opalFinesService + .patchDefendantAccount( + this.finesAccStore.account_id()!, + payload, + this.finesAccStore.base_version()!, + this.finesAccStore.business_unit_id()!, + ) + .pipe( + catchError(() => { + this.utilsService.scrollToTop(); + return EMPTY; + }), + takeUntil(this.ngUnsubscribe), + ) + .subscribe(() => { + this.finesAccStore.setSuccessMessage('Enforcement override added'); + this.routerNavigate(this.finesDefendantRoutingPaths.children.details, false, undefined, null, 'enforcement'); + }); + } + + /** + * Handles unsaved changes coming from the child component + * + * @param unsavedChanges boolean value from child component + */ + public handleUnsavedChanges(unsavedChanges: boolean): void { + this.stateUnsavedChanges = unsavedChanges; + } + + /** + * Unsubscribes from all active subscriptions to prevent memory leaks when the component is destroyed. + */ + public ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts new file mode 100644 index 0000000000..e0bef04bea --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-field-errors.interface.ts @@ -0,0 +1,9 @@ +import { + IAbstractFormBaseFieldError, + IAbstractFormBaseFieldErrors, +} from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IFinesAccEnfOverrideAddChangeFormState } from './fines-acc-enf-override-add-change-form-state.interface'; + +export type IFinesAccEnfOverrideAddChangeFieldErrors = IAbstractFormBaseFieldErrors & { + [K in keyof IFinesAccEnfOverrideAddChangeFormState]: IAbstractFormBaseFieldError; +}; diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts new file mode 100644 index 0000000000..ecda5df1b5 --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface.ts @@ -0,0 +1,5 @@ +export interface IFinesAccEnfOverrideAddChangeFormState { + fenf_account_enforcement_action: string | null; + fenf_account_enforcement_enforcer: string | null; + fenf_account_enforcement_lja: string | null; +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts new file mode 100644 index 0000000000..f7fe8d145c --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form.interface.ts @@ -0,0 +1,6 @@ +import { IAbstractFormBaseForm } from '@hmcts/opal-frontend-common/components/abstract/abstract-form-base/interfaces'; +import { IFinesAccEnfOverrideAddChangeFormState } from './fines-acc-enf-override-add-change-form-state.interface'; + +export interface IFinesAccEnfOverrideAddChangeForm extends IAbstractFormBaseForm { + formData: IFinesAccEnfOverrideAddChangeFormState; +} diff --git a/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts new file mode 100644 index 0000000000..cd87acedfa --- /dev/null +++ b/src/app/flows/fines/fines-acc/fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-routing-paths.interface.ts @@ -0,0 +1,10 @@ +import { IChildRoutingPaths } from '@hmcts/opal-frontend-common/pages/routing/interfaces'; + +export interface IFinesAccEnfOverrideAddChangeRoutingPaths extends IChildRoutingPaths { + root: string; + children: { + add: string; + remove: string; + change: string; + }; +} diff --git a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts index 8c6b36df90..0c380e98ba 100644 --- a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts +++ b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-paths.constant.ts @@ -10,5 +10,6 @@ export const FINES_ACC_DEFENDANT_ROUTING_PATHS: IFinesAccDefendantRoutingPaths = 'payment-terms': 'payment-terms', party: 'party', 'payment-card': 'payment-card', + enforcement: 'enforcement', }, }; diff --git a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts index f1d8b7bd04..c7ddb586d4 100644 --- a/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts +++ b/src/app/flows/fines/fines-acc/routing/constants/fines-acc-defendant-routing-titles.constant.ts @@ -10,5 +10,6 @@ export const FINES_ACC_DEFENDANT_ROUTING_TITLES: IFinesAccDefendantRoutingPaths 'payment-terms': 'Amend payment terms', party: 'Amend party details', 'payment-card': 'Request payment card', + enforcement: 'Enforcement', }, }; diff --git a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts index acf3c220ec..e8420f4f79 100644 --- a/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts +++ b/src/app/flows/fines/fines-acc/routing/fines-acc.routes.ts @@ -16,6 +16,12 @@ import { FINES_ACC_MINOR_CREDITOR_ROUTING_TITLES } from './constants/fines-acc-m import { defendantAccountPartyResolver } from './resolvers/defendant-account-party.resolver'; import { defendantAccountPaymentTermsLatestResolver } from './resolvers/defendant-account-payment-terms-latest.resolver'; import { minorCreditorAccountHeadingResolver } from './resolvers/defendant-minor-creditor-heading.resolver'; +import { fetchResultsWithParamsResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver'; +import { IOpalFinesResultsParams } from '../../services/opal-fines-service/interfaces/opal-fines-results-params.interface'; +import { fetchLocalJusticeAreasResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver'; +import { fetchEnforcersResolver } from '../../routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES } from '../fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-titles.constant'; +import { FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS } from '../fines-acc-enf-override-add-change/constants/fines-acc-enf-override-add-change-routing-paths.constant'; const accRootPermissionIds = FINES_PERMISSIONS; @@ -168,6 +174,26 @@ export const routing: Routes = [ defendantAccountPaymentTermsData: defendantAccountPaymentTermsLatestResolver, }, }, + { + path: `${FINES_ACC_DEFENDANT_ROUTING_PATHS.children.enforcement}/${FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS.root}/${FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_PATHS.children.add}`, + loadComponent: () => + import('../fines-acc-enf-override-add-change/fines-acc-enf-override-add-change.component').then( + (c) => c.FinesAccEnfOverrideAddChangeComponent, + ), + canActivate: [routePermissionsGuard, finesAccStateGuard], + canDeactivate: [canDeactivateGuard], + data: { + resultsParams: { enforcement_override: true } as IOpalFinesResultsParams, + title: FINES_ACC_ENF_OVERRIDE_ADD_CHANGE_ROUTING_TITLES.children.add, + routePermissionId: [accRootPermissionIds['account-maintenance']], + }, + resolve: { + titleResolver: TitleResolver, + resultsRefData: fetchResultsWithParamsResolver, + localJusticeAreasRefData: fetchLocalJusticeAreasResolver, + enforcersRefData: fetchEnforcersResolver, + }, + }, ], }, { diff --git a/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts b/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts index cc514a485e..04763cdef9 100644 --- a/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts +++ b/src/app/flows/fines/fines-acc/routing/interfaces/fines-acc-defendant-routing-paths.interface.ts @@ -10,5 +10,6 @@ export interface IFinesAccDefendantRoutingPaths extends IChildRoutingPaths { 'payment-terms': string; party: string; 'payment-card': string; + enforcement: string; }; } diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts index 69988df8bf..74a28b2b30 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.spec.ts @@ -545,6 +545,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: 'Updated note 2', free_text_note_3: 'Updated note 3', }, + enforcement_override: null, }); }); @@ -565,6 +566,7 @@ describe('FinesAccPayloadService', () => { free_text_note_2: null, free_text_note_3: null, }, + enforcement_override: null, }); }); diff --git a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts index 5daed96efd..06959f87ef 100644 --- a/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts +++ b/src/app/flows/fines/fines-acc/services/fines-acc-payload.service.ts @@ -25,6 +25,8 @@ import { IOpalFinesAmendPaymentTermsPayload } from '@services/fines/opal-fines-s import { buildPaymentTermsAmendPayloadUtil } from './utils/fines-acc-payload-build-payment-terms-amend.utils'; import { buildAccountPartyFromFormState } from './utils/fines-acc-payload-build-defendant-data.utils'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; +import { IFinesAccEnfOverrideAddChangeFormState } from '../fines-acc-enf-override-add-change/interfaces/fines-acc-enf-override-add-change-form-state.interface'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from '../../services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant'; @Injectable({ providedIn: 'root', @@ -153,7 +155,7 @@ export class FinesAccPayloadService { } /** - * Transforms the given IFinesAccAddCommentsFormState and account version into an update payload + * Transforms the given IFinesAccAddCommentsFormState into an update payload * for the defendant account API. * * @param formState - The form state containing the comment and note data @@ -161,6 +163,7 @@ export class FinesAccPayloadService { */ public buildCommentsFormPayload(formState: IFinesAccAddCommentsFormState): IOpalFinesUpdateDefendantAccountPayload { return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: formState.facc_add_comment || null, free_text_note_1: formState.facc_add_free_text_1 || null, @@ -170,6 +173,43 @@ export class FinesAccPayloadService { }; } + /** + * Transforms the given IFinesAccEnfOverrideAddChangeFormState into an update payload + * for the defendant account API. + * + * @param formState - The form state containing the enforcement override data + * @returns The transformed payload for updating the defendant account + */ + public buildEnforcementOverrideFormPayload( + formState: IFinesAccEnfOverrideAddChangeFormState, + ): IOpalFinesUpdateDefendantAccountPayload { + const { + fenf_account_enforcement_action: enforcement_override_result_id, + fenf_account_enforcement_enforcer: enforcer_id, + fenf_account_enforcement_lja: lja_id, + } = formState; + return { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, + enforcement_override: { + enforcement_override_result: enforcement_override_result_id + ? { + enforcement_override_result_id, + } + : null, + enforcer: enforcer_id + ? { + enforcer_id, + } + : null, + lja: lja_id + ? { + lja_id, + } + : null, + }, + }; + } + /** * Transforms the given finesMacPayload object by applying the transformations diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts new file mode 100644 index 0000000000..016c37caea --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-enforcers-resolver.ts @@ -0,0 +1,9 @@ +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesEnforcersRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface'; +import { ResolveFn } from '@angular/router'; + +export const fetchEnforcersResolver: ResolveFn = () => { + const opalFinesService = inject(OpalFines); + return opalFinesService.getEnforcers(); +}; diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts new file mode 100644 index 0000000000..4313c2cf36 --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-ljas-resolver.ts @@ -0,0 +1,9 @@ +import { ResolveFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesLocalJusticeAreaRefData } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-local-justice-area-ref-data.interface'; + +export const fetchLocalJusticeAreasResolver: ResolveFn = () => { + const opalFinesService = inject(OpalFines); + return opalFinesService.getLocalJusticeAreas(); +}; diff --git a/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts new file mode 100644 index 0000000000..3c8a6fab73 --- /dev/null +++ b/src/app/flows/fines/routing/resolvers/fetch-results-with-params-resolver/fetch-results-with-params-resolver.ts @@ -0,0 +1,11 @@ +import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router'; +import { inject } from '@angular/core'; +import { OpalFines } from '@services/fines/opal-fines-service/opal-fines.service'; +import { IOpalFinesResultsRefData } from '@services/fines/opal-fines-service/interfaces/opal-fines-results-ref-data.interface'; +import { IOpalFinesResultsParams } from '@app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface'; + +export const fetchResultsWithParamsResolver: ResolveFn = (route: ActivatedRouteSnapshot) => { + const resultsParams = route.data['resultsParams'] as IOpalFinesResultsParams; + const opalFinesService = inject(OpalFines); + return opalFinesService.getResults([], resultsParams); +}; diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts index 6825c62482..b6c033308e 100644 --- a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-cache-defaults.constant.ts @@ -20,4 +20,5 @@ export const OPAL_FINES_CACHE_DEFAULTS: IOpalFinesCache = { defendantAccountHistoryAndNotesCache$: null, defendantAccountPaymentTermsLatestCache$: null, defendantAccountFixedPenaltyCache$: null, + enforcersCache$: null, }; diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts new file mode 100644 index 0000000000..b9fb0ce6fd --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-defendant-account-patch-payload-defaults.constant.ts @@ -0,0 +1,6 @@ +import { IOpalFinesUpdateDefendantAccountPayload } from '../interfaces/opal-fines-update-defendant-account.interface'; + +export const OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS: IOpalFinesUpdateDefendantAccountPayload = { + comment_and_notes: null, + enforcement_override: null, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts index c8031d3674..3623667939 100644 --- a/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts +++ b/src/app/flows/fines/services/opal-fines-service/constants/opal-fines-paths.constant.ts @@ -14,6 +14,7 @@ const defendantAccounts = 'defendant-accounts'; const notes = 'notes/add'; const minorCreditorAccounts = 'minor-creditor-accounts'; const searchAccounts = '/search'; +const enforcers = 'enforcers'; export const OPAL_FINES_PATHS: IOpalFinesPaths = { businessUnitRefData: `${baseUrl}${businessUnit}`, @@ -30,4 +31,5 @@ export const OPAL_FINES_PATHS: IOpalFinesPaths = { searchDefendantAccounts: `${baseUrl}${defendantAccounts}${searchAccounts}`, searchMinorCreditorAccounts: `${baseUrl}${minorCreditorAccounts}${searchAccounts}`, minorCreditorAccounts: `${baseUrl}${minorCreditorAccounts}`, + enforcersRefData: `${baseUrl}${enforcers}`, }; diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts index 7fea806704..d3b3d9a64b 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-account-defendant-details-enforcement-tab-ref-data.interface.ts @@ -23,7 +23,7 @@ export interface IOpalFinesAccountDefendantDetailsEnforcementTabRefData { lja_id: number; lja_name: string; }; - }; + } | null; enforcement_overview: { collection_order: IOpalFinesAccountDefendantDetailsEnforcementTabRefDataEnforcementOverviewCollectionOrder | null; days_in_default: number | null; @@ -51,5 +51,5 @@ export interface IOpalFinesAccountDefendantDetailsEnforcementTabRefData { }>; warrant_number: string; } | null; - next_enforcement_action_data: string; + next_enforcement_action_data: string | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts index 2e502b06e3..69a68ff459 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-cache.interface.ts @@ -15,6 +15,7 @@ import { IOpalFinesAccountDefendantDetailsHistoryAndNotesTabRefData } from './op import { IOpalFinesAccountDefendantDetailsPaymentTermsLatest } from './opal-fines-account-defendant-details-payment-terms-latest.interface'; import { IOpalFinesAccountDefendantDetailsFixedPenaltyTabRefData } from './opal-fines-account-defendant-details-fixed-penalty-tab-ref-data.interface'; import { IOpalFinesResultRefData } from './opal-fines-result-ref-data.interface'; +import { IOpalFinesEnforcersRefData } from './opal-fines-enforcers-ref-data.interface'; export interface IOpalFinesCache { courtRefDataCache$: { [key: string]: Observable }; @@ -36,4 +37,5 @@ export interface IOpalFinesCache { defendantAccountHistoryAndNotesCache$: Observable | null; defendantAccountPaymentTermsLatestCache$: Observable | null; defendantAccountFixedPenaltyCache$: Observable | null; + enforcersCache$: Observable | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts new file mode 100644 index 0000000000..99cb0a036b --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcer.interface.ts @@ -0,0 +1,6 @@ +export interface IOpalFinesEnforcer { + enforcer_id: number; + enforcer_code: number; + name: string; + name_cy: string | null; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts new file mode 100644 index 0000000000..954988349a --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-enforcers-ref-data.interface.ts @@ -0,0 +1,6 @@ +import { IOpalFinesEnforcer } from './opal-fines-enforcer.interface'; + +export interface IOpalFinesEnforcersRefData { + count: number; + refData: IOpalFinesEnforcer[]; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts index 6b2add8d7c..79db4a2ced 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-paths.interface.ts @@ -13,4 +13,5 @@ export interface IOpalFinesPaths { searchDefendantAccounts: string; searchMinorCreditorAccounts: string; minorCreditorAccounts: string; + enforcersRefData: string; } diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts new file mode 100644 index 0000000000..b00eef01d5 --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-results-params.interface.ts @@ -0,0 +1,3 @@ +export interface IOpalFinesResultsParams { + enforcement_override: boolean; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts new file mode 100644 index 0000000000..86d105cc25 --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account-enforcement-override.interface.ts @@ -0,0 +1,11 @@ +export interface IOpalFinesUpdateDefendantAccountEnforcementOverride { + enforcement_override_result: { + enforcement_override_result_id: string; + } | null; + enforcer: { + enforcer_id: string; + } | null; + lja: { + lja_id: string; + } | null; +} diff --git a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts index 14ad4ce15a..09d92d1f52 100644 --- a/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts +++ b/src/app/flows/fines/services/opal-fines-service/interfaces/opal-fines-update-defendant-account.interface.ts @@ -1,8 +1,10 @@ import { IOpalFinesUpdateDefendantAccountCommentsNotes } from './opal-fines-update-defendant-account-comments-notes.interface'; +import { IOpalFinesUpdateDefendantAccountEnforcementOverride } from './opal-fines-update-defendant-account-enforcement-override.interface'; /** * Interface for the payload to update a defendant account *Subject to change */ export interface IOpalFinesUpdateDefendantAccountPayload { - comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes; + comment_and_notes: IOpalFinesUpdateDefendantAccountCommentsNotes | null; + enforcement_override: IOpalFinesUpdateDefendantAccountEnforcementOverride | null; } diff --git a/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts new file mode 100644 index 0000000000..2bdd27ad3b --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-enforcer.mock.ts @@ -0,0 +1,8 @@ +import { IOpalFinesEnforcer } from '../interfaces/opal-fines-enforcer.interface'; + +export const OPAL_FINES_ENFORCER_MOCK: IOpalFinesEnforcer = { + enforcer_id: 101, + enforcer_code: 202, + name: 'Enforcer One', + name_cy: null, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts new file mode 100644 index 0000000000..cf0aae1c7e --- /dev/null +++ b/src/app/flows/fines/services/opal-fines-service/mocks/opal-fines-results-params.mock.ts @@ -0,0 +1,5 @@ +import { IOpalFinesResultsParams } from '../interfaces/opal-fines-results-params.interface'; + +export const OPAL_FINES_RESULTS_PARAMS_MOCK: IOpalFinesResultsParams = { + enforcement_override: true, +}; diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts index 0e1a27aeb2..c9338550cc 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.spec.ts @@ -53,7 +53,11 @@ import { of } from 'rxjs'; import { OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_FIXED_PENALTY_MOCK } from './mocks/opal-fines-account-defendant-details-fixed-penalty.mock'; import { IOpalFinesResultRefData } from './interfaces/opal-fines-result-ref-data.interface'; import { FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK } from '../../fines-acc/fines-acc-minor-creditor-details/mocks/fines-acc-minor-creditor-details-header.mock'; +import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { OPAL_FINES_ENFORCER_MOCK } from './mocks/opal-fines-enforcer.mock'; +import { OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS } from './constants/opal-fines-defendant-account-patch-payload-defaults.constant'; describe('OpalFines', () => { let service: OpalFines; @@ -275,6 +279,52 @@ describe('OpalFines', () => { expect(result).toEqual(`${localJusticeArea.name} (${localJusticeArea.lja_code})`); }); + it('should send a GET request to enforcers ref data API', () => { + const mockEnforcers: IOpalFinesEnforcersRefData = { + count: 1, + refData: [OPAL_FINES_ENFORCER_MOCK], + }; + const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + const req = httpMock.expectOne(expectedUrl); + expect(req.request.method).toBe('GET'); + req.flush(mockEnforcers); + }); + + it('should return cached response for enforcers ref data', () => { + const mockEnforcers: IOpalFinesEnforcersRefData = { + count: 1, + refData: [OPAL_FINES_ENFORCER_MOCK], + }; + const expectedUrl = `${OPAL_FINES_PATHS.enforcersRefData}`; + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + const req = httpMock.expectOne(expectedUrl); + expect(req.request.method).toBe('GET'); + req.flush(mockEnforcers); + + service.getEnforcers().subscribe((response) => { + expect(response).toEqual(mockEnforcers); + }); + + httpMock.expectNone(expectedUrl); + }); + + it('should return the enforcer name and code in a pretty format', () => { + const enforcer: IOpalFinesEnforcer = OPAL_FINES_ENFORCER_MOCK; + + const result = service.getEnforcerPrettyName(enforcer); + + expect(result).toEqual(`${enforcer.name} (${enforcer.enforcer_code})`); + }); + it('should return the item value for a given configuration item name', () => { const businessUnit = OPAL_FINES_BUSINESS_UNIT_REF_DATA_MOCK.refData[0]; const expectedValue = 'Item1'; @@ -293,6 +343,30 @@ describe('OpalFines', () => { expect(result).toBeNull(); }); + it('should return the item value for a given configuration item name on non-snake-case business unit', () => { + const businessUnit = OPAL_FINES_BUSINESS_UNIT_NON_SNAKE_CASE_MOCK; + const itemName = 'Item1'; + + const result = service.getConfigurationItemValue(businessUnit, itemName); + + expect(result).toEqual('Item1'); + }); + + it('should return null for non-snake-case business unit when item exists but itemValue is null', () => { + const businessUnit = structuredClone(OPAL_FINES_BUSINESS_UNIT_NON_SNAKE_CASE_MOCK); + businessUnit.configurationItems = [ + { + itemName: 'Item1', + itemValue: null, + itemValues: [], + }, + ]; + + const result = service.getConfigurationItemValue(businessUnit, 'Item1'); + + expect(result).toBeNull(); + }); + it('should send a GET request to results ref data API', () => { const resultIds = ['1', '2', '3']; const expectedResponse: IOpalFinesResultsRefData = OPAL_FINES_RESULTS_REF_DATA_MOCK; @@ -842,12 +916,12 @@ describe('OpalFines', () => { req.flush(expectedResponse); }); - it('should getDefendantAccountEnforcementTabData', () => { + it('should getDefendantAccountEnforcementStatus', () => { const account_id: number = 77; const apiUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${account_id}/enforcement-status`; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK; - service.getDefendantAccountEnforcementTabData(account_id).subscribe((response) => { + service.getDefendantAccountEnforcementStatus(account_id).subscribe((response) => { response.version = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_ENFORCEMENT_TAB_REF_DATA_MOCK.version; expect(response).toEqual(expectedResponse); }); @@ -859,9 +933,6 @@ describe('OpalFines', () => { }); it('should getDefendantAccountImpositionsTabData', () => { - // const account_id: number = 77; - // const business_unit_id: string = '12'; - // const business_unit_user_id: string | null = '12'; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_IMPOSITIONS_TAB_REF_DATA_MOCK; service.getDefendantAccountImpositionsTabData().subscribe((response) => { @@ -905,9 +976,6 @@ describe('OpalFines', () => { }); it('should getDefendantAccountHistoryAndNotesTabData', () => { - // const account_id: number = 77; - // const business_unit_id: string = '12'; - // const business_unit_user_id: string | null = '12'; const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_DETAILS_HISTORY_AND_NOTES_TAB_REF_DATA_MOCK; service.getDefendantAccountHistoryAndNotesTabData().subscribe((response) => { @@ -1149,6 +1217,7 @@ describe('OpalFines', () => { it('should return a mock response for patching defendant account', () => { const accountId = 123456; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, comment_and_notes: { account_comment: 'Updated comment', free_text_note_1: 'Updated note 1', @@ -1170,6 +1239,7 @@ describe('OpalFines', () => { it('should handle different payload values in mock response for patching defendant account', () => { const accountId = 789012; const updatePayload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, version: 5, comment_and_notes: { account_comment: 'Different comment', @@ -1189,6 +1259,85 @@ describe('OpalFines', () => { req.flush({ defendant_account_id: accountId, message: 'Account comments notes updated successfully' }); }); + describe('patchDefendantAccount headers', () => { + it('should include If-Match and Business-Unit-Id headers when both are provided', () => { + const accountId = 123456; + const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; + const version = '10'; + const businessUnitId = '2002'; + + service.patchDefendantAccount(accountId, payload, version, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.get('If-Match')).toBe(version); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + req.flush({ defendant_account_id: accountId }); + }); + + it('should include only Business-Unit-Id when version is not provided', () => { + const accountId = 123456; + const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; + const businessUnitId = '2002'; + + service.patchDefendantAccount(accountId, payload, undefined, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + req.flush({ defendant_account_id: accountId }); + }); + + it('should not include If-Match and should include Business-Unit-Id when version is empty string and business unit is empty string', () => { + const accountId = 123456; + const payload = { + ...OPAL_FINES_DEFENDANT_ACCOUNT_PATCH_PAYLOAD_DEFAULTS, + version: 10, + comment_and_notes: { + account_comment: 'Test comment', + free_text_note_1: null, + free_text_note_2: null, + free_text_note_3: null, + }, + }; + const version = ''; + const businessUnitId = ''; + + service.patchDefendantAccount(accountId, payload, version, businessUnitId).subscribe((response) => { + expect(response).toEqual({ defendant_account_id: accountId }); + }); + + const req = httpMock.expectOne(`${OPAL_FINES_PATHS.defendantAccounts}/${accountId}`); + expect(req.request.method).toBe('PATCH'); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.get('Business-Unit-Id')).toBe(''); + req.flush({ defendant_account_id: accountId }); + }); + }); + describe('putDefendantAccountPaymentTerms', () => { it('should send a PUT request to amend payment terms for a defendant account', () => { const defendantAccountId = 123456; @@ -1237,6 +1386,54 @@ describe('OpalFines', () => { }); }); + describe('putDefendantAccountParty', () => { + it('should send a PUT request to amend defendant account party with optional headers', () => { + const defendantAccountId = 123456; + const defendantAccountPartyId = 'PARTY-123'; + const version = '8'; + const businessUnitId = '61'; + const payload = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK.defendant_account_party; + const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK; + + service + .putDefendantAccountParty(defendantAccountId, defendantAccountPartyId, payload, version, businessUnitId) + .subscribe((response) => { + expect(response).toEqual(expectedResponse); + }); + + const expectedUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${defendantAccountId}/defendant-account-parties/${defendantAccountPartyId}`; + const req = httpMock.expectOne(expectedUrl); + + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(payload); + expect(req.request.headers.get('If-Match')).toBe(version); + expect(req.request.headers.get('Business-Unit-Id')).toBe(businessUnitId); + + req.flush(expectedResponse); + }); + + it('should send a PUT request without optional headers when version and businessUnitId are not provided', () => { + const defendantAccountId = 123456; + const defendantAccountPartyId = 'PARTY-123'; + const payload = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK.defendant_account_party; + const expectedResponse = OPAL_FINES_ACCOUNT_DEFENDANT_ACCOUNT_PARTY_MOCK; + + service.putDefendantAccountParty(defendantAccountId, defendantAccountPartyId, payload).subscribe((response) => { + expect(response).toEqual(expectedResponse); + }); + + const expectedUrl = `${OPAL_FINES_PATHS.defendantAccounts}/${defendantAccountId}/defendant-account-parties/${defendantAccountPartyId}`; + const req = httpMock.expectOne(expectedUrl); + + expect(req.request.method).toBe('PUT'); + expect(req.request.body).toEqual(payload); + expect(req.request.headers.has('If-Match')).toBe(false); + expect(req.request.headers.has('Business-Unit-Id')).toBe(false); + + req.flush(expectedResponse); + }); + }); + it('should getMinorCreditorAccountHeader', () => { const accountId = 456; const expectedResponse = FINES_ACC_MINOR_CREDITOR_DETAILS_HEADER_MOCK; diff --git a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts index c21d55f88d..dbf2067941 100644 --- a/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts +++ b/src/app/flows/fines/services/opal-fines-service/opal-fines.service.ts @@ -50,6 +50,9 @@ import { IOpalFinesAccountDefendantDetailsFixedPenaltyTabRefData } from './inter import { IOpalFinesResultRefData } from './interfaces/opal-fines-result-ref-data.interface'; import { IOpalFinesAccountMinorCreditorDetailsHeader } from '../../fines-acc/fines-acc-minor-creditor-details/interfaces/fines-acc-minor-creditor-details-header.interface'; import { IOpalFinesAccountRequestPaymentCardResponse } from './interfaces/opal-fines-account-request-payment-card-response.interface'; +import { IOpalFinesResultsParams } from './interfaces/opal-fines-results-params.interface'; +import { IOpalFinesEnforcersRefData } from './interfaces/opal-fines-enforcers-ref-data.interface'; +import { IOpalFinesEnforcer } from './interfaces/opal-fines-enforcer.interface'; @Injectable({ providedIn: 'root', @@ -317,12 +320,18 @@ export class OpalFines { /** * Retrieves the Opal fines results based on the provided result IDs. * @param result_ids - An array of result IDs. + * @param params - The parameters for fetching the results. * @returns An Observable that emits the Opal fines results reference data. */ - public getResults(result_ids: string[]): Observable { + public getResults( + result_ids: string[], + params: IOpalFinesResultsParams | null = null, + ): Observable { if (!this.cache.resultsCache$) { this.cache.resultsCache$ = this.http - .get(OPAL_FINES_PATHS.resultsRefData, { params: { result_ids } }) + .get(OPAL_FINES_PATHS.resultsRefData, { + params: { result_ids, ...params }, + }) .pipe(shareReplay(1)); } @@ -608,6 +617,31 @@ export class OpalFines { return this.cache.prosecutorDataCache$[business_unit]; } + /** + * Retrieves the prosecutor data for a specific business unit. + * If the prosecutor data is not already cached, it makes an HTTP request to fetch the data and caches it for future use. + * @param business_unit - The business unit for which to retrieve the prosecutor data. + * @returns An Observable that emits the prosecutor data for the specified business unit. + */ + public getEnforcers(): Observable { + if (!this.cache.enforcersCache$) { + this.cache.enforcersCache$ = this.http + .get(OPAL_FINES_PATHS.enforcersRefData) + .pipe(shareReplay(1)); + } + + return this.cache.enforcersCache$; + } + + /** + * Returns the pretty name of an enforcer. + * @param enforcer - The enforcer object. + * @returns The pretty name of the enforcer. + */ + public getEnforcerPrettyName(enforcer: IOpalFinesEnforcer): string { + return `${enforcer.name} (${enforcer.enforcer_code})`; + } + /** * Retrieves the defendant account details at a glance for a specific tab. * If the account details for the specified tab are not already cached, it makes an HTTP request to fetch the data and caches it for future use. @@ -728,13 +762,13 @@ export class OpalFines { } /** - * Retrieves the defendant account details enforcement tab data. - * If the account details for the specified tab are not already cached, it makes an HTTP request to fetch the data and caches it for future use. + * Retrieves the defendant account details enforcement status. + * If the account details for the enforcement status are not already cached, it makes an HTTP request to fetch the data and caches it for future use. * * @param account_id - The ID of the defendant account. - * @returns An Observable that emits the account details at a glance for the specified tab. + * @returns An Observable of the defendants enforcement status. */ - public getDefendantAccountEnforcementTabData( + public getDefendantAccountEnforcementStatus( account_id: number | null, ): Observable { if (!this.cache.defendantAccountEnforcementCache$) {