Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions spp_change_request_v2/static/src/js/create_change_request.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,39 @@ import {FormController} from "@web/views/form/form_controller";
import {ListController} from "@web/views/list/list_controller";
import {onWillStart} from "@odoo/owl";
import {patch} from "@web/core/utils/patch";
import {registry} from "@web/core/registry";
import {useService} from "@web/core/utils/hooks";
import {user} from "@web/core/user";

/**
* Client action to close wizard modal and then open CR detail form.
* Uses async/await to ensure the modal is fully closed before navigating.
*/
async function openCRCloseModal(env, action) {
const actionService = env.services.action;
const params = action.params || {};

await actionService.doAction(
{type: "ir.actions.act_window_close"},
{clearBreadcrumbs: true}
);

if (params.res_id) {
await actionService.doAction({
type: "ir.actions.act_window",
name: params.name || "Change Request Details",
res_model: params.res_model,
res_id: params.res_id,
view_mode: "form",
views: [[params.view_id || false, "form"]],
target: "current",
context: params.context || {},
});
}
}

registry.category("actions").add("open_cr_close_modal", openCRCloseModal);

patch(ListController.prototype, {
setup() {
super.setup();
Expand Down
43 changes: 23 additions & 20 deletions spp_change_request_v2/wizards/create_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,35 +232,38 @@ def action_create_draft(self):
}
)

# Open the detail form directly for editing
# Close wizard modal and open detail form using client action
# The client action ensures the modal is fully closed before navigating
detail = cr.get_detail()
if detail:
view_id = self.request_type_id.get_detail_form_view_id()
return {
"type": "ir.actions.act_window",
"name": "Change Request Details",
"res_model": cr.detail_res_model,
"res_id": detail.id,
"view_mode": "form",
"view_id": view_id,
"target": "current",
"context": {
"create": False,
"delete": False,
"form_view_initial_mode": "edit",
"type": "ir.actions.client",
"tag": "open_cr_close_modal",
"params": {
"name": _("Change Request Details"),
"res_model": cr.detail_res_model,
"res_id": detail.id,
"view_id": view_id,
"context": {
"create": False,
"delete": False,
"form_view_initial_mode": "edit",
},
},
}

# Fallback: open CR form if no detail model configured
return {
"type": "ir.actions.act_window",
"name": "Change Request",
"res_model": "spp.change.request",
"res_id": cr.id,
"view_mode": "form",
"target": "current",
"context": {
"form_view_initial_mode": "edit",
"type": "ir.actions.client",
"tag": "open_cr_close_modal",
"params": {
"name": _("Change Request"),
"res_model": "spp.change.request",
"res_id": cr.id,
"context": {
"form_view_initial_mode": "edit",
},
},
}

Expand Down
6 changes: 4 additions & 2 deletions spp_programs/static/src/js/create_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {useService} from "@web/core/utils/hooks";
async function openProgramCloseModal(env, action) {
const actionService = env.services.action;
const programId = action.params?.program_id;
const programName = action.params?.name || "Program";
const viewId = action.params?.view_id || false;

await actionService.doAction(
{type: "ir.actions.act_window_close"},
Expand All @@ -24,10 +26,10 @@ async function openProgramCloseModal(env, action) {
if (programId) {
await actionService.doAction({
type: "ir.actions.act_window",
name: "Program",
name: programName,
res_model: "spp.program",
res_id: programId,
views: [[false, "form"]],
views: [[viewId, "form"]],
target: "current",
});
}
Comment on lines 16 to 35

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The name of the action window is hardcoded as "Program", which isn't translatable. Also, the views parameter is hardcoded to use the default form view ([[false, "form"]]), which differs from the previous implementation that allowed a specific view_id to be used. To improve internationalization and restore the original flexibility, consider passing the name and view_id from the server-side action's parameters.

    const actionService = env.services.action;
    const programId = action.params?.program_id;
    const programName = action.params?.name || "Program";
    const viewId = action.params?.view_id || false;

    // First close the modal
    await actionService.doAction(
        {type: "ir.actions.act_window_close"},
        {clearBreadcrumbs: true}
    );

    // Then open the program form
    if (programId) {
        await actionService.doAction({
            type: "ir.actions.act_window",
            name: programName,
            res_model: "spp.program",
            res_id: programId,
            views: [[viewId, "form"]],
            target: "current",
        });
    }

Expand Down
18 changes: 9 additions & 9 deletions spp_programs/tests/test_create_program_wiz.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def setUp(self):
}
)

self.program = self._program_create_wiz.create_program()
self._program_action = self._program_create_wiz.create_program()
# self.cycle_manager_default = (
# self._program_create_wiz.create_cycle_manager_default(self.program.id)
# )
Expand Down Expand Up @@ -69,19 +69,19 @@ def test_02_create_program(self):
)
res = new_wiz.create_program()
self.assertEqual(type(res), dict, "Action should be in json format!")
for key in ("type", "res_model", "res_id"):
for key in ("type", "tag", "params"):
self.assertIn(key, res.keys(), f"Key `{key}` is missing!")
self.assertEqual(
res["type"],
"ir.actions.act_window",
"ir.actions.client",
"Action for program should be returned!",
)
self.assertEqual(res["res_model"], "spp.program", "Action for program should be return!")
self.assertTrue(res["res_id"], "New record for program should be existed!")
self.assertEqual(res["tag"], "open_program_close_modal", "Client action tag should match!")
self.assertTrue(res["params"]["program_id"], "New record for program should be existed!")

def test_03_get_eligibility_manager(self):
self._program_create_wiz.eligibility_type = "default_eligibility"
res = self._program_create_wiz._get_eligibility_manager(self.program["res_id"])
res = self._program_create_wiz._get_eligibility_manager(self._program_action["params"]["program_id"])

self.assertIn("eligibility_managers", res)

Expand Down Expand Up @@ -115,7 +115,7 @@ def test_06_create_program(self):
"entitlement_item_ids": [Command.create({"product_id": self.product.id, "quantity": 1})],
}
)
program = new_wiz.create_program()
action = new_wiz.create_program()

self.assertEqual(program["res_model"], "spp.program")
self.assertIsNotNone(program)
self.assertEqual(action["type"], "ir.actions.client")
self.assertTrue(action["params"]["program_id"])
2 changes: 1 addition & 1 deletion spp_programs/tests/test_spp_cycle_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def setUpClass(cls):
}
)
action = cls._test.create_program()
cls.program = cls.env["spp.program"].browse(action["res_id"])
cls.program = cls.env["spp.program"].browse(action["params"]["program_id"])

