This SDK empowers developers to leverage the timveroOS platform with flexibility, allowing extensive customization through code. By utilizing these components, teams can create bespoke financial solutions that align perfectly with their business goals.

For a complete working example, see the timvero-example project.

1. Data model setup

This section describes how to set up and manage the data model using SQL file autogeneration and Flyway migrations.

1.1. SQL File Autogeneration

The platform automatically generates SQL files based on your entity definitions. This process creates the necessary database schema files that can be used with Flyway for database migrations.

Automatic Generation Process

After the class is configured, run the application. The system will analyze changes in the data model of Java classes and generate an SQL script with the necessary changes V241012192920.sql in the project’s home directory (application.home=path), in the subdirectory hbm2ddl.

The generation process works as follows:

  1. Entity Analysis: The system scans all JPA entity classes for changes

  2. Schema Comparison: Compares current entity definitions with the existing database schema

  3. SQL Generation: Creates appropriate DDL statements (CREATE, ALTER, DROP) for detected changes

  4. File Creation: Generates timestamped migration files in the hbm2ddl directory

  5. Migration Integration: Files can be moved to Flyway migration directory for deployment

Entity Definition Example

Let’s look at the Participant entity as an example:

Unresolved directive in _data-model.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/entity/Participant.java[tags=entity]
    // getters and setters...
}
Enum Definitions

The entity uses several enums that define the possible values:

ParticipantStatus enum
Unresolved directive in _data-model.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/entity/ParticipantStatus.java[tags=enum]
Employment enum
Unresolved directive in _data-model.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/entity/Employment.java[tags=enum]
Periodicity enum
Unresolved directive in _data-model.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/entity/Periodicity.java[tags=enum]

1.2. Flyway Migration Integration

Migration File Structure

Flyway migration files are stored in the src/main/resources/db/migration/ directory and follow the naming convention:

V{version}__{description}.sql

For example:

V250530170222__init.sql
V250609220043__participantStatus.sql
Generated SQL Example

Based on the Participant entity definition, the system generates the following SQL:

    create table participant (
        id uuid not null,
        created_at timestamp(6) with time zone not null,
        updated_at timestamp(6) with time zone not null,
        employment varchar(255) not null,
        how_often_income_is_paid varchar(255) not null,
        monthly_outgoings_currency varchar(3),
        monthly_outgoings_number numeric(19,2),
        total_annual_income_currency varchar(3),
        total_annual_income_number numeric(19,2),
        created_by uuid,
        updated_by uuid,
        -- Foreign key to application table
        application_id uuid not null,
        -- Foreign key to client table
        client_id uuid not null,
        primary key (id)
    );

    -- Foreign key constraints for participant table
    -- Links participant to their associated loan application
    alter table if exists participant 
       add constraint FKa8akyngsbkcpy4ev19q53x56h 
       foreign key (application_id) 
       references application;

    -- Links participant to their client profile containing personal information
    alter table if exists participant 
       add constraint FKcmejtugfqk653qthh0jalsx54 
       foreign key (client_id) 
       references client;
Migration Workflow
  1. Entity Definition: Define your entity classes with appropriate JPA annotations

  2. Application Execution: Run the application to trigger the automatic analysis process

  3. SQL Autogeneration: The platform analyzes entity changes and generates SQL scripts in the hbm2ddl subdirectory

  4. Migration File Preparation: Move generated SQL files from hbm2ddl to the Flyway migration directory (src/main/resources/db/migration/)

  5. File Naming: Rename files to follow Flyway convention: V{version}__{description}.sql

  6. Flyway Execution: During application startup, Flyway executes pending migrations in version order

  7. Schema Versioning: Database schema version is tracked automatically in the schema_version table

Best Practices
  • Incremental Changes: Create separate migration files for each schema change

  • Descriptive Names: Use clear, descriptive names for migration files

  • Testing: Test migrations on development environments before production

  • Rollback Strategy: Consider rollback scenarios when designing schema changes

Migration File Example

Here’s an actual migration file that adds participant status functionality:

V250609220043__participantStatus.sql
-- Migration: Add participant status functionality

-- Add status column to audit table (for historical tracking)
alter table if exists aud_participant 
   add column status varchar(255);

-- Add status column to main participant table
alter table if exists participant 
   add column status varchar(255);

-- Set default status for all existing participants
update participant set status = 'NEW';

-- Make status column mandatory after setting default values
alter table if exists participant 
   alter column status set not null;

