commit 48f93fe5c61fc1de07a54f284c0b676ced70f90d
parent c9c4b6f06c57d42835c6f4cecb1371f7a884763b
Author: Sebastian <sebasjm@gmail.com>
Date: Thu, 22 May 2025 11:23:15 -0300
wip #9988
Diffstat:
10 files changed, 58 insertions(+), 176 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/hooks/decision-request.ts b/packages/aml-backoffice-ui/src/hooks/decision-request.ts
@@ -30,6 +30,7 @@ import {
codecOptionalDefault,
KycRule,
MeasureInformation,
+ TalerExchangeApi,
} from "@gnu-taler/taler-util";
import {
buildStorageKey,
@@ -51,6 +52,7 @@ export interface AccountAttributes {
* With all of this we need to create a AmlDecisionRequest
*/
export interface DecisionRequest {
+ original: TalerExchangeApi.AmlDecision | undefined;
/**
* Next legitimization rules
*/
@@ -124,6 +126,7 @@ export const codecForAccountAttributes = (): Codec<AccountAttributes> =>
export const codecForDecisionRequest = (): Codec<DecisionRequest> =>
buildCodecForObject<DecisionRequest>()
+ .property("original", codecOptional(codecForAny()))
.property("rules", codecOptional(codecForList(codecForKycRules())))
.property("deadline", codecOptional(codecForAbsoluteTime))
.property("properties", codecOptional(codecForMap(codecForAny())))
@@ -143,6 +146,7 @@ export const codecForDecisionRequest = (): Codec<DecisionRequest> =>
.build("DecisionRequest");
const DECISION_REQUEST_EMPTY: DecisionRequest = {
+ original: undefined,
deadline: undefined,
custom_properties: undefined,
onExpire_measure: undefined,
diff --git a/packages/aml-backoffice-ui/src/hooks/decisions.ts b/packages/aml-backoffice-ui/src/hooks/decisions.ts
@@ -144,17 +144,7 @@ export function useAccountDecisions(accountStr: string) {
* @param args
* @returns
*/
-export function useAccountActiveDecision(accountStr?: string):
- | OperationFail<HttpStatusCode.NotFound>
- | OperationFail<HttpStatusCode.Forbidden>
- | OperationFail<HttpStatusCode.Conflict>
- | TalerError<{
- requestUrl: string;
- requestMethod: string;
- }>
- | OperationOk<undefined>
- | OperationOk<AmlDecision>
- | undefined {
+export function useAccountActiveDecision(accountStr?: string) {
const officer = useOfficer();
const session =
accountStr !== undefined && officer.state === "ready"
diff --git a/packages/aml-backoffice-ui/src/hooks/transfers.ts b/packages/aml-backoffice-ui/src/hooks/transfers.ts
@@ -139,52 +139,6 @@ export function useTransferList({
);
}
-/**
- * @param account
- * @param args
- * @returns
- */
-export function useAccountActiveDecision(accountStr?: string) {
- const officer = useOfficer();
- const session =
- accountStr !== undefined && officer.state === "ready"
- ? officer.account
- : undefined;
- const {
- lib: { exchange: api },
- } = useExchangeApiContext();
-
- const [offset, setOffset] = useState<string>();
-
- async function fetcher([officer, account, offset]: [
- OfficerAccount,
- string,
- string | undefined,
- ]) {
- return await api.getAmlDecisions(officer, {
- order: "dec",
- offset,
- account,
- active: true,
- limit: PAGINATED_LIST_REQUEST,
- });
- }
-
- const { data, error } = useSWR<
- TalerExchangeResultByMethod<"getAmlDecisions">,
- TalerHttpError
- >(
- !session ? undefined : [session, accountStr, offset, "getAmlDecisions"],
- fetcher,
- );
-
- if (error) return error;
- if (data === undefined) return undefined;
- if (data.type !== "ok") return data;
-
- if (!data.body.records.length) return opFixedSuccess(undefined);
- return opFixedSuccess(data.body.records[0]);
-}
type PaginatedResult<T> = OperationOk<T> & {
isLastPage: boolean;
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -129,8 +129,6 @@ export function CaseDetails({
routeToShowCollectedInfo: RouteDefinition<{ cid: string; rowId: string }>;
account: string;
}) {
- const [selected, setSelected] = useState<AbsoluteTime | undefined>(undefined); //AbsoluteTime.now());
-
const { i18n } = useTranslationContext();
const details = useAccountInformation(account);
const history = useAccountDecisions(account);
@@ -174,6 +172,7 @@ export function CaseDetails({
// const events = getEventsFromAmlHistory(accountDetails, i18n);
+
function ShortcutActionButtons(): VNode {
return (
<div>
@@ -183,8 +182,9 @@ export function CaseDetails({
// instead here all the values from the current decision should be
// loaded into the new decision request, like we are doing with e
// custom measures
- // FIXME add properties, limits, investigation state
+ // FIXME-do-this add properties, limits, investigation state
onNewDecision({
+ original: activeDecision,
custom_measures: activeDecision?.limits.custom_measures,
});
}}
@@ -217,9 +217,9 @@ export function CaseDetails({
<ShortcutActionButtons />
- {selected && (
+ {/* {selected && (
<ShowConsolidated history={collectionEvents} until={selected} />
- )}
+ )} */}
<div class="p-4">
<h1 class="text-base font-semibold leading-6 text-black">
<i18n.Translate>Collected information</i18n.Translate>
diff --git a/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx b/packages/aml-backoffice-ui/src/pages/decision/AmlDecisionRequestWizard.tsx
@@ -120,15 +120,15 @@ export function AmlDecisionRequestWizard({
const content = (function () {
switch (stepOrDefault) {
case "rules":
- return <Rules account={account} newPayto={newPayto} />;
+ return <Rules newPayto={newPayto} />;
case "properties":
- return <Properties account={account} />;
+ return <Properties />;
case "events":
- return <Events account={account} />;
+ return <Events />;
case "measures":
return <Measures />;
case "justification":
- return <Justification account={account} newPayto={newPayto} />;
+ return <Justification newPayto={newPayto} />;
case "attributes":
return <Attributes formId={formId} />;
case "summary":
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Events.tsx b/packages/aml-backoffice-ui/src/pages/decision/Events.tsx
@@ -4,16 +4,14 @@ import {
assertUnreachable,
EventsDerivation_TOPS,
GLS_AmlEventsName,
- HttpStatusCode,
MeasureInformation,
- TalerError,
+ TalerFormAttributes,
TOPS_AmlEventsName,
} from "@gnu-taler/taler-util";
import {
FormDesign,
FormUI,
InternationalizationAPI,
- Loading,
onComponentUnload,
SelectUiChoice,
useExchangeApiContext,
@@ -21,12 +19,10 @@ import {
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
-import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
import {
DecisionRequest,
useCurrentDecisionRequest,
} from "../../hooks/decision-request.js";
-import { useAccountActiveDecision } from "../../hooks/decisions.js";
import { usePreferences } from "../../hooks/preferences.js";
/**
@@ -35,8 +31,7 @@ import { usePreferences } from "../../hooks/preferences.js";
* @param param0
* @returns
*/
-export function Events({ account }: { account: string }): VNode {
- const activeDecision = useAccountActiveDecision(account);
+export function Events({}: {}): VNode {
const { i18n } = useTranslationContext();
const [request, updateRequest] = useCurrentDecisionRequest();
const [pref] = usePreferences();
@@ -46,22 +41,22 @@ export function Events({ account }: { account: string }): VNode {
(pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
AmlSpaDialect.TESTING;
- if (!activeDecision) {
- return <Loading />;
- }
- if (activeDecision instanceof TalerError) {
- return <ErrorLoadingWithDebug error={activeDecision} />;
- }
- if (activeDecision.type === "fail") {
- switch (activeDecision.case) {
- case HttpStatusCode.Forbidden:
- case HttpStatusCode.NotFound:
- case HttpStatusCode.Conflict:
- return <div>couldn't load the last active decision</div>;
- default:
- assertUnreachable(activeDecision);
- }
- }
+ // if (!activeDecision) {
+ // return <Loading />;
+ // }
+ // if (activeDecision instanceof TalerError) {
+ // return <ErrorLoadingWithDebug error={activeDecision} />;
+ // }
+ // if (activeDecision.type === "fail") {
+ // switch (activeDecision.case) {
+ // case HttpStatusCode.Forbidden:
+ // case HttpStatusCode.NotFound:
+ // case HttpStatusCode.Conflict:
+ // return <div>couldn't load the last active decision</div>;
+ // default:
+ // assertUnreachable(activeDecision);
+ // }
+ // }
function ShowEventForm({ events: calculatedEvents }: { events: Events }) {
const design = formDesign(i18n, calculatedEvents);
@@ -86,7 +81,7 @@ export function Events({ account }: { account: string }): VNode {
});
onComponentUnload(() => {
- updateRequest("onload event",{
+ updateRequest("onload event", {
custom_events: !form.status.result.custom
? []
: form.status.result.custom,
@@ -105,7 +100,7 @@ export function Events({ account }: { account: string }): VNode {
}
const events = calculateEventsBasedOnState(
- activeDecision.body,
+ request.original,
request,
i18n,
dialect,
@@ -223,7 +218,7 @@ function labelForEvent_tops(
case TOPS_AmlEventsName.MROS_REPORTED_SUSPICION_SUBSTANTIATED:
case TOPS_AmlEventsName.INCR_INVESTIGATION_CONCLUDED:
case TOPS_AmlEventsName.DECR_INVESTIGATION_CONCLUDED:
- // case TOPS_AmlEventsName.ACCOUNT_OPENED:
+ // case TOPS_AmlEventsName.ACCOUNT_OPENED:
return i18n.str`Account opened`;
// case TOPS_AmlEventsName.ACCOUNT_CLOSED:
// return i18n.str`Account closed`;
@@ -283,14 +278,17 @@ function calculateEventsBasedOnState(
dialect: AmlSpaDialect,
): Events {
const init: Events = { triggered: [], rest: [] };
+ const form = (request.attributes ?? {}) as Record<string, unknown>;
+ const formId = form[TalerFormAttributes.FORM_ID]! as string;
return Object.entries(EventsDerivation_TOPS).reduce((prev, [name, info]) => {
if (
info.shouldBeTriggered(
+ formId,
currentState?.limits?.rules,
request.rules,
currentState?.properties,
request.properties,
- (request.attributes ?? {}) as Record<string, unknown>,
+ form,
)
) {
prev.triggered.push(name);
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx b/packages/aml-backoffice-ui/src/pages/decision/Justification.tsx
@@ -1,59 +1,36 @@
-import { AmlDecision, PaytoString, TalerError } from "@gnu-taler/taler-util";
+import { PaytoString } from "@gnu-taler/taler-util";
import {
FormDesign,
FormUI,
InternationalizationAPI,
- Loading,
onComponentUnload,
useForm,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
import { useCurrentDecisionRequest } from "../../hooks/decision-request.js";
-import { useAccountActiveDecision } from "../../hooks/decisions.js";
/**
* Mark for further investigation and explain decision
* @param param0
* @returns
*/
-export function Justification({
- account,
- newPayto,
-}: {
- account: string;
- newPayto?: PaytoString;
-}): VNode {
+export function Justification({ newPayto }: { newPayto?: PaytoString }): VNode {
const isNewAccount = !!newPayto;
- const activeDecision = useAccountActiveDecision(isNewAccount ? undefined :account);
- const info =
- !activeDecision ||
- activeDecision instanceof TalerError ||
- activeDecision.type === "fail"
- ? undefined
- : activeDecision.body;
-
- if (!info && !isNewAccount) {
- return <Loading />;
- }
-
- return <JustificationForm isNewAccount={isNewAccount} info={info}/>
-}
-
-function JustificationForm({info, isNewAccount}:{info: AmlDecision | undefined, isNewAccount: boolean}):VNode {
const { i18n } = useTranslationContext();
const [request, updateRequest] = useCurrentDecisionRequest();
const design = formDesign(i18n, isNewAccount);
const form = useForm<FormType>(design, {
- investigate: request.keep_investigating ?? info?.to_investigate ?? false,
+ investigate:
+ request.keep_investigating ?? request.original?.to_investigate ?? false,
justification: request.justification,
accountName: request.accountName,
});
onComponentUnload(() => {
- updateRequest("unload justification",{
+ updateRequest("unload justification", {
keep_investigating: !!form.status.result.investigate,
justification: form.status.result.justification ?? "",
accountName: form.status.result.accountName ?? "",
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx b/packages/aml-backoffice-ui/src/pages/decision/Properties.tsx
@@ -1,36 +1,30 @@
import {
AccountProperties,
AmlSpaDialect,
- assertUnreachable,
GLS_AccountProperties,
GLS_AML_PROPERTIES,
- HttpStatusCode,
LegitimizationRuleSet,
PropertiesDerivation_TOPS,
PropertiesDerivationFunctionByPropertyName,
TalerAmlProperties,
- TalerError,
TOPS_AccountProperties
} from "@gnu-taler/taler-util";
import {
FormDesign,
FormUI,
InternationalizationAPI,
- Loading,
onComponentUnload,
UIFormElementConfig,
UIHandlerId,
useExchangeApiContext,
useForm,
- useTranslationContext,
+ useTranslationContext
} from "@gnu-taler/web-util/browser";
import { h, VNode } from "preact";
-import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
import {
DecisionRequest,
useCurrentDecisionRequest,
} from "../../hooks/decision-request.js";
-import { useAccountActiveDecision } from "../../hooks/decisions.js";
import { usePreferences } from "../../hooks/preferences.js";
import { DEFAULT_LIMITS_WHEN_NEW_ACCOUNT } from "./Rules.js";
@@ -39,31 +33,12 @@ import { DEFAULT_LIMITS_WHEN_NEW_ACCOUNT } from "./Rules.js";
* @param param0
* @returns
*/
-export function Properties({ account }: { account: string }): VNode {
- const activeDecision = useAccountActiveDecision(account);
+export function Properties({}: {}): VNode {
const [request] = useCurrentDecisionRequest();
const { config } = useExchangeApiContext();
const [pref] = usePreferences();
- if (!activeDecision) {
- return <Loading />;
- }
- if (activeDecision instanceof TalerError) {
- return <ErrorLoadingWithDebug error={activeDecision} />;
- }
- if (activeDecision.type === "fail") {
- switch (activeDecision.case) {
- // case HttpStatusCode.Unauthorized:
- case HttpStatusCode.Forbidden:
- case HttpStatusCode.NotFound:
- case HttpStatusCode.Conflict:
- return <div />;
- default:
- assertUnreachable(activeDecision);
- }
- }
-
- const lastDecision = activeDecision.body;
+ const lastDecision = request.original;
const dialect =
(pref.testingDialect ? undefined : config.config.aml_spa_dialect) ??
@@ -106,7 +81,7 @@ function ReloadForm({ merged }: { merged: any }): VNode {
});
onComponentUnload(() => {
- updateRequest("unload properties",{
+ updateRequest("unload properties", {
properties: (form.status.result.defined ?? {}) as Record<string, boolean>,
custom_properties: (form.status.result.custom ?? []).reduce(
(prev, cur) => {
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx b/packages/aml-backoffice-ui/src/pages/decision/Rules.tsx
@@ -120,17 +120,13 @@ export function findRuleInconsistency(
* @returns
*/
export function Rules({
- account,
newPayto,
}: {
- account: string;
newPayto?: PaytoString;
}): VNode {
const { i18n } = useTranslationContext();
const { config } = useExchangeApiContext();
- const isNewAccount = !!newPayto;
-
let newPaytoParsed: PaytoUri | undefined;
const isNewAccountAWallet =
newPayto === undefined
@@ -146,38 +142,25 @@ export function Rules({
!measures || measures instanceof TalerError || measures.type === "fail"
? undefined
: measures.body.roots;
-
- const activeDecision = useAccountActiveDecision(
- isNewAccount ? undefined : account,
- );
- const info =
- !activeDecision ||
- activeDecision instanceof TalerError ||
- activeDecision.type === "fail"
- ? undefined
- : activeDecision.body;
-
- if (!info && !isNewAccount) {
- return <Loading />;
- }
+ const [request] = useCurrentDecisionRequest()
// info may be undefined if this is a new account
// for which we use the payto:// parameter
- const isWallet = info?.is_wallet ?? isNewAccountAWallet;
+ const isWallet = request.original?.is_wallet ?? isNewAccountAWallet;
return (
<div>
<UpdateRulesForm
rootMeasures={rootMeasures}
config={config.config}
isWallet={isWallet ?? false}
- limits={info?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT}
+ limits={request.original?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT}
/>
<div>
<h2 class="mt-4 mb-2">
<i18n.Translate>Current active rules</i18n.Translate>
</h2>
- {info === undefined ? (
+ {request.original === undefined ? (
<p>
<i18n.Translate>
There are no rules for this account.
@@ -186,13 +169,13 @@ export function Rules({
) : (
<ShowDecisionLimitInfo
fixed
- since={AbsoluteTime.fromProtocolTimestamp(info.decision_time)}
+ since={AbsoluteTime.fromProtocolTimestamp(request.original.decision_time)}
until={AbsoluteTime.fromProtocolTimestamp(
- info.limits.expiration_time,
+ request.original.limits.expiration_time,
)}
- rules={info.limits.rules}
+ rules={request.original.limits.rules}
startOpen
- measure={info.limits.successor_measure ?? ""}
+ measure={request.original.limits.successor_measure ?? ""}
/>
)}
</div>
diff --git a/packages/taler-util/src/aml/events.ts b/packages/taler-util/src/aml/events.ts
@@ -1,7 +1,8 @@
import { Amounts } from "../amounts.js";
-import { isOneOf, TalerAmlProperties } from "../index.js";
import { LimitOperationType } from "../types-taler-exchange.js";
import { AccountProperties, KycRule } from "../types-taler-kyc-aml.js";
+import { TalerAmlProperties } from "../taler-account-properties.js";
+import { isOneOf } from "./properties.js";
/**
* List of events triggered by TOPS