fix(products): named GL account changes (e.g. Savings Interest Payable/Paid) were silently lost on a pending edit
Root cause: ProductPendingEditFieldMapper.populateCoreFields() captured
otherGlMappings with stringValueOfParameterNamed(), which only handles a
JSON string primitive. The maker's edit actually submits otherGlMappings
as a nested {assets,liabilities,expenses,income} object -- the same shape
SavingsProduct#update() already reads correctly via jsonFragment() -- so
the pending-edit row was staged with this field blank every time,
regardless of what the maker changed.
Two consequences, both now fixed:
- The checker's diff dialog showed "No changes to review" even when a GL
account mapping (e.g. Savings/Current/FD/RD Interest Payable or Paid)
was genuinely changed, since the submitted value never made it into
proposedChanges.
- Approving the edit replayed a payload with no otherGlMappings at all,
so the GL account change was silently dropped rather than applied --
the edit showed as approved, but the product's accounting mapping
never actually moved.
Fixed the write side (jsonFragment instead of stringValueOfParameterNamed)
and the read-side replay (writeCoreFieldsToJson now re-adds the stored
value as a parsed JSON element via the new putJsonFragment, not a quoted
string, which would have double-encoded it on replay). Shared by Savings,
Current, FD and RD products, since they all go through this one mapper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+28
-2
@@ -158,7 +158,15 @@ public final class ProductPendingEditFieldMapper {
|
||||
present.add("taxGroupId");
|
||||
}
|
||||
if (command.hasParameter("otherGlMappings")) {
|
||||
f.setOtherGlMappings(command.stringValueOfParameterNamed("otherGlMappings"));
|
||||
// otherGlMappings arrives as a nested JSON object ({assets:[...], liabilities:[...],
|
||||
// expenses:[...]}), the same shape SavingsProduct#update() reads via jsonFragment() --
|
||||
// NOT a JSON string primitive. stringValueOfParameterNamed() only handles the latter and
|
||||
// silently came back blank for the former, so every "Savings Interest Payable"/"Savings
|
||||
// Interest Paid" (and other named GL account) edit was staged with an empty value: it
|
||||
// never reached the pending-edit row, so the checker's diff had nothing to show and
|
||||
// approving replayed no GL account change at all. jsonFragment() is the same call the
|
||||
// live (non-pending) update path already uses for this exact parameter.
|
||||
f.setOtherGlMappings(command.jsonFragment("otherGlMappings"));
|
||||
present.add("otherGlMappings");
|
||||
}
|
||||
if (command.hasParameter("isDormancyTrackingActive")) {
|
||||
@@ -355,7 +363,7 @@ public final class ProductPendingEditFieldMapper {
|
||||
putNumber(root, submitted, "minBalanceForInterestCalculation", f.getMinBalanceForInterestCalculation());
|
||||
putBoolean(root, submitted, "withHoldTax", f.getWithHoldTax());
|
||||
putNumber(root, submitted, "taxGroupId", f.getTaxGroupId());
|
||||
putString(root, submitted, "otherGlMappings", f.getOtherGlMappings());
|
||||
putJsonFragment(root, submitted, "otherGlMappings", f.getOtherGlMappings());
|
||||
putBoolean(root, submitted, "isDormancyTrackingActive", f.getIsDormancyTrackingActive());
|
||||
putNumber(root, submitted, "daysToInactive", f.getDaysToInactive());
|
||||
putNumber(root, submitted, "daysToDormancy", f.getDaysToDormancy());
|
||||
@@ -733,6 +741,24 @@ public final class ProductPendingEditFieldMapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #putString}, but for a value that is itself raw JSON (an object/array captured
|
||||
* via {@code JsonCommand#jsonFragment}) rather than a plain string. {@code addProperty} would wrap
|
||||
* it in quotes as an escaped string literal, which {@code jsonFragment} on the replayed command
|
||||
* would then hand back as that literal quoted text instead of the original structure -- breaking
|
||||
* the downstream `other_gl_mappings::jsonb` cast this field's value round-trips through.
|
||||
*/
|
||||
private static void putJsonFragment(final JsonObject root, final Set<String> submitted, final String name, final String rawJson) {
|
||||
if (!submitted.contains(name)) {
|
||||
return;
|
||||
}
|
||||
if (rawJson == null || rawJson.isBlank()) {
|
||||
root.add(name, com.google.gson.JsonNull.INSTANCE);
|
||||
return;
|
||||
}
|
||||
root.add(name, com.google.gson.JsonParser.parseString(rawJson));
|
||||
}
|
||||
|
||||
private static void putNumber(final JsonObject root, final Set<String> submitted, final String name, final Number value) {
|
||||
if (submitted.contains(name)) {
|
||||
root.addProperty(name, value);
|
||||
|
||||
Reference in New Issue
Block a user