This approach ensures that your database schema evolves in a controlled, versioned manner while maintaining data integrity throughout the development lifecycle.

2. Form classes setup and usage

This section describes how to set up and manage form classes for data input validation and processing in the application.

2.1. Form Class Architecture

The platform uses form classes to handle user input validation, data binding, and form processing. Form classes serve as DTOs (Data Transfer Objects) that define the structure and validation rules for user interfaces.

Form Class Hierarchy

The application uses a hierarchical form structure:

  • Main Forms: Top-level forms like ClientForm and ApplicationForm

  • Nested Forms: Component forms like IndividualInfoForm and ContactInfoForm

  • Validation: Bean Validation (JSR-303) annotations for field validation

Form Class Examples
ClientForm Structure

The ClientForm class handles client registration and profile management:

Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/form/ClientForm.java[tags=client-form]
ApplicationForm Structure

The ApplicationForm class manages loan application data:

Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/application/form/ApplicationForm.java[tags=application-form]
Nested Form Components
IndividualInfoForm

Personal information component:

Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/form/IndividualInfoForm.java[tags=individual-info-form]
ContactInfoForm

Contact information component:

Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/form/ContactInfoForm.java[tags=contact-info-form]
ParticipantForm

Financial participant information:

Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/form/ParticipantForm.java[tags=participant-form]
Validation Annotations Used

The form classes use standard Bean Validation (JSR-303) annotations:

Validation Annotations in Use
@NotNull          // Field cannot be null
@NotBlank         // String field cannot be null, empty, or whitespace only
@Email            // Valid email format
@PastOrPresent    // Date must be in the past or present
@Valid            // Cascade validation to nested objects
@Phone            // Custom phone validation (platform-specific)

2.2. Form Processing Architecture

Action Classes

The platform uses generic action classes to handle form operations:

Generic Action Structure
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/action/CreateClientAction.java[tags=action-class]
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/action/EditClientAction.java[tags=action-class]

These actions are parameterized with: * ID Type: UUID - The entity identifier type * Entity Type: Client - The JPA entity class * Form Type: ClientForm - The form DTO class

Form Service Layer

Actions delegate form processing to specialized service classes:

EntityFormService Usage
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/form/ClientFormService.java[tags=form-service]

The EntityFormService provides: * Entity to Form mapping: Converting entities to form objects for editing * Form to Entity mapping: Converting form submissions to entity objects * Validation integration: Coordinating with Bean Validation * Persistence operations: Saving and updating entities

MapStruct Mappers

Form-to-entity conversion is handled by MapStruct mappers:

Mapper Structure
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/client/form/ClientFormMapper.java[tags=mapper]
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/application/form/ApplicationFormMapper.java[tags=mapper]
Unresolved directive in _forms.adoc - include::/opt/teamcity-agent3/work/8187ccecf864e5bd/src/main/java/participant/form/ParticipantFormMapper.java[tags=mapper]

MapStruct automatically generates implementation classes that provide: * Bidirectional mapping: Entity ↔ Form conversion * Nested object mapping: Automatic handling of complex object structures * Type conversion: Automatic conversion between compatible types * Null handling: Safe mapping of optional fields

For detailed information about MapStruct features and configuration, see the official MapStruct documentation.

Processing Flow

The complete form processing flow:

  1. Action Invocation: CreateClientAction or EditClientAction is called

  2. Service Delegation: Action delegates to ClientFormService

  3. Mapper Usage: Service uses ClientFormMapper for conversions

  4. Entity Operations: Service performs database operations

  5. Response Generation: Converted data is returned to the controller

Example Flow for Edit Operation
EditClientAction<UUID, Client, ClientForm>
    ↓
ClientFormService.prepareEditModel(UUID id)
    ↓
ClientFormMapper.entityToForm(Client entity)
    ↓
ClientForm (ready for template rendering)
Example Flow for Save Operation
CreateClientAction<UUID, Client, ClientForm>
    ↓
ClientFormService.save(ClientForm form)
    ↓
ClientFormMapper.formToEntity(ClientForm form)
    ↓
Client entity (persisted to database)

2.3. Template Integration

Form classes integrate with HTML templates using Thymeleaf for rendering user interfaces. The templates use nested field access (dot notation) and reusable form components for consistent styling and validation.

For detailed information about HTML template integration, form components, and Thymeleaf usage, see [_html_template_integration].

3. HTML Template Integration

This section describes how form classes integrate with HTML templates using Thymeleaf for rendering user interfaces.

3.1. Template Structure

