From 05bceea2d4f0b2e4212383aad8f6420a77a255e6 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 8 Sep 2026 15:16:07 +0530 Subject: [PATCH] feat(office): maker-checker delete for offices; fix DB password leak, migration ordering, job id drift Adds the missing DELETE /offices/{officeId} endpoint -- the UI's "Remove" action called it already, but no backend route existed, so every attempt failed with a generic error regardless of how empty the office was. Wired the standard command-handler chain (CommandWrapperBuilder.deleteOffice -> DeleteOfficeCommandHandler -> OfficeWritePlatformService.deleteOffice), which pre-checks sub-offices/staff/clients/groups/users before deleting, and catches any remaining FK violation on delete itself. The new DELETE_OFFICE permission (0537) is seeded maker-checker-enabled (0538), since this app runs with the global maker-checker switch on and office deletion is irreversible -- 0538 is a separate changeset rather than 0537 itself because 0537 had already executed on local tenants by the time that requirement surfaced, and editing an already-run changeset's content breaks Liquibase's checksum validation on every tenant that ran it. Also bundles a few DB/startup fixes found while getting this feature running across local and a remote environment: - application.properties: restores the tenant DB password default that a teammate's own local password had overwritten in a previous push, and adds server.error.include-message=always so a servlet-level rejection (e.g. TenantAwareBasicAuthenticationFilter on a missing/invalid Fineract-Platform-TenantId header) carries a real message instead of a bare {timestamp,status,error,path}. - changelog-tenant.xml / final-changelog-tenant.xml: moves the 0517/0518 table-creation includes ahead of 0525-0527/0533, which read/write those tables but previously ran first per db.changelog-master.xml's include order -- caused "relation ... does not exist" on any tenant reaching that point for the first time. - 0536_sync_job_identity_sequence.xml: job.id is GENERATED BY DEFAULT AS IDENTITY; explicit-id job-seed inserts never advanced the backing sequence, so a later identity-generated insert could collide with an earlier explicit id ("duplicate key value violates ... job_pkey"). - ClientAllAccountsApiResource: NotFoundException(String) only sets the exception's own message for logs, it never reaches the response body, so a "client not found" 404 always came back empty; now built as a proper JSON error body. Also collapsed the message/defaultUserMessage/ errors[0].defaultUserMessage triple down to one message field -- this endpoint never raises more than one error at a time, so the repetition added nothing. Co-Authored-By: Claude Sonnet 5 --- .../service/CommandWrapperBuilder.java | 8 ++ .../domain/OfficeRepositoryWrapper.java | 11 +++ .../office/api/OfficesApiResource.java | 15 ++++ .../handler/DeleteOfficeCommandHandler.java | 47 ++++++++++++ .../service/OfficeWritePlatformService.java | 2 + ...WritePlatformServiceJpaRepositoryImpl.java | 75 +++++++++++++++++++ .../OrganisationOfficeConfiguration.java | 4 +- .../api/ClientAllAccountsApiResource.java | 27 ++++++- .../src/main/resources/application.properties | 12 ++- .../db/changelog/tenant/changelog-tenant.xml | 13 ++++ .../tenant/final-changelog-tenant.xml | 14 +++- .../parts/0536_sync_job_identity_sequence.xml | 30 ++++++++ .../0537_seed_delete_office_permission.xml | 51 +++++++++++++ 13 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeCommandHandler.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0536_sync_job_identity_sequence.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0537_seed_delete_office_permission.xml diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index a8443f749..af0fb4ddd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -664,6 +664,14 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder deleteOffice(final Long officeId) { + this.actionName = "DELETE"; + this.entityName = "OFFICE"; + this.entityId = officeId; + this.href = "/offices/" + officeId; + return this; + } + public CommandWrapperBuilder createOfficeTransaction() { this.actionName = "CREATE"; this.entityName = "OFFICETRANSACTION"; diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java b/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java index 325cc5cd2..2c437fdcd 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/office/domain/OfficeRepositoryWrapper.java @@ -61,4 +61,15 @@ public class OfficeRepositoryWrapper { public void delete(final Office entity) { this.repository.delete(entity); } + + /** + * Deletes and flushes in the same call, so a foreign-key violation the caller did not already check + * for (a savings transaction, a GL journal entry, a teller position -- anything on m_office's long + * tail of referencing tables) surfaces synchronously here, inside the caller's own try/catch, rather + * than at transaction-commit time where it can no longer be turned into a clean error message. + */ + public void deleteAndFlush(final Office entity) { + this.repository.delete(entity); + this.repository.flush(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java index 341754aac..1d1c4c0d9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/api/OfficesApiResource.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; @@ -200,6 +201,20 @@ public class OfficesApiResource { return toApiJsonSerializer.serialize(result); } + @DELETE + @Path("{officeId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Delete an Office", description = "Deletes an office outright. Fails if the office still has sub-offices, staff, clients, groups, users, or any other record attached to it.") + @ApiResponse(responseCode = "200", description = "OK") + public String deleteOffice(@PathParam("officeId") @Parameter(description = "officeId") final Long officeId) { + final CommandWrapper commandRequest = new CommandWrapperBuilder() // + .deleteOffice(officeId) // + .build(); + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return toApiJsonSerializer.serialize(result); + } + @PUT @Path("/external-id/{externalId}") @Consumes({ MediaType.APPLICATION_JSON }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeCommandHandler.java new file mode 100644 index 000000000..009fb1158 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/handler/DeleteOfficeCommandHandler.java @@ -0,0 +1,47 @@ +/** + * 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. + */ +package org.apache.fineract.organisation.office.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.organisation.office.service.OfficeWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "OFFICE", action = "DELETE") +public class DeleteOfficeCommandHandler implements NewCommandSourceHandler { + + private final OfficeWritePlatformService writePlatformService; + + @Autowired + public DeleteOfficeCommandHandler(final OfficeWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.deleteOffice(command.entityId(), command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java index 5cf0fbd8a..d90e3f28c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformService.java @@ -27,6 +27,8 @@ public interface OfficeWritePlatformService { CommandProcessingResult updateOffice(Long officeId, JsonCommand command); + CommandProcessingResult deleteOffice(Long officeId, JsonCommand command); + CommandProcessingResult officeTransaction(JsonCommand command); CommandProcessingResult deleteOfficeTransaction(Long id, JsonCommand command); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java index edc4180c1..bfab9ce27 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeWritePlatformServiceJpaRepositoryImpl.java @@ -44,6 +44,7 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Caching; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.orm.jpa.JpaSystemException; import org.springframework.transaction.annotation.Transactional; @@ -57,6 +58,13 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP private final OfficeRepositoryWrapper officeRepositoryWrapper; private final OfficeTransactionRepository officeTransactionRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; + // Lightweight existence checks ahead of delete -- m_office has ~30 tables with a foreign key back + // to it (clients, staff, GL entries, tellers, cheque stock, current accounts, ...); pulling in a + // repository for every one of them just to count rows would be a lot of new wiring for a check + // that is fundamentally "does anything point at this row". A handful of raw counts up front cover + // the cases a user can actually resolve themselves (move the clients, reassign the staff); the + // long tail that is left is caught by the try/catch around the delete itself. + private final JdbcTemplate jdbcTemplate; @Transactional @Override @@ -147,6 +155,73 @@ public class OfficeWritePlatformServiceJpaRepositoryImpl implements OfficeWriteP } } + /** + * Deletes an office outright -- distinct from every other office command, which only ever changes + * status or fields. There is no "closed" state for an office the way there is for a client or a + * savings account, and no command handler ever implemented this, so the UI's "Remove" action always + * failed with a generic error no matter how empty the office was; the endpoint it called never + * existed. + * + * Checked up front, in the order a user could actually act on them: sub-offices first (the deepest + * structural block), then staff, clients and groups (the day-to-day reason an office still has + * anything in it), then any AppUser still homed there. Everything else that references m_office -- + * GL journal entries, teller cash positions, cheque stock, current accounts, loan/savings + * transactions, and more -- is not spelled out here one table at a time; a real foreign-key + * violation on the delete itself is caught below and turned into one clear message instead. An + * office with real banking history behind it is not something this should make easy to remove either + * way, checked up front or not. + */ + @Transactional + @Override + @Caching(evict = { + @CacheEvict(value = "offices", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')"), + @CacheEvict(value = "officesForDropdown", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')"), + @CacheEvict(value = "officesById", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#officeId)") }) + public CommandProcessingResult deleteOffice(final Long officeId, final JsonCommand command) { + + this.context.authenticatedUser(); + + final Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId); + + requireNoneReference(officeId, "m_office", "parent_id", "sub-office", "sub-offices", + "Move or remove its sub-offices before deleting this office."); + requireNoneReference(officeId, "m_staff", "office_id", "staff member", "staff members", + "Reassign its staff to another office before deleting this office."); + requireNoneReference(officeId, "m_client", "office_id", "client", "clients", + "Transfer its clients to another office before deleting this office."); + requireNoneReference(officeId, "m_group", "office_id", "group", "groups", + "Transfer its groups to another office before deleting this office."); + requireNoneReference(officeId, "m_appuser", "office_id", "user", "users", + "Reassign the users homed at this office before deleting it."); + + try { + this.officeRepositoryWrapper.deleteAndFlush(office); + } catch (final JpaSystemException | DataIntegrityViolationException dve) { + throw ErrorHandler.getMappable(dve, "error.msg.office.cannot.delete.has.references", + "This office cannot be deleted: other records (accounts, transactions or history) still reference it."); + } catch (final PersistenceException dve) { + throw ErrorHandler.getMappable(dve, "error.msg.office.cannot.delete.has.references", + "This office cannot be deleted: other records (accounts, transactions or history) still reference it."); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(officeId) // + .withOfficeId(officeId) // + .build(); + } + + private void requireNoneReference(final Long officeId, final String table, final String column, final String singular, + final String plural, final String remedy) { + final Long count = this.jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + table + " WHERE " + column + " = ?", Long.class, + officeId); + if (count != null && count > 0) { + final String noun = count == 1 ? singular : plural; + throw new PlatformDataIntegrityException("error.msg.office.cannot.delete.still.referenced", + "Office cannot be deleted: it has " + count + " " + noun + " attached. " + remedy, "officeId", officeId); + } + } + @Transactional @Override public CommandProcessingResult officeTransaction(final JsonCommand command) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java index e07746e6c..c5fc22384 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/starter/OrganisationOfficeConfiguration.java @@ -56,9 +56,9 @@ public class OrganisationOfficeConfiguration { OfficeCommandFromApiJsonDeserializer fromApiJsonDeserializer, OfficeTransactionCommandFromApiJsonDeserializer moneyTransferCommandFromApiJsonDeserializer, OfficeRepositoryWrapper officeRepositoryWrapper, OfficeTransactionRepository officeTransactionRepository, - ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository) { + ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, JdbcTemplate jdbcTemplate) { return new OfficeWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, moneyTransferCommandFromApiJsonDeserializer, officeRepositoryWrapper, officeTransactionRepository, - applicationCurrencyRepository); + applicationCurrencyRepository, jdbcTemplate); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAllAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAllAccountsApiResource.java index 6391f8268..e7c7d6c37 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAllAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/api/ClientAllAccountsApiResource.java @@ -25,12 +25,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.GET; import jakarta.ws.rs.HeaderParam; -import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -234,11 +235,33 @@ public class ClientAllAccountsApiResource { return client; } - throw new NotFoundException("No client found with client number or mobile number '" + identifier + "' in tenant '" + throw apiError(404, "No client found with client number or mobile number '" + identifier + "' in tenant '" + tenant.getTenantIdentifier() + "' (" + tenant.getName() + "). If you expected a match, check the " + "Fineract-Platform-TenantId header -- this identifier may belong to a different bank."); } + /** + * Builds the error response body itself rather than throwing a bare {@code jakarta.ws.rs.*} + * exception: {@code NotFoundException(String)} only sets the Java exception's own message (for + * logs), it never puts that text on the wire -- no exception mapper in this app converts a plain + * NotFoundException's message into the response body, so the caller always got + * {@code {timestamp,status,error,path}} with nothing else, no matter how descriptive the message + * passed to the constructor was. + * + * One message field, not the {@code message}/{@code defaultUserMessage}/{@code errors[0] + * .defaultUserMessage} triple {@code ClerkClosureRequestApiResource#apiError} uses: that shape + * exists there to line up with Fineract's own multi-field validation errors, where {@code errors} + * really does carry one entry per invalid parameter. This endpoint never raises more than one + * error at a time, so the same sentence repeated three times said nothing extra, only made the + * body longer. + */ + private WebApplicationException apiError(final int httpStatus, final String message) { + final JsonObject body = new JsonObject(); + body.addProperty("httpStatusCode", httpStatus); + body.addProperty("message", message); + return new WebApplicationException(Response.status(httpStatus).entity(gson.toJson(body)).type(MediaType.APPLICATION_JSON).build()); + } + private String resolveClientIp(final HttpServletRequest request) { for (final String header : IP_HEADER_CANDIDATES) { final String value = request.getHeader(header); diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 70fbe79ba..108e7421a 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -46,7 +46,7 @@ fineract.security.oauth2.client.registrations.frontend-client.require-authorizat fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost} fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432} fineract.tenant.username=${FINERACT_DEFAULT_TENANTDB_UID:postgres} -fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:Keshav@898966} +fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:313863} fineract.tenant.parameters=${FINERACT_DEFAULT_TENANTDB_CONN_PARAMS:} fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata} fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default} @@ -384,6 +384,14 @@ server.forward-headers-strategy=framework server.port=${FINERACT_SERVER_PORT:8443} server.servlet.context-path=${FINERACT_SERVER_SERVLET_CONTEXT_PATH:/fineract-provider} server.compression.enabled=${FINERACT_SERVER_COMPRESSION_ENABLED:true} +# Spring Boot strips the "message" field from a servlet-container-level error response by default +# (server.error.include-message defaults to "never"), so any rejection that happens before a request +# reaches Jersey -- most commonly TenantAwareBasicAuthenticationFilter's response.sendError() when +# Fineract-Platform-TenantId is missing or invalid -- rendered as a bare {timestamp,status,error,path} +# with no indication of what was actually wrong. Every other error path in this app (JAX-RS's own +# exception mappers) already carries a message; this makes the pre-Jersey filter layer consistent +# with that instead of the framework default. +server.error.include-message=always server.ssl.enabled=${FINERACT_SERVER_SSL_ENABLED:true} server.ssl.protocol=TLS @@ -407,7 +415,7 @@ server.tomcat.mbeanregistry.enabled=${FINERACT_SERVER_TOMCAT_MBEANREGISTRY_ENABL spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver} spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants} spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres} -spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:Keshav@898966} +spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:313863} spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3} spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10} spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 518b7221b..d0c837041 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -587,6 +587,19 @@ + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml index 5c0ea6560..06323958f 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/final-changelog-tenant.xml @@ -43,8 +43,16 @@ - - + @@ -54,4 +62,6 @@ + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0536_sync_job_identity_sequence.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0536_sync_job_identity_sequence.xml new file mode 100644 index 000000000..2f6589894 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0536_sync_job_identity_sequence.xml @@ -0,0 +1,30 @@ + + + + + + + SELECT setval(pg_get_serial_sequence('job', 'id'), COALESCE((SELECT MAX(id) FROM job), 1), true); + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0537_seed_delete_office_permission.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0537_seed_delete_office_permission.xml new file mode 100644 index 000000000..295fd1f91 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0537_seed_delete_office_permission.xml @@ -0,0 +1,51 @@ + + + + + + + SELECT COUNT(*) FROM m_permission WHERE code = 'DELETE_OFFICE' + + + + + + + + + + + + + + SELECT COUNT(*) FROM m_permission WHERE code = 'DELETE_OFFICE' AND can_maker_checker = false + + + + code = 'DELETE_OFFICE' + + + +