@classmethod
def _create_individual(self, vals):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def test_01_create_program_without_compliance_manager(self):
wizard = self.program_create_wizard({})
wizard._check_compliance_manager_info()
action = wizard.create_program()
program = self.env["spp.program"].browse(action["res_id"])
program = self.env["spp.program"].browse(action["params"]["program_id"])
self.assertFalse(
bool(program.compliance_manager_ids),
"Should not create compliance manager for new program!",
Expand All @@ -38,7 +38,7 @@ def test_03_create_program_default_compliance_manager(self):
}
)
action = wizard.create_program()
program = self.env["spp.program"].browse(action["res_id"])
program = self.env["spp.program"].browse(action["params"]["program_id"])
self.assertTrue(
bool(program.compliance_manager_ids),
"Should create compliance manager for new program!",
Expand Down
2 changes: 1 addition & 1 deletion spp_programs/views/managers/eligibility_manager_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Part of OpenSPP. See LICENSE file for full copyright and licensing details.
<field name="program_id" invisible="1" />
</div>

<group string="Geographic Targeting" name="geographic_targeting" colspan="4" col="4">
<group string="Geographic Targeting" name="geographic_targeting" colspan="4" col="4" invisible="1">
<field
name="admin_area_ids"
widget="many2many_tags"
Expand Down
95 changes: 48 additions & 47 deletions spp_programs/wizard/create_program_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,72 +366,73 @@ def _insert_domain_operator(self, domain):
return new_domain

def create_program(self):
self.ensure_one()
self._check_required_fields()
for rec in self:
program_vals = rec.get_program_vals()
program = self.env["spp.program"].with_context(skip_default_managers=True).create(program_vals)

program_id = program.id
vals = {}
program_vals = self.get_program_vals()
program = self.env["spp.program"].with_context(skip_default_managers=True).create(program_vals)

# Set Default Eligibility Manager settings
vals.update(rec._get_eligibility_manager(program_id))
program_id = program.id
vals = {}

# Set Default Cycle Manager settings
# Add a new record to default cycle manager model
# Set Default Eligibility Manager settings
vals.update(self._get_eligibility_manager(program_id))

cycle_manager_default_val = rec.get_cycle_manager_default_val(program_id)
def_mgr = self.env["spp.cycle.manager.default"].create(cycle_manager_default_val)
# Set Default Cycle Manager settings
# Add a new record to default cycle manager model

# Add a new record to cycle manager parent model
cycle_manager_default_val = self.get_cycle_manager_default_val(program_id)
def_mgr = self.env["spp.cycle.manager.default"].create(cycle_manager_default_val)

cycle_manager_val = rec.get_cycle_manager_val(program_id, def_mgr)
mgr = self.env["spp.cycle.manager"].create(cycle_manager_val)
# Add a new record to cycle manager parent model

vals.update({"cycle_manager_ids": [(4, mgr.id)]})
cycle_manager_val = self.get_cycle_manager_val(program_id, def_mgr)
mgr = self.env["spp.cycle.manager"].create(cycle_manager_val)

# Set Default Entitlement Manager
vals.update(rec._get_entitlement_manager(program_id))
vals.update({"cycle_manager_ids": [(4, mgr.id)]})

# Set Default Program Manager
vals.update(rec._get_program_manager(program_id))
# Set Default Entitlement Manager
vals.update(self._get_entitlement_manager(program_id))

# Clean legacy aliases that are not real fields on spp.program
vals.pop("eligibility_managers", None)
vals.pop("cycle_managers", None)
# Convert legacy entitlement_managers key to entitlement_manager_ids
if "entitlement_managers" in vals:
entitlement_managers = vals.pop("entitlement_managers")
if "entitlement_manager_ids" not in vals:
vals["entitlement_manager_ids"] = entitlement_managers
# Set Default Program Manager
vals.update(self._get_program_manager(program_id))

vals.update({"is_one_time_distribution": rec.is_one_time_distribution})
# Clean legacy aliases that are not real fields on spp.program
vals.pop("eligibility_managers", None)
vals.pop("cycle_managers", None)
# Convert legacy entitlement_managers key to entitlement_manager_ids
if "entitlement_managers" in vals:
entitlement_managers = vals.pop("entitlement_managers")
if "entitlement_manager_ids" not in vals:
vals["entitlement_manager_ids"] = entitlement_managers

# Complete the program data
program.update(vals)
vals.update({"is_one_time_distribution": self.is_one_time_distribution})

if rec.import_beneficiaries == "yes" or rec.is_one_time_distribution:
rec.program_wizard_import_beneficiaries(program)
# Complete the program data
program.update(vals)

if rec.is_one_time_distribution:
program.create_new_cycle()
if self.import_beneficiaries == "yes" or self.is_one_time_distribution:
self.program_wizard_import_beneficiaries(program)

view_id = self.env.ref("spp_programs.view_program_list_form")
if rec.view_id:
view_id = rec.view_id
if self.is_one_time_distribution:
program.create_new_cycle()

program.view_id = view_id.id
view_id = self.env.ref("spp_programs.view_program_list_form")
if self.view_id:
view_id = self.view_id

# Open the newly created program
return {
"name": _("Programs"),
"view_mode": "form",
"res_model": "spp.program",
"res_id": program_id,
program.view_id = view_id.id

# Close modal and open program form using client action with async/await
return {
"type": "ir.actions.client",
"tag": "open_program_close_modal",
"params": {
"program_id": program_id,
"name": _("Program"),
"view_id": view_id.id,
"type": "ir.actions.act_window",
"target": "current",
}
},
Comment on lines 430 to 434

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support internationalization and allow for specific form views, it's a good practice to pass the translated window name and the calculated view_id to the client action. The JavaScript action can then use these parameters instead of hardcoded values.

            "params": {
                "program_id": program_id,
                "name": _("Program"),
                "view_id": view_id.id,
            },

}

def _get_default_eligibility_manager_val(self, program_id):
return {
Expand Down
2 changes: 1 addition & 1 deletion spp_programs/wizard/create_program_wizard.xml
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Part of OpenSPP. See LICENSE file for full copyright and licensing details.
name="default_eligibility"
invisible="eligibility_type != 'default_eligibility'"
>
<group colspan="4" string="Geographic Targeting">
<group colspan="4" string="Geographic Targeting" invisible="1">
<field
name="admin_area_ids"
colspan="4"
Expand Down
2 changes: 1 addition & 1 deletion spp_programs/wizard/create_program_wizard_cel.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def create_program(self):

# Create dedicated compliance manager if CEL compliance is enabled
if self.enable_compliance_cel and self.compliance_cel_expression:
program = self.env["spp.program"].browse(action["res_id"])
program = self.env["spp.program"].browse(action["params"]["program_id"])
self._create_cel_compliance_manager(program)

return action
Expand Down
2 changes: 1 addition & 1 deletion spp_programs/wizard/create_program_wizard_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _check_required_fields(self):
def create_program(self):
action = super().create_program()
if self.enable_compliance_verification:
program = self.env["spp.program"].browse(action["res_id"])
program = self.env["spp.program"].browse(action["params"]["program_id"])
self._create_compliance_manager(program)
return action

Expand Down
Loading