FINERACT-2246: New command processing - document management

This commit is contained in:
Aleksandar Vidakovic
2026-02-16 14:56:40 +01:00
committed by Aleksandar Vidakovic
parent 8a61e30a45
commit 2ff0c62f40
172 changed files with 5928 additions and 3100 deletions
@@ -19,7 +19,13 @@
package org.apache.fineract.infrastructure.bulkimport.data;
import java.time.LocalDate;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public final class ImportData {
@SuppressWarnings("unused")
@@ -27,6 +33,8 @@ public final class ImportData {
@SuppressWarnings("unused")
private Long documentId;
@SuppressWarnings("unused")
private Integer entityType;
@SuppressWarnings("unused")
private String name;
@SuppressWarnings("unused")
private LocalDate importTime;
@@ -18,12 +18,10 @@
*/
package org.apache.fineract.infrastructure.bulkimport.service;
import jakarta.ws.rs.core.Response;
import java.io.InputStream;
import java.util.Collection;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
import org.apache.fineract.infrastructure.bulkimport.data.ImportData;
import org.apache.fineract.infrastructure.documentmanagement.data.DocumentData;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
public interface BulkImportWorkbookService {
@@ -33,8 +31,5 @@ public interface BulkImportWorkbookService {
Collection<ImportData> getImports(GlobalEntityType type);
DocumentData getOutputTemplateLocation(String importDocumentId);
Response getOutputTemplate(String importDocumentId);
ImportData getImport(Long id);
}
@@ -364,6 +364,7 @@ public class FineractProperties {
private List<String> regexWhitelist;
private boolean mimeWhitelistEnabled;
private List<String> mimeWhitelist;
private Integer defaultBufferSize;
private FineractContentFilesystemProperties filesystem;
private FineractContentS3Properties s3;
}
@@ -16,36 +16,36 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.documentmanagement.domain;
package org.apache.fineract.infrastructure.documentmanagement.adapter;
import java.util.HashMap;
import java.util.Map;
import java.io.Serial;
import java.io.Serializable;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
public enum StorageType {
// NOTE: this is a trick to decouple the entity image IDs from the image service
@Deprecated
public interface EntityImageIdAdapter {
FILE_SYSTEM(1), //
S3(2); //
boolean accept(String entityType);
private final Integer value;
Optional<ImageIdResult> get(Long entityId);
StorageType(final Integer value) {
this.value = value;
}
Optional<ImageIdResult> set(Long entityId, Long imageId);
public Integer getValue() {
return this.value;
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
class ImageIdResult implements Serializable {
private static final Map<Integer, StorageType> intToEnumMap = new HashMap<>();
@Serial
private static final long serialVersionUID = 1L;
static {
for (final StorageType type : StorageType.values()) {
intToEnumMap.put(type.value, type);
}
}
public static StorageType fromInt(final int i) {
final StorageType type = intToEnumMap.get(Integer.valueOf(i));
return type;
private Long id;
private String displayName;
}
}
@@ -1,51 +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.documentmanagement.data;
import java.io.Serial;
import java.io.Serializable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.documentmanagement.domain.StorageType;
/**
* Immutable data object representing a user document being managed on the platform.
*/
@Getter
@RequiredArgsConstructor
public class DocumentData implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private final Long id;
private final String parentEntityType;
private final Long parentEntityId;
private final String name;
private final String fileName;
private final Long size;
private final String type;
private final String location;
private final String description;
private final Integer storageType;
public StorageType storageType() {
return StorageType.fromInt(this.storageType);
}
}
@@ -1,61 +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.documentmanagement.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_image")
public final class Image extends AbstractPersistableCustom<Long> {
@Column(name = "location", length = 500)
private String location;
@Column(name = "storage_type_enum")
private Integer storageType;
public Image(final String location, final StorageType storageType) {
this.location = location;
this.storageType = storageType.getValue();
}
Image() {
}
public String getLocation() {
return this.location;
}
public Integer getStorageType() {
return this.storageType;
}
public void setLocation(final String location) {
this.location = location;
}
public void setStorageType(final Integer storageType) {
this.storageType = storageType;
}
}
@@ -40,7 +40,6 @@ public class CurrencyUpdateCommandHandler implements CommandHandler<CurrencyUpda
@Transactional
@Override
public CurrencyUpdateResponse handle(final Command<CurrencyUpdateRequest> command) {
// NOTE: fallback method needs to be in the same class
return writePlatformService.updateAllowedCurrencies(command.getPayload());
}
@@ -22,7 +22,6 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.LocalDate;
@@ -33,7 +32,6 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
import org.apache.fineract.organisation.office.domain.Office;
@Getter
@@ -81,9 +79,8 @@ public class Staff extends AbstractPersistableCustom<Long> {
@JoinColumn(name = "organisational_role_parent_staff_id")
private Staff organisationalRoleParentStaff;
@OneToOne(optional = true)
@JoinColumn(name = "image_id")
private Image image;
@Column(name = "image_id")
private Long imageId;
public static Staff fromJson(final Office staffOffice, final JsonCommand command) {
@@ -262,8 +259,4 @@ public class Staff extends AbstractPersistableCustom<Long> {
return this.office;
}
public void setImage(Image image) {
this.image = image;
}
}
@@ -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.client.adapter;
import static java.util.Objects.nonNull;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.documentmanagement.adapter.EntityImageIdAdapter;
import org.apache.fineract.portfolio.client.domain.ClientRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
@Component
@Deprecated
class ClientImageIdAdapter implements EntityImageIdAdapter {
private static final String ENTITY_TYPE = "clients";
private final ClientRepository repository;
@Override
public boolean accept(String entityType) {
return ENTITY_TYPE.equalsIgnoreCase(entityType);
}
@Override
@Transactional(readOnly = true)
public Optional<ImageIdResult> get(Long entityId) {
return repository.findById(entityId).filter(client -> nonNull(client.getImageId()))
.map(client -> ImageIdResult.builder().id(client.getImageId()).displayName(client.getDisplayName()).build());
}
@Override
@Transactional
public Optional<ImageIdResult> set(Long entityId, Long imageId) {
final var result = get(entityId);
if (imageId == null) {
repository.removeImageId(entityId);
} else {
repository.updateByIdAndImageId(entityId, imageId);
}
return result;
}
}
@@ -27,7 +27,6 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.time.LocalDate;
@@ -46,7 +45,6 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.documentmanagement.domain.Image;
import org.apache.fineract.infrastructure.security.service.RandomPasswordGenerator;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
@@ -72,13 +70,9 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
@JoinColumn(name = "transfer_to_office_id")
private Office transferToOffice;
@OneToOne(optional = true)
@JoinColumn(name = "image_id")
private Image image;
@Column(name = "image_id")
private Long imageId;
/**
* A value from {@link ClientStatus}.
*/
@Column(name = "status_enum", nullable = false)
private Integer status;
@@ -494,10 +488,6 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return this.office.getId();
}
public void setImage(final Image image) {
this.image = image;
}
public String mobileNo() {
return this.mobileNo;
}
@@ -510,10 +500,6 @@ public class Client extends AbstractAuditableWithUTCDateTimeCustom<Long> {
this.mobileNo = mobileNo;
}
public boolean isNotStaff() {
return !isStaff();
}
public boolean isStaff() {
return this.isStaff;
}
@@ -22,6 +22,7 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.client.domain.search.SearchingClientRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -46,4 +47,11 @@ public interface ClientRepository extends JpaRepository<Client, Long>, JpaSpecif
@Query("SELECT c.id FROM Client c WHERE c.externalId = :externalId")
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
@Modifying
@Query("UPDATE Client client SET client.imageId = :imageId WHERE client.id = :clientId")
void updateByIdAndImageId(@Param("clientId") Long staffId, @Param("imageId") Long imageId);
@Modifying
@Query("UPDATE Client client SET client.imageId = null WHERE client.id = :clientId")
void removeImageId(@Param("clientId") Long clientId);
}
@@ -0,0 +1,89 @@
/**
* 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.util;
import jakarta.ws.rs.container.AsyncResponse;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@Slf4j
public final class StreamResponseUtil {
public static String DISPOSITION_TYPE_ATTACHMENT = "attachment";
public static String DISPOSITION_TYPE_INLINE = "inline";
private static final ExecutorService executor = Executors.newCachedThreadPool();
private StreamResponseUtil() {}
public static Response ok(final StreamResponseData content) {
final var stream = new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException {
IOUtils.copy(content.getStream(), out);
}
};
if (StringUtils.isEmpty(content.getDispositionType())) {
return Response.ok(stream, content.getType()).build();
} else {
return Response.ok(stream, content.getType()).header(HttpHeaders.CONTENT_DISPOSITION,
String.format("%s; filename=\"%s\"", content.getDispositionType(), content.getFileName())).build();
}
}
public static Future<?> ok(final AsyncResponse asyncResponse, final StreamResponseData content) {
return executor.submit(() -> {
if (StringUtils.isEmpty(content.getDispositionType())) {
asyncResponse.resume(Response.ok(content.getStream(), content.getType()).build());
} else {
asyncResponse.resume(Response.ok(content.getStream(), content.getType()).header(HttpHeaders.CONTENT_DISPOSITION,
String.format("%s; filename=\"%s\"", content.getDispositionType(), content.getFileName())).build());
}
});
}
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public static final class StreamResponseData {
private InputStream stream;
private String type;
private String fileName;
private String dispositionType;
private Long size;
}
}
@@ -34,7 +34,6 @@
<!-- Core Module Entities -->
<class>org.apache.fineract.useradministration.domain.Role</class>
<class>org.apache.fineract.portfolio.fund.domain.Fund</class>
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Image</class>
<class>org.apache.fineract.organisation.workingdays.domain.WorkingDays</class>
<class>org.apache.fineract.useradministration.domain.Permission</class>
<class>org.apache.fineract.useradministration.domain.AppUserClientMapping</class>