Attachments or File Upload per entity/record

Dear All,

In my billing application, I have a module that handles signed projects/proposals.

The requirement is to be able to upload supporting documents (such as contract pdfs, excel sheets, emails, etc…) per contract.

I have checked and tried the FileUpload field, but this would upload all files to one storage location.
How can I upload files pertaining to a specific entity, and to a specific record within this entity?
And then how I can view and download the uploaded files per project or proposal.

Please advise on the best approach to achieve this.

Many Thanks.

Shady Hanna

Hello!
First of all, you should add a property that will store a list of FileDescriptors. This property should have the OneToMany (or ManyToMany) relationship.

// example
@OneToMany(mappedBy = "project", cascade = CascadeType.ALL
private List<FileDescriptor> supportingDocuments

Then you should add the FileUploadField component to your project/proposal edit screen programmatically. In this case, the component will use MANUAL mode and you can add uploaded files to the entity:

uploadField.addFileUploadSucceedListener(event -> {
    FileDescriptor fd = uploadField.getFileDescriptor();
    try {
        fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
    } catch (FileStorageException e) {
        throw new RuntimeException("Error saving file to FileStorage", e);
    }

    // note that FileDescriptor should be commited before main entity
    // save file descriptor to database
    FileDescriptor commitedFd = dataSupplier.commit(fd);
    project.getSupportingDocuments().add(commitedFd);
});

There are few options to view uploaded documents:

  1. the Embedded component - if you want view document in the app UI
  2. the Export Display - if you just want to have an ability to download documents

Hi Daniil,
Many thanks for your help.
Could you please clarify further how can this be achieved?
I added the following in my entity class:

@OneToMany(mappedBy = "projects", cascade = CascadeType.ALL)
	private List<FileDescriptor> supportingDocs; 

And the following in my editor screen class:

@Override
	public void init(Map<String, Object> params) {
		uploadField.addFileUploadSucceedListener(event -> {
			FileDescriptor fd = uploadField.getFileDescriptor();
			try {
				fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
			} catch (FileStorageException e) {
				throw new RuntimeException("Error saving file to FileStorage", e);
			}

			FileDescriptor commitedFd = dataSupplier.commit(fd);
			getItem().getSupportingDocs().add(commitedFd);
		});
	}

I am getting the following exception:
Exception Description: The attribute [supportingDocs] in entity class [class com.company.billing.entity.Projects] has a mappedBy value of [projects] which does not exist in its owning entity class [class com.haulmont.cuba.core.entity.FileDescriptor]. If the owning entity class is a @MappedSuperclass, this is invalid, and your attribute should reference the correct subclass.
Do I have to edit the core FileDescription entity by adding the needed field? If so, how to do that?
What shall I put in the mappedBy value?
Thanks again for your help.

Your annotation @OneToMany(mappedBy = “projects”, cascade = CascadeType.ALL)
means that FileDescriptor class has a field “projects” when it does not have it.

You can create a new entity class (via Cuba Studio) with a ‘projects’ field that will replace the default FileDescriptor. The only thing you need is to choose FileDescriptor as parent class and tick replace parent checkbox.