The application uses Thymeleaf templates to render forms with automatic data binding and validation integration.

Client Form Template

The client edit form demonstrates nested form structure:

        <h2 class="form-group__title" th:text="#{client.clientInfo}">Personal
            Information</h2>
        <th:block
            th:insert="~{/form/components :: text(#{client.individualInfo.fullName},
                'individualInfo.fullName', 'v-required v-name')}"
            th:with="maxlength = 120" />
        <th:block
            th:insert="~{/form/components :: text(#{client.individualInfo.nationalId},
                'individualInfo.nationalId', 'v-required')}" />
        <th:block
            th:insert="~{/form/components :: date (#{client.individualInfo.birthDate},
                'individualInfo.dateOfBirth', '')}"
            th:with="maxDate = ${#dates.format(#dates.createNow())}" />
        <th:block
            th:insert="~{/form/components :: select(#{client.address.stateOfResidence},
                'individualInfo.residenceCountry', ${countries})}" />

        <h2 class="form-group__title" th:text="#{client.contactInfo}">Contact
            Information</h2>
        <th:block
            th:insert="~{/form/components :: text(#{client.contactInfo.email},
                'contactInfo.email', 'v-required v-email')}" />
        <th:block
            th:insert="~{/form/components :: text(#{client.contactInfo.phone},
                'contactInfo.phone', 'v-required v-phone')}" />

Key features: * Nested field access: Uses dot notation like individualInfo.fullName * Validation classes: CSS classes for client-side validation (v-required, v-email) * Component reuse: Uses Thymeleaf fragments for consistent field rendering

Application Form Template

The application edit form shows financial data handling:

        <h2 th:text="#{application.borrowerInfo}">Borrower
            Information</h2>
        <th:block
            th:insert="~{/form/components :: select(#{participant.employment},
                'borrowerParticipant.employment', ${employmentTypes})}" />
        <th:block
            th:insert="~{/form/components :: select(#{participant.howOftenIncomeIsPaid},
                'borrowerParticipant.howOftenIncomeIsPaid', ${periodicities})}" />

        <h2 class="mt-10" th:text="#{participant.financialInfo}">Financial
            Information</h2>
        <th:block
            th:insert="~{/form/components :: amount(#{participant.totalAnnualIncome},
                'borrowerParticipant.totalAnnualIncome', 'v-required')}" />
        <th:block
            th:insert="~{/form/components :: amount(#{participant.monthlyOutgoings},
                'borrowerParticipant.monthlyOutgoings', '')}" />

Features: * Enum handling: Select dropdowns for employment and periodicities * Monetary amounts: Special amount component for financial fields * Nested participant: Access to borrowerParticipant fields

3.2. Form Component System

The platform uses Thymeleaf fragments for consistent form rendering across all forms. These components are defined in /form/components.html and provide standardized UI elements with built-in validation support.

Available Form Components
Text Input Component
~{/form/components :: text(name, fieldname, inputclass)}
Table 1. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the input field (e.g., #{client.individualInfo.fullName})

fieldname

String

Field path for data binding (e.g., 'individualInfo.fullName')

inputclass

String

CSS validation classes (e.g., 'v-required v-armenian-name')

Additional Variables
  • maxlength - Maximum character limit (default: 256)

  • minlength - Minimum character limit (default: 0)

  • placeholder - Placeholder text for the input field

Usage Example
<th:block th:insert="~{/form/components :: text(
    #{client.individualInfo.fullName},
    'individualInfo.fullName',
    'v-required v-armenian-name')}"
    th:with="maxlength = 120, placeholder = #{placeholder.fullName}" />
Select Dropdown Component
~{/form/components :: select(name, fieldname, values)}
Table 2. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the select field

fieldname

String

Field path for data binding

values

Collection/Map

Options data (Map for key-value pairs, Collection for simple lists)

Usage Example
<th:block th:insert="~{/form/components :: select(
    #{client.address.stateOfResidence},
    'individualInfo.residenceCountry',
    ${countries})}" />
Date Picker Component
~{/form/components :: date(name, fieldname, inputclass)}
Table 3. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the date field

fieldname

String

Field path for data binding

inputclass

String

CSS validation classes (optional)

Additional Variables
  • maxDate - Maximum selectable date

  • minDate - Minimum selectable date

  • startDate - Default selected date

Usage Example
<th:block th:insert="~{/form/components :: date(
    #{client.individualInfo.birthDate},
    'individualInfo.dateOfBirth',
    'v-required')}"
    th:with="maxDate = ${#dates.format(#dates.createNow())}" />
