Skip to content

Commit d9d9b66

Browse files
Merge pull request #28 from OpenSPP/fix/custom-list-create-button-conflict
fix(js): resolve create button conflict between Programs and Change Request
2 parents 58ef751 + 7b8880b commit d9d9b66

File tree

7 files changed

+175
-106
lines changed

7 files changed

+175
-106
lines changed

spp_base_common/__manifest__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
"assets": {
3535
"web.assets_backend": [
3636
"spp_base_common/static/src/scss/navbar.scss",
37+
"spp_base_common/static/src/js/custom_list_create.js",
38+
"spp_base_common/static/src/xml/custom_list_create_template.xml",
3739
],
3840
"web._assets_primary_variables": [
3941
"spp_base_common/static/src/scss/colors.scss",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/** @odoo-module **/
2+
3+
import {ListController} from "@web/views/list/list_controller";
4+
import {patch} from "@web/core/utils/patch";
5+
6+
/**
7+
* Base handler for the generic custom create button.
8+
* Modules override this method via patch to handle their specific model.
9+
*
10+
* Usage in other modules:
11+
* patch(ListController.prototype, {
12+
* async onCustomListCreate() {
13+
* if (this.model.root.resModel === "my.model") {
14+
* // open wizard
15+
* return;
16+
* }
17+
* return super.onCustomListCreate(...arguments);
18+
* },
19+
* });
20+
*/
21+
patch(ListController.prototype, {
22+
async onCustomListCreate() {
23+
// Base no-op — overridden by consuming modules
24+
},
25+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates xml:space="preserve">
3+
4+
<!-- Generic extensible create button for ListView.
5+
Any module can set this.customListCreateButton = {label, title, className}
6+
and override onCustomListCreate() in their ListController patch
7+
to replace the default "New" button with a custom one. -->
8+
<t t-inherit="web.ListView" t-inherit-mode="extension">
9+
<xpath expr="//Layout/t[@t-set-slot='control-panel-create-button']" position="replace">
10+
<t t-set-slot="control-panel-create-button">
11+
<t t-if="customListCreateButton">
12+
<button
13+
t-if="!editedRecord and activeActions.create and props.showButtons"
14+
type="button"
15+
t-att-class="'btn btn-primary ' + (customListCreateButton.className or '')"
16+
data-hotkey="c"
17+
t-att-title="customListCreateButton.title or ''"
18+
t-on-click="onCustomListCreate"
19+
t-esc="customListCreateButton.label"
20+
/>
21+
</t>
22+
<t t-else="">
23+
<button
24+
t-if="!editedRecord and activeActions.create and props.showButtons"
25+
type="button"
26+
class="btn btn-primary o_list_button_add"
27+
data-hotkey="c"
28+
t-on-click="onClickCreate"
29+
data-bounce-button=""
30+
>New</button>
31+
<t t-if="props.showButtons and !env.inDialog" t-call="web.ListView.EditableButtons" />
32+
</t>
33+
</t>
34+
</xpath>
35+
</t>
36+
37+
<!-- Generic extensible form create button hide.
38+
Any module can set this.hideFormCreateButton = true in their
39+
FormController patch to hide the default "New" button. -->
40+
<t t-inherit="web.FormView" t-inherit-mode="extension">
41+
<xpath expr="//button[hasclass('o_form_button_create')]" position="attributes">
42+
<attribute name="t-if">!hideFormCreateButton</attribute>
43+
</xpath>
44+
</t>
45+
46+
</templates>
Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* @odoo-module */
22

3+
import {FormController} from "@web/views/form/form_controller";
34
import {ListController} from "@web/views/list/list_controller";
45
import {onWillStart} from "@odoo/owl";
56
import {patch} from "@web/core/utils/patch";
@@ -11,25 +12,47 @@ patch(ListController.prototype, {
1112
super.setup();
1213
this.actionService = useService("action");
1314
onWillStart(async () => {
15+
if (this.model.root.resModel !== "spp.change.request") {
16+
return;
17+
}
1418
const is_admin = await user.hasGroup("spp_security.group_spp_admin");
1519
const is_cr_user = await user.hasGroup(
1620
"spp_change_request_v2.group_cr_user"
1721
);
18-
this.canCreateChangeRequest = is_admin || is_cr_user;
22+
if (is_admin || is_cr_user) {
23+
this.customListCreateButton = {
24+
label: "New Request",
25+
title: "Create a New Change Request",
26+
className: "o_list_button_add_cr",
27+
};
28+
}
1929
});
2030
},
2131

22-
async loadChangeRequestWizard() {
23-
if (this.model.root.resModel !== "spp.change.request") {
32+
/**
33+
* Opens the Create Change Request wizard when the custom button is clicked.
34+
*/
35+
async onCustomListCreate() {
36+
if (this.model.root.resModel === "spp.change.request") {
37+
await this.actionService.doAction(
38+
"spp_change_request_v2.action_cr_create_wizard",
39+
{
40+
onClose: async () => {
41+
await this.model.root.load();
42+
},
43+
}
44+
);
2445
return;
2546
}
26-
await this.actionService.doAction(
27-
"spp_change_request_v2.action_cr_create_wizard",
28-
{
29-
onClose: async () => {
30-
await this.model.root.load();
31-
},
32-
}
33-
);
47+
return super.onCustomListCreate(...arguments);
48+
},
49+
});
50+
51+
patch(FormController.prototype, {
52+
setup() {
53+
super.setup();
54+
if (this.props.resModel === "spp.change.request") {
55+
this.hideFormCreateButton = true;
56+
}
3457
},
3558
});
Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<templates xml:space="preserve">
33

4-
<t t-inherit="web.ListView" t-inherit-mode="extension">
5-
<xpath
6-
expr="//Layout/t[@t-set-slot='control-panel-create-button']"
7-
position="replace"
8-
>
9-
<t t-set-slot="control-panel-create-button">
10-
<t t-if="model.root.resModel == 'spp.change.request'">
11-
<button
12-
t-if="!editedRecord and activeActions.create and props.showButtons and canCreateChangeRequest"
13-
type="button"
14-
class="btn btn-primary o_list_button_add_cr"
15-
accesskey="c"
16-
title="Create a New Change Request"
17-
t-on-click="loadChangeRequestWizard"
18-
>New Request</button>
19-
</t>
20-
<t t-else="">
21-
<button
22-
t-if="!editedRecord and activeActions.create and props.showButtons"
23-
type="button"
24-
class="btn btn-primary o_list_button_add"
25-
data-hotkey="c"
26-
t-on-click="onClickCreate"
27-
data-bounce-button=""
28-
>New</button>
29-
<t
30-
t-if="props.showButtons and !env.inDialog"
31-
t-call="web.ListView.EditableButtons"
32-
/>
33-
</t>
34-
</t>
35-
</xpath>
36-
</t>
37-
38-
<t t-inherit="web.FormView" t-inherit-mode="extension">
39-
<xpath expr="//button[hasclass('o_form_button_create')]" position="attributes">
40-
<attribute
41-
name="t-if"
42-
>model.root.resModel != 'spp.change.request'</attribute>
43-
</xpath>
44-
</t>
4+
<!-- No ListView/FormView slot replacement here.
5+
The generic customListCreateButton pattern lives in spp_base_common.
6+
See spp_base_common/static/src/xml/custom_list_create_template.xml -->
457

468
</templates>
Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,86 @@
11
/** @odoo-module **/
22

3+
import {FormController} from "@web/views/form/form_controller";
34
import {ListController} from "@web/views/list/list_controller";
45
import {onWillStart} from "@odoo/owl";
56
import {patch} from "@web/core/utils/patch";
7+
import {registry} from "@web/core/registry";
68
import {user} from "@web/core/user";
79
import {useService} from "@web/core/utils/hooks";
810

11+
/**
12+
* Client action to close modal and then open program form.
13+
* This ensures proper sequencing with async/await.
14+
*/
15+
async function openProgramCloseModal(env, action) {
16+
const actionService = env.services.action;
17+
const programId = action.params?.program_id;
18+
19+
await actionService.doAction(
20+
{type: "ir.actions.act_window_close"},
21+
{clearBreadcrumbs: true}
22+
);
23+
24+
if (programId) {
25+
await actionService.doAction({
26+
type: "ir.actions.act_window",
27+
name: "Program",
28+
res_model: "spp.program",
29+
res_id: programId,
30+
views: [[false, "form"]],
31+
target: "current",
32+
});
33+
}
34+
}
35+
36+
registry.category("actions").add("open_program_close_modal", openProgramCloseModal);
37+
938
patch(ListController.prototype, {
1039
setup() {
1140
super.setup();
1241
this.actionService = useService("action");
1342
onWillStart(async () => {
14-
// Check if user has permission to create programs
15-
// Groups: spp_security.group_spp_admin OR spp_programs.group_programs_manager
43+
if (this.model.root.resModel !== "spp.program") {
44+
return;
45+
}
1646
const is_admin = await user.hasGroup("spp_security.group_spp_admin");
17-
const is_program_manager = await user.hasGroup("spp_programs.group_programs_manager");
18-
this.canCreateProgram = is_admin || is_program_manager;
47+
const is_program_manager = await user.hasGroup(
48+
"spp_programs.group_programs_manager"
49+
);
50+
if (is_admin || is_program_manager) {
51+
this.customListCreateButton = {
52+
label: "Create Program",
53+
title: "Create a New Program",
54+
className: "o_list_button_add_program",
55+
};
56+
}
1957
});
2058
},
2159

2260
/**
23-
* Opens the Create Program wizard when the "Create Program" button is clicked.
24-
* This is called from the custom button in the Programs list view.
61+
* Opens the Create Program wizard when the custom button is clicked.
2562
*/
26-
async load_wizard() {
27-
// Only proceed if we're on the spp.program model
28-
if (this.model.root.resModel !== "spp.program") {
63+
async onCustomListCreate() {
64+
if (this.model.root.resModel === "spp.program") {
65+
await this.actionService.doAction(
66+
"spp_programs.action_create_program_wizard",
67+
{
68+
onClose: async () => {
69+
await this.model.root.load();
70+
},
71+
}
72+
);
2973
return;
3074
}
75+
return super.onCustomListCreate(...arguments);
76+
},
77+
});
3178

32-
// Open the create program wizard action
33-
await this.actionService.doAction("spp_programs.action_create_program_wizard", {
34-
onClose: async () => {
35-
// Refresh the list after the wizard closes
36-
await this.model.root.load();
37-
},
38-
});
79+
patch(FormController.prototype, {
80+
setup() {
81+
super.setup();
82+
if (this.props.resModel === "spp.program") {
83+
this.hideFormCreateButton = true;
84+
}
3985
},
4086
});
Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<templates xml:space="preserve">
33

4-
<!-- Extend the ListView control panel to add custom "Create Program" button
5-
for the spp.program model while keeping default behavior for other models. -->
6-
<t t-inherit="web.ListView" t-inherit-mode="extension">
7-
<xpath expr="//Layout/t[@t-set-slot='control-panel-create-button']" position="replace">
8-
<t t-set-slot="control-panel-create-button">
9-
<!-- Custom Create Program button for spp.program model -->
10-
<t t-if="model.root.resModel == 'spp.program'">
11-
<button
12-
t-if="!editedRecord and activeActions.create and props.showButtons and canCreateProgram"
13-
type="button"
14-
class="btn btn-primary o_list_button_add_program"
15-
accesskey="c"
16-
title="Create a New Program"
17-
t-on-click="load_wizard"
18-
>Create Program</button>
19-
</t>
20-
<!-- Default "New" button for all other models -->
21-
<t t-else="">
22-
<button
23-
t-if="!editedRecord and activeActions.create and props.showButtons"
24-
type="button"
25-
class="btn btn-primary o_list_button_add"
26-
data-hotkey="c"
27-
t-on-click="onClickCreate"
28-
data-bounce-button=""
29-
>New</button>
30-
<t t-if="props.showButtons and !env.inDialog" t-call="web.ListView.EditableButtons" />
31-
</t>
32-
</t>
33-
</xpath>
34-
</t>
35-
36-
<!-- Hide the default Create button in Form view for spp.program -->
37-
<t t-inherit="web.FormView" t-inherit-mode="extension">
38-
<xpath expr="//button[hasclass('o_form_button_create')]" position="attributes">
39-
<attribute name="t-if">model.root.resModel != 'spp.program'</attribute>
40-
</xpath>
41-
</t>
4+
<!-- No ListView/FormView slot replacement here.
5+
The generic customListCreateButton pattern lives in spp_base_common.
6+
See spp_base_common/static/src/xml/custom_list_create_template.xml -->
427

438
</templates>

0 commit comments

Comments
 (0)