blob: 48ea9c07f0a338464daa2ecc889419478dfb4186 [file] [log] [blame]
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed 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 io.mifos.teller.service.rest;
import io.mifos.accounting.api.v1.domain.Account;
import io.mifos.anubis.annotation.AcceptedTokenType;
import io.mifos.anubis.annotation.Permittable;
import io.mifos.core.api.util.UserContextHolder;
import io.mifos.core.command.gateway.CommandGateway;
import io.mifos.core.lang.DateConverter;
import io.mifos.core.lang.ServiceException;
import io.mifos.teller.ServiceConstants;
import io.mifos.teller.api.v1.PermittableGroupIds;
import io.mifos.teller.api.v1.domain.Teller;
import io.mifos.teller.api.v1.domain.TellerTransaction;
import io.mifos.teller.api.v1.domain.TellerTransactionCosts;
import io.mifos.teller.api.v1.domain.UnlockDrawerCommand;
import io.mifos.teller.service.internal.command.CancelTellerTransactionCommand;
import io.mifos.teller.service.internal.command.ConfirmTellerTransactionCommand;
import io.mifos.teller.service.internal.command.DrawerUnlockCommand;
import io.mifos.teller.service.internal.command.InitializeTellerTransactionCommand;
import io.mifos.teller.service.internal.command.PauseTellerCommand;
import io.mifos.teller.service.internal.service.TellerManagementService;
import io.mifos.teller.service.internal.service.TellerOperationService;
import io.mifos.teller.service.internal.service.helper.AccountingService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/teller/{tellerCode}")
public class TellerOperationRestController {
private final Logger logger;
private final CommandGateway commandGateway;
private final TellerOperationService tellerOperationService;
private final TellerManagementService tellerManagementService;
private final AccountingService accountingService;
@Autowired
public TellerOperationRestController(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
final CommandGateway commandGateway,
final TellerOperationService tellerOperationService,
final TellerManagementService tellerManagementService,
final AccountingService accountingService) {
this.logger = logger;
this.commandGateway = commandGateway;
this.tellerOperationService = tellerOperationService;
this.tellerManagementService = tellerManagementService;
this.accountingService = accountingService;
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.TELLER_OPERATION)
@RequestMapping(
value = "/drawer",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
ResponseEntity<Teller> unlockDrawer(@PathVariable("tellerCode") final String tellerCode,
@RequestBody @Valid final UnlockDrawerCommand unlockDrawerCommand) {
final Teller teller = this.verifyTeller(tellerCode);
if (teller.getState().equals(Teller.State.CLOSED.name())) {
throw ServiceException.badRequest("Teller {0} is closed.", teller.getCode());
}
if (!teller.getAssignedEmployee().equals(unlockDrawerCommand.getEmployeeIdentifier())) {
throw ServiceException.badRequest("User {0} is not assigned to teller.", unlockDrawerCommand.getEmployeeIdentifier());
}
this.verifyEmployee(teller);
try {
final String unlockedTeller =
this.commandGateway.process(new DrawerUnlockCommand(tellerCode, unlockDrawerCommand), String.class).get();
this.logger.debug("Drawer {0} unlocked", unlockedTeller);
return ResponseEntity.ok(teller);
} catch (final Exception e) {
throw ServiceException.notFound("Teller {0} not found.", teller.getCode());
}
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.TELLER_OPERATION)
@RequestMapping(
value = "",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
ResponseEntity<Void> post(@PathVariable("tellerCode") final String tellerCode,
@RequestParam(value = "command", required = true) final String command) {
final Teller teller = this.verifyTeller(tellerCode);
this.verifyEmployee(teller);
switch (command.toUpperCase()) {
case "PAUSE":
if (!teller.getState().equals(Teller.State.ACTIVE.name())) {
throw ServiceException.badRequest("Teller {0} is not active.", tellerCode);
}
this.commandGateway.process(new PauseTellerCommand(tellerCode));
break;
default :
throw ServiceException.badRequest("Unknonw command {0}", command);
}
return ResponseEntity.accepted().build();
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.TELLER_OPERATION)
@RequestMapping(
value = "/transactions",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
ResponseEntity<TellerTransactionCosts> post(@PathVariable("tellerCode") final String tellerCode,
@RequestBody @Valid final TellerTransaction tellerTransaction) {
final Teller teller = this.verifyTeller(tellerCode);
this.verifyEmployee(teller);
if (!teller.getState().equals(Teller.State.ACTIVE.name())) {
throw ServiceException.conflict("Teller {0} ist not active.", tellerCode);
}
final String transactionType = tellerTransaction.getTransactionType();
if (transactionType.equals(ServiceConstants.TX_CASH_WITHDRAWAL)
|| transactionType.equals(ServiceConstants.TX_CLOSE_ACCOUNT)) {
if (tellerTransaction.getAmount().compareTo(teller.getCashdrawLimit()) > 0) {
throw ServiceException.conflict("Amount exceeds cash drawl limit.");
}
}
if (transactionType.equals(ServiceConstants.TX_CHEQUE)) {
final LocalDate dateIssued = DateConverter.dateFromIsoString(tellerTransaction.getCheque().getDateIssued());
final LocalDate sixMonth = LocalDate.now(Clock.systemUTC()).minusMonths(6);
if (dateIssued.isBefore(sixMonth)) {
throw ServiceException.conflict("Cheque is older than 6 month.");
}
}
final Optional<Account> optionalCustomerAccount =
this.accountingService.findAccount(tellerTransaction.getCustomerAccountIdentifier());
if (!optionalCustomerAccount.isPresent()) {
throw ServiceException.badRequest("Customer account {0} not found.");
} else {
final Account customerAccount = optionalCustomerAccount.get();
if (!customerAccount.getState().equals(Account.State.OPEN.name())) {
throw ServiceException.conflict("Account {0} is not open.", customerAccount.getIdentifier());
}
if (transactionType.equals(ServiceConstants.TX_ACCOUNT_TRANSFER)
|| transactionType.equals(ServiceConstants.TX_CASH_WITHDRAWAL)
|| transactionType.equals(ServiceConstants.TX_CLOSE_ACCOUNT)) {
if (tellerTransaction.getAmount().compareTo(BigDecimal.valueOf(customerAccount.getBalance())) > 0 ) {
throw ServiceException.conflict("Not enough balance.");
}
}
if (transactionType.equals(ServiceConstants.TX_CLOSE_ACCOUNT)) {
final BigDecimal newBalance =
BigDecimal.valueOf(customerAccount.getBalance()).subtract(tellerTransaction.getAmount());
if (newBalance.compareTo(BigDecimal.ZERO) > 0) {
throw ServiceException.conflict("Account has remaining balance");
}
}
}
if (tellerTransaction.getTargetAccountIdentifier() != null &&
!this.accountingService.findAccount(tellerTransaction.getTargetAccountIdentifier()).isPresent()) {
throw ServiceException.badRequest("Target account {0} not found.");
}
try {
return ResponseEntity.ok(
this.commandGateway.process(
new InitializeTellerTransactionCommand(tellerCode, tellerTransaction), TellerTransactionCosts.class).get()
);
} catch (final Exception e) {
throw ServiceException.badRequest("Transaction for teller {0} not valid.", tellerCode);
}
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.TELLER_OPERATION)
@RequestMapping(
value = "/transactions/{identifier}",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
ResponseEntity<Void> confirm(@PathVariable("tellerCode") final String tellerCode,
@PathVariable("identifier") final String tellerTransactionIdentifier,
@RequestParam(value = "command", required = true) final String command,
@RequestParam(value = "charges", required = false, defaultValue = "excluded") final String charges) {
final Teller teller = this.verifyTeller(tellerCode);
this.verifyEmployee(teller);
if (!this.tellerOperationService.tellerTransactionExists(tellerTransactionIdentifier)) {
throw ServiceException.notFound("Transaction {0} not found.", tellerTransactionIdentifier);
}
switch (command.toUpperCase()) {
case "CONFIRM" :
this.commandGateway.process(new ConfirmTellerTransactionCommand(tellerTransactionIdentifier, charges));
break;
case "CANCEL" :
this.commandGateway.process(new CancelTellerTransactionCommand(tellerTransactionIdentifier));
break;
default :
throw ServiceException.badRequest("Unsupported teller transaction command {0}.", command);
}
return ResponseEntity.accepted().build();
}
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.TELLER_OPERATION)
@RequestMapping(
value = "/transactions",
method = RequestMethod.GET,
consumes = MediaType.ALL_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
ResponseEntity<List<TellerTransaction>> fetch(@PathVariable("tellerCode") final String tellerCode,
@RequestParam(value = "status", required = false) final String status) {
this.verifyTeller(tellerCode);
return ResponseEntity.ok(
this.tellerOperationService.fetchTellerTransactions(tellerCode, status)
);
}
private Teller verifyTeller(final String tellerCode) {
final Optional<Teller> optionalTeller = this.tellerManagementService.findByIdentifier(tellerCode);
if (!optionalTeller.isPresent()) {
throw ServiceException.notFound("Teller {0} not found.", tellerCode);
} else {
return optionalTeller.get();
}
}
private void verifyEmployee(final Teller teller) {
final String currentUser = UserContextHolder.checkedGetUser();
if (!currentUser.equals(teller.getAssignedEmployee())) {
throw ServiceException.badRequest("User {0} is not assigned to teller {1}", currentUser, teller.getCode());
}
}
}