Amount/Currency Component
~{/form/components :: amount(name, fieldname, inputclass)}
Table 4. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the amount field

fieldname

String

Field path for data binding

inputclass

String

CSS validation classes (e.g., 'v-required v-positive')

Additional Variables
  • inputAmountPrefix - Prefix for field IDs (optional)

  • currencies - Available currency options

Usage Example
<th:block th:insert="~{/form/components :: amount(
    #{participant.totalAnnualIncome},
    'borrowerParticipant.totalAnnualIncome',
    'v-required v-armenian-tax-id')}" />
Checkbox Component
~{/form/components :: checkbox(name, fieldname, inputclass)}
Table 5. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the checkbox

fieldname

String

Field path for data binding

inputclass

String

CSS classes for styling/validation

Usage Example
<th:block th:insert="~{/form/components :: checkbox(
    #{client.agreeToTerms},
    'agreeToTerms',
    'v-required')}" />
Textarea Component
~{/form/components :: textarea(name, fieldname, inputclass)}
Table 6. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the textarea

fieldname

String

Field path for data binding

inputclass

String

CSS validation classes

Additional Variables
  • rows - Number of textarea rows (default: 5)

  • maxlength - Maximum character limit

Usage Example
<th:block th:insert="~{/form/components :: textarea(
    #{application.comments},
    'comments',
    'v-required')}"
    th:with="rows = 3, maxlength = 500" />
Radio Button Component
~{/form/components :: radio(name, fieldname, params)}
Table 7. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the radio group

fieldname

String

Field path for data binding

params

Map

Key-value pairs for radio options

Usage Example
<th:block th:insert="~{/form/components :: radio(
    #{client.gender},
    'gender',
    ${genderOptions})}" />
File Upload Component
~{/form/components :: fileInput(name, filelabel, fieldname, inputclass)}
Table 8. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the file input

filelabel

String

Button text for file selection

fieldname

String

Field path for data binding

inputclass

String

CSS classes for styling/validation

Usage Example
<th:block th:insert="~{/form/components :: fileInput(
    #{document.upload},
    #{button.chooseFile},
    'documentFile',
    'v-required')}" />
Read-only Component
~{/form/components :: readonly(name, fieldname, inputclass)}
Table 9. Parameters
Parameter Type Description

name

String (i18n key)

Label text for the read-only field

fieldname

String

Field path for data binding

inputclass

String

CSS classes for styling

Usage Example
<th:block th:insert="~{/form/components :: readonly(
    #{client.id},
    'id',
    '')}" />
Component Benefits

This component system ensures: * Consistency: All forms use the same styling and behavior * Maintainability: Changes to form components affect all forms * Validation Integration: Client-side validation works seamlessly * Accessibility: Standard form components ensure accessibility compliance * Internationalization: Built-in support for i18n message keys * Reusability: Components can be used across different forms and contexts

3.3. Client-Side Validation Classes

The platform provides CSS-based validation classes that integrate with jQuery Validation for client-side form validation:

Standard Validation Classes
Table 10. Built-in Validation Rules
CSS Class Description Usage Example

v-required

Field is mandatory and cannot be empty

'individualInfo.fullName', 'v-required v-name'

v-number

Field must contain a valid number

'amount', 'v-number'

v-digits

Field must contain only digits (0-9)

'quantity', 'v-digits'

v-email

Field must contain a valid email address

'contactInfo.email', 'v-required v-email'

v-url

Field must contain a valid URL

'website', 'v-url'

v-phone

Field must contain a valid phone number

'contactInfo.phone', 'v-required v-phone'

v-positive

Field must contain a positive number (> 0)

'totalAnnualIncome', 'v-required v-positive'

v-name

Field must contain valid name characters (letters, spaces, hyphens, apostrophes), max 256 characters

'individualInfo.fullName', 'v-required v-name'

Custom Validation Methods

The platform extends jQuery Validation with custom validation methods:

