commit a796e980e096e5be49a105325151fb7f2d076056
parent 9c0ebc8928de096f0a7ed05c75414050d1688688
Author: Florian Dold <florian@dold.me>
Date: Tue, 22 Jul 2025 00:54:38 +0200
aml: rudimentary property rendering, default to existing props in decision
Diffstat:
2 files changed, 165 insertions(+), 110 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx b/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx
@@ -15,6 +15,7 @@
*/
import {
AbsoluteTime,
+ AccountProperties,
assertUnreachable,
HttpStatusCode,
TalerError,
@@ -44,6 +45,24 @@ import { BANK_RULES, WALLET_RULES } from "./decision/Rules.js";
const TALER_SCREEN_ID = 116;
+export function ShowProperties(props: { properties?: AccountProperties }) {
+ const { properties } = props;
+ const keys = Object.keys(properties ?? {});
+ if (!properties || keys.length == 0) {
+ return <span>No properties defined for this account.</span>;
+ }
+ return keys.map((x) => {
+ if (properties[x] == null) {
+ return null;
+ }
+ return (
+ <p>
+ {x}: {properties[x]}
+ </p>
+ );
+ });
+}
+
export function AccountDetails({
account,
routeToShowCollectedInfo,
@@ -158,6 +177,8 @@ export function AccountDetails({
: BANK_RULES.includes(r.operation_type);
});
+ console.log(activeDecision?.properties);
+
return (
<div class="min-w-60">
<header class="flex flex-col justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8 gap-2">
@@ -215,6 +236,12 @@ export function AccountDetails({
/>
)}
+ <h1 class="mb-4 text-base font-semibold leading-6 text-black">
+ <i18n.Translate>Current account properties</i18n.Translate>
+ </h1>
+
+ <ShowProperties properties={activeDecision?.properties} />
+
{!activeDecision ? (
<Fragment>
<Attention title={i18n.str`No active decision found`} type="warning">
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
@@ -35,7 +35,7 @@ import {
UIHandlerId,
useExchangeApiContext,
useForm,
- useTranslationContext
+ useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import {
@@ -45,8 +45,6 @@ import {
import { usePreferences } from "../../hooks/preferences.js";
import { DEFAULT_LIMITS_WHEN_NEW_ACCOUNT } from "./Rules.js";
-const TALER_SCREEN_ID = 104;
-
/**
* Update account properties
* @param param0
@@ -68,6 +66,8 @@ export function Properties(): VNode {
dialect,
);
+ console.log(`calculated props`, calculatedProps);
+
const merged = Object.entries(calculatedProps).reduce(
(prev, [key, value]) => {
if (prev[key] === undefined && value !== undefined) {
@@ -92,18 +92,28 @@ function officerMustCheckInvestigationState(
return true;
}
-function ReloadForm({ merged }: { merged: Record<string, string | boolean> }): VNode {
+function ReloadForm({
+ merged,
+}: {
+ merged: Record<string, string | boolean>;
+}): VNode {
const { i18n } = useTranslationContext();
const [request, updateRequest] = useCurrentDecisionRequest();
+ const [pref] = usePreferences();
+ const { config } = useExchangeApiContext();
+ const dialect =
+ (pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
+ AmlSpaDialect.TESTING;
const design = propertiesForm(
i18n,
- propertiesByDialect(i18n, {
+ propertiesByDialect(i18n, dialect, {
MANDATORY_INVESTIGATION_STATE: officerMustCheckInvestigationState(
// @ts-expect-error data is the form
- (request.attributes?.data ?? {}),
+ request.attributes?.data ?? {},
),
}),
);
+
// const [id, setId] = useState(new Date().getTime());
const customProps = Object.entries(request.custom_properties ?? {}).map(
@@ -198,113 +208,128 @@ const propertiesForm = (
function propertiesByDialect(
i18n: InternationalizationAPI,
+ dialect: AmlSpaDialect,
options: {
MANDATORY_INVESTIGATION_STATE?: boolean;
} = {},
): UIFormElementConfig[] {
- return [
- {
- id: TalerAmlProperties.FILE_NOTE,
- label: i18n.str`Current note on the GWG File`,
- type: "text",
- },
- {
- id: TalerAmlProperties.CUSTOMER_LABEL,
- label: i18n.str`Customer name or internal alias.`,
- type: "text",
- },
- {
- id: TalerAmlProperties.ACCOUNT_OPEN,
- label: i18n.str`Is the account active for deposit and payments?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.ACCOUNT_IDLE,
- label: i18n.str`Is the account idle?`,
- help: i18n.str`No operation for a period of time`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.PEP_DOMESTIC,
- label: i18n.str`Does account belong to a domestic PEP?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.PEP_FOREIGN,
- label: i18n.str`Does account belong to a foreign PEP?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.PEP_INTERNATIONAL_ORGANIZATION,
- label: i18n.str`Does account belong to a international organization PEP?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.HIGH_RISK_CUSTOMER,
- label: i18n.str`Does account belong to a high risk customer?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.HIGH_RISK_COUNTRY,
- label: i18n.str`Does account belong to a person from a high risk country?`,
- type: "toggle",
- },
- {
- id: TalerAmlProperties.INVESTIGATION_STATE,
- label: i18n.str`The MROS reporting state for the account.`,
- type: "choiceStacked",
- required: options.MANDATORY_INVESTIGATION_STATE,
- choices: [
- {
- label: i18n.str`None`,
- value: "NONE",
- },
- {
- label: i18n.str`Pending investigation`,
- value: "INVESTIGATION_PENDING",
- },
- {
- label: i18n.str`Investigated without suspicion`,
- value: "INVESTIGATION_COMPLETED_WITHOUT_SUSPICION",
- },
- {
- label: i18n.str`Reported simple suspicion`,
- value: "REPORTED_SUSPICION_SIMPLE",
- },
- {
- label: i18n.str`Reported substantaited suspicion`,
- value: "REPORTED_SUSPICION_SUBSTANTIATED",
- },
- ],
- },
- {
- id: TalerAmlProperties.INVESTIGATION_TRIGGER,
- label: i18n.str`Informal reason why the AML investigation was triggered`,
- type: "text",
- },
- {
- id: TalerAmlProperties.SANCTION_LIST_BEST_MATCH,
- label: i18n.str`Identifies the sanction list entry that the account matched against`,
- type: "text",
- help: i18n.str`Best match, does not mean it was a good match.`,
- },
- {
- id: TalerAmlProperties.SANCTION_LIST_RATING,
- label: i18n.str`Score for how good the sanction list match was`,
- type: "integer",
- },
- {
- id: TalerAmlProperties.SANCTION_LIST_CONFIDENCE,
- label: i18n.str`Score for how much supporting data we had for the sanction list match`,
- type: "integer",
- },
- {
- id: TalerAmlProperties.SANCTION_LIST_SUPPRESS,
- label: i18n.str`Suppress flagging this account when it creates a hit on a sanctions list, this is a false-positive`,
- type: "toggle",
- threeState: true,
- },
- ];
+ if (dialect === AmlSpaDialect.TOPS) {
+ return [
+ {
+ id: TalerAmlProperties.FILE_NOTE,
+ label: i18n.str`Current note on the GWG File`,
+ type: "text",
+ },
+ {
+ id: TalerAmlProperties.CUSTOMER_LABEL,
+ label: i18n.str`Customer name or internal alias.`,
+ type: "text",
+ },
+ {
+ id: TalerAmlProperties.ACCOUNT_OPEN,
+ label: i18n.str`Is the account active for deposit and payments?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.ACCOUNT_IDLE,
+ label: i18n.str`Is the account idle?`,
+ help: i18n.str`No operation for a period of time`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.PEP_DOMESTIC,
+ label: i18n.str`Does account belong to a domestic PEP?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.PEP_FOREIGN,
+ label: i18n.str`Does account belong to a foreign PEP?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.PEP_INTERNATIONAL_ORGANIZATION,
+ label: i18n.str`Does account belong to a international organization PEP?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.HIGH_RISK_CUSTOMER,
+ label: i18n.str`Does account belong to a high risk customer?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.HIGH_RISK_COUNTRY,
+ label: i18n.str`Does account belong to a person from a high risk country?`,
+ type: "toggle",
+ },
+ {
+ id: TalerAmlProperties.INVESTIGATION_STATE,
+ label: i18n.str`The MROS reporting state for the account.`,
+ type: "choiceStacked",
+ required: options.MANDATORY_INVESTIGATION_STATE,
+ choices: [
+ {
+ label: i18n.str`None`,
+ value: "NONE",
+ },
+ {
+ label: i18n.str`Pending investigation`,
+ value: "INVESTIGATION_PENDING",
+ },
+ {
+ label: i18n.str`Investigated without suspicion`,
+ value: "INVESTIGATION_COMPLETED_WITHOUT_SUSPICION",
+ },
+ {
+ label: i18n.str`Reported simple suspicion`,
+ value: "REPORTED_SUSPICION_SIMPLE",
+ },
+ {
+ label: i18n.str`Reported substantaited suspicion`,
+ value: "REPORTED_SUSPICION_SUBSTANTIATED",
+ },
+ ],
+ },
+ {
+ id: TalerAmlProperties.INVESTIGATION_TRIGGER,
+ label: i18n.str`Informal reason why the AML investigation was triggered`,
+ type: "text",
+ },
+ {
+ id: TalerAmlProperties.SANCTION_LIST_BEST_MATCH,
+ label: i18n.str`Identifies the sanction list entry that the account matched against`,
+ type: "text",
+ help: i18n.str`Best match, does not mean it was a good match.`,
+ },
+ {
+ id: TalerAmlProperties.SANCTION_LIST_RATING,
+ label: i18n.str`Score for how good the sanction list match was`,
+ type: "integer",
+ },
+ {
+ id: TalerAmlProperties.SANCTION_LIST_CONFIDENCE,
+ label: i18n.str`Score for how much supporting data we had for the sanction list match`,
+ type: "integer",
+ },
+ {
+ id: TalerAmlProperties.SANCTION_LIST_SUPPRESS,
+ label: i18n.str`Suppress flagging this account when it creates a hit on a sanctions list, this is a false-positive`,
+ type: "toggle",
+ threeState: true,
+ },
+ ];
+ }
+
+ if (dialect === AmlSpaDialect.GLS) {
+ return [
+ {
+ id: "GLS_CUSTOMER_ID",
+ label: i18n.str`GLS Customer ID`,
+ type: "text",
+ },
+ ];
+ }
+
+ return [];
}
function calculatePropertiesBasedOnState(
@@ -321,7 +346,10 @@ function calculatePropertiesBasedOnState(
const formId = request.attributes?.formId;
- if (!formId) return initial;
+ if (!formId) {
+ return state;
+ }
+
const FORM_ID = formId;
function mergeProperties<T extends string>(