FINERACT-2326: Rework business date validation and dto handling

This commit is contained in:
Adam Saghy
2025-08-19 13:18:40 +02:00
parent f41d986b2a
commit 942ad0183c
24 changed files with 296 additions and 168 deletions
@@ -36,8 +36,10 @@ import java.util.function.Supplier;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.command.core.CommandPipeline;
import org.apache.fineract.infrastructure.businessdate.command.BusinessDateUpdateCommand;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateResponse;
import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateMapper;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.springframework.stereotype.Component;
@@ -50,13 +52,14 @@ public class BusinessDateApiResource {
private final BusinessDateReadPlatformService readPlatformService;
private final CommandPipeline commandPipeline;
private final BusinessDateMapper businessDateMapper;
@GET
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List all business dates", description = "")
public List<BusinessDateResponse> getBusinessDates() {
return this.readPlatformService.findAll();
return businessDateMapper.mapFetchResponse(this.readPlatformService.findAll());
}
@GET
@@ -65,24 +68,24 @@ public class BusinessDateApiResource {
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve a specific Business date", description = "")
public BusinessDateResponse getBusinessDate(@PathParam("type") @Parameter(description = "type") final String type) {
return this.readPlatformService.findByType(type);
return businessDateMapper.mapFetchResponse(this.readPlatformService.findByType(type));
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Business Date", description = "")
public BusinessDateResponse updateBusinessDate(@HeaderParam("Idempotency-Key") String idempotencyKey,
public BusinessDateUpdateResponse updateBusinessDate(@HeaderParam("Idempotency-Key") String idempotencyKey,
@Valid BusinessDateUpdateRequest request) {
final var command = new BusinessDateUpdateCommand();
final BusinessDateUpdateCommand command = new BusinessDateUpdateCommand();
command.setId(UUID.randomUUID());
command.setIdempotencyKey(idempotencyKey);
command.setCreatedAt(DateUtils.getAuditOffsetDateTime());
command.setPayload(request);
final Supplier<BusinessDateResponse> response = commandPipeline.send(command);
final Supplier<BusinessDateUpdateResponse> response = commandPipeline.send(command);
return response.get();
}
@@ -21,7 +21,7 @@ package org.apache.fineract.infrastructure.businessdate.command;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateRequest;
@Data
@EqualsAndHashCode(callSuper = true)
@@ -0,0 +1,44 @@
/**
* 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.infrastructure.businessdate.data.api;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonLocalDateArrayFormat
public class BusinessDateResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String description;
private BusinessDateType type;
private LocalDate date;
}
@@ -16,8 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.businessdate.data;
package org.apache.fineract.infrastructure.businessdate.data.api;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.io.Serial;
@@ -27,6 +28,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.validation.constraints.EnumValue;
import org.apache.fineract.validation.constraints.LocalDate;
import org.apache.fineract.validation.constraints.Locale;
@@ -42,8 +44,10 @@ public class BusinessDateUpdateRequest implements Serializable {
@NotBlank(message = "{org.apache.fineract.businessdate.date-format.not-blank}")
private String dateFormat;
@NotNull(message = "{org.apache.fineract.businessdate.type.not-null}")
private BusinessDateType type;
@Schema(description = "Type of business date", example = "BUSINESS_DATE", allowableValues = { "BUSINESS_DATE", "COB_DATE" })
@EnumValue(enumClass = BusinessDateType.class, message = "{org.apache.fineract.businessdate.type.invalid}")
@NotNull(message = "{org.apache.fineract.businessdate.type.not-blank}")
private String type;
@NotBlank(message = "{org.apache.fineract.businessdate.date.not-blank}")
private String date;
@NotBlank(message = "{org.apache.fineract.businessdate.locale.not-blank}")
@@ -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.infrastructure.businessdate.data.api;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.jersey.serializer.legacy.JsonLocalDateArrayFormat;
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonLocalDateArrayFormat
public class BusinessDateUpdateResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String description;
private BusinessDateType type;
private LocalDate date;
private Map<BusinessDateType, LocalDate> changes;
}
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.businessdate.data;
package org.apache.fineract.infrastructure.businessdate.data.service;
import java.io.Serial;
import java.io.Serializable;
@@ -33,7 +33,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessDateResponse implements Serializable {
public class BusinessDateDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@@ -22,8 +22,10 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.command.core.Command;
import org.apache.fineract.command.core.CommandHandler;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateResponse;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateMapper;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateWritePlatformService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -31,13 +33,16 @@ import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Component
@RequiredArgsConstructor
public class BusinessDateUpdateHandler implements CommandHandler<BusinessDateUpdateRequest, BusinessDateResponse> {
public class BusinessDateUpdateHandler implements CommandHandler<BusinessDateUpdateRequest, BusinessDateUpdateResponse> {
private final BusinessDateWritePlatformService businessDateWritePlatformService;
private final BusinessDateMapper businessDateMapper;
@Transactional
@Override
public BusinessDateResponse handle(Command<BusinessDateUpdateRequest> command) {
return businessDateWritePlatformService.updateBusinessDate(command.getPayload());
public BusinessDateUpdateResponse handle(Command<BusinessDateUpdateRequest> command) {
BusinessDateDTO businessDateDto = businessDateMapper.mapUpdateRequest(command.getPayload());
businessDateDto = businessDateWritePlatformService.updateBusinessDate(businessDateDto);
return businessDateMapper.mapUpdateResponse(businessDateDto);
}
}
@@ -19,7 +19,10 @@
package org.apache.fineract.infrastructure.businessdate.mapper;
import java.util.List;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.api.BusinessDateUpdateResponse;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.mapstruct.Mapper;
@@ -29,8 +32,20 @@ import org.mapstruct.Mappings;
@Mapper(config = MapstructMapperConfig.class)
public interface BusinessDateMapper {
@Mappings({ @Mapping(target = "description", source = "source.type.description"), @Mapping(target = "changes", ignore = true) })
BusinessDateResponse map(BusinessDate source);
@Mappings({ @Mapping(target = "description", source = "type.description"), @Mapping(target = "changes", ignore = true) })
BusinessDateDTO mapEntity(BusinessDate source);
List<BusinessDateResponse> map(List<BusinessDate> sources);
List<BusinessDateDTO> mapEntity(List<BusinessDate> sources);
@Mapping(target = "description", expression = "java(org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.valueOf(source.getType()).getDescription())")
@Mapping(target = "type", expression = "java(org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.valueOf(source.getType()))")
@Mapping(target = "date", expression = "java(org.apache.fineract.infrastructure.core.service.DateUtils.toLocalDate(source.getLocale(), source.getDate(), source.getDateFormat()))")
@Mapping(target = "changes", ignore = true)
BusinessDateDTO mapUpdateRequest(BusinessDateUpdateRequest source);
List<BusinessDateResponse> mapFetchResponse(List<BusinessDateDTO> sources);
BusinessDateResponse mapFetchResponse(BusinessDateDTO source);
BusinessDateUpdateResponse mapUpdateResponse(BusinessDateDTO source);
}
@@ -1,35 +0,0 @@
/**
* 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.infrastructure.businessdate.mapper;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(config = MapstructMapperConfig.class)
public interface BusinessDateUpdateRequestMapper {
@Mapping(target = "description", expression = "java(source.getType().getDescription())")
@Mapping(source = "type", target = "type")
@Mapping(target = "date", expression = "java(org.apache.fineract.infrastructure.core.service.DateUtils.toLocalDate(source.getLocale(), source.getDate(), source.getDateFormat()))")
@Mapping(target = "changes", ignore = true)
BusinessDateResponse map(BusinessDateUpdateRequest source);
}
@@ -21,14 +21,14 @@ package org.apache.fineract.infrastructure.businessdate.service;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
public interface BusinessDateReadPlatformService {
List<BusinessDateResponse> findAll();
List<BusinessDateDTO> findAll();
BusinessDateResponse findByType(String type);
BusinessDateDTO findByType(String type);
HashMap<BusinessDateType, LocalDate> getBusinessDates();
}
@@ -24,7 +24,7 @@ import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -40,17 +40,17 @@ import org.springframework.stereotype.Service;
public class BusinessDateReadPlatformServiceImpl implements BusinessDateReadPlatformService {
private final BusinessDateRepository repository;
private final BusinessDateMapper businessDatemapper;
private final BusinessDateMapper businessDateMapper;
private final ConfigurationDomainService configurationDomainService;
@Override
public List<BusinessDateResponse> findAll() {
public List<BusinessDateDTO> findAll() {
List<BusinessDate> businessDateList = repository.findAll();
return businessDatemapper.map(businessDateList);
return businessDateMapper.mapEntity(businessDateList);
}
@Override
public BusinessDateResponse findByType(String type) {
public BusinessDateDTO findByType(String type) {
BusinessDateType businessDateType;
try {
businessDateType = BusinessDateType.valueOf(type);
@@ -63,7 +63,7 @@ public class BusinessDateReadPlatformServiceImpl implements BusinessDateReadPlat
log.error("Business date with the provided type cannot be found {}", type);
throw BusinessDateNotFoundException.notFound(type);
}
return businessDatemapper.map(businessDate.get());
return businessDateMapper.mapEntity(businessDate.get());
}
@Override
@@ -73,8 +73,8 @@ public class BusinessDateReadPlatformServiceImpl implements BusinessDateReadPlat
businessDateMap.put(BusinessDateType.BUSINESS_DATE, tenantDate);
businessDateMap.put(BusinessDateType.COB_DATE, tenantDate);
if (configurationDomainService.isBusinessDateEnabled()) {
final List<BusinessDateResponse> businessDateResponseList = this.findAll();
for (BusinessDateResponse businessDateResponse : businessDateResponseList) {
final List<BusinessDateDTO> businessDateResponseList = this.findAll();
for (BusinessDateDTO businessDateResponse : businessDateResponseList) {
businessDateMap.put(businessDateResponse.getType(), businessDateResponse.getDate());
}
}
@@ -18,14 +18,13 @@
*/
package org.apache.fineract.infrastructure.businessdate.service;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
public interface BusinessDateWritePlatformService {
BusinessDateResponse updateBusinessDate(BusinessDateUpdateRequest request);
BusinessDateDTO updateBusinessDate(BusinessDateDTO businessDateDTO);
void increaseDateByTypeByOneDay(BusinessDateType businessDateType) throws JobExecutionException;
}
@@ -24,13 +24,11 @@ import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateResponse;
import org.apache.fineract.infrastructure.businessdate.data.BusinessDateUpdateRequest;
import org.apache.fineract.infrastructure.businessdate.data.service.BusinessDateDTO;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDate;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateRepository;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.businessdate.exception.BusinessDateActionException;
import org.apache.fineract.infrastructure.businessdate.mapper.BusinessDateUpdateRequestMapper;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
@@ -46,14 +44,10 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
private final BusinessDateRepository repository;
private final ConfigurationDomainService configurationDomainService;
private final BusinessDateUpdateRequestMapper updateRequestMapper;
@Override
public BusinessDateResponse updateBusinessDate(BusinessDateUpdateRequest request) {
BusinessDateResponse businessDateDto = updateRequestMapper.map(request);
public BusinessDateDTO updateBusinessDate(BusinessDateDTO businessDateDto) {
adjustDate(businessDateDto);
return businessDateDto;
}
@@ -65,8 +59,8 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
LocalDate businessDate = businessDateEntity.map(BusinessDate::getDate).orElse(DateUtils.getLocalDateOfTenant());
businessDate = businessDate.plusDays(1);
try {
BusinessDateResponse response = BusinessDateResponse.builder().type(businessDateType)
.description(businessDateType.getDescription()).date(businessDate).build();
BusinessDateDTO response = BusinessDateDTO.builder().type(businessDateType).description(businessDateType.getDescription())
.date(businessDate).build();
adjustDate(response);
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
@@ -86,7 +80,7 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
}
}
private void adjustDate(BusinessDateResponse response) {
private void adjustDate(BusinessDateDTO businessDateDto) {
boolean isCOBDateAdjustmentEnabled = configurationDomainService.isCOBDateAdjustmentEnabled();
boolean isBusinessDateEnabled = configurationDomainService.isBusinessDateEnabled();
@@ -94,16 +88,16 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
log.error("Business date functionality is not enabled!");
throw new BusinessDateActionException("business.date.is.not.enabled", "Business date functionality is not enabled");
}
updateOrCreateBusinessDate(response);
if (isCOBDateAdjustmentEnabled && BusinessDateType.BUSINESS_DATE.equals(response.getType())) {
BusinessDateResponse res = BusinessDateResponse.builder().type(BusinessDateType.COB_DATE)
.description(BusinessDateType.COB_DATE.getDescription()).date(response.getDate().minusDays(1)).build();
updateOrCreateBusinessDate(businessDateDto);
if (isCOBDateAdjustmentEnabled && BusinessDateType.BUSINESS_DATE.equals(businessDateDto.getType())) {
BusinessDateDTO res = BusinessDateDTO.builder().type(BusinessDateType.COB_DATE)
.description(BusinessDateType.COB_DATE.getDescription()).date(businessDateDto.getDate().minusDays(1)).build();
updateOrCreateBusinessDate(res);
response.addAllChanges(res.getChanges());
businessDateDto.addAllChanges(res.getChanges());
}
}
private void updateOrCreateBusinessDate(BusinessDateResponse businessDateDto) {
private void updateOrCreateBusinessDate(BusinessDateDTO businessDateDto) {
BusinessDateType businessDateType = businessDateDto.getType();
Optional<BusinessDate> businessDate = repository.findByType(businessDateType);
@@ -116,7 +110,7 @@ public class BusinessDateWritePlatformServiceImpl implements BusinessDateWritePl
}
}
private void updateBusinessDate(BusinessDate businessDate, BusinessDateResponse businessDateDto) {
private void updateBusinessDate(BusinessDate businessDate, BusinessDateDTO businessDateDto) {
if (DateUtils.isEqual(businessDate.getDate(), businessDateDto.getDate())) {
return;
}