timveroOS provides a ready-to-use module for handling client documents as part of a loan application participant.
timveroOS supports two types of documents:
Uploadable Documents: Documents that need to be uploaded to the system at various stages of participant evaluation and verification. These are categorized into mandatory and optional documents.
Signable Documents: Documents generated automatically by the system that require attachment of a document confirming the signature and an explicit note that the document has been signed.
During the evaluation and decision-making process, it may be necessary to request and verify various client documents. timveroOS offers a set of flexible tools to easily configure lists of required documents for upload, depending on the stage and role of the participant.
To enable the system to store documents for a participant, this capability must be explicitly indicated. For this purpose, the system provides a ready-made class EntityDocumentFinder. This utility class offers a wide range of methods for flexible document handling.
@Embeddable
public class EntityDocumentFinder {
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn
private List<EntityDocument> allDocuments;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@MapKey(name = SignableDocument_.DOCUMENT_TYPE)
@JoinColumn
private Map<SignableDocumentType, SignableDocument> signableDocuments;
public List<EntityDocument> getDocuments() {
return allDocuments.stream().filter(EntityDocument::isActive).toList();
}
protected List<EntityDocument> getAllDocuments() {
return allDocuments;
}
public Map<SignableDocumentType, SignableDocument> getSignableDocuments() {
return signableDocuments;
}
/**
* Returns the last added document of supplied type <code>documentType</code> for this document container
* @param documentType desired document type of document
* @return Optional with the last added document or empty Optional
*/
public Optional<EntityDocument> latest(EntityDocumentType documentType) {
return getDocuments().stream()
.filter(document -> document.getDocument() != null)
.filter(document -> document.getDocumentType().equals(documentType))
.max(Comparator.comparing(BaseEntity::getCreatedAt));
}
public Optional<SignableDocument> latest(SignableDocumentType type) {
return Optional.ofNullable(getSignableDocuments().get(type))
.filter(SignableDocument::isActive);
}
/**
* Returns signature of the last added document of passed type <code>documentType</code> for this
* document container
* @param documentType desired document type of document
* @return Optional with signature of the last added document or empty Optional
*/
public Optional<DocumentSignature> signatureOfLatest(SignableDocumentType documentType) {
return latest(documentType).map(SignableDocument::getSignature);
}
/**
* Checks whether the last added document of passed type is signed.
* @param documentType desired document type of document
* @return true when the last added document is signed or false when it's ot signed or
* when container doesn't include any documents of passed type
*/
public boolean isLatestSigned(SignableDocumentType documentType) {
return latest(documentType).map(SignableDocument::isSigned).orElse(false);
}
/**
* Check whether the document of the passed type is added.
* @param documentType desired document type of document
* @return true if document is present and false otherwise
*/
public boolean isPresent(EntityDocumentType documentType) {
return getDocuments().stream()
.filter(document -> document.getDocument() != null)
.anyMatch(document -> document.getDocumentType().equals(documentType));
}
/**
* Check whether the document of the passed type is <b>not</b> added.
* @param documentType desired document type of document
* @return true if document is missing and false if is added
*/
public boolean isMissing(EntityDocumentType documentType) {
return getDocuments().stream()
.filter(document -> document.getDocument() != null)
.noneMatch(document -> document.getDocumentType().equals(documentType));
}
}
This class should be added to the Participant entity as an embedded object as follows:
@Entity
@Table(name = "participant")
public class Participant extends BaseEntity<Long> {
...
@Embedded
private EntityDocumentFinder documentFinder;
...
This configures the document storage for the specified entity.
1. Display documents
To make the UI interface available for working with documents, we need to enable the display of the Documents tab for the participant. For this, the ParticipantDocumentsTab class must be added ParticipantDocumentsTab
@RequestMapping("/documents")
@Controller
public class ParticipantDocumentsTab extends EntityDocumentTabController<Long, Participant> {
@Override
public boolean isVisible(Participant entity) {
return true;
}
}
As a result, a tab for the participant will be displayed.
2. Document Types
Now we can proceed to configuring the request for a mandatory document. First, we need to specify the type of document that can be uploaded in the system. To do this, we need to create a configuration class where document types will be declared. We will create an instance of EntityDocumentType with a unique code for the document type ``Identity Document.''
@Configuration
public class EntityDocumentTypeConfiguration {
public static final EntityDocumentType ID_SCAN = new EntityDocumentType("ID_SCAN");
}
EntityDocumentType is a pre-built class in timveroOS used to label uploadable documents.
To indicate to the system at which stage which document should be requested, timveroOS provides the DocumentTypeAssociation interface and methods:
forEntityClass(Class entityClass): A method that takes the target entity class as a parameter and returns a builder configurator with methods.
required(EntityDocumentType documentType): A method that takes the document type as a parameter and specifies that this type of document is mandatory for upload.
uploadable(EntityDocumentType documentType): A method that takes the document type as a parameter and specifies that this type of document is available but not mandatory for upload.
predicate(Predicate predicate): A method that allows adding conditions under which the system will request the specified document type in the required method as mandatory.
build: A method that returns the final configuration.
Here is the DocumentTypeAssociation implementation:
public static <E> Builder<E> forEntityClass(Class<E> entityClass) {
return new Builder<>(entityClass);
}
public static class Builder<E> {
private final Class<E> entityClass;
private Predicate<E> predicate;
private List<EntityDocumentType> uploadableTypes = new ArrayList<>();
private List<EntityDocumentType> requiredTypes = new ArrayList<>();
public Builder<E> uploadable(EntityDocumentType documentType) {
this.uploadableTypes.add(documentType);
return this;
}
public Builder<E> required(EntityDocumentType documentType) {
this.requiredTypes.add(documentType);
return this;
}
public Builder<E> predicate(Predicate<E> predicate) {
this.predicate = predicate
return this;
}
public DocumentTypeAssociation<E> build() {
return new DocumentTypeAssociationImpl<>(entityClass, predicate,
uploadableTypes, requiredTypes);
}
}
}
Here is an example of how to configure a request for a mandatory identity document for a participant with the status New,'' with the option to upload a document of type Other'' in this status. To do this, we need to declare a @Bean named documents with the necessary configurations in the EntityDocumentTypeConfiguration class.
@Configuration
public class EntityDocumentTypeConfiguration {
public static final EntityDocumentType ID_SCAN = new EntityDocumentType("ID_SCAN");
@Bean
public List<DocumentTypeAssociation<Participant>> documents() {
return List.of(
DocumentTypeAssociation.forEntityClass(Participant.class) // Target entity
.required(ID_SCAN) // Required document type
.predicate(p -> p.getStatus().in(NEW)) // Participant in New status
.build());
}
}
As a result, the system will analyze this configuration and request an identity document from participants with the status ``New.''
Once the document is uploaded, the system will provide the ability to view or delete the uploaded document.
Besides mandatory documents, the system allows configuring the process of working with documents that are generated by the system and require a physical signature.
3. Document Generation
To enable the system to generate a document, you need to set up a document template. To do this, you need to create a template type, a Java class annotated with @Component that extends the DocumentCategory class. This class is responsible for editing the template, preparing the data model, and generating the document.
@Component
public class ConsentDocumentCategory extends
DocumentCategory<Long, Participant, DocumentModel> {
public static final DocumentType TYPE = new DocumentType("CONSENT_DOCUMENT"); // Type
@Override
public DocumentType getType() {
return TYPE;
}
@Override
protected DocumentModel getModel(Participant entity) {
return new DocumentModel(); // Data model
}
}
As a result, the system will have the ability to create a template according to which the document will be generated.
After the preliminary system setup, we can configure when the system should generate and request the signing of the required document.
To do this, we need to specify the type of signable document available in the system. For this, we need to add an instance of SignableDocumentType to our configuration class, specifying the unique code for the document type ``Consent for Verification''.
SignableDocumentType is a class used to mark signable documents. When creating this instance, the input parameters are:
name: the unique code of the signable document
category: the template type
@Configuration
public class EntityDocumentTypeConfiguration {
public static final EntityDocumentType ID_SCAN = new EntityDocumentType("ID_SCAN");
public static final SignableDocumentType CONSENT_DOCUMENT =
new SignableDocumentType("CONSENT_DOCUMENT", ConsentDocumentCategory.TYPE);
@Bean
public List<DocumentTypeAssociation<Participant>> documents() {
return List.of(
DocumentTypeAssociation.forEntityClass(Participant.class) // Target entity
.required(ID_SCAN) // Required document type
.predicate(p -> p.getStatus().in(NEW)) // Participant in New status
.build());
}
}
timveroOS provides a ready-made service for working with signable documents, which among other things has two generation methods:
generate(T target, SignableDocumentType type): This method takes the target entity and the type of document to be generated as input. The document is generated based on the latest version of the template.
generate(T target, SignableDocumentType type, UUID template): This method takes the target entity, the type of document to be generated, and the unique identifier of the template version as input.
4. Add documents to workflow
timveroOS allows flexible configuration of the document generation and signing process, allowing documents to be generated both automatically and manually. In this section, we will look at an example of generating a document at the moment when the participant transitions to the Pre-approved status.
To do this, we need to create a Java class PreapprovedParticipantChecker, implement the EntityChecker class, and in the perform method, call the document generation method, passing the target entity and the type of document to be generated.
After the participant transitions to the Pre-approved status, the system will analyze the configuration and generate the required document, which will be available for viewing and downloading.
5. Document Actions
To enable the system to upload/delete/sign such types of documents, it is necessary to implement the corresponding actions.