feat(clients): maker-checker for client creation and edits (backend)
- Client activation now requires a different user than the one who created the client
(client.getCreatedBy() vs current user) — no bypass, including CHECKER_SUPER_USER, since a
bypass would recreate the exact "role bypasses the hold" loophole this closes. Client creation
itself is unaffected (the wizard needs a real client id immediately for photo/KYC/address/family
follow-up uploads), only activation is gated.
- New maker-checker queue for Client edits, modelled on this codebase's existing
m_share_account_pending_edit / chequebook patterns rather than Fineract's native
command-approval engine (which any ALL_FUNCTIONS role bypasses unconditionally):
- m_client_pending_edit (migrations 0429-0431): stores the maker's raw edit payload plus a
point-in-time "before" snapshot (original_json, extracted from the frontend's _originalValues
key) so the checker's diff stays accurate even after the edit is approved/rejected, when the
live client no longer reflects the pre-edit state.
- ClientEditWritePlatformService: submitEditForApproval() stages the edit (refusing a second
submission while one's already pending, via a nullable-but-unique pending_lock_key);
approveEdit() replays the payload through the existing updateClient() so all of Client's
validation/change-detection runs unchanged, just deferred; rejectEdit() discards it. The maker
can never approve/reject their own submission.
- ClientEditApiResource: /clients/{id}/edit (submit), /clients/edit/pending (paginated +
status/clientName/date-range filterable list, mirrors /chequebook/pending-requests),
/clients/{id}/edit/approve, /clients/{id}/edit/reject. New permissions (migration 0430):
SUBMITEDIT_CLIENT, APPROVEEDIT_CLIENT, REJECTEDIT_CLIENT, APPROVEEDIT_CLIENT_CHECKER (the
last one surfaces pending edits in the generic Checker Inbox too).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -449,6 +449,9 @@
|
||||
<include file="parts/0426_add_graduate_education_level.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0427_add_caretaker_relationship_value.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0428_add_minor_kyc_id_proof_codes.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0429_add_client_pending_edit_table.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0430_add_client_edit_approval_permissions.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0431_add_original_json_to_client_pending_edit.xml" relativeToChangelogFile="true"/>
|
||||
<!-- NOTE: the numbers below (0341-0359) re-use prefixes already used above by the chequebook/CTS
|
||||
clearing catchup migrations — these come from a long-diverged branch (uate) that assigned its own
|
||||
sequence independently. Liquibase tracks changesets by filename + id + author, not by the numeric
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.13.xsd">
|
||||
|
||||
<!-- Maker-checker for Client edits: the maker's PUT /clients/{id}/edit payload is staged here
|
||||
(raw JSON, replayed through the existing ClientWritePlatformService.updateClient() on
|
||||
approval, so all existing validation/business logic is reused unchanged) rather than
|
||||
applied immediately. `pending_lock_key` is a nullable-but-unique column (NULL while not
|
||||
pending) used to prevent a second maker from submitting another edit while one is already
|
||||
awaiting approval for the same client — see m_share_account_pending_edit /
|
||||
m_savings_product_temp for the same pattern elsewhere in this codebase. Resolved rows are
|
||||
kept (status APPROVED/REJECTED) for audit history rather than deleted, which is exactly
|
||||
why the lock can't be a plain UNIQUE(client_id) — a client could never be edited a second
|
||||
time after its first edit was ever resolved. -->
|
||||
<changeSet author="CBS" id="0429-1-create-client-pending-edit-table">
|
||||
<createTable tableName="m_client_pending_edit">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="client_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_client_pending_edit_client" references="m_client(id)"/>
|
||||
</column>
|
||||
<column name="payload_json" type="TEXT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="maker_id" type="BIGINT">
|
||||
<constraints nullable="false" foreignKeyName="fk_client_pending_edit_maker" references="m_appuser(id)"/>
|
||||
</column>
|
||||
<column name="submitted_date" type="TIMESTAMP WITH TIME ZONE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="checker_id" type="BIGINT">
|
||||
<constraints nullable="true" foreignKeyName="fk_client_pending_edit_checker" references="m_appuser(id)"/>
|
||||
</column>
|
||||
<column name="checked_date" type="TIMESTAMP WITH TIME ZONE"/>
|
||||
<column name="reject_reason" type="VARCHAR(500)"/>
|
||||
<column name="pending_lock_key" type="VARCHAR(60)">
|
||||
<constraints nullable="true" unique="true" uniqueConstraintName="uq_client_pending_edit_lock_key"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="CBS" id="0429-2-index-client-pending-edit-client-id">
|
||||
<createIndex indexName="idx_client_pending_edit_client_id" tableName="m_client_pending_edit">
|
||||
<column name="client_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="CBS" id="0429-3-index-client-pending-edit-status">
|
||||
<createIndex indexName="idx_client_pending_edit_status" tableName="m_client_pending_edit">
|
||||
<column name="status"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.13.xsd">
|
||||
|
||||
<!-- Permissions for the Client edit maker-checker workflow (ClientEditApiResource /
|
||||
m_client_pending_edit). can_maker_checker=false on all four — these rows ARE the bespoke
|
||||
gate itself, so layering Fineract's native command-hold on top would be redundant.
|
||||
APPROVEEDIT_CLIENT_CHECKER (action_name ending "_CHECKER") makes pending Client edits also
|
||||
show up in the generic Checker Inbox (GET /v1/makercheckers) for limited checkers whose
|
||||
role holds it, mirroring 2022_add_product_checker_permissions.xml elsewhere in this repo. -->
|
||||
<changeSet author="CBS" id="0430-1-add-client-edit-approval-permissions">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_permission WHERE code = 'SUBMITEDIT_CLIENT'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="SUBMITEDIT_CLIENT"/>
|
||||
<column name="entity_name" value="CLIENT"/>
|
||||
<column name="action_name" value="SUBMITEDIT"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="APPROVEEDIT_CLIENT"/>
|
||||
<column name="entity_name" value="CLIENT"/>
|
||||
<column name="action_name" value="APPROVEEDIT"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="REJECTEDIT_CLIENT"/>
|
||||
<column name="entity_name" value="CLIENT"/>
|
||||
<column name="action_name" value="REJECTEDIT"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="portfolio"/>
|
||||
<column name="code" value="APPROVEEDIT_CLIENT_CHECKER"/>
|
||||
<column name="entity_name" value="CLIENT"/>
|
||||
<column name="action_name" value="APPROVEEDIT_CHECKER"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.13.xsd">
|
||||
|
||||
<!-- Point-in-time "before" snapshot of the client's own values, captured by the frontend from
|
||||
the record it loaded when the edit page was opened (see buildOriginalValuesSnapshot() in
|
||||
client-edit-diff.util.ts) and submitted alongside the edit under the _originalValues key,
|
||||
which ClientEditWritePlatformServiceImpl strips out before storing/replaying the rest as
|
||||
payload_json. Needed because diffing the staged payload against the LIVE client (as the
|
||||
checker UI originally did) shows nothing once an edit is approved — by then the live
|
||||
client already equals the proposed values. Nullable: existing pending-edit rows created
|
||||
before this column existed simply have no "before" snapshot to diff against. -->
|
||||
<changeSet author="CBS" id="0431-1-add-original-json-to-client-pending-edit">
|
||||
<addColumn tableName="m_client_pending_edit">
|
||||
<column name="original_json" type="TEXT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user