fix(products): show named GL account changes in the pending-edit diff dialog
otherGlMappings (the nested document behind Savings/Current/FD/RD Interest Payable/Paid and similar named accounts) was excluded from the generic field diff entirely, and the backend was silently dropping its value anyway (see the paired backend fix). Now that the backend correctly stages this field, diff it explicitly: find each reserved account name across original.otherGlMappingsMap and proposed.otherGlMappings, resolve the GL code to a readable name, and show a row only when it actually moved -- same convention as every other field in this dialog, instead of a raw JSON blob or nothing at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+77
-5
@@ -52,16 +52,29 @@ export class ProductEditDiffDialogComponent implements OnInit {
|
||||
productName = '';
|
||||
rows: DiffRow[] = [];
|
||||
|
||||
/** Request plumbing the edit form has to send, but which isn't a product setting a checker reviews. */
|
||||
/** Request plumbing the edit form has to send, but which isn't a product setting a checker reviews.
|
||||
* otherGlMappings is here too -- it is a nested {assets,liabilities,expenses,income} document, not
|
||||
* a scalar, so it is handled separately by namedGlMappingRows() rather than the generic loop below
|
||||
* (which would otherwise dump it as a raw, unreadable JSON blob). */
|
||||
private static readonly IGNORED_KEYS = new Set([
|
||||
'locale',
|
||||
'dateFormat',
|
||||
'monthDayFormat',
|
||||
'id',
|
||||
'productId',
|
||||
'savingsProductId'
|
||||
'savingsProductId',
|
||||
'otherGlMappings'
|
||||
]);
|
||||
|
||||
/** The two reserved otherGlMappings entries a checker actually needs to see by name -- see
|
||||
* AccountingProcessorHelper.SAVINGS_INTEREST_PAYABLE_MAPPING / SAVINGS_INTEREST_PAID_MAPPING. Any
|
||||
* other entry in the assets/liabilities/expenses/income lists is a free-form "Other" row with no
|
||||
* fixed identity to diff against by name, so those are left out of this dialog same as before. */
|
||||
private static readonly NAMED_GL_MAPPINGS = [
|
||||
'Savings Interest Payable',
|
||||
'Savings Interest Paid'
|
||||
];
|
||||
|
||||
/**
|
||||
* Payload fields the stored product exposes under a different path, so the "current" side has
|
||||
* something to compare against instead of always reading as newly set.
|
||||
@@ -115,6 +128,9 @@ export class ProductEditDiffDialogComponent implements OnInit {
|
||||
};
|
||||
|
||||
private glAccountNamesById = new Map<string, string>();
|
||||
/** Same pool as glAccountNamesById, keyed by GL code instead of id -- otherGlMappings entries (see
|
||||
* namedGlMappingRows) identify an account by its code, not its id. */
|
||||
private glAccountNamesByCode = new Map<string, string>();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.productName = this.data.productName ?? '';
|
||||
@@ -131,10 +147,11 @@ export class ProductEditDiffDialogComponent implements OnInit {
|
||||
original: this.displayValue(originalValue, key, original),
|
||||
proposed: this.displayValue(proposed[key], key, original)
|
||||
}))
|
||||
.concat(this.namedGlMappingRows(original, proposed))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
/** Pools every GL account the template offers so an account id can be shown as its name. */
|
||||
/** Pools every GL account the template offers so an account id/code can be shown as its name. */
|
||||
private buildGlAccountLookup(template: any): void {
|
||||
const optionGroups = template?.accountingMappingOptions ?? {};
|
||||
for (const group of Object.values<any>(optionGroups)) {
|
||||
@@ -146,13 +163,68 @@ export class ProductEditDiffDialogComponent implements OnInit {
|
||||
continue;
|
||||
}
|
||||
const label = account.name ?? account.glCode ?? null;
|
||||
if (label) {
|
||||
this.glAccountNamesById.set(String(account.id), account.glCode ? `${label} (${account.glCode})` : label);
|
||||
if (!label) {
|
||||
continue;
|
||||
}
|
||||
const display = account.glCode ? `${label} (${account.glCode})` : label;
|
||||
this.glAccountNamesById.set(String(account.id), display);
|
||||
if (account.glCode) {
|
||||
this.glAccountNamesByCode.set(String(account.glCode), display);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diffs the two reserved otherGlMappings entries by name across every category (assets/liabilities/
|
||||
* expenses/income) rather than the generic key loop above, since otherGlMappings is a nested
|
||||
* {assets,liabilities,expenses,income} document whose entries move category if the maker changes
|
||||
* which account type they picked -- comparing category-by-category would miss that. `original`
|
||||
* carries this document as otherGlMappingsMap (falling back to the older otherGlMappings key for a
|
||||
* template response that hasn't been re-fetched since), `proposed` as otherGlMappings.
|
||||
*/
|
||||
private namedGlMappingRows(original: any, proposed: any): DiffRow[] {
|
||||
const proposedOther = proposed?.otherGlMappings;
|
||||
if (proposedOther === undefined) {
|
||||
// Not part of this edit at all -- nothing to compare, same as any other field the maker didn't touch.
|
||||
return [];
|
||||
}
|
||||
const originalOther = original?.otherGlMappingsMap ?? original?.otherGlMappings;
|
||||
|
||||
const findByName = (doc: any, accountName: string): { glCode?: string } | null => {
|
||||
const categories: any[] = doc ? Object.values(doc) : [];
|
||||
for (const category of categories) {
|
||||
if (!Array.isArray(category)) {
|
||||
continue;
|
||||
}
|
||||
const match = category.find((entry: any) => entry?.accountName === accountName);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const resolve = (entry: { glCode?: string } | null): string => {
|
||||
if (!entry?.glCode) {
|
||||
return '—';
|
||||
}
|
||||
return this.glAccountNamesByCode.get(String(entry.glCode)) ?? entry.glCode;
|
||||
};
|
||||
|
||||
return ProductEditDiffDialogComponent.NAMED_GL_MAPPINGS.map((accountName) => {
|
||||
const before = findByName(originalOther, accountName);
|
||||
const after = findByName(proposedOther, accountName);
|
||||
return { accountName, before, after };
|
||||
})
|
||||
.filter(({ before, after }) => (before?.glCode ?? null) !== (after?.glCode ?? null))
|
||||
.map(({ accountName, before, after }) => ({
|
||||
label: accountName,
|
||||
original: resolve(before),
|
||||
proposed: resolve(after)
|
||||
}));
|
||||
}
|
||||
|
||||
/** Reads the current value for a payload field, following an alias or GL mapping when needed. */
|
||||
private originalValueFor(key: string, original: any): any {
|
||||
const glMappingKey = ProductEditDiffDialogComponent.GL_FIELD_TO_MAPPING[key];
|
||||
|
||||
Reference in New Issue
Block a user