Custom Validators
// Armenian name validation (Armenian letters, spaces, hyphens only)
$.validator.addMethod('armenianName', function(value, element) {
  const ARMENIAN_NAME_REGEX = /^[\u0531-\u0556\u0561-\u0587\s\-']+$/;
  return this.optional(element) || ARMENIAN_NAME_REGEX.test(value);
});

// Tax identification number validation (Armenian format)
$.validator.addMethod('armenianTaxId', function(value, element) {
  const TAX_ID_REGEX = /^\d{8}$/;
  return this.optional(element) || TAX_ID_REGEX.test(value);
});

// Armenian postal code validation
$.validator.addMethod('armenianPostal', function(value, element) {
  const POSTAL_REGEX = /^\d{4}$/;
  return this.optional(element) || POSTAL_REGEX.test(value);
});
Validation Class Rules Mapping

The CSS classes are mapped to validation rules using jQuery Validation:

Class Rules Configuration
$.validator.addClassRules({
  'v-armenian-name': {armenianName: true, maxlength: 256},
  'v-armenian-tax-id': {armenianTaxId: true},
  'v-armenian-postal': {armenianPostal: true},
});
Usage in Templates

Validation classes are applied as the third parameter in form component calls:

Template Usage Examples
<!-- Required text field with name validation -->
<th:block th:insert="~{/form/components :: text(
  #{client.individualInfo.fullName},
  'individualInfo.fullName',
  'v-required v-name')}" />

<!-- Required email field -->
<th:block th:insert="~{/form/components :: text(
  #{client.contactInfo.email},
  'contactInfo.email',
  'v-required v-email')}" />

<!-- Required positive amount field -->
<th:block th:insert="~{/form/components :: amount(
  #{participant.totalAnnualIncome},
  'borrowerParticipant.totalAnnualIncome',
  'v-required v-positive')}" />
Combining Validation Classes

Multiple validation classes can be combined using space separation:

  • 'v-required v-email' - Required email field

  • 'v-required v-name' - Required name field with character validation

  • 'v-required v-positive' - Required positive number field

  • 'v-number v-positive' - Optional positive number field

4. DataSource Integration

This section describes how to implement and use DataSource interfaces for integrating external data sources into the Feature Store system.

4.1. DataSource Overview

The DataSource framework provides a standardized way to fetch and process data from external sources. It consists of two main interfaces:

  • DataSource<E> - Basic interface for fetching raw data with getData(E subject) method

  • MappedDataSource<E, T> - Extended interface that adds automatic parsing to typed objects with parseRecord(Content data) and getType() methods

When external data is unavailable or the service returns an error, implementations should throw DataUnavaliableException to indicate the data cannot be retrieved.

4.2. Implementation Example: GitHub DataSource

The GitHub DataSource demonstrates a complete implementation that fetches user data from the GitHub API.

Class Structure
@Service(GithubDataSource.DATASOURCE_NAME)
public class GithubDataSource implements MappedDataSource<GithubDataSourceSubject, GithubUser> {
    public static final String DATASOURCE_NAME = "github";

    private final RestTemplate restTemplate = new RestTemplate();
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final String GITHUB_API_BASE_URL = "https://api.github.com";
Data Retrieval Implementation
    @Override
    public Content getData(GithubDataSourceSubject subject) throws Exception {
        try {
            String url = GITHUB_API_BASE_URL + "/users/" + subject.getGithubUsername();
            ResponseEntity<byte[]> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                createHttpEntity(),
                byte[].class
            );
            return new Content(response.getBody(), MediaType.APPLICATION_JSON_VALUE) ;
        } catch (HttpClientErrorException.NotFound e) {
            throw new DataUnavaliableException("User not found: " + subject.getGithubUsername());
        }
    }
Data Parsing Implementation
    @Override
    public GithubUser parseRecord(Content data) throws Exception {
        return objectMapper.readValue(data.getBody(), GithubUser.class);
    }
Subject and Target Objects

The subject object defines what data to fetch. In this example, it’s a simple wrapper for the GitHub username:

package com.timvero.example.admin.risk.github;

public interface GithubDataSourceSubject {

    String getGithubUsername();

}

The target object represents the parsed data structure:

package com.timvero.example.admin.risk.github;

import com.fasterxml.jackson.annotation.JsonProperty;

public class GithubUser {
    private String login;
    private String name;
    @JsonProperty("followers")
    private int followersCount;
    @JsonProperty("following")
    private int followingCount;
    @JsonProperty("public_repos")
    private int publicRepos;
    @JsonProperty("avatar_url")
    private String avatarUrl;
In the platform, Participant entity implements the DataSource Subject pattern and can be used directly as a subject for various data sources.

5. Document Management

This section describes how to implement document management functionality for entities in the platform, including document type associations and UI integration.

5.1. Document System Overview

The document management system allows entities to have associated documents with configurable upload and requirement rules. The system consists of:

  • HasDocuments - Interface marking entities that can have documents

  • DocumentTypeAssociation - Configuration for document types per entity

  • EntityDocumentTabController - UI integration for document management tabs

5.2. Document Type Configuration

Document types are configured using DocumentTypeAssociation with a builder pattern that allows defining uploadable and required document types with conditional logic.

Document Type Associations
Required Document Configuration

Documents that must be uploaded based on entity conditions:

    private static final Predicate<Participant> PARTICIPANT_GUARANTOR =
        participant -> participant.getRoles().contains(ParticipantRole.GUARANTOR);
    private static final Predicate<Participant> PARTICIPANT_BORROWER =
        participant -> participant.getRoles().contains(ParticipantRole.BORROWER);

    @Bean
    DocumentTypeAssociation<Participant> idScanDocumentTypeAssociations() {
        return DocumentTypeAssociation.forEntityClass(Participant.class).required(ID_SCAN)
            .predicate(participant -> participant.getStatus().in(ParticipantStatus.NEW))
            .predicate(PARTICIPANT_GUARANTOR.or(PARTICIPANT_BORROWER)).build();
    }

This configuration: * Makes ID_SCAN document required * Only applies when participant status is NEW * Only applies to participants with GUARANTOR or BORROWER roles

Optional Document Configuration

Documents that can be uploaded without restrictions:

    @Bean
    DocumentTypeAssociation<Participant> otherDocumentTypeAssociations() {
        return DocumentTypeAssociation.forEntityClass(Participant.class).uploadable(OTHER).build();
    }

This allows OTHER document type to be uploaded for any participant without conditions.

Document Type Definitions

Document types are defined as constants in the configuration:

    public static final EntityDocumentType OTHER = new EntityDocumentType("OTHER");
    public static final EntityDocumentType ID_SCAN = new EntityDocumentType("ID_SCAN");

5.3. UI Integration

Document Tab Implementation

To display document management interface, create a tab controller extending EntityDocumentTabController:

package com.timvero.example.admin.participant.tab;

import com.timvero.example.admin.participant.entity.Participant;
import com.timvero.web.common.tab.EntityDocumentTabController;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Controller;

@Controller
@Order(1500)
public class ParticipantDocumentsTab extends EntityDocumentTabController<Participant> {

    @Override
    public boolean isVisible(Participant entity) {
        return true;
    }
}

Key features: * @Order(1500) - Controls tab display order in the UI * isVisible() - Determines when the tab should be shown * Automatic functionality - Upload, download, and delete operations are handled automatically

Entity Requirements

Entities must implement the HasDocuments interface:

public interface HasDocuments extends Persistable<UUID> {
    // No additional methods required
}

5.4. Builder Pattern Usage

The DocumentTypeAssociation uses a fluent builder pattern:

Available Methods
  • uploadable(EntityDocumentType) - Adds a document type that can be uploaded

  • required(EntityDocumentType) - Adds a document type that must be uploaded

  • predicate(Predicate<E>) - Adds conditional logic for when the association applies

Predicate Chaining

Multiple predicates can be combined:

    public static final EntityDocumentType ID_SCAN = new EntityDocumentType("ID_SCAN");

    private static final Predicate<Participant> PARTICIPANT_GUARANTOR =
        participant -> participant.getRoles().contains(ParticipantRole.GUARANTOR);
    private static final Predicate<Participant> PARTICIPANT_BORROWER =

Predicates are combined using AND logic - all conditions must be true.

5.5. Complete Implementation Example

To implement document management for an entity:

  1. Entity implements HasDocuments:

@Entity
public class Participant implements HasDocuments {
    // Entity implementation
}
  1. Create document type configuration:

@Configuration
public class ParticipantDocumentTypesConfiguration {

    @Bean
    DocumentTypeAssociation<Participant> requiredDocuments() {
        return DocumentTypeAssociation.forEntityClass(Participant.class)
            .required(ID_SCAN)
            .predicate(participant -> participant.getStatus() == ParticipantStatus.NEW)
            .build();
    }

    @Bean
    DocumentTypeAssociation<Participant> optionalDocuments() {
        return DocumentTypeAssociation.forEntityClass(Participant.class)
            .uploadable(OTHER)
            .build();
    }
}
  1. Create document tab controller:

@Controller
@Order(1500)
public class ParticipantDocumentsTab extends EntityDocumentTabController<Participant> {

    @Override
    public boolean isVisible(Participant entity) {
        return true; // Always show documents tab
    }
}

This provides a complete document management system with conditional requirements and integrated UI.