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
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,22 @@ function __completeTask(action) {
__notifications.show({ type: 'negative', title: 'Cannot submit', description: 'This form was not opened from a task (no taskId).' });
return;
}
// The clicked button's `action` MUST win. The form model can carry a STALE
// `action` (a prior task in the flow set it as a process variable, preloaded
// into the model on open) plus control-only vars (__*) that are not entity
// data; strip both, then set `action` LAST so the gateway branches on the
// button, not the stale value (fixes approve-completing-as-reject).
const __data = {};
const __model = $scope.model || {};
Object.keys(__model).forEach((key) => {
if (key !== 'action' && key.indexOf('__') !== 0) {
__data[key] = __model[key];
}
});
__data.action = action;
$http.post('/services/inbox/tasks/' + __taskId, {
action: 'COMPLETE',
data: Object.assign({ action: action }, $scope.model || {})
data: __data
}).then(() => {
__notifications.show({ type: 'positive', title: 'Task submitted', description: 'The task was completed (' + action + ').' });
__dialogs.closeWindow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,12 @@ document.addEventListener('alpine:init', () => {
harmoniaHttp.get('/services/inbox/tasks/' + encodeURIComponent(params.taskId) + '/variables')
.then((res) => {
const vars = (res && res.variables) || {};
Object.keys(vars).forEach((k) => { self.model[k] = vars[k]; });
// Never preload the `action` process variable into the model: it is a control value a
// PRIOR task in the flow set (e.g. 'submit'), not a form field, and letting it sit in the
// model made the next task's completion submit the stale action - a decision gateway then
// branched wrong (approve completing as reject). The __* locators DO stay - the runtime
// below reads __entityUrl / __<Fk>EntityUrl from the model.
Object.keys(vars).forEach((k) => { if (k !== 'action') self.model[k] = vars[k]; });
// Clear D: the process context holds only a locator (the entity's REST URL + id), so fetch the
// LIVE entity now and overlay its current fields onto the model. This is what makes the task
// form show up-to-date values (e.g. a document total updated after the task was created)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2174,6 +2174,15 @@ private void assertForm() {
assertTrue(body.contains("closeWindow(") && body.contains("window.close("),
"on completion the form should close its host (dialog via closeWindow, standalone via window.close)");
assertFalse(body.contains("TODO: wire"), "the action handlers must no longer be TODO stubs");
// The clicked button's action MUST win over a stale `action` in the model (a prior task in a
// multi-step flow set it as a process variable, preloaded on open): the completion payload
// strips `action` + control vars (__*) from the model and sets `action` LAST. Without this,
// an Approve completes down the reject branch (the approve-as-reject bug). The .form code is
// Gson-escaped (' -> \\u0027), so match escape-free substrings.
assertTrue(body.contains("__data.action") && body.contains(".indexOf(") && body.contains("__data"),
"the completion payload must be rebuilt with the button action winning, not Object.assign with the model last");
assertFalse(body.contains("Object.assign({ action: action }, $scope.model"),
"the buggy merge (stale model action overwrites the button) must be gone");
}

private void assertReport() {
Expand Down
Loading