Skip to content

Commit f41f618

Browse files
authored
Merge pull request #8655 from ProcessMaker/bugfix/FOUR-28469
FOUR-28469: Error with Subrocesses - Missing data between from child request to parent request
2 parents 2e62b9a + fea2fcb commit f41f618

File tree

2 files changed

+164
-16
lines changed

2 files changed

+164
-16
lines changed

ProcessMaker/Models/CallActivity.php

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,10 @@ protected function completeSubprocess(TokenInterface $token, ExecutionInstanceIn
9797
$store = $closedInstance->getDataStore();
9898
$allData = $store->getData();
9999

100-
// Determine which data should be merged back from the subprocess.
101-
$updatedKeys = method_exists($store, 'getUpdated')
102-
? $store->getUpdated()
103-
: null;
104-
105-
if ($updatedKeys === null) {
106-
// Legacy behavior or no tracking available: copy all data.
107-
$data = $allData;
108-
} elseif ($updatedKeys === []) {
109-
// Nothing was updated in the subprocess: do not merge anything.
110-
$data = [];
111-
} else {
112-
// Merge only the explicitly updated keys.
113-
$updatedKeys = array_values((array) $updatedKeys);
114-
$data = array_intersect_key($allData, array_flip($updatedKeys));
115-
}
100+
$data = $this->resolveUpdatedData($store, $allData);
101+
$parentData = $token->getInstance()->getDataStore()->getData();
102+
$data = $this->mergeNewKeys($data, $allData, $parentData);
103+
$data = $this->mergeChangedKeys($data, $allData, $parentData);
116104

117105
$dataManager = new DataManager();
118106
$dataManager->updateData($token, $data);
@@ -125,6 +113,62 @@ protected function completeSubprocess(TokenInterface $token, ExecutionInstanceIn
125113
return $this;
126114
}
127115

116+
/**
117+
* Decide which data from the subprocess should be merged based on updated keys.
118+
*/
119+
protected function resolveUpdatedData($store, array $allData): array
120+
{
121+
$updatedKeys = method_exists($store, 'getUpdated')
122+
? $store->getUpdated()
123+
: null;
124+
125+
if ($updatedKeys === null) {
126+
return $allData;
127+
}
128+
129+
if ($updatedKeys === []) {
130+
return [];
131+
}
132+
133+
$updatedKeys = array_values((array) $updatedKeys);
134+
135+
return array_intersect_key($allData, array_flip($updatedKeys));
136+
}
137+
138+
/**
139+
* Merge keys that exist only in the subprocess data.
140+
*/
141+
protected function mergeNewKeys(array $data, array $allData, array $parentData): array
142+
{
143+
$newKeys = array_diff(array_keys($allData), array_keys($parentData));
144+
if (empty($newKeys)) {
145+
return $data;
146+
}
147+
148+
return $data + array_intersect_key($allData, array_flip($newKeys));
149+
}
150+
151+
/**
152+
* Merge keys that changed in the subprocess but may not have been tracked.
153+
*/
154+
protected function mergeChangedKeys(array $data, array $allData, array $parentData): array
155+
{
156+
$changedKeys = [];
157+
foreach ($allData as $key => $value) {
158+
if (array_key_exists($key, $parentData) && $parentData[$key] !== $value) {
159+
$changedKeys[] = $key;
160+
}
161+
}
162+
163+
if (empty($changedKeys)) {
164+
return $data;
165+
}
166+
167+
$pendingKeys = array_diff($changedKeys, array_keys($data));
168+
169+
return $data + array_intersect_key($allData, array_flip($pendingKeys));
170+
}
171+
128172
/**
129173
* Catch a subprocess error
130174
*
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace Tests\Unit\ProcessMaker\Models;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use ProcessMaker\Models\CallActivity;
7+
8+
class CallActivityMergeTest extends TestCase
9+
{
10+
private function callActivity(): CallActivity
11+
{
12+
return new class extends CallActivity {
13+
public function callResolveUpdatedData($store, array $allData): array
14+
{
15+
return $this->resolveUpdatedData($store, $allData);
16+
}
17+
18+
public function callMergeNewKeys(array $data, array $allData, array $parentData): array
19+
{
20+
return $this->mergeNewKeys($data, $allData, $parentData);
21+
}
22+
23+
public function callMergeChangedKeys(array $data, array $allData, array $parentData): array
24+
{
25+
return $this->mergeChangedKeys($data, $allData, $parentData);
26+
}
27+
};
28+
}
29+
30+
public function testResolveUpdatedDataReturnsAllWhenUpdatedIsNull(): void
31+
{
32+
$store = new class {
33+
public function getUpdated()
34+
{
35+
return null;
36+
}
37+
};
38+
39+
$callActivity = $this->callActivity();
40+
$all = ['a' => 1, 'b' => 2];
41+
42+
$result = $callActivity->callResolveUpdatedData($store, $all);
43+
44+
$this->assertSame($all, $result);
45+
}
46+
47+
public function testResolveUpdatedDataReturnsEmptyWhenUpdatedIsEmptyArray(): void
48+
{
49+
$store = new class {
50+
public function getUpdated(): array
51+
{
52+
return [];
53+
}
54+
};
55+
56+
$callActivity = $this->callActivity();
57+
$all = ['a' => 1, 'b' => 2];
58+
59+
$result = $callActivity->callResolveUpdatedData($store, $all);
60+
61+
$this->assertSame([], $result);
62+
}
63+
64+
public function testResolveUpdatedDataReturnsOnlyUpdatedKeys(): void
65+
{
66+
$store = new class {
67+
public function getUpdated(): array
68+
{
69+
return ['b'];
70+
}
71+
};
72+
73+
$callActivity = $this->callActivity();
74+
$all = ['a' => 1, 'b' => 2, 'c' => 3];
75+
76+
$result = $callActivity->callResolveUpdatedData($store, $all);
77+
78+
$this->assertSame(['b' => 2], $result);
79+
}
80+
81+
public function testMergeNewKeysAddsKeysAbsentInParent(): void
82+
{
83+
$callActivity = $this->callActivity();
84+
$data = ['a' => 1];
85+
$all = ['a' => 1, 'b' => 2, 'c' => 3];
86+
$parent = ['a' => 1];
87+
88+
$result = $callActivity->callMergeNewKeys($data, $all, $parent);
89+
90+
$this->assertSame(['a' => 1, 'b' => 2, 'c' => 3], $result);
91+
}
92+
93+
public function testMergeChangedKeysAddsDifferencesNotTracked(): void
94+
{
95+
$callActivity = $this->callActivity();
96+
$data = ['a' => 1]; // already tracked
97+
$all = ['a' => 1, 'b' => 99, 'c' => 'new'];
98+
$parent = ['a' => 1, 'b' => 2, 'c' => 'old'];
99+
100+
$result = $callActivity->callMergeChangedKeys($data, $all, $parent);
101+
102+
$this->assertSame(['a' => 1, 'b' => 99, 'c' => 'new'], $result);
103+
}
104+
}

0 commit comments

Comments
 (0)