Locker module: class master, locker list, waiting list, allotment (tables + client details API)
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s
This commit is contained in:
+48
@@ -95,6 +95,54 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createLockerClass() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "LOCKERCLASS";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/lockerclasses";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateLockerClass(final Long lockerClassId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "LOCKERCLASS";
|
||||||
|
this.entityId = lockerClassId;
|
||||||
|
this.href = "/lockerclasses/" + lockerClassId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder deleteLockerClass(final Long lockerClassId) {
|
||||||
|
this.actionName = "DELETE";
|
||||||
|
this.entityName = "LOCKERCLASS";
|
||||||
|
this.entityId = lockerClassId;
|
||||||
|
this.href = "/lockerclasses/" + lockerClassId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder createLockerWaitingList() {
|
||||||
|
this.actionName = "CREATE";
|
||||||
|
this.entityName = "LOCKERWAITINGLIST";
|
||||||
|
this.entityId = null;
|
||||||
|
this.href = "/lockerwaitinglist";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder updateLockerWaitingList(final Long waitingListId) {
|
||||||
|
this.actionName = "UPDATE";
|
||||||
|
this.entityName = "LOCKERWAITINGLIST";
|
||||||
|
this.entityId = waitingListId;
|
||||||
|
this.href = "/lockerwaitinglist/" + waitingListId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandWrapperBuilder cancelLockerWaitingList(final Long waitingListId) {
|
||||||
|
this.actionName = "CANCEL";
|
||||||
|
this.entityName = "LOCKERWAITINGLIST";
|
||||||
|
this.entityId = waitingListId;
|
||||||
|
this.href = "/lockerwaitinglist/" + waitingListId + "?command=cancel";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createChequeStockLot() {
|
public CommandWrapperBuilder createChequeStockLot() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "CHEQUESTOCKLOT";
|
this.entityName = "CHEQUESTOCKLOT";
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.PathParam;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerAllotmentClientData;
|
||||||
|
import org.apache.fineract.portfolio.locker.service.LockerReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/** Locker allotment — applications for a locker by an existing client, approved by the Branch Manager. */
|
||||||
|
@Path("/v1/lockerallotments")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Locker Allotment", description = "Locker applications: client, locker, joint holders, security deposit, nominees; Branch Manager approval.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerAllotmentApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final LockerReadPlatformService readPlatformService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("clientdetails/{clientId}")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Client details for an allotment", description = "Contact, address, gender, occupation, PAN, masked Aadhaar, membership, active savings accounts and fixed deposits. Only clients within the user's branch scope.")
|
||||||
|
public LockerAllotmentClientData retrieveClientDetails(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_ALLOTMENT);
|
||||||
|
return this.readPlatformService.retrieveAllotmentClientDetails(clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
public final class LockerApiConstants {
|
||||||
|
|
||||||
|
private LockerApiConstants() {}
|
||||||
|
|
||||||
|
public static final String ENTITY_CLASS = "LOCKERCLASS";
|
||||||
|
public static final String ENTITY_LOCKER = "LOCKER";
|
||||||
|
public static final String ENTITY_WAITING_LIST = "LOCKERWAITINGLIST";
|
||||||
|
public static final String ENTITY_ALLOTMENT = "LOCKERALLOTMENT";
|
||||||
|
|
||||||
|
public static final String RESOURCE_CLASS = "lockerclass";
|
||||||
|
public static final String RESOURCE_WAITING_LIST = "lockerwaitinglist";
|
||||||
|
|
||||||
|
/** Upper bound on one range, so a typo such as 1-100000 cannot insert a hundred thousand locker rows. */
|
||||||
|
public static final int MAX_LOCKERS_PER_RANGE = 1000;
|
||||||
|
}
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public enum LockerApiJsonInputParams {
|
||||||
|
|
||||||
|
// class create/update
|
||||||
|
OFFICE_ID("officeId"), //
|
||||||
|
CLASS_CODE("classCode"), //
|
||||||
|
CLASS_NAME("className"), //
|
||||||
|
AREA("area"), //
|
||||||
|
RENT("rent"), //
|
||||||
|
MEMBER_RENT("memberRent"), //
|
||||||
|
DEPOSIT("deposit"), //
|
||||||
|
MEMBER_DEPOSIT("memberDeposit"), //
|
||||||
|
START_LOCKER_NO("startLockerNo"), //
|
||||||
|
END_LOCKER_NO("endLockerNo"), //
|
||||||
|
EFFECTIVE_FROM_DATE("effectiveFromDate"), //
|
||||||
|
EFFECTIVE_TO_DATE("effectiveToDate"), //
|
||||||
|
// waiting list create/update/cancel
|
||||||
|
CLIENT_ID("clientId"), //
|
||||||
|
INQUIRY_DATE("inquiryDate"), //
|
||||||
|
CUSTOMER_NAME("customerName"), //
|
||||||
|
ADDRESS_LINE1("addressLine1"), //
|
||||||
|
ADDRESS_LINE2("addressLine2"), //
|
||||||
|
ADDRESS_LINE3("addressLine3"), //
|
||||||
|
CITY("city"), //
|
||||||
|
PINCODE("pincode"), //
|
||||||
|
PHONE("phone"), //
|
||||||
|
EMAIL("email"), //
|
||||||
|
PREFERRED_CLASSES("preferredClasses"), //
|
||||||
|
REMARKS("remarks"), //
|
||||||
|
ADDITIONAL_REMARKS("additionalRemarks"), //
|
||||||
|
// common — the web client always sends these on command payloads
|
||||||
|
LOCALE("locale"), //
|
||||||
|
DATE_FORMAT("dateFormat");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
LockerApiJsonInputParams(final String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> setOf(final LockerApiJsonInputParams... params) {
|
||||||
|
return Arrays.stream(params).map(LockerApiJsonInputParams::getValue).collect(Collectors.toCollection(HashSet::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> classCreateParams() {
|
||||||
|
return setOf(OFFICE_ID, CLASS_CODE, CLASS_NAME, AREA, RENT, MEMBER_RENT, DEPOSIT, MEMBER_DEPOSIT, START_LOCKER_NO, END_LOCKER_NO,
|
||||||
|
EFFECTIVE_FROM_DATE, EFFECTIVE_TO_DATE, LOCALE, DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class code, branch and locker range are fixed once saved — a different range is a new row. */
|
||||||
|
public static Set<String> classUpdateParams() {
|
||||||
|
return setOf(CLASS_NAME, AREA, RENT, MEMBER_RENT, DEPOSIT, MEMBER_DEPOSIT, EFFECTIVE_FROM_DATE, EFFECTIVE_TO_DATE, LOCALE,
|
||||||
|
DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> waitingListCreateParams() {
|
||||||
|
final Set<String> params = waitingListUpdateParams();
|
||||||
|
params.add(OFFICE_ID.getValue());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branch is fixed once the entry is taken. */
|
||||||
|
public static Set<String> waitingListUpdateParams() {
|
||||||
|
return setOf(CLIENT_ID, INQUIRY_DATE, CUSTOMER_NAME, ADDRESS_LINE1, ADDRESS_LINE2, ADDRESS_LINE3, CITY, PINCODE, PHONE, EMAIL,
|
||||||
|
PREFERRED_CLASSES, REMARKS, LOCALE, DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> waitingListCancelParams() {
|
||||||
|
return setOf(ADDITIONAL_REMARKS, LOCALE, DATE_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerSummaryData;
|
||||||
|
import org.apache.fineract.portfolio.locker.service.LockerReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/** Read-only locker inventory — the boxes created from class master ranges, with their status. */
|
||||||
|
@Path("/v1/lockers")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Lockers", description = "Safe deposit locker inventory — every locker of the caller's branch with class, key number and status.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final LockerReadPlatformService readPlatformService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "List lockers", description = "Filter by class code, status (VACANT / ALLOTTED / BLOCKED) and locker number.")
|
||||||
|
public Page<LockerData> retrieveAll(@QueryParam("officeId") final Long officeId, @QueryParam("classCode") final String classCode,
|
||||||
|
@QueryParam("status") final String status, @QueryParam("lockerNo") final Integer lockerNo,
|
||||||
|
@QueryParam("offset") @DefaultValue("0") final Integer offset, @QueryParam("limit") @DefaultValue("20") final Integer limit) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_LOCKER);
|
||||||
|
final int safeLimit = Math.min(limit, 500);
|
||||||
|
return this.readPlatformService.retrieveLockers(officeId, classCode, status, lockerNo, offset, safeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("summary")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Locker counts by status")
|
||||||
|
public LockerSummaryData retrieveSummary(@QueryParam("officeId") final Long officeId) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_LOCKER);
|
||||||
|
return this.readPlatformService.retrieveLockerSummary(officeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.DELETE;
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.PUT;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.PathParam;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
|
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClassData;
|
||||||
|
import org.apache.fineract.portfolio.locker.service.LockerReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/v1/lockerclasses")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Locker Class Master", description = "Safe deposit locker classes — member / non-member rent and deposit per locker number range of a branch.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerClassApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final LockerReadPlatformService readPlatformService;
|
||||||
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "List locker classes", description = "A Super user sees every branch (optionally filtered by officeId); everyone else sees only their own branch.")
|
||||||
|
public Page<LockerClassData> retrieveAll(@QueryParam("status") final String status, @QueryParam("officeId") final Long officeId,
|
||||||
|
@QueryParam("offset") @DefaultValue("0") final Integer offset, @QueryParam("limit") @DefaultValue("20") final Integer limit) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_CLASS);
|
||||||
|
final int safeLimit = Math.min(limit, 500);
|
||||||
|
return this.readPlatformService.retrieveAllClasses(status, officeId, offset, safeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{id}")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Retrieve a locker class")
|
||||||
|
public LockerClassData retrieveOne(@PathParam("id") @Parameter(description = "id") final Long id) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_CLASS);
|
||||||
|
return this.readPlatformService.retrieveClass(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Create a locker class", description = "Also creates one VACANT locker for every number in the range.")
|
||||||
|
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().createLockerClass().withJson(apiRequestBodyAsJson).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{id}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Update a locker class", description = "Rates, class name, area and effective dates only — class code and range are fixed.")
|
||||||
|
public CommandProcessingResult update(@PathParam("id") @Parameter(description = "id") final Long id,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().updateLockerClass(id).withJson(apiRequestBodyAsJson).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("{id}")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Deactivate a locker class", description = "Refused while any of its lockers is allotted.")
|
||||||
|
public CommandProcessingResult delete(@PathParam("id") @Parameter(description = "id") final Long id) {
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().deleteLockerClass(id).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.api;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.ws.rs.Consumes;
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.PUT;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.PathParam;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||||
|
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||||
|
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClientContactData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerWaitingListData;
|
||||||
|
import org.apache.fineract.portfolio.locker.service.LockerReadPlatformService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Path("/v1/lockerwaitinglist")
|
||||||
|
@Component
|
||||||
|
@Tag(name = "Locker Waiting List", description = "Locker inquiries in branch queue order — the id is the waiting number given to the applicant.")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerWaitingListApiResource {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final LockerReadPlatformService readPlatformService;
|
||||||
|
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "List waiting list entries", description = "Oldest inquiry first. status: PENDING / ALLOTTED / CANCELLED.")
|
||||||
|
public Page<LockerWaitingListData> retrieveAll(@QueryParam("status") final String status, @QueryParam("officeId") final Long officeId,
|
||||||
|
@QueryParam("offset") @DefaultValue("0") final Integer offset, @QueryParam("limit") @DefaultValue("20") final Integer limit) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_WAITING_LIST);
|
||||||
|
final int safeLimit = Math.min(limit, 500);
|
||||||
|
return this.readPlatformService.retrieveWaitingList(status, officeId, offset, safeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{id}")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Retrieve a waiting list entry")
|
||||||
|
public LockerWaitingListData retrieveOne(@PathParam("id") @Parameter(description = "id") final Long id) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_WAITING_LIST);
|
||||||
|
return this.readPlatformService.retrieveWaitingListEntry(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("clientcontact/{clientId}")
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Client name, contact and address to pre-fill an inquiry", description = "Only clients within the user's branch scope.")
|
||||||
|
public LockerClientContactData retrieveClientContact(@PathParam("clientId") @Parameter(description = "clientId") final Long clientId) {
|
||||||
|
this.context.authenticatedUser().validateHasReadPermission(LockerApiConstants.ENTITY_WAITING_LIST);
|
||||||
|
return this.readPlatformService.retrieveClientContact(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Add a waiting list entry")
|
||||||
|
public CommandProcessingResult create(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().createLockerWaitingList().withJson(apiRequestBodyAsJson).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("{id}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Update a pending waiting list entry")
|
||||||
|
public CommandProcessingResult update(@PathParam("id") @Parameter(description = "id") final Long id,
|
||||||
|
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().updateLockerWaitingList(id).withJson(apiRequestBodyAsJson).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{id}")
|
||||||
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
|
@Produces({ MediaType.APPLICATION_JSON })
|
||||||
|
@Operation(summary = "Cancel a pending waiting list entry", description = "command=cancel, optional additionalRemarks as the reason.")
|
||||||
|
public CommandProcessingResult action(@PathParam("id") @Parameter(description = "id") final Long id,
|
||||||
|
@QueryParam("command") final String command, @Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
|
if (!"cancel".equalsIgnoreCase(command)) {
|
||||||
|
throw new UnrecognizedQueryParamException("command", command);
|
||||||
|
}
|
||||||
|
final String json = apiRequestBodyAsJson == null || apiRequestBodyAsJson.isBlank() ? "{}" : apiRequestBodyAsJson;
|
||||||
|
final CommandWrapper request = new CommandWrapperBuilder().cancelLockerWaitingList(id).withJson(json).build();
|
||||||
|
return this.commandsSourceWritePlatformService.logCommandSource(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Everything the allotment form fills from the client: contact, address, gender, occupation, PAN, masked Aadhaar,
|
||||||
|
* membership (decides member rates), active savings accounts (rent debit) and active fixed deposits (security
|
||||||
|
* deposit, with whether each is already under a locker lien).
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerAllotmentClientData {
|
||||||
|
|
||||||
|
private final Long clientId;
|
||||||
|
private final String displayName;
|
||||||
|
private final String gender;
|
||||||
|
private final String occupation;
|
||||||
|
private final String mobileNo;
|
||||||
|
private final String alternateMobileNo;
|
||||||
|
private final String email;
|
||||||
|
private final String addressLine1;
|
||||||
|
private final String addressLine2;
|
||||||
|
private final String addressLine3;
|
||||||
|
private final String city;
|
||||||
|
private final String pincode;
|
||||||
|
private final String panNumber;
|
||||||
|
/** Only the last four digits are ever sent. */
|
||||||
|
private final String aadhaarMasked;
|
||||||
|
private final boolean member;
|
||||||
|
private final String membershipNo;
|
||||||
|
private final Long totalShares;
|
||||||
|
private final String riskRating;
|
||||||
|
private final List<SavingsOption> savingsAccounts;
|
||||||
|
private final List<FixedDepositOption> fixedDeposits;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class SavingsOption {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String accountNo;
|
||||||
|
private final String productName;
|
||||||
|
private final BigDecimal balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class FixedDepositOption {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final String accountNo;
|
||||||
|
private final String productName;
|
||||||
|
private final BigDecimal depositAmount;
|
||||||
|
private final LocalDate depositDate;
|
||||||
|
private final LocalDate maturityDate;
|
||||||
|
/** Already given as security for another locker application. */
|
||||||
|
private final boolean onHold;
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerClassData {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long officeId;
|
||||||
|
private final String officeName;
|
||||||
|
private final String classCode;
|
||||||
|
private final String className;
|
||||||
|
private final Integer area;
|
||||||
|
private final BigDecimal rent;
|
||||||
|
private final BigDecimal memberRent;
|
||||||
|
private final BigDecimal deposit;
|
||||||
|
private final BigDecimal memberDeposit;
|
||||||
|
private final Integer startLockerNo;
|
||||||
|
private final Integer endLockerNo;
|
||||||
|
private final Integer totalLockers;
|
||||||
|
/** Counted from allotted lockers at read time — never stored. */
|
||||||
|
private final Integer occupied;
|
||||||
|
private final LocalDate effectiveFromDate;
|
||||||
|
private final LocalDate effectiveToDate;
|
||||||
|
private final boolean active;
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/** A client's name, contact and first active address — what the waiting-list form fills in when a client is picked. */
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerClientContactData {
|
||||||
|
|
||||||
|
private final Long clientId;
|
||||||
|
private final String displayName;
|
||||||
|
private final String mobileNo;
|
||||||
|
private final String email;
|
||||||
|
private final String addressLine1;
|
||||||
|
private final String addressLine2;
|
||||||
|
private final String addressLine3;
|
||||||
|
private final String city;
|
||||||
|
private final String pincode;
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerData {
|
||||||
|
|
||||||
|
private final Long id;
|
||||||
|
private final Long officeId;
|
||||||
|
private final String officeName;
|
||||||
|
private final Long lockerClassId;
|
||||||
|
private final String classCode;
|
||||||
|
private final String className;
|
||||||
|
private final Integer lockerNo;
|
||||||
|
private final String keyNo;
|
||||||
|
private final String status;
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/** Locker counts by status for the branches the caller may see. */
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerSummaryData {
|
||||||
|
|
||||||
|
private final long total;
|
||||||
|
private final long vacant;
|
||||||
|
private final long allotted;
|
||||||
|
private final long blocked;
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LockerWaitingListData {
|
||||||
|
|
||||||
|
/** Also the waiting number given to the applicant. */
|
||||||
|
private final Long id;
|
||||||
|
private final Long officeId;
|
||||||
|
private final String officeName;
|
||||||
|
/** Null when the inquirer is not a client. */
|
||||||
|
private final Long clientId;
|
||||||
|
private final String clientName;
|
||||||
|
private final LocalDate inquiryDate;
|
||||||
|
private final String customerName;
|
||||||
|
private final String addressLine1;
|
||||||
|
private final String addressLine2;
|
||||||
|
private final String addressLine3;
|
||||||
|
private final String city;
|
||||||
|
private final String pincode;
|
||||||
|
private final String phone;
|
||||||
|
private final String email;
|
||||||
|
private final List<String> preferredClasses;
|
||||||
|
private final String remarks;
|
||||||
|
private final String status;
|
||||||
|
private final String additionalRemarks;
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
|
||||||
|
/** One physical locker box, created from its class row's range. Unique per branch + class code + number. */
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_locker")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
public class Locker extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "office_id", nullable = false)
|
||||||
|
private Long officeId;
|
||||||
|
|
||||||
|
@Column(name = "locker_class_id", nullable = false)
|
||||||
|
private Long lockerClassId;
|
||||||
|
|
||||||
|
@Column(name = "class_code", nullable = false, length = 10)
|
||||||
|
private String classCode;
|
||||||
|
|
||||||
|
@Column(name = "locker_no", nullable = false)
|
||||||
|
private Integer lockerNo;
|
||||||
|
|
||||||
|
@Column(name = "key_no", length = 20)
|
||||||
|
private String keyNo;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
public static Locker createVacant(final LockerClass lockerClass, final Integer lockerNo) {
|
||||||
|
final Locker locker = new Locker();
|
||||||
|
locker.officeId = lockerClass.getOfficeId();
|
||||||
|
locker.lockerClassId = lockerClass.getId();
|
||||||
|
locker.classCode = lockerClass.getClassCode();
|
||||||
|
locker.lockerNo = lockerNo;
|
||||||
|
locker.status = LockerStatus.VACANT.name();
|
||||||
|
return locker;
|
||||||
|
}
|
||||||
|
}
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One locker-number range of a class at a branch — a class such as A1 can have several rows — with member and
|
||||||
|
* non-member rent and deposit and an effective period. Class code, branch and range never change once saved.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_locker_class")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
public class LockerClass extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "office_id", nullable = false)
|
||||||
|
private Long officeId;
|
||||||
|
|
||||||
|
@Column(name = "class_code", nullable = false, length = 10)
|
||||||
|
private String classCode;
|
||||||
|
|
||||||
|
@Column(name = "class_name", nullable = false, length = 10)
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
@Column(name = "area", nullable = false)
|
||||||
|
private Integer area;
|
||||||
|
|
||||||
|
@Column(name = "rent", nullable = false, scale = 6, precision = 19)
|
||||||
|
private BigDecimal rent;
|
||||||
|
|
||||||
|
@Column(name = "member_rent", nullable = false, scale = 6, precision = 19)
|
||||||
|
private BigDecimal memberRent;
|
||||||
|
|
||||||
|
@Column(name = "deposit", nullable = false, scale = 6, precision = 19)
|
||||||
|
private BigDecimal deposit;
|
||||||
|
|
||||||
|
@Column(name = "member_deposit", nullable = false, scale = 6, precision = 19)
|
||||||
|
private BigDecimal memberDeposit;
|
||||||
|
|
||||||
|
@Column(name = "start_locker_no", nullable = false)
|
||||||
|
private Integer startLockerNo;
|
||||||
|
|
||||||
|
@Column(name = "end_locker_no", nullable = false)
|
||||||
|
private Integer endLockerNo;
|
||||||
|
|
||||||
|
@Column(name = "effective_from_date", nullable = false)
|
||||||
|
private LocalDate effectiveFromDate;
|
||||||
|
|
||||||
|
@Column(name = "effective_to_date")
|
||||||
|
private LocalDate effectiveToDate;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
@Column(name = "created_by")
|
||||||
|
private Long createdBy;
|
||||||
|
|
||||||
|
@Column(name = "created_on")
|
||||||
|
private OffsetDateTime createdOn;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_by")
|
||||||
|
private Long lastModifiedBy;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_on")
|
||||||
|
private OffsetDateTime lastModifiedOn;
|
||||||
|
|
||||||
|
public static LockerClass createNew(final Long officeId, final String classCode, final LockerClassName className, final Integer area,
|
||||||
|
final BigDecimal rent, final BigDecimal memberRent, final BigDecimal deposit, final BigDecimal memberDeposit,
|
||||||
|
final Integer startLockerNo, final Integer endLockerNo, final LocalDate effectiveFromDate, final LocalDate effectiveToDate,
|
||||||
|
final Long createdBy) {
|
||||||
|
final LockerClass lockerClass = new LockerClass();
|
||||||
|
lockerClass.officeId = officeId;
|
||||||
|
lockerClass.classCode = classCode;
|
||||||
|
lockerClass.className = className.name();
|
||||||
|
lockerClass.area = area;
|
||||||
|
lockerClass.rent = rent;
|
||||||
|
lockerClass.memberRent = memberRent;
|
||||||
|
lockerClass.deposit = deposit;
|
||||||
|
lockerClass.memberDeposit = memberDeposit;
|
||||||
|
lockerClass.startLockerNo = startLockerNo;
|
||||||
|
lockerClass.endLockerNo = endLockerNo;
|
||||||
|
lockerClass.effectiveFromDate = effectiveFromDate;
|
||||||
|
lockerClass.effectiveToDate = effectiveToDate;
|
||||||
|
lockerClass.active = true;
|
||||||
|
lockerClass.createdBy = createdBy;
|
||||||
|
lockerClass.createdOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
|
return lockerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Null arguments leave the field unchanged, except effective-to, which the caller sets only when sent. */
|
||||||
|
public void updateDetails(final LockerClassName className, final Integer area, final BigDecimal rent, final BigDecimal memberRent,
|
||||||
|
final BigDecimal deposit, final BigDecimal memberDeposit, final LocalDate effectiveFromDate, final Long modifiedBy) {
|
||||||
|
if (className != null) {
|
||||||
|
this.className = className.name();
|
||||||
|
}
|
||||||
|
if (area != null) {
|
||||||
|
this.area = area;
|
||||||
|
}
|
||||||
|
if (rent != null) {
|
||||||
|
this.rent = rent;
|
||||||
|
}
|
||||||
|
if (memberRent != null) {
|
||||||
|
this.memberRent = memberRent;
|
||||||
|
}
|
||||||
|
if (deposit != null) {
|
||||||
|
this.deposit = deposit;
|
||||||
|
}
|
||||||
|
if (memberDeposit != null) {
|
||||||
|
this.memberDeposit = memberDeposit;
|
||||||
|
}
|
||||||
|
if (effectiveFromDate != null) {
|
||||||
|
this.effectiveFromDate = effectiveFromDate;
|
||||||
|
}
|
||||||
|
this.lastModifiedBy = modifiedBy;
|
||||||
|
this.lastModifiedOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEffectiveToDate(final LocalDate effectiveToDate) {
|
||||||
|
this.effectiveToDate = effectiveToDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivate(final Long modifiedBy) {
|
||||||
|
this.active = false;
|
||||||
|
this.lastModifiedBy = modifiedBy;
|
||||||
|
this.lastModifiedOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
/** Size bucket of a locker class, as named on the legacy class master. */
|
||||||
|
public enum LockerClassName {
|
||||||
|
SMALL, MEDIUM, LARGE
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
public interface LockerClassRepository extends JpaRepository<LockerClass, Long> {
|
||||||
|
|
||||||
|
/** Active rows of the same branch and class whose range shares at least one number with [start, end]. */
|
||||||
|
@Query("select c from LockerClass c where c.active = true and c.officeId = :officeId and upper(c.classCode) = upper(:classCode)"
|
||||||
|
+ " and c.startLockerNo <= :endNo and c.endLockerNo >= :startNo")
|
||||||
|
List<LockerClass> findActiveOverlapping(@Param("officeId") Long officeId, @Param("classCode") String classCode,
|
||||||
|
@Param("startNo") Integer startNo, @Param("endNo") Integer endNo);
|
||||||
|
|
||||||
|
/** Class codes a waiting-list inquiry at this branch may ask for. */
|
||||||
|
@Query("select distinct upper(c.classCode) from LockerClass c where c.active = true and c.officeId = :officeId")
|
||||||
|
List<String> findActiveClassCodes(@Param("officeId") Long officeId);
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.portfolio.locker.exception.LockerClassNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerClassRepositoryWrapper {
|
||||||
|
|
||||||
|
private final LockerClassRepository repository;
|
||||||
|
|
||||||
|
public LockerClass findOneWithNotFoundDetection(final Long id) {
|
||||||
|
return this.repository.findById(id).orElseThrow(() -> new LockerClassNotFoundException(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockerClass saveAndFlush(final LockerClass lockerClass) {
|
||||||
|
return this.repository.saveAndFlush(lockerClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
public interface LockerRepository extends JpaRepository<Locker, Long> {
|
||||||
|
|
||||||
|
/** Any locker (of any class row, active or not) already holding a number in [start, end] for this branch and class. */
|
||||||
|
@Query("select count(l) from Locker l where l.officeId = :officeId and upper(l.classCode) = upper(:classCode)"
|
||||||
|
+ " and l.lockerNo between :startNo and :endNo")
|
||||||
|
long countExistingInRange(@Param("officeId") Long officeId, @Param("classCode") String classCode, @Param("startNo") Integer startNo,
|
||||||
|
@Param("endNo") Integer endNo);
|
||||||
|
|
||||||
|
long countByLockerClassIdAndStatus(Long lockerClassId, String status);
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Query("delete from Locker l where l.lockerClassId = :lockerClassId and l.status <> 'ALLOTTED'")
|
||||||
|
int deleteUnallottedByLockerClassId(@Param("lockerClassId") Long lockerClassId);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
/** VACANT can be allotted, ALLOTTED is with a customer, BLOCKED is out of use (repair, freeze of the box). */
|
||||||
|
public enum LockerStatus {
|
||||||
|
VACANT, ALLOTTED, BLOCKED
|
||||||
|
}
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A locker inquiry in the branch queue. The inquirer need not be a client yet; the id is the waiting number handed
|
||||||
|
* to them. Editable only while PENDING.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "m_locker_waiting_list")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
public class LockerWaitingList extends AbstractPersistableCustom<Long> {
|
||||||
|
|
||||||
|
@Column(name = "office_id", nullable = false)
|
||||||
|
private Long officeId;
|
||||||
|
|
||||||
|
/** Set when the inquirer is already a client; null for a walk-in who is not. */
|
||||||
|
@Column(name = "client_id")
|
||||||
|
private Long clientId;
|
||||||
|
|
||||||
|
@Column(name = "inquiry_date", nullable = false)
|
||||||
|
private LocalDate inquiryDate;
|
||||||
|
|
||||||
|
@Column(name = "customer_name", nullable = false, length = 100)
|
||||||
|
private String customerName;
|
||||||
|
|
||||||
|
@Column(name = "address_line1", nullable = false, length = 150)
|
||||||
|
private String addressLine1;
|
||||||
|
|
||||||
|
@Column(name = "address_line2", length = 150)
|
||||||
|
private String addressLine2;
|
||||||
|
|
||||||
|
@Column(name = "address_line3", length = 150)
|
||||||
|
private String addressLine3;
|
||||||
|
|
||||||
|
@Column(name = "city", nullable = false, length = 60)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@Column(name = "pincode", nullable = false, length = 6)
|
||||||
|
private String pincode;
|
||||||
|
|
||||||
|
@Column(name = "phone", length = 15)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Column(name = "email", length = 100)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/** Comma-separated class codes, e.g. "A1,B1". */
|
||||||
|
@Column(name = "preferred_classes", nullable = false, length = 200)
|
||||||
|
private String preferredClasses;
|
||||||
|
|
||||||
|
@Column(name = "remarks", length = 500)
|
||||||
|
private String remarks;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false, length = 20)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "additional_remarks", length = 500)
|
||||||
|
private String additionalRemarks;
|
||||||
|
|
||||||
|
@Column(name = "created_by")
|
||||||
|
private Long createdBy;
|
||||||
|
|
||||||
|
@Column(name = "created_on")
|
||||||
|
private OffsetDateTime createdOn;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_by")
|
||||||
|
private Long lastModifiedBy;
|
||||||
|
|
||||||
|
@Column(name = "lastmodified_on")
|
||||||
|
private OffsetDateTime lastModifiedOn;
|
||||||
|
|
||||||
|
public static LockerWaitingList createNew(final Long officeId, final Long clientId, final LocalDate inquiryDate,
|
||||||
|
final String customerName, final String addressLine1, final String addressLine2, final String addressLine3, final String city,
|
||||||
|
final String pincode, final String phone, final String email, final String preferredClasses, final String remarks,
|
||||||
|
final Long createdBy) {
|
||||||
|
final LockerWaitingList entry = new LockerWaitingList();
|
||||||
|
entry.officeId = officeId;
|
||||||
|
entry.clientId = clientId;
|
||||||
|
entry.status = LockerWaitingListStatus.PENDING.name();
|
||||||
|
entry.createdBy = createdBy;
|
||||||
|
entry.createdOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
|
entry.applyDetails(inquiryDate, customerName, addressLine1, addressLine2, addressLine3, city, pincode, phone, email,
|
||||||
|
preferredClasses, remarks);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Replaces every inquiry detail — the counter form always sends the whole entry. */
|
||||||
|
public void updateDetails(final Long clientId, final LocalDate inquiryDate, final String customerName, final String addressLine1,
|
||||||
|
final String addressLine2, final String addressLine3, final String city, final String pincode, final String phone,
|
||||||
|
final String email, final String preferredClasses, final String remarks, final Long modifiedBy) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
applyDetails(inquiryDate, customerName, addressLine1, addressLine2, addressLine3, city, pincode, phone, email, preferredClasses,
|
||||||
|
remarks);
|
||||||
|
touch(modifiedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel(final String additionalRemarks, final Long modifiedBy) {
|
||||||
|
this.status = LockerWaitingListStatus.CANCELLED.name();
|
||||||
|
if (additionalRemarks != null) {
|
||||||
|
this.additionalRemarks = additionalRemarks;
|
||||||
|
}
|
||||||
|
touch(modifiedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPending() {
|
||||||
|
return LockerWaitingListStatus.PENDING.name().equals(this.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDetails(final LocalDate inquiryDate, final String customerName, final String addressLine1, final String addressLine2,
|
||||||
|
final String addressLine3, final String city, final String pincode, final String phone, final String email,
|
||||||
|
final String preferredClasses, final String remarks) {
|
||||||
|
this.inquiryDate = inquiryDate;
|
||||||
|
this.customerName = customerName;
|
||||||
|
this.addressLine1 = addressLine1;
|
||||||
|
this.addressLine2 = addressLine2;
|
||||||
|
this.addressLine3 = addressLine3;
|
||||||
|
this.city = city;
|
||||||
|
this.pincode = pincode;
|
||||||
|
this.phone = phone;
|
||||||
|
this.email = email;
|
||||||
|
this.preferredClasses = preferredClasses;
|
||||||
|
this.remarks = remarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void touch(final Long modifiedBy) {
|
||||||
|
this.lastModifiedBy = modifiedBy;
|
||||||
|
this.lastModifiedOn = DateUtils.getOffsetDateTimeOfTenant();
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface LockerWaitingListRepository extends JpaRepository<LockerWaitingList, Long> {}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.portfolio.locker.exception.LockerWaitingListNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerWaitingListRepositoryWrapper {
|
||||||
|
|
||||||
|
private final LockerWaitingListRepository repository;
|
||||||
|
|
||||||
|
public LockerWaitingList findOneWithNotFoundDetection(final Long id) {
|
||||||
|
return this.repository.findById(id).orElseThrow(() -> new LockerWaitingListNotFoundException(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockerWaitingList saveAndFlush(final LockerWaitingList entry) {
|
||||||
|
return this.repository.saveAndFlush(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.domain;
|
||||||
|
|
||||||
|
/** PENDING is in the queue; ALLOTTED is set by allotment; CANCELLED is withdrawn at the counter. */
|
||||||
|
public enum LockerWaitingListStatus {
|
||||||
|
PENDING, ALLOTTED, CANCELLED
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
|
||||||
|
public class LockerClassNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public LockerClassNotFoundException(final Long id) {
|
||||||
|
super("error.msg.lockerclass.id.invalid", "Locker class with identifier " + id + " does not exist", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.exception;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||||
|
|
||||||
|
public class LockerWaitingListNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||||
|
|
||||||
|
public LockerWaitingListNotFoundException(final Long id) {
|
||||||
|
super("error.msg.lockerwaitinglist.id.invalid", "Locker waiting list entry " + id + " does not exist", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERWAITINGLIST", action = "CANCEL")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CancelLockerWaitingListCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.cancelWaitingListEntry(command.entityId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERCLASS", action = "CREATE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CreateLockerClassCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.createClass(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERWAITINGLIST", action = "CREATE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CreateLockerWaitingListCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.createWaitingListEntry(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERCLASS", action = "DELETE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DeleteLockerClassCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.deactivateClass(command.entityId());
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERCLASS", action = "UPDATE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateLockerClassCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.updateClass(command.entityId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.portfolio.locker.service.LockerWritePlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@CommandType(entity = "LOCKERWAITINGLIST", action = "UPDATE")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UpdateLockerWaitingListCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
|
private final LockerWritePlatformService writePlatformService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||||
|
return this.writePlatformService.updateWaitingListEntry(command.entityId(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.serialization;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
|
import org.apache.fineract.portfolio.locker.api.LockerApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.locker.api.LockerApiJsonInputParams;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerClassName;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public final class LockerCommandFromApiJsonDeserializer {
|
||||||
|
|
||||||
|
private static final String CLASS_CODE_PATTERN = "^[A-Za-z0-9]+$";
|
||||||
|
private static final String PINCODE_PATTERN = "^[0-9]{6}$";
|
||||||
|
private static final String PHONE_PATTERN = "^[0-9]{10,15}$";
|
||||||
|
private static final String EMAIL_PATTERN = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
|
||||||
|
|
||||||
|
private final FromJsonHelper fromApiJsonHelper;
|
||||||
|
|
||||||
|
public void validateForCreateClass(final String json) {
|
||||||
|
final JsonElement element = parse(json, LockerApiJsonInputParams.classCreateParams());
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_CLASS);
|
||||||
|
|
||||||
|
final Long officeId = this.fromApiJsonHelper.extractLongNamed(LockerApiJsonInputParams.OFFICE_ID.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.OFFICE_ID.getValue()).value(officeId).ignoreIfNull().longGreaterThanZero();
|
||||||
|
|
||||||
|
final String classCode = this.fromApiJsonHelper.extractStringNamed(LockerApiJsonInputParams.CLASS_CODE.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.CLASS_CODE.getValue()).value(classCode).notBlank().notExceedingLengthOf(10)
|
||||||
|
.matchesRegularExpression(CLASS_CODE_PATTERN, "Class may contain only letters and digits");
|
||||||
|
|
||||||
|
validateClassName(element, v, true);
|
||||||
|
validateArea(element, v, true);
|
||||||
|
validateAmounts(element, v, true);
|
||||||
|
|
||||||
|
final Integer startLockerNo = this.fromApiJsonHelper
|
||||||
|
.extractIntegerSansLocaleNamed(LockerApiJsonInputParams.START_LOCKER_NO.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.START_LOCKER_NO.getValue()).value(startLockerNo).notNull().integerGreaterThanZero();
|
||||||
|
final Integer endLockerNo = this.fromApiJsonHelper
|
||||||
|
.extractIntegerSansLocaleNamed(LockerApiJsonInputParams.END_LOCKER_NO.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.END_LOCKER_NO.getValue()).value(endLockerNo).notNull().integerGreaterThanZero();
|
||||||
|
if (startLockerNo != null && endLockerNo != null) {
|
||||||
|
if (endLockerNo < startLockerNo) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.END_LOCKER_NO.getValue()).value(endLockerNo)
|
||||||
|
.failWithCodeAndMessage("less.than.start", "End Locker No cannot be less than Start Locker No");
|
||||||
|
} else if (endLockerNo - startLockerNo + 1 > LockerApiConstants.MAX_LOCKERS_PER_RANGE) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.END_LOCKER_NO.getValue()).value(endLockerNo).failWithCodeAndMessage("range.too.large",
|
||||||
|
"One range can have at most " + LockerApiConstants.MAX_LOCKERS_PER_RANGE + " lockers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final LocalDate from = this.fromApiJsonHelper.extractLocalDateNamed(LockerApiJsonInputParams.EFFECTIVE_FROM_DATE.getValue(),
|
||||||
|
element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.EFFECTIVE_FROM_DATE.getValue()).value(from).notNull();
|
||||||
|
final LocalDate to = this.fromApiJsonHelper.extractLocalDateNamed(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue(), element);
|
||||||
|
validateEffectivePeriod(from, to, v);
|
||||||
|
|
||||||
|
throwIfErrors(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForUpdateClass(final String json) {
|
||||||
|
final JsonElement element = parse(json, LockerApiJsonInputParams.classUpdateParams());
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_CLASS);
|
||||||
|
|
||||||
|
validateClassName(element, v, false);
|
||||||
|
validateArea(element, v, false);
|
||||||
|
validateAmounts(element, v, false);
|
||||||
|
|
||||||
|
final LocalDate from = this.fromApiJsonHelper.extractLocalDateNamed(LockerApiJsonInputParams.EFFECTIVE_FROM_DATE.getValue(),
|
||||||
|
element);
|
||||||
|
final LocalDate to = this.fromApiJsonHelper.extractLocalDateNamed(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue(), element);
|
||||||
|
validateEffectivePeriod(from, to, v);
|
||||||
|
|
||||||
|
throwIfErrors(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForCreateWaitingList(final String json) {
|
||||||
|
final JsonElement element = parse(json, LockerApiJsonInputParams.waitingListCreateParams());
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_WAITING_LIST);
|
||||||
|
final Long officeId = this.fromApiJsonHelper.extractLongNamed(LockerApiJsonInputParams.OFFICE_ID.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.OFFICE_ID.getValue()).value(officeId).ignoreIfNull().longGreaterThanZero();
|
||||||
|
validateWaitingListDetails(element, v);
|
||||||
|
throwIfErrors(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForUpdateWaitingList(final String json) {
|
||||||
|
final JsonElement element = parse(json, LockerApiJsonInputParams.waitingListUpdateParams());
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_WAITING_LIST);
|
||||||
|
validateWaitingListDetails(element, v);
|
||||||
|
throwIfErrors(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateForCancelWaitingList(final String json) {
|
||||||
|
final JsonElement element = parse(json, LockerApiJsonInputParams.waitingListCancelParams());
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
final DataValidatorBuilder v = new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_WAITING_LIST);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.ADDITIONAL_REMARKS, false, 500);
|
||||||
|
throwIfErrors(errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Same shape on create and update — the counter form always sends the whole inquiry. */
|
||||||
|
private void validateWaitingListDetails(final JsonElement element, final DataValidatorBuilder v) {
|
||||||
|
final Long clientId = this.fromApiJsonHelper.extractLongNamed(LockerApiJsonInputParams.CLIENT_ID.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.CLIENT_ID.getValue()).value(clientId).ignoreIfNull().longGreaterThanZero();
|
||||||
|
|
||||||
|
final LocalDate inquiryDate = this.fromApiJsonHelper.extractLocalDateNamed(LockerApiJsonInputParams.INQUIRY_DATE.getValue(),
|
||||||
|
element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.INQUIRY_DATE.getValue()).value(inquiryDate).notNull();
|
||||||
|
if (inquiryDate != null && DateUtils.isAfterBusinessDate(inquiryDate)) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.INQUIRY_DATE.getValue()).value(inquiryDate).failWithCodeAndMessage("in.future",
|
||||||
|
"Inquiry Date cannot be in the future");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.CUSTOMER_NAME, true, 100);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.ADDRESS_LINE1, true, 150);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.ADDRESS_LINE2, false, 150);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.ADDRESS_LINE3, false, 150);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.CITY, true, 60);
|
||||||
|
validateText(element, v, LockerApiJsonInputParams.REMARKS, false, 500);
|
||||||
|
|
||||||
|
final String pincode = this.fromApiJsonHelper.extractStringNamed(LockerApiJsonInputParams.PINCODE.getValue(), element);
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.PINCODE.getValue()).value(pincode).notBlank()
|
||||||
|
.matchesRegularExpression(PINCODE_PATTERN, "Pincode must be 6 digits");
|
||||||
|
|
||||||
|
final String phone = this.fromApiJsonHelper.extractStringNamed(LockerApiJsonInputParams.PHONE.getValue(), element);
|
||||||
|
if (StringUtils.isNotBlank(phone)) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.PHONE.getValue()).value(phone.trim()).matchesRegularExpression(PHONE_PATTERN,
|
||||||
|
"Phone must be 10 to 15 digits");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String email = this.fromApiJsonHelper.extractStringNamed(LockerApiJsonInputParams.EMAIL.getValue(), element);
|
||||||
|
if (StringUtils.isNotBlank(email)) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.EMAIL.getValue()).value(email.trim()).notExceedingLengthOf(100)
|
||||||
|
.matchesRegularExpression(EMAIL_PATTERN, "Email is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> classes = extractPreferredClasses(element);
|
||||||
|
if (classes.isEmpty()) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.PREFERRED_CLASSES.getValue()).value(null).failWithCodeAndMessage("empty",
|
||||||
|
"Choose at least one locker class");
|
||||||
|
} else if (String.join(",", classes).length() > 200) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.PREFERRED_CLASSES.getValue()).value(classes).failWithCodeAndMessage("too.many",
|
||||||
|
"Too many locker classes chosen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Upper-cased, de-duplicated class codes from the preferredClasses array; blanks dropped. */
|
||||||
|
public List<String> extractPreferredClasses(final JsonElement element) {
|
||||||
|
final JsonArray array = this.fromApiJsonHelper.extractJsonArrayNamed(LockerApiJsonInputParams.PREFERRED_CLASSES.getValue(),
|
||||||
|
element);
|
||||||
|
final List<String> classes = new ArrayList<>();
|
||||||
|
if (array != null) {
|
||||||
|
array.forEach(item -> {
|
||||||
|
final String code = item.isJsonNull() ? null : item.getAsString().trim().toUpperCase();
|
||||||
|
if (StringUtils.isNotBlank(code) && !classes.contains(code)) {
|
||||||
|
classes.add(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateText(final JsonElement element, final DataValidatorBuilder v, final LockerApiJsonInputParams param,
|
||||||
|
final boolean required, final int maxLength) {
|
||||||
|
final String value = this.fromApiJsonHelper.extractStringNamed(param.getValue(), element);
|
||||||
|
final DataValidatorBuilder b = v.reset().parameter(param.getValue()).value(value);
|
||||||
|
(required ? b.notBlank() : b.ignoreIfNull()).notExceedingLengthOf(maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Effective-to may be blank (open-ended) but never before effective-from. */
|
||||||
|
public void validateEffectivePeriod(final LocalDate from, final LocalDate to, final DataValidatorBuilder v) {
|
||||||
|
if (from != null && to != null && to.isBefore(from)) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue()).value(to).failWithCodeAndMessage("before.effective.from",
|
||||||
|
"Effective To cannot be before Effective From");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateClassName(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
||||||
|
final String className = this.fromApiJsonHelper.extractStringNamed(LockerApiJsonInputParams.CLASS_NAME.getValue(), element);
|
||||||
|
final DataValidatorBuilder b = v.reset().parameter(LockerApiJsonInputParams.CLASS_NAME.getValue()).value(className);
|
||||||
|
if (required) {
|
||||||
|
b.notBlank();
|
||||||
|
} else {
|
||||||
|
b.ignoreIfNull();
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(className)) {
|
||||||
|
v.reset().parameter(LockerApiJsonInputParams.CLASS_NAME.getValue()).value(className.toUpperCase())
|
||||||
|
.isOneOfTheseStringValues(Arrays.stream(LockerClassName.values()).map(Enum::name).toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateArea(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
||||||
|
final Integer area = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(LockerApiJsonInputParams.AREA.getValue(), element);
|
||||||
|
final DataValidatorBuilder b = v.reset().parameter(LockerApiJsonInputParams.AREA.getValue()).value(area);
|
||||||
|
(required ? b.notNull() : b.ignoreIfNull()).integerGreaterThanZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rent and deposit are mandatory on create; the member rates are optional and fall back to them. */
|
||||||
|
private void validateAmounts(final JsonElement element, final DataValidatorBuilder v, final boolean required) {
|
||||||
|
validateAmount(element, v, LockerApiJsonInputParams.RENT, required);
|
||||||
|
validateAmount(element, v, LockerApiJsonInputParams.DEPOSIT, required);
|
||||||
|
validateAmount(element, v, LockerApiJsonInputParams.MEMBER_RENT, false);
|
||||||
|
validateAmount(element, v, LockerApiJsonInputParams.MEMBER_DEPOSIT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAmount(final JsonElement element, final DataValidatorBuilder v, final LockerApiJsonInputParams param,
|
||||||
|
final boolean required) {
|
||||||
|
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(param.getValue(), element);
|
||||||
|
final DataValidatorBuilder b = v.reset().parameter(param.getValue()).value(amount);
|
||||||
|
(required ? b.notNull() : b.ignoreIfNull()).zeroOrPositiveAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonElement parse(final String json, final Set<String> supportedParams) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
throw new InvalidJsonException();
|
||||||
|
}
|
||||||
|
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, supportedParams);
|
||||||
|
return this.fromApiJsonHelper.parse(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwIfErrors(final List<ApiParameterError> errors) {
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.service;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.security.exception.NoAuthorizationException;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
|
||||||
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branch rule for lockers: the Super user (ALL_FUNCTIONS) works across every branch; everyone else — a Branch Manager
|
||||||
|
* creating or editing, or any role reading — is confined to their own branch.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerOfficeAccess {
|
||||||
|
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final OfficeRepositoryWrapper officeRepositoryWrapper;
|
||||||
|
|
||||||
|
public AppUser currentUser() {
|
||||||
|
return this.context.authenticatedUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuperUser(final AppUser user) {
|
||||||
|
return !user.hasNotPermissionForAnyOf("ALL_FUNCTIONS");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branch a list is confined to: the requested one (or none = all) for a Super user, otherwise the user's own. */
|
||||||
|
public Long officeForRead(final Long requestedOfficeId) {
|
||||||
|
final AppUser user = currentUser();
|
||||||
|
return isSuperUser(user) ? requestedOfficeId : user.getOffice().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branch a new class belongs to: a Super user may name any branch; anyone else gets their own and may not name another. */
|
||||||
|
public Long officeForCreate(final Long requestedOfficeId) {
|
||||||
|
final AppUser user = currentUser();
|
||||||
|
final Long ownOfficeId = user.getOffice().getId();
|
||||||
|
if (requestedOfficeId == null) {
|
||||||
|
return ownOfficeId;
|
||||||
|
}
|
||||||
|
if (isSuperUser(user)) {
|
||||||
|
return this.officeRepositoryWrapper.findOneWithNotFoundDetection(requestedOfficeId).getId();
|
||||||
|
}
|
||||||
|
if (!ownOfficeId.equals(requestedOfficeId)) {
|
||||||
|
throw new NoAuthorizationException("You can only manage lockers of your own branch.");
|
||||||
|
}
|
||||||
|
return ownOfficeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Refuses a read or change of another branch's record for anyone but the Super user. */
|
||||||
|
public void assertOwnBranch(final Long recordOfficeId) {
|
||||||
|
final AppUser user = currentUser();
|
||||||
|
if (!isSuperUser(user) && !user.getOffice().getId().equals(recordOfficeId)) {
|
||||||
|
throw new NoAuthorizationException("You can only manage lockers of your own branch.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerAllotmentClientData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClassData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClientContactData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerSummaryData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerWaitingListData;
|
||||||
|
|
||||||
|
public interface LockerReadPlatformService {
|
||||||
|
|
||||||
|
Page<LockerClassData> retrieveAllClasses(String status, Long officeId, Integer offset, Integer limit);
|
||||||
|
|
||||||
|
LockerClassData retrieveClass(Long id);
|
||||||
|
|
||||||
|
Page<LockerData> retrieveLockers(Long officeId, String classCode, String status, Integer lockerNo, Integer offset, Integer limit);
|
||||||
|
|
||||||
|
LockerSummaryData retrieveLockerSummary(Long officeId);
|
||||||
|
|
||||||
|
Page<LockerWaitingListData> retrieveWaitingList(String status, Long officeId, Integer offset, Integer limit);
|
||||||
|
|
||||||
|
LockerWaitingListData retrieveWaitingListEntry(Long id);
|
||||||
|
|
||||||
|
LockerClientContactData retrieveClientContact(Long clientId);
|
||||||
|
|
||||||
|
LockerAllotmentClientData retrieveAllotmentClientDetails(Long clientId);
|
||||||
|
}
|
||||||
+344
@@ -0,0 +1,344 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.service;
|
||||||
|
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.Page;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerAllotmentClientData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClassData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerClientContactData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerSummaryData;
|
||||||
|
import org.apache.fineract.portfolio.locker.data.LockerWaitingListData;
|
||||||
|
import org.apache.fineract.portfolio.locker.exception.LockerClassNotFoundException;
|
||||||
|
import org.apache.fineract.portfolio.locker.exception.LockerWaitingListNotFoundException;
|
||||||
|
import org.springframework.dao.EmptyResultDataAccessException;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerReadPlatformServiceImpl implements LockerReadPlatformService {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
private final LockerOfficeAccess officeAccess;
|
||||||
|
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
|
private final ClassMapper classMapper = new ClassMapper();
|
||||||
|
private final LockerMapper lockerMapper = new LockerMapper();
|
||||||
|
private final WaitingListMapper waitingListMapper = new WaitingListMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<LockerClassData> retrieveAllClasses(final String status, final Long officeId, final Integer offset, final Integer limit) {
|
||||||
|
final StringBuilder where = new StringBuilder(" where 1=1");
|
||||||
|
final List<Object> args = new ArrayList<>();
|
||||||
|
if ("ACTIVE".equalsIgnoreCase(status)) {
|
||||||
|
where.append(" and lc.is_active = true");
|
||||||
|
} else if ("INACTIVE".equalsIgnoreCase(status)) {
|
||||||
|
where.append(" and lc.is_active = false");
|
||||||
|
}
|
||||||
|
final Long scopedOfficeId = this.officeAccess.officeForRead(officeId);
|
||||||
|
if (scopedOfficeId != null) {
|
||||||
|
where.append(" and lc.office_id = ?");
|
||||||
|
args.add(scopedOfficeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String countSql = "select count(*) from m_locker_class lc" + where;
|
||||||
|
final Long totalFilteredRecords = this.jdbcTemplate.queryForObject(countSql, Long.class, args.toArray());
|
||||||
|
|
||||||
|
final StringBuilder pageSql = new StringBuilder("select ").append(this.classMapper.schema()).append(where)
|
||||||
|
.append(" order by o.name, lc.class_code, lc.start_locker_no limit ? offset ?");
|
||||||
|
final List<Object> pageArgs = new ArrayList<>(args);
|
||||||
|
pageArgs.add(limit);
|
||||||
|
pageArgs.add(offset);
|
||||||
|
final List<LockerClassData> classes = this.jdbcTemplate.query(pageSql.toString(), this.classMapper, pageArgs.toArray());
|
||||||
|
return new Page<>(classes, totalFilteredRecords == null ? 0 : totalFilteredRecords.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerClassData retrieveClass(final Long id) {
|
||||||
|
final LockerClassData data;
|
||||||
|
try {
|
||||||
|
final String sql = "select " + this.classMapper.schema() + " where lc.id = ?";
|
||||||
|
data = this.jdbcTemplate.queryForObject(sql, this.classMapper, id);
|
||||||
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
|
throw new LockerClassNotFoundException(id);
|
||||||
|
}
|
||||||
|
this.officeAccess.assertOwnBranch(data.getOfficeId());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<LockerData> retrieveLockers(final Long officeId, final String classCode, final String status, final Integer lockerNo,
|
||||||
|
final Integer offset, final Integer limit) {
|
||||||
|
final StringBuilder where = new StringBuilder(" where 1=1");
|
||||||
|
final List<Object> args = new ArrayList<>();
|
||||||
|
final Long scopedOfficeId = this.officeAccess.officeForRead(officeId);
|
||||||
|
if (scopedOfficeId != null) {
|
||||||
|
where.append(" and l.office_id = ?");
|
||||||
|
args.add(scopedOfficeId);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(classCode)) {
|
||||||
|
where.append(" and upper(l.class_code) = ?");
|
||||||
|
args.add(classCode.trim().toUpperCase());
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(status)) {
|
||||||
|
where.append(" and l.status = ?");
|
||||||
|
args.add(status.trim().toUpperCase());
|
||||||
|
}
|
||||||
|
if (lockerNo != null) {
|
||||||
|
where.append(" and l.locker_no = ?");
|
||||||
|
args.add(lockerNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String countSql = "select count(*) from m_locker l" + where;
|
||||||
|
final Long totalFilteredRecords = this.jdbcTemplate.queryForObject(countSql, Long.class, args.toArray());
|
||||||
|
|
||||||
|
final StringBuilder pageSql = new StringBuilder("select ").append(this.lockerMapper.schema()).append(where)
|
||||||
|
.append(" order by o.name, l.class_code, l.locker_no limit ? offset ?");
|
||||||
|
final List<Object> pageArgs = new ArrayList<>(args);
|
||||||
|
pageArgs.add(limit);
|
||||||
|
pageArgs.add(offset);
|
||||||
|
final List<LockerData> lockers = this.jdbcTemplate.query(pageSql.toString(), this.lockerMapper, pageArgs.toArray());
|
||||||
|
return new Page<>(lockers, totalFilteredRecords == null ? 0 : totalFilteredRecords.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerSummaryData retrieveLockerSummary(final Long officeId) {
|
||||||
|
final Long scopedOfficeId = this.officeAccess.officeForRead(officeId);
|
||||||
|
final String sql = "select count(*) as total, count(*) filter (where status = 'VACANT') as vacant,"
|
||||||
|
+ " count(*) filter (where status = 'ALLOTTED') as allotted, count(*) filter (where status = 'BLOCKED') as blocked"
|
||||||
|
+ " from m_locker" + (scopedOfficeId != null ? " where office_id = ?" : "");
|
||||||
|
final Object[] args = scopedOfficeId != null ? new Object[] { scopedOfficeId } : new Object[0];
|
||||||
|
return this.jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new LockerSummaryData(rs.getLong("total"), rs.getLong("vacant"),
|
||||||
|
rs.getLong("allotted"), rs.getLong("blocked")), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<LockerWaitingListData> retrieveWaitingList(final String status, final Long officeId, final Integer offset,
|
||||||
|
final Integer limit) {
|
||||||
|
final StringBuilder where = new StringBuilder(" where 1=1");
|
||||||
|
final List<Object> args = new ArrayList<>();
|
||||||
|
final Long scopedOfficeId = this.officeAccess.officeForRead(officeId);
|
||||||
|
if (scopedOfficeId != null) {
|
||||||
|
where.append(" and w.office_id = ?");
|
||||||
|
args.add(scopedOfficeId);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(status)) {
|
||||||
|
where.append(" and w.status = ?");
|
||||||
|
args.add(status.trim().toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
final String countSql = "select count(*) from m_locker_waiting_list w" + where;
|
||||||
|
final Long totalFilteredRecords = this.jdbcTemplate.queryForObject(countSql, Long.class, args.toArray());
|
||||||
|
|
||||||
|
// Queue order: whoever asked first is served first.
|
||||||
|
final StringBuilder pageSql = new StringBuilder("select ").append(this.waitingListMapper.schema()).append(where)
|
||||||
|
.append(" order by w.inquiry_date, w.id limit ? offset ?");
|
||||||
|
final List<Object> pageArgs = new ArrayList<>(args);
|
||||||
|
pageArgs.add(limit);
|
||||||
|
pageArgs.add(offset);
|
||||||
|
final List<LockerWaitingListData> entries = this.jdbcTemplate.query(pageSql.toString(), this.waitingListMapper,
|
||||||
|
pageArgs.toArray());
|
||||||
|
return new Page<>(entries, totalFilteredRecords == null ? 0 : totalFilteredRecords.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerWaitingListData retrieveWaitingListEntry(final Long id) {
|
||||||
|
final LockerWaitingListData data;
|
||||||
|
try {
|
||||||
|
final String sql = "select " + this.waitingListMapper.schema() + " where w.id = ?";
|
||||||
|
data = this.jdbcTemplate.queryForObject(sql, this.waitingListMapper, id);
|
||||||
|
} catch (final EmptyResultDataAccessException e) {
|
||||||
|
throw new LockerWaitingListNotFoundException(id);
|
||||||
|
}
|
||||||
|
this.officeAccess.assertOwnBranch(data.getOfficeId());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checked against the user's branch scope first, then name, contact and the first active, non-deleted address. */
|
||||||
|
@Override
|
||||||
|
public LockerClientContactData retrieveClientContact(final Long clientId) {
|
||||||
|
this.clientRepositoryWrapper.getActiveClientInUserScope(clientId);
|
||||||
|
final String sql = "select c.id, c.display_name, c.mobile_no, c.email_address, a.address_line_1, a.address_line_2,"
|
||||||
|
+ " a.address_line_3, coalesce(a.city, a.town_village) as city, a.postal_code from m_client c"
|
||||||
|
+ " left join lateral (select ad.* from m_client_address ca join m_address ad on ad.id = ca.address_id"
|
||||||
|
+ " where ca.client_id = c.id and ca.is_active = true and coalesce(ca.is_deleted, false) = false"
|
||||||
|
+ " and coalesce(ad.is_deleted, false) = false order by ca.id limit 1) a on true where c.id = ?";
|
||||||
|
return this.jdbcTemplate.queryForObject(sql,
|
||||||
|
(rs, rowNum) -> LockerClientContactData.builder().clientId(rs.getLong("id")).displayName(rs.getString("display_name"))
|
||||||
|
.mobileNo(rs.getString("mobile_no")).email(rs.getString("email_address"))
|
||||||
|
.addressLine1(rs.getString("address_line_1")).addressLine2(rs.getString("address_line_2"))
|
||||||
|
.addressLine3(rs.getString("address_line_3")).city(rs.getString("city")).pincode(rs.getString("postal_code"))
|
||||||
|
.build(),
|
||||||
|
clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branch scope and active status first (through the client repository), then each part from its own table: the
|
||||||
|
* client row, the first active address, PAN / Aadhaar identifiers, active share accounts (membership), active
|
||||||
|
* savings accounts and active fixed deposits.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public LockerAllotmentClientData retrieveAllotmentClientDetails(final Long clientId) {
|
||||||
|
final LockerClientContactData contact = retrieveClientContact(clientId);
|
||||||
|
|
||||||
|
final Map<String, Object> client = this.jdbcTemplate.queryForMap(
|
||||||
|
"select c.alternate_mobile_no, g.code_value as gender, oc.code_value as occupation from m_client c"
|
||||||
|
+ " left join m_code_value g on g.id = c.gender_cv_id left join m_code_value oc on oc.id = c.occupation_cv_id"
|
||||||
|
+ " where c.id = ?",
|
||||||
|
clientId);
|
||||||
|
|
||||||
|
String pan = null;
|
||||||
|
String aadhaar = null;
|
||||||
|
for (final Map<String, Object> row : this.jdbcTemplate.queryForList(
|
||||||
|
"select cv.code_value as doc_type, ci.document_key from m_client_identifier ci"
|
||||||
|
+ " join m_code_value cv on cv.id = ci.document_type_id where ci.client_id = ? order by ci.id",
|
||||||
|
clientId)) {
|
||||||
|
final String type = String.valueOf(row.get("doc_type")).toUpperCase();
|
||||||
|
final String key = (String) row.get("document_key");
|
||||||
|
if (pan == null && type.contains("PAN")) {
|
||||||
|
pan = key;
|
||||||
|
} else if (aadhaar == null && (type.contains("AADHAAR") || type.contains("AADHAR"))) {
|
||||||
|
aadhaar = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Map<String, Object>> shares = this.jdbcTemplate.queryForList(
|
||||||
|
"select account_no, coalesce(total_approved_shares, 0) as shares from m_share_account"
|
||||||
|
+ " where client_id = ? and status_enum = 300 order by id",
|
||||||
|
clientId);
|
||||||
|
final long totalShares = shares.stream().mapToLong(s -> ((Number) s.get("shares")).longValue()).sum();
|
||||||
|
|
||||||
|
final List<LockerAllotmentClientData.SavingsOption> savings = this.jdbcTemplate.query(
|
||||||
|
"select sa.id, sa.account_no, sp.name as product_name, sa.account_balance_derived from m_savings_account sa"
|
||||||
|
+ " join m_savings_product sp on sp.id = sa.product_id"
|
||||||
|
+ " where sa.client_id = ? and sa.deposit_type_enum = 100 and sa.status_enum = 300 order by sa.id",
|
||||||
|
(rs, rowNum) -> new LockerAllotmentClientData.SavingsOption(rs.getLong("id"), rs.getString("account_no"),
|
||||||
|
rs.getString("product_name"), rs.getBigDecimal("account_balance_derived")),
|
||||||
|
clientId);
|
||||||
|
|
||||||
|
final List<LockerAllotmentClientData.FixedDepositOption> fixedDeposits = this.jdbcTemplate.query(
|
||||||
|
"select sa.id, sa.account_no, sp.name as product_name, t.deposit_amount, sa.activatedon_date, t.maturity_date,"
|
||||||
|
+ " exists (select 1 from m_locker_allotment_deposit d where d.savings_account_id = sa.id"
|
||||||
|
+ " and d.lien_status in ('PENDING', 'ACTIVE')) as on_hold"
|
||||||
|
+ " from m_savings_account sa join m_savings_product sp on sp.id = sa.product_id"
|
||||||
|
+ " join m_deposit_account_term_and_preclosure t on t.savings_account_id = sa.id"
|
||||||
|
+ " where sa.client_id = ? and sa.deposit_type_enum = 200 and sa.status_enum = 300 order by sa.id",
|
||||||
|
(rs, rowNum) -> {
|
||||||
|
final java.sql.Date opened = rs.getDate("activatedon_date");
|
||||||
|
final java.sql.Date matures = rs.getDate("maturity_date");
|
||||||
|
return new LockerAllotmentClientData.FixedDepositOption(rs.getLong("id"), rs.getString("account_no"),
|
||||||
|
rs.getString("product_name"), rs.getBigDecimal("deposit_amount"), opened == null ? null : opened.toLocalDate(),
|
||||||
|
matures == null ? null : matures.toLocalDate(), rs.getBoolean("on_hold"));
|
||||||
|
},
|
||||||
|
clientId);
|
||||||
|
|
||||||
|
return LockerAllotmentClientData.builder().clientId(contact.getClientId()).displayName(contact.getDisplayName())
|
||||||
|
.gender((String) client.get("gender")).occupation((String) client.get("occupation")).mobileNo(contact.getMobileNo())
|
||||||
|
.alternateMobileNo((String) client.get("alternate_mobile_no")).email(contact.getEmail())
|
||||||
|
.addressLine1(contact.getAddressLine1()).addressLine2(contact.getAddressLine2()).addressLine3(contact.getAddressLine3())
|
||||||
|
.city(contact.getCity()).pincode(contact.getPincode()).panNumber(pan).aadhaarMasked(maskAadhaar(aadhaar))
|
||||||
|
.member(totalShares > 0).membershipNo(shares.isEmpty() ? null : (String) shares.get(0).get("account_no"))
|
||||||
|
.totalShares(totalShares).riskRating(null).savingsAccounts(savings).fixedDeposits(fixedDeposits).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "XXXX XXXX 1234" — the full number never leaves the server. */
|
||||||
|
private static String maskAadhaar(final String aadhaar) {
|
||||||
|
if (StringUtils.isBlank(aadhaar)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String digits = aadhaar.replaceAll("\\D", "");
|
||||||
|
return digits.length() < 4 ? "XXXX" : "XXXX XXXX " + digits.substring(digits.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LockerMapper implements RowMapper<LockerData> {
|
||||||
|
|
||||||
|
String schema() {
|
||||||
|
return " l.id, l.office_id, o.name as office_name, l.locker_class_id, l.class_code, lc.class_name, l.locker_no, l.key_no,"
|
||||||
|
+ " l.status from m_locker l join m_locker_class lc on lc.id = l.locker_class_id join m_office o on o.id = l.office_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||||
|
return LockerData.builder().id(rs.getLong("id")).officeId(rs.getLong("office_id")).officeName(rs.getString("office_name"))
|
||||||
|
.lockerClassId(rs.getLong("locker_class_id")).classCode(rs.getString("class_code"))
|
||||||
|
.className(rs.getString("class_name")).lockerNo(rs.getInt("locker_no")).keyNo(rs.getString("key_no"))
|
||||||
|
.status(rs.getString("status")).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WaitingListMapper implements RowMapper<LockerWaitingListData> {
|
||||||
|
|
||||||
|
String schema() {
|
||||||
|
return " w.id, w.office_id, o.name as office_name, w.client_id, c.display_name as client_name, w.inquiry_date,"
|
||||||
|
+ " w.customer_name, w.address_line1, w.address_line2, w.address_line3, w.city, w.pincode, w.phone, w.email,"
|
||||||
|
+ " w.preferred_classes, w.remarks, w.status, w.additional_remarks from m_locker_waiting_list w"
|
||||||
|
+ " join m_office o on o.id = w.office_id left join m_client c on c.id = w.client_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerWaitingListData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||||
|
final String classes = rs.getString("preferred_classes");
|
||||||
|
return LockerWaitingListData.builder().id(rs.getLong("id")).officeId(rs.getLong("office_id"))
|
||||||
|
.officeName(rs.getString("office_name")).clientId((Long) rs.getObject("client_id", Long.class))
|
||||||
|
.clientName(rs.getString("client_name")).inquiryDate(rs.getDate("inquiry_date").toLocalDate())
|
||||||
|
.customerName(rs.getString("customer_name")).addressLine1(rs.getString("address_line1"))
|
||||||
|
.addressLine2(rs.getString("address_line2")).addressLine3(rs.getString("address_line3")).city(rs.getString("city"))
|
||||||
|
.pincode(rs.getString("pincode")).phone(rs.getString("phone")).email(rs.getString("email"))
|
||||||
|
.preferredClasses(StringUtils.isBlank(classes) ? List.of() : Arrays.asList(classes.split(",")))
|
||||||
|
.remarks(rs.getString("remarks")).status(rs.getString("status")).additionalRemarks(rs.getString("additional_remarks"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ClassMapper implements RowMapper<LockerClassData> {
|
||||||
|
|
||||||
|
String schema() {
|
||||||
|
return " lc.id, lc.office_id, o.name as office_name, lc.class_code, lc.class_name, lc.area, lc.rent, lc.member_rent,"
|
||||||
|
+ " lc.deposit, lc.member_deposit, lc.start_locker_no, lc.end_locker_no, lc.effective_from_date,"
|
||||||
|
+ " lc.effective_to_date, lc.is_active,"
|
||||||
|
+ " (select count(*) from m_locker l where l.locker_class_id = lc.id and l.status = 'ALLOTTED') as occupied"
|
||||||
|
+ " from m_locker_class lc join m_office o on o.id = lc.office_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LockerClassData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
|
||||||
|
final int startLockerNo = rs.getInt("start_locker_no");
|
||||||
|
final int endLockerNo = rs.getInt("end_locker_no");
|
||||||
|
final Date effectiveTo = rs.getDate("effective_to_date");
|
||||||
|
return LockerClassData.builder().id(rs.getLong("id")).officeId(rs.getLong("office_id")).officeName(rs.getString("office_name"))
|
||||||
|
.classCode(rs.getString("class_code")).className(rs.getString("class_name")).area(rs.getInt("area"))
|
||||||
|
.rent(rs.getBigDecimal("rent")).memberRent(rs.getBigDecimal("member_rent")).deposit(rs.getBigDecimal("deposit"))
|
||||||
|
.memberDeposit(rs.getBigDecimal("member_deposit")).startLockerNo(startLockerNo).endLockerNo(endLockerNo)
|
||||||
|
.totalLockers(endLockerNo - startLockerNo + 1).occupied(rs.getInt("occupied"))
|
||||||
|
.effectiveFromDate(rs.getDate("effective_from_date").toLocalDate())
|
||||||
|
.effectiveToDate(effectiveTo == null ? null : effectiveTo.toLocalDate()).active(rs.getBoolean("is_active")).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.service;
|
||||||
|
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
|
||||||
|
public interface LockerWritePlatformService {
|
||||||
|
|
||||||
|
CommandProcessingResult createClass(JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult updateClass(Long id, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult deactivateClass(Long id);
|
||||||
|
|
||||||
|
CommandProcessingResult createWaitingListEntry(JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult updateWaitingListEntry(Long id, JsonCommand command);
|
||||||
|
|
||||||
|
CommandProcessingResult cancelWaitingListEntry(Long id, JsonCommand command);
|
||||||
|
}
|
||||||
+265
@@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* 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.portfolio.locker.service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||||
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
|
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.locker.api.LockerApiConstants;
|
||||||
|
import org.apache.fineract.portfolio.locker.api.LockerApiJsonInputParams;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.Locker;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerClass;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerClassName;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerClassRepository;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerClassRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerRepository;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerStatus;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerWaitingList;
|
||||||
|
import org.apache.fineract.portfolio.locker.domain.LockerWaitingListRepositoryWrapper;
|
||||||
|
import org.apache.fineract.portfolio.locker.serialization.LockerCommandFromApiJsonDeserializer;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LockerWritePlatformServiceImpl implements LockerWritePlatformService {
|
||||||
|
|
||||||
|
private final LockerCommandFromApiJsonDeserializer validator;
|
||||||
|
private final LockerOfficeAccess officeAccess;
|
||||||
|
private final LockerClassRepository lockerClassRepository;
|
||||||
|
private final LockerClassRepositoryWrapper lockerClassRepositoryWrapper;
|
||||||
|
private final LockerRepository lockerRepository;
|
||||||
|
private final LockerWaitingListRepositoryWrapper lockerWaitingListRepositoryWrapper;
|
||||||
|
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||||
|
|
||||||
|
/** Saves the class row and creates one VACANT locker per number in its range — all or nothing. */
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult createClass(final JsonCommand command) {
|
||||||
|
this.validator.validateForCreateClass(command.json());
|
||||||
|
|
||||||
|
final Long officeId = this.officeAccess
|
||||||
|
.officeForCreate(command.longValueOfParameterNamed(LockerApiJsonInputParams.OFFICE_ID.getValue()));
|
||||||
|
final String classCode = command.stringValueOfParameterNamed(LockerApiJsonInputParams.CLASS_CODE.getValue()).trim()
|
||||||
|
.toUpperCase();
|
||||||
|
final Integer startLockerNo = command.integerValueSansLocaleOfParameterNamed(LockerApiJsonInputParams.START_LOCKER_NO.getValue());
|
||||||
|
final Integer endLockerNo = command.integerValueSansLocaleOfParameterNamed(LockerApiJsonInputParams.END_LOCKER_NO.getValue());
|
||||||
|
assertRangeFree(officeId, classCode, startLockerNo, endLockerNo);
|
||||||
|
|
||||||
|
final LockerClassName className = LockerClassName
|
||||||
|
.valueOf(command.stringValueOfParameterNamed(LockerApiJsonInputParams.CLASS_NAME.getValue()).toUpperCase());
|
||||||
|
final Integer area = command.integerValueSansLocaleOfParameterNamed(LockerApiJsonInputParams.AREA.getValue());
|
||||||
|
final BigDecimal rent = command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.RENT.getValue());
|
||||||
|
final BigDecimal deposit = command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.DEPOSIT.getValue());
|
||||||
|
// A blank member rate means members pay the normal rate, as on the legacy class master.
|
||||||
|
final BigDecimal memberRent = valueOrDefault(command, LockerApiJsonInputParams.MEMBER_RENT, rent);
|
||||||
|
final BigDecimal memberDeposit = valueOrDefault(command, LockerApiJsonInputParams.MEMBER_DEPOSIT, deposit);
|
||||||
|
final LocalDate effectiveFrom = command.localDateValueOfParameterNamed(LockerApiJsonInputParams.EFFECTIVE_FROM_DATE.getValue());
|
||||||
|
final LocalDate effectiveTo = command.localDateValueOfParameterNamed(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue());
|
||||||
|
|
||||||
|
final LockerClass lockerClass = this.lockerClassRepositoryWrapper.saveAndFlush(LockerClass.createNew(officeId, classCode, className,
|
||||||
|
area, rent, memberRent, deposit, memberDeposit, startLockerNo, endLockerNo, effectiveFrom, effectiveTo,
|
||||||
|
this.officeAccess.currentUser().getId()));
|
||||||
|
|
||||||
|
final List<Locker> lockers = new ArrayList<>(endLockerNo - startLockerNo + 1);
|
||||||
|
for (int lockerNo = startLockerNo; lockerNo <= endLockerNo; lockerNo++) {
|
||||||
|
lockers.add(Locker.createVacant(lockerClass, lockerNo));
|
||||||
|
}
|
||||||
|
this.lockerRepository.saveAll(lockers);
|
||||||
|
this.lockerRepository.flush();
|
||||||
|
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(lockerClass.getId())
|
||||||
|
.withOfficeId(officeId).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult updateClass(final Long id, final JsonCommand command) {
|
||||||
|
this.validator.validateForUpdateClass(command.json());
|
||||||
|
final LockerClass lockerClass = this.lockerClassRepositoryWrapper.findOneWithNotFoundDetection(id);
|
||||||
|
this.officeAccess.assertOwnBranch(lockerClass.getOfficeId());
|
||||||
|
if (!lockerClass.isActive()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerclass.inactive",
|
||||||
|
"Locker class " + lockerClass.getClassCode() + " is inactive and cannot be edited.", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String classNameValue = command.stringValueOfParameterNamed(LockerApiJsonInputParams.CLASS_NAME.getValue());
|
||||||
|
final LockerClassName className = classNameValue == null || classNameValue.isBlank() ? null
|
||||||
|
: LockerClassName.valueOf(classNameValue.toUpperCase());
|
||||||
|
final LocalDate effectiveFrom = command.localDateValueOfParameterNamed(LockerApiJsonInputParams.EFFECTIVE_FROM_DATE.getValue());
|
||||||
|
lockerClass.updateDetails(className, command.integerValueSansLocaleOfParameterNamed(LockerApiJsonInputParams.AREA.getValue()),
|
||||||
|
command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.RENT.getValue()),
|
||||||
|
command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.MEMBER_RENT.getValue()),
|
||||||
|
command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.DEPOSIT.getValue()),
|
||||||
|
command.bigDecimalValueOfParameterNamed(LockerApiJsonInputParams.MEMBER_DEPOSIT.getValue()), effectiveFrom,
|
||||||
|
this.officeAccess.currentUser().getId());
|
||||||
|
// Effective-to is cleared by sending it blank, so it is applied whenever the key is present.
|
||||||
|
if (command.parameterExists(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue())) {
|
||||||
|
lockerClass.updateEffectiveToDate(
|
||||||
|
command.localDateValueOfParameterNamed(LockerApiJsonInputParams.EFFECTIVE_TO_DATE.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only one side may have been sent, so the pair is checked again against the saved values.
|
||||||
|
final List<ApiParameterError> errors = new ArrayList<>();
|
||||||
|
this.validator.validateEffectivePeriod(lockerClass.getEffectiveFromDate(), lockerClass.getEffectiveToDate(),
|
||||||
|
new DataValidatorBuilder(errors).resource(LockerApiConstants.RESOURCE_CLASS));
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lockerClassRepositoryWrapper.saveAndFlush(lockerClass);
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id)
|
||||||
|
.withOfficeId(lockerClass.getOfficeId()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refused while any locker of the class is allotted. Otherwise the class goes inactive and its unallotted lockers
|
||||||
|
* are removed, so the same numbers can be set up again as a fresh row (for example with a new rate).
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult deactivateClass(final Long id) {
|
||||||
|
final LockerClass lockerClass = this.lockerClassRepositoryWrapper.findOneWithNotFoundDetection(id);
|
||||||
|
this.officeAccess.assertOwnBranch(lockerClass.getOfficeId());
|
||||||
|
if (!lockerClass.isActive()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerclass.already.inactive",
|
||||||
|
"Locker class " + lockerClass.getClassCode() + " is already inactive.", id);
|
||||||
|
}
|
||||||
|
final long allotted = this.lockerRepository.countByLockerClassIdAndStatus(id, LockerStatus.ALLOTTED.name());
|
||||||
|
if (allotted > 0) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerclass.has.allotted.lockers",
|
||||||
|
"Cannot deactivate class " + lockerClass.getClassCode() + " — " + allotted + " of its lockers are allotted.", id,
|
||||||
|
allotted);
|
||||||
|
}
|
||||||
|
this.lockerRepository.deleteUnallottedByLockerClassId(id);
|
||||||
|
lockerClass.deactivate(this.officeAccess.currentUser().getId());
|
||||||
|
this.lockerClassRepositoryWrapper.saveAndFlush(lockerClass);
|
||||||
|
return new CommandProcessingResultBuilder().withEntityId(id).withOfficeId(lockerClass.getOfficeId()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult createWaitingListEntry(final JsonCommand command) {
|
||||||
|
this.validator.validateForCreateWaitingList(command.json());
|
||||||
|
final Long officeId = this.officeAccess
|
||||||
|
.officeForCreate(command.longValueOfParameterNamed(LockerApiJsonInputParams.OFFICE_ID.getValue()));
|
||||||
|
final String preferredClasses = resolvePreferredClasses(officeId, command);
|
||||||
|
|
||||||
|
final LockerWaitingList entry = this.lockerWaitingListRepositoryWrapper.saveAndFlush(LockerWaitingList.createNew(officeId,
|
||||||
|
resolveClientId(command), command.localDateValueOfParameterNamed(LockerApiJsonInputParams.INQUIRY_DATE.getValue()),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.CUSTOMER_NAME), trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE1),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE2), trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE3),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.CITY), trimmed(command, LockerApiJsonInputParams.PINCODE),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.PHONE), trimmed(command, LockerApiJsonInputParams.EMAIL), preferredClasses,
|
||||||
|
trimmed(command, LockerApiJsonInputParams.REMARKS), this.officeAccess.currentUser().getId()));
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(entry.getId()).withOfficeId(officeId)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult updateWaitingListEntry(final Long id, final JsonCommand command) {
|
||||||
|
this.validator.validateForUpdateWaitingList(command.json());
|
||||||
|
final LockerWaitingList entry = findPendingWaitingListEntry(id, "edited");
|
||||||
|
final String preferredClasses = resolvePreferredClasses(entry.getOfficeId(), command);
|
||||||
|
entry.updateDetails(resolveClientId(command), command.localDateValueOfParameterNamed(LockerApiJsonInputParams.INQUIRY_DATE.getValue()),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.CUSTOMER_NAME), trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE1),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE2), trimmed(command, LockerApiJsonInputParams.ADDRESS_LINE3),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.CITY), trimmed(command, LockerApiJsonInputParams.PINCODE),
|
||||||
|
trimmed(command, LockerApiJsonInputParams.PHONE), trimmed(command, LockerApiJsonInputParams.EMAIL), preferredClasses,
|
||||||
|
trimmed(command, LockerApiJsonInputParams.REMARKS), this.officeAccess.currentUser().getId());
|
||||||
|
this.lockerWaitingListRepositoryWrapper.saveAndFlush(entry);
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id)
|
||||||
|
.withOfficeId(entry.getOfficeId()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public CommandProcessingResult cancelWaitingListEntry(final Long id, final JsonCommand command) {
|
||||||
|
this.validator.validateForCancelWaitingList(command.json());
|
||||||
|
final LockerWaitingList entry = findPendingWaitingListEntry(id, "cancelled");
|
||||||
|
entry.cancel(trimmed(command, LockerApiJsonInputParams.ADDITIONAL_REMARKS), this.officeAccess.currentUser().getId());
|
||||||
|
this.lockerWaitingListRepositoryWrapper.saveAndFlush(entry);
|
||||||
|
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(id)
|
||||||
|
.withOfficeId(entry.getOfficeId()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Own branch only, and only while still in the queue — an allotted or cancelled entry is history. */
|
||||||
|
private LockerWaitingList findPendingWaitingListEntry(final Long id, final String action) {
|
||||||
|
final LockerWaitingList entry = this.lockerWaitingListRepositoryWrapper.findOneWithNotFoundDetection(id);
|
||||||
|
this.officeAccess.assertOwnBranch(entry.getOfficeId());
|
||||||
|
if (!entry.isPending()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerwaitinglist.not.pending",
|
||||||
|
"Waiting list entry " + id + " is " + entry.getStatus() + " and cannot be " + action + ".", id);
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A linked client must exist, be active and be within the user's branch scope; no client id means a walk-in. */
|
||||||
|
private Long resolveClientId(final JsonCommand command) {
|
||||||
|
final Long clientId = command.longValueOfParameterNamed(LockerApiJsonInputParams.CLIENT_ID.getValue());
|
||||||
|
return clientId == null ? null : this.clientRepositoryWrapper.getActiveClientInUserScope(clientId).getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Every chosen class must be an active class at the entry's branch; stored as "A1,B1". */
|
||||||
|
private String resolvePreferredClasses(final Long officeId, final JsonCommand command) {
|
||||||
|
final List<String> chosen = this.validator.extractPreferredClasses(command.parsedJson());
|
||||||
|
final List<String> available = this.lockerClassRepository.findActiveClassCodes(officeId);
|
||||||
|
final List<String> unknown = chosen.stream().filter(code -> !available.contains(code)).toList();
|
||||||
|
if (!unknown.isEmpty()) {
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerwaitinglist.class.unknown",
|
||||||
|
"Locker class " + String.join(", ", unknown) + " does not exist at this branch.", String.join(",", unknown));
|
||||||
|
}
|
||||||
|
return String.join(",", chosen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Blank text is stored as null, so an empty optional field never shows as a stray space. */
|
||||||
|
private String trimmed(final JsonCommand command, final LockerApiJsonInputParams param) {
|
||||||
|
final String value = command.stringValueOfParameterNamedAllowingNull(param.getValue());
|
||||||
|
return value == null || value.isBlank() ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A number already used by this branch's class — in any row — cannot be created a second time. */
|
||||||
|
private void assertRangeFree(final Long officeId, final String classCode, final Integer startLockerNo, final Integer endLockerNo) {
|
||||||
|
if (this.lockerRepository.countExistingInRange(officeId, classCode, startLockerNo, endLockerNo) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<LockerClass> clashes = this.lockerClassRepository.findActiveOverlapping(officeId, classCode, startLockerNo,
|
||||||
|
endLockerNo);
|
||||||
|
final String existing = clashes.isEmpty() ? "some of these numbers"
|
||||||
|
: "lockers " + clashes.get(0).getStartLockerNo() + "–" + clashes.get(0).getEndLockerNo();
|
||||||
|
throw new GeneralPlatformDomainRuleException("error.msg.lockerclass.range.overlap",
|
||||||
|
"Class " + classCode + " already has " + existing + " at this branch.", classCode, startLockerNo, endLockerNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal valueOrDefault(final JsonCommand command, final LockerApiJsonInputParams param, final BigDecimal fallback) {
|
||||||
|
final BigDecimal value = command.bigDecimalValueOfParameterNamed(param.getValue());
|
||||||
|
return value == null ? fallback : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,4 +116,8 @@
|
|||||||
<include file="parts/0568_add_settlement_mode_to_clerk_share_withdrawal_request.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0568_add_settlement_mode_to_clerk_share_withdrawal_request.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0569_aml_alert_release_stale_under_review.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0569_aml_alert_release_stale_under_review.xml" relativeToChangelogFile="true"/>
|
||||||
<include file="parts/0570_add_dd_payable_gl_to_sponsor_bank.xml" relativeToChangelogFile="true"/>
|
<include file="parts/0570_add_dd_payable_gl_to_sponsor_bank.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0571_locker_class_master.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0572_locker_waiting_list.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0573_locker_waiting_list_client_link.xml" relativeToChangelogFile="true"/>
|
||||||
|
<include file="parts/0574_locker_allotment.xml" relativeToChangelogFile="true"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
<?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.9.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Safe Deposit Locker - class master and locker inventory.
|
||||||
|
|
||||||
|
A class row is one locker-number range of a class at a branch (a class such as A1 can have several
|
||||||
|
ranges), with member and non-member rent and deposit and an effective period. Saving a class row
|
||||||
|
materialises one m_locker row per number in its range, so every physical box exists as a record
|
||||||
|
that allotment, rent and the operation register can point at.
|
||||||
|
|
||||||
|
A locker is unique per branch + class + number. B1 and C1 may both have a locker 1; A1 may not have
|
||||||
|
two. Occupied is never stored on the class: it is counted from m_locker.status = 'ALLOTTED'.
|
||||||
|
-->
|
||||||
|
<changeSet id="0571-1-locker-class" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_locker_class"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_locker_class">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="office_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_class_office" references="m_office(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="class_code" type="VARCHAR(10)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="class_name" type="VARCHAR(10)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="area" type="INTEGER">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="rent" type="DECIMAL(19,6)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="member_rent" type="DECIMAL(19,6)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="deposit" type="DECIMAL(19,6)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="member_deposit" type="DECIMAL(19,6)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="start_locker_no" type="INTEGER">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="end_locker_no" type="INTEGER">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="effective_from_date" type="DATE">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="effective_to_date" type="DATE"/>
|
||||||
|
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="created_by" type="BIGINT"/>
|
||||||
|
<column name="created_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
<column name="lastmodified_by" type="BIGINT"/>
|
||||||
|
<column name="lastmodified_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
<createIndex tableName="m_locker_class" indexName="idx_locker_class_office_code">
|
||||||
|
<column name="office_id"/>
|
||||||
|
<column name="class_code"/>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="0571-2-locker" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_locker"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_locker">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="office_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_office" references="m_office(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="locker_class_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_class" references="m_locker_class(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="class_code" type="VARCHAR(10)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="locker_no" type="INTEGER">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="key_no" type="VARCHAR(20)"/>
|
||||||
|
<!-- VACANT / ALLOTTED / BLOCKED -->
|
||||||
|
<column name="status" type="VARCHAR(20)" defaultValue="VACANT">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
</createTable>
|
||||||
|
<addUniqueConstraint tableName="m_locker" columnNames="office_id, class_code, locker_no"
|
||||||
|
constraintName="uk_locker_office_class_no"/>
|
||||||
|
<createIndex tableName="m_locker" indexName="idx_locker_class_status">
|
||||||
|
<column name="locker_class_id"/>
|
||||||
|
<column name="status"/>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Only a Branch Manager (own branch) or the Super user (every branch, through ALL_FUNCTIONS) may create,
|
||||||
|
edit or deactivate a class. Read goes to Branch Manager too; other roles that hold ALL_FUNCTIONS_READ
|
||||||
|
(Clerk) can already see it.
|
||||||
|
-->
|
||||||
|
<changeSet id="0571-3-locker-class-permissions" author="CBS">
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'READ_LOCKERCLASS', 'LOCKERCLASS', 'READ', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_LOCKERCLASS');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'CREATE_LOCKERCLASS', 'LOCKERCLASS', 'CREATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_LOCKERCLASS');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'UPDATE_LOCKERCLASS', 'LOCKERCLASS', 'UPDATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'UPDATE_LOCKERCLASS');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'DELETE_LOCKERCLASS', 'LOCKERCLASS', 'DELETE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'DELETE_LOCKERCLASS');
|
||||||
|
</sql>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM m_role r
|
||||||
|
JOIN m_permission p ON p.code IN ('READ_LOCKERCLASS', 'CREATE_LOCKERCLASS', 'UPDATE_LOCKERCLASS', 'DELETE_LOCKERCLASS')
|
||||||
|
WHERE r.name = 'Branch Manager'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM m_role_permission rp WHERE rp.role_id = r.id AND rp.permission_id = p.id);
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
<?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.9.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Safe Deposit Locker - locker list read access and the waiting list.
|
||||||
|
|
||||||
|
Waiting list: an inquiry for a locker, taken at the counter from anyone (the inquirer need not be a client
|
||||||
|
yet), with one or more preferred classes. Its id is the waiting number given to the applicant, as the RBI
|
||||||
|
locker directions require. Allotment will later move an entry to ALLOTTED; the counter can CANCEL it.
|
||||||
|
-->
|
||||||
|
<changeSet id="0572-1-locker-waiting-list" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_locker_waiting_list"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_locker_waiting_list">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="office_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_waiting_office" references="m_office(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="inquiry_date" type="DATE">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="customer_name" type="VARCHAR(100)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="address_line1" type="VARCHAR(150)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="address_line2" type="VARCHAR(150)"/>
|
||||||
|
<column name="address_line3" type="VARCHAR(150)"/>
|
||||||
|
<column name="city" type="VARCHAR(60)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="pincode" type="VARCHAR(6)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="phone" type="VARCHAR(15)"/>
|
||||||
|
<column name="email" type="VARCHAR(100)"/>
|
||||||
|
<!-- comma-separated class codes, e.g. A1,B1 -->
|
||||||
|
<column name="preferred_classes" type="VARCHAR(200)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="remarks" type="VARCHAR(500)"/>
|
||||||
|
<!-- PENDING / ALLOTTED / CANCELLED -->
|
||||||
|
<column name="status" type="VARCHAR(20)" defaultValue="PENDING">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="additional_remarks" type="VARCHAR(500)"/>
|
||||||
|
<column name="created_by" type="BIGINT"/>
|
||||||
|
<column name="created_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
<column name="lastmodified_by" type="BIGINT"/>
|
||||||
|
<column name="lastmodified_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
<createIndex tableName="m_locker_waiting_list" indexName="idx_locker_waiting_office_status">
|
||||||
|
<column name="office_id"/>
|
||||||
|
<column name="status"/>
|
||||||
|
</createIndex>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!-- Locker list and waiting list are counter work: Branch Manager and Clerk, each confined to their own branch. -->
|
||||||
|
<changeSet id="0572-2-locker-permissions" author="CBS">
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'READ_LOCKER', 'LOCKER', 'READ', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_LOCKER');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'READ_LOCKERWAITINGLIST', 'LOCKERWAITINGLIST', 'READ', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_LOCKERWAITINGLIST');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'CREATE_LOCKERWAITINGLIST', 'LOCKERWAITINGLIST', 'CREATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_LOCKERWAITINGLIST');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'UPDATE_LOCKERWAITINGLIST', 'LOCKERWAITINGLIST', 'UPDATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'UPDATE_LOCKERWAITINGLIST');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'CANCEL_LOCKERWAITINGLIST', 'LOCKERWAITINGLIST', 'CANCEL', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CANCEL_LOCKERWAITINGLIST');
|
||||||
|
</sql>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM m_role r
|
||||||
|
JOIN m_permission p ON p.code IN ('READ_LOCKER', 'READ_LOCKERWAITINGLIST', 'CREATE_LOCKERWAITINGLIST',
|
||||||
|
'UPDATE_LOCKERWAITINGLIST', 'CANCEL_LOCKERWAITINGLIST')
|
||||||
|
WHERE r.name IN ('Branch Manager', 'Clerk')
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM m_role_permission rp WHERE rp.role_id = r.id AND rp.permission_id = p.id);
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
<?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.9.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Links a waiting-list inquiry to an existing client when the inquirer already banks here, so the counter can
|
||||||
|
pick the client instead of retyping name, address and contact, and allotment can later start from the same
|
||||||
|
client. Nullable: an inquirer who is not yet a client is still taken by name, as before.
|
||||||
|
-->
|
||||||
|
<changeSet id="0573-1-locker-waiting-list-client" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<and>
|
||||||
|
<tableExists tableName="m_locker_waiting_list"/>
|
||||||
|
<not><columnExists tableName="m_locker_waiting_list" columnName="client_id"/></not>
|
||||||
|
</and>
|
||||||
|
</preConditions>
|
||||||
|
<addColumn tableName="m_locker_waiting_list">
|
||||||
|
<column name="client_id" type="BIGINT">
|
||||||
|
<constraints nullable="true" foreignKeyName="fk_locker_waiting_client" references="m_client(id)"/>
|
||||||
|
</column>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
<?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.9.xsd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Safe Deposit Locker - allotment (the legacy "A/c Master Maintenance": application master, joint members,
|
||||||
|
other entries, nominees).
|
||||||
|
|
||||||
|
An application is submitted PENDING_APPROVAL and holds its locker so no one else can take it; the Branch
|
||||||
|
Manager approves it (locker ALLOTTED, deposits under lien, waiting-list entry ALLOTTED) or rejects it
|
||||||
|
(locker vacant again). Its id is the application number. Lockers are only ever allotted to existing clients.
|
||||||
|
|
||||||
|
The partial unique index on locker_id is the database's own guarantee that one locker cannot be held by two
|
||||||
|
live applications at once, whatever happens in the application code.
|
||||||
|
|
||||||
|
Security deposit is optional. A fixed deposit given against a locker is recorded with a lien status: PENDING
|
||||||
|
while the application waits, ACTIVE once approved, RELEASED on reject or surrender.
|
||||||
|
-->
|
||||||
|
<changeSet id="0574-1-locker-allotment" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_locker_allotment"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_locker_allotment">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true">
|
||||||
|
<constraints primaryKey="true" nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="office_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_allot_office" references="m_office(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="client_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_allot_client" references="m_client(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="locker_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_allot_locker" references="m_locker(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="key_no" type="VARCHAR(20)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<!-- one-way hash, never the password itself -->
|
||||||
|
<column name="password_hash" type="VARCHAR(100)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<!-- MEMBER / NON_MEMBER / STAFF -->
|
||||||
|
<column name="category" type="VARCHAR(20)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<!-- SELF / EITHER_OR_SURVIVOR / FORMER_OR_SURVIVOR / ANYONE_OR_SURVIVOR / JOINTLY -->
|
||||||
|
<column name="pay_mode" type="VARCHAR(30)">
|
||||||
|
<constraints nullable="false"/>
|
||||||
|
</column>
|
||||||
|
<column name="dual_auth" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="sms_active" type="BOOLEAN" defaultValueBoolean="true"><constraints nullable="false"/></column>
|
||||||
|
<column name="email_active" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="rent_exemption" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="rent_account_id" type="BIGINT">
|
||||||
|
<constraints nullable="true" foreignKeyName="fk_locker_allot_rent_account" references="m_savings_account(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="risk" type="VARCHAR(10)"/>
|
||||||
|
<column name="is_frozen" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="freeze_remark" type="VARCHAR(200)"/>
|
||||||
|
<column name="waiting_list_id" type="BIGINT">
|
||||||
|
<constraints nullable="true" foreignKeyName="fk_locker_allot_waiting" references="m_locker_waiting_list(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="phone2" type="VARCHAR(15)"/>
|
||||||
|
<column name="photo_received" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="address_proof_received" type="BOOLEAN" defaultValueBoolean="false"><constraints nullable="false"/></column>
|
||||||
|
<column name="remarks" type="VARCHAR(500)"/>
|
||||||
|
<column name="transaction_date" type="DATE"><constraints nullable="false"/></column>
|
||||||
|
<column name="opening_date" type="DATE"/>
|
||||||
|
<column name="close_date" type="DATE"/>
|
||||||
|
<column name="rent_up_to_date" type="DATE"/>
|
||||||
|
<!-- PENDING_APPROVAL / ACTIVE / REJECTED / CLOSED -->
|
||||||
|
<column name="status" type="VARCHAR(20)" defaultValue="PENDING_APPROVAL"><constraints nullable="false"/></column>
|
||||||
|
<column name="rejection_reason" type="VARCHAR(500)"/>
|
||||||
|
<column name="submitted_by" type="BIGINT"/>
|
||||||
|
<column name="submitted_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
<column name="approved_by" type="BIGINT"/>
|
||||||
|
<column name="approved_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
<column name="rejected_by" type="BIGINT"/>
|
||||||
|
<column name="rejected_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
<column name="lastmodified_by" type="BIGINT"/>
|
||||||
|
<column name="lastmodified_on" type="TIMESTAMP WITH TIME ZONE"/>
|
||||||
|
</createTable>
|
||||||
|
<createIndex tableName="m_locker_allotment" indexName="idx_locker_allot_office_status">
|
||||||
|
<column name="office_id"/>
|
||||||
|
<column name="status"/>
|
||||||
|
</createIndex>
|
||||||
|
<createIndex tableName="m_locker_allotment" indexName="idx_locker_allot_client">
|
||||||
|
<column name="client_id"/>
|
||||||
|
</createIndex>
|
||||||
|
<sql>
|
||||||
|
CREATE UNIQUE INDEX uk_locker_allot_live_locker ON m_locker_allotment (locker_id)
|
||||||
|
WHERE status IN ('PENDING_APPROVAL', 'ACTIVE');
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<changeSet id="0574-2-locker-allotment-children" author="CBS">
|
||||||
|
<preConditions onFail="MARK_RAN">
|
||||||
|
<not><tableExists tableName="m_locker_allotment_joint"/></not>
|
||||||
|
</preConditions>
|
||||||
|
<createTable tableName="m_locker_allotment_joint">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true"><constraints primaryKey="true" nullable="false"/></column>
|
||||||
|
<column name="allotment_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_joint_allot" references="m_locker_allotment(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="client_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_joint_client" references="m_client(id)"/>
|
||||||
|
</column>
|
||||||
|
<!-- JOINT_HOLDER / MANDATE_HOLDER / AUTHORISED_SIGNATORY -->
|
||||||
|
<column name="join_category" type="VARCHAR(30)"><constraints nullable="false"/></column>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<createTable tableName="m_locker_allotment_guarantor">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true"><constraints primaryKey="true" nullable="false"/></column>
|
||||||
|
<column name="allotment_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_guar_allot" references="m_locker_allotment(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="client_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_guar_client" references="m_client(id)"/>
|
||||||
|
</column>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<createTable tableName="m_locker_allotment_nominee">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true"><constraints primaryKey="true" nullable="false"/></column>
|
||||||
|
<column name="allotment_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_nom_allot" references="m_locker_allotment(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="nomination_date" type="DATE"><constraints nullable="false"/></column>
|
||||||
|
<column name="name" type="VARCHAR(100)"><constraints nullable="false"/></column>
|
||||||
|
<column name="mobile_no" type="VARCHAR(15)"/>
|
||||||
|
<column name="date_of_birth" type="DATE"/>
|
||||||
|
<column name="relation" type="VARCHAR(20)"><constraints nullable="false"/></column>
|
||||||
|
<column name="gender" type="VARCHAR(10)"><constraints nullable="false"/></column>
|
||||||
|
<column name="guardian_name" type="VARCHAR(100)"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<createTable tableName="m_locker_allotment_deposit">
|
||||||
|
<column name="id" type="BIGINT" autoIncrement="true"><constraints primaryKey="true" nullable="false"/></column>
|
||||||
|
<column name="allotment_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_dep_allot" references="m_locker_allotment(id)"/>
|
||||||
|
</column>
|
||||||
|
<column name="savings_account_id" type="BIGINT">
|
||||||
|
<constraints nullable="false" foreignKeyName="fk_locker_dep_account" references="m_savings_account(id)"/>
|
||||||
|
</column>
|
||||||
|
<!-- deposit amount when it was given, kept even if the account later changes -->
|
||||||
|
<column name="deposit_amount" type="DECIMAL(19,6)"><constraints nullable="false"/></column>
|
||||||
|
<!-- PENDING / ACTIVE / RELEASED -->
|
||||||
|
<column name="lien_status" type="VARCHAR(20)" defaultValue="PENDING"><constraints nullable="false"/></column>
|
||||||
|
<column name="lien_marked_on" type="DATE"/>
|
||||||
|
<column name="lien_released_on" type="DATE"/>
|
||||||
|
</createTable>
|
||||||
|
<sql>
|
||||||
|
CREATE UNIQUE INDEX uk_locker_dep_live_account ON m_locker_allotment_deposit (savings_account_id)
|
||||||
|
WHERE lien_status IN ('PENDING', 'ACTIVE');
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Application entry and edits are counter work (Clerk, Branch Manager); approval and rejection are the Branch
|
||||||
|
Manager's alone. The Super user holds ALL_FUNCTIONS. Submitter and approver must be different users.
|
||||||
|
-->
|
||||||
|
<changeSet id="0574-3-locker-allotment-permissions" author="CBS">
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'READ_LOCKERALLOTMENT', 'LOCKERALLOTMENT', 'READ', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'READ_LOCKERALLOTMENT');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'CREATE_LOCKERALLOTMENT', 'LOCKERALLOTMENT', 'CREATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_LOCKERALLOTMENT');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'UPDATE_LOCKERALLOTMENT', 'LOCKERALLOTMENT', 'UPDATE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'UPDATE_LOCKERALLOTMENT');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'APPROVE_LOCKERALLOTMENT', 'LOCKERALLOTMENT', 'APPROVE', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'APPROVE_LOCKERALLOTMENT');
|
||||||
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
|
SELECT 'portfolio', 'REJECT_LOCKERALLOTMENT', 'LOCKERALLOTMENT', 'REJECT', false
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'REJECT_LOCKERALLOTMENT');
|
||||||
|
</sql>
|
||||||
|
<sql>
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM m_role r
|
||||||
|
JOIN m_permission p ON p.code IN ('READ_LOCKERALLOTMENT', 'CREATE_LOCKERALLOTMENT', 'UPDATE_LOCKERALLOTMENT')
|
||||||
|
WHERE r.name IN ('Branch Manager', 'Clerk')
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM m_role_permission rp WHERE rp.role_id = r.id AND rp.permission_id = p.id);
|
||||||
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
|
SELECT r.id, p.id
|
||||||
|
FROM m_role r
|
||||||
|
JOIN m_permission p ON p.code IN ('APPROVE_LOCKERALLOTMENT', 'REJECT_LOCKERALLOTMENT')
|
||||||
|
WHERE r.name = 'Branch Manager'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM m_role_permission rp WHERE rp.role_id = r.id AND rp.permission_id = p.id);
|
||||||
|
</sql>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
||||||
Reference in New Issue
Block a user