Skip to content

Mass signing and encrypting

Available since v3.0.2 Live Sample GitHub

Mass signing and encrypting is a mass operation that signs and encrypts the files of the selected rows with CryptoPro. All operation types of Signing and encrypting are available: signing, encrypting, signing and encrypting, encrypting and signing.

To use this functionality, the user must have the required software CryptoPro installed.

How does it look?

[:material-play-circle: Live Sample](http://code-samples.cxbox.org/ui/#/screen/myexample3713/view/myexample3713massencryptgh){:target="_blank"}
[:fontawesome-brands-github: GitHub](https://github.com/CX-Box/cxbox-code-samples/tree/main/src/main/java/org/demo/documentation/feature/encryptsign/encrypt/mass){:target="_blank"}
<video controls width="800">
<source src="/widget/type/property/massoperations/sign/04_encrypt_gh_3713.mp4" type="video/mp4">
</video>
[:material-play-circle: Live Sample](http://code-samples.cxbox.org/ui/#/screen/myexample3714/view/myexample3714masssignencryptgh){:target="_blank"}
[:fontawesome-brands-github: GitHub](https://github.com/CX-Box/cxbox-code-samples/tree/main/src/main/java/org/demo/documentation/feature/encryptsign/signencrypt/mass){:target="_blank"}
<video controls width="800">
<source src="/widget/type/property/massoperations/sign/06_sign_encrypt_gh_3714.mp4" type="video/mp4">
</video>

Basic

It saves the user from signing or encrypting documents one by one: the certificates are selected once, and the files of all selected rows are processed in one run. For example:

  • Sign — sign a batch of contracts, acts or invoices with an electronic signature.
  • Encrypt — encrypt the documents of the selected rows before sending them to a counterparty.
  • Sign and encrypt / Encrypt and sign — prepare a batch of documents for sending in one action, in the required order of operations.

The user sees the result for each row and can repeat the operation for the rows that failed, see Retry for failed rows.

This function is available for widgets:

After clicking on the mass action, the user goes through the steps:

  1. Select rows
  2. Review rows
  3. Certificate selection
  4. Confirm operation (this step is optional)
  5. View results

Specifics

  • The mass action has the same settings as the action for one document: an item in options.cryptoGenerator with the name of the mass action.
  • The user selects the certificates once for all rows.
  • The files are processed row by row: the file of a row is signed or encrypted, and the new files are saved to the file storage. When all rows are processed, the action is sent once for all of them.
  • A row that cannot be processed is skipped, the other rows are processed: a row without a file, a CryptoPro error, a file that was not saved.
  • In GroupingHierarchy only rows with records can be selected: a group row without its own record has a disabled checkbox. In the samples the rows are grouped by status.
  • The file preview works on all steps. Its arrows go through the rows, the active row does not change.
  • The postAction of the mass action runs when the user clicks Close on Step 5.
  • The general rules of mass operations apply: the action is executed using the ID of the first selected row, and on the server it runs in a single transaction — if an error occurs, the changes are rolled back for all rows.

How to add?

Example

Step 1 Add a mass action to the corresponding Service.

  • data.getMassIds_() gives only the rows processed on the frontend. The files of a row are in its options: mass.getOption(MassOptionType.SIGNATURE_FILE_ID) and other keys of MassOptionType.
  • The rows that failed on the frontend get their errors in the result automatically. If the handler returns its own result for such a row, its own result is shown.

Step 2 Add an item with the name of the mass action to options.cryptoGenerator and the action to actionGroups in the .widget configuration options. The settings are the same for List and GroupingHierarchy; GroupingHierarchy also needs options.groupingHierarchy.fields.

List: Live Sample

GroupingHierarchy: Live Sample

GitHub

    @Override
    public Actions<MyexampleDTO> getActions() {
        return Actions.<MyexampleDTO>builder()
                .action(act -> act
                        .action("massDocumentSign", "Mass Document Sign")
                        .massInvoker((bc, data, ids) -> {
                            // only the rows signed on the frontend, the others get the frontend errors automatically
                            var massResult = data.getMassIds_().stream()
                                    .map(mass -> {
                                        Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
                                        entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
                                        entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
                                        entity.setStatus(StatusSignEnum.SIGNED);
                                        return MassDTO.success(mass.getId());
                                    })
                                    .collect(Collectors.toSet());
                            data.getMassErrors_().forEach(error ->
                                    log.warn("Row {} was not signed: {}", error.getId(), error.getErrorMessage()));
                            return new MassActionResultDTO<MyexampleDTO>(massResult);
                        })
                )
                .build();
    }
{
  "name": "MyExampleMassList",
  "title": "",
  "type": "List",
  "bc": "myexamplemass",
  "fields": [
    {
      "title": "File",
      "key": "file",
      "type": "fileUpload",
      "fileIdKey": "fileId",
      "preview": {
        "enabled": true
      }
    },
    {
      "title": "Status",
      "key": "status",
      "type": "dictionary",
      "bgColorKey": "color"
    },
    {
      "title": "File Signed",
      "key": "fileSignRO",
      "type": "fileUpload",
      "fileIdKey": "fileSignId"
    }
  ],
  "options": {
    "cryptoGenerator": [
      {
        "type": "sign",
        "actionName": "massDocumentSign",
        "documentFileIdKey": "fileId",
        "documentFileNameKey": "file",
        "signatureFileIdKey": "fileSignId",
        "signatureFileNameKey": "fileSign"
      }
    ],
    "actionGroups": {
      "include": [
        "massDocumentSign"
      ]
    }
  }
}

List: Live Sample

GroupingHierarchy: Live Sample

GitHub

    @Override
    public Actions<MyexampleDTO> getActions() {
        return Actions.<MyexampleDTO>builder()
                .action(act -> act
                        .action("massDocumentEncrypt", "Mass Document Encrypt")
                        .massInvoker((bc, data, ids) -> {
                            // only the rows processed on the frontend, the others get the frontend errors automatically
                            var massResult = data.getMassIds_().stream()
                                    .map(mass -> {
                                        Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
                                        entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
                                        entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
                                        entity.setStatus(StatusEncryptEnum.ENCRYPT);
                                        return MassDTO.success(mass.getId());
                                    })
                                    .collect(Collectors.toSet());
                            return new MassActionResultDTO<MyexampleDTO>(massResult);
                        })
                )
                .build();
    }
{
  "name": "MyExampleMassList",
  "title": "",
  "type": "List",
  "bc": "myexamplemass",
  "fields": [
    {
      "title": "File",
      "key": "file",
      "type": "fileUpload",
      "fileIdKey": "fileId",
      "preview": {
        "enabled": true
      }
    },
    {
      "title": "Status",
      "key": "status",
      "type": "dictionary",
      "bgColorKey": "color"
    },
    {
      "title": "File Encrypted",
      "key": "fileEncrypt",
      "type": "fileUpload",
      "fileIdKey": "fileEncryptId"
    }
  ],
  "options": {
    "cryptoGenerator": [
      {
        "type": "encrypt",
        "actionName": "massDocumentEncrypt",
        "documentFileIdKey": "fileId",
        "documentFileNameKey": "file",
        "encryptedFileIdKey": "fileEncryptId",
        "encryptedFileNameKey": "fileEncrypt"
      }
    ],
    "actionGroups": {
      "include": [
        "massDocumentEncrypt"
      ]
    }
  }
}

List: Live Sample

GroupingHierarchy: Live Sample

GitHub

    @Override
    public Actions<MyexampleDTO> getActions() {
        return Actions.<MyexampleDTO>builder()
                .action(act -> act
                        .action("massDocumentSignEncrypt", "Mass Document Sign Encrypt")
                        .massInvoker((bc, data, ids) -> {
                            // only the rows processed on the frontend, the others get the frontend errors automatically
                            var massResult = data.getMassIds_().stream()
                                    .map(mass -> {
                                        Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
                                        entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
                                        entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
                                        entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
                                        entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
                                        entity.setStatus(StatusSignEncryptEnum.SIGN_ENCRYPT);
                                        return MassDTO.success(mass.getId());
                                    })
                                    .collect(Collectors.toSet());
                            return new MassActionResultDTO<MyexampleDTO>(massResult);
                        })
                )
                .build();
    }
{
  "name": "MyExampleMassList",
  "title": "",
  "type": "List",
  "bc": "myexamplemass",
  "fields": [
    {
      "title": "File",
      "key": "file",
      "type": "fileUpload",
      "fileIdKey": "fileId",
      "preview": {
        "enabled": true
      }
    },
    {
      "title": "Status",
      "key": "status",
      "type": "dictionary",
      "bgColorKey": "color"
    },
    {
      "title": "File Encrypted",
      "key": "fileEncryptRO",
      "type": "fileUpload",
      "fileIdKey": "fileEncryptId"
    },
    {
      "title": "File Signed",
      "key": "fileSignRO",
      "type": "fileUpload",
      "fileIdKey": "fileSignId"
    }
  ],
  "options": {
    "cryptoGenerator": [
      {
        "type": "signAndEncrypt",
        "actionName": "massDocumentSignEncrypt",
        "documentFileIdKey": "fileId",
        "documentFileNameKey": "file",
        "signatureFileIdKey": "fileSignId",
        "signatureFileNameKey": "fileSign",
        "encryptedFileIdKey": "fileEncryptId",
        "encryptedFileNameKey": "fileEncrypt"
      }
    ],
    "actionGroups": {
      "include": [
        "massDocumentSignEncrypt"
      ]
    }
  }
}

List: Live Sample

GroupingHierarchy: Live Sample

GitHub

    @Override
    public Actions<MyexampleDTO> getActions() {
        return Actions.<MyexampleDTO>builder()
                .action(act -> act
                        .action("massDocumentEncryptSign", "Mass Document Encrypt Sign")
                        .massInvoker((bc, data, ids) -> {
                            // only the rows processed on the frontend, the others get the frontend errors automatically
                            var massResult = data.getMassIds_().stream()
                                    .map(mass -> {
                                        Myexample entity = myexampleRepository.getReferenceById(Long.parseLong(mass.getId()));
                                        entity.setFileEncryptId(mass.getOption(MassOptionType.ENCRYPTED_FILE_ID));
                                        entity.setFileEncrypt(mass.getOption(MassOptionType.ENCRYPTED_FILE_NAME));
                                        entity.setFileSignId(mass.getOption(MassOptionType.SIGNATURE_FILE_ID));
                                        entity.setFileSign(mass.getOption(MassOptionType.SIGNATURE_FILE_NAME));
                                        entity.setStatus(StatusEncryptSignEnum.SIGN_ENCRYPT);
                                        return MassDTO.success(mass.getId());
                                    })
                                    .collect(Collectors.toSet());
                            return new MassActionResultDTO<MyexampleDTO>(massResult);
                        })
                )
                .build();
    }
{
  "name": "MyExampleMassList",
  "title": "",
  "type": "List",
  "bc": "myexamplemass",
  "fields": [
    {
      "title": "File",
      "key": "file",
      "type": "fileUpload",
      "fileIdKey": "fileId",
      "preview": {
        "enabled": true
      }
    },
    {
      "title": "Status",
      "key": "status",
      "type": "dictionary",
      "bgColorKey": "color"
    },
    {
      "title": "File Encrypted",
      "key": "fileEncryptRO",
      "type": "fileUpload",
      "fileIdKey": "fileEncryptId"
    },
    {
      "title": "File Signed",
      "key": "fileSignRO",
      "type": "fileUpload",
      "fileIdKey": "fileSignId"
    }
  ],
  "options": {
    "cryptoGenerator": [
      {
        "type": "encryptAndSign",
        "actionName": "massDocumentEncryptSign",
        "documentFileIdKey": "fileId",
        "documentFileNameKey": "file",
        "signatureFileIdKey": "fileSignId",
        "signatureFileNameKey": "fileSign",
        "encryptedFileIdKey": "fileEncryptId",
        "encryptedFileNameKey": "fileEncrypt"
      }
    ],
    "actionGroups": {
      "include": [
        "massDocumentEncryptSign"
      ]
    }
  }
}

To react to the rows that failed on the frontend, read data.getMassErrors_():

                            data.getMassErrors_().forEach(error ->
                                    log.warn("Row {} was not signed: {}", error.getId(), error.getErrorMessage()));

Step 1. Select rows

How does it look?

The step works as Select rows of mass operations: checkboxes in the first column, chips of the selected rows, Clear and Select from file.

Available actions:

  • Next — moves the user to Step 2.
  • Cancel — exits mass-operation mode.

Step 2. Review rows

How does it look?

massreview.png

The step works as Review rows of mass operations: only the selected rows are shown, read-only.

Available actions:

  • Next — moves the user to Step 3.
  • Back — returns the user to Step 1.
  • Cancel — exits mass-operation mode.

Step 3. Certificate selection

How does it look?

If the CryptoPro browser plugin is not installed, the step shows a message with a link to the setup instructions, and only Back is available.

masscertificates.png

  1. The user selects the certificates, as in the popup for one document: for signing, for encryption or both, by the operation type.
  2. Available actions:

    • Execute - processes the files of the rows and sends the action. The button is shown when the action has no confirmation.
    • Next - moves the user to Step 4. The button is shown when the action has a confirmation. The files are not processed yet.
    • Back - returns the user to Step 2.
  3. While the files are processed, the step shows a progress bar and Processed N of M. Back is not available.

    • Stop and send processed - stops the processing at once. The processed rows are sent, the current row and the rows after it get the error "Interrupted". If no row is processed yet, nothing is sent and the step is shown again.
  4. If the action fails on the server, the user can go back and run the step again. A row that is already processed with the same certificates and the same file is not processed again until the mass operation is closed.

Step 4. Confirm operation

The step is shown only when the mass action has a confirmation, as in Mass operations with confirmation.

  1. The confirmation is shown with its own buttons.
  2. After the yes button, the files are processed on this step, as on Step 3: a progress bar, Processed N of M, Stop and send processed. Then the action is sent.
  3. The no button closes the mass operation. Nothing is signed or encrypted.
  4. Back returns the user to Step 3.

The title and the text of the yes and no buttons are customized as in Mass operations with confirmation. The values entered in the confirmation form are applied to all selected rows; an empty field means "clear".

Step 5. View results

The step works as View results of mass operations.

A row that was not processed has the status Fail and the error:

  • "There is no file in this row" - the row has no file;
  • the text of the CryptoPro error;
  • "Interrupted" - the processing was stopped with Stop and send processed before this row.

The widget header displays the number of processed rows, successful and failed. Export and Retry → Export failed work as in mass operations.

The postAction of the mass action runs when the user clicks Close.

Retry for failed rows

The operation is repeated for the failed rows as in mass operations:

  1. Export — on Step 5 click Retry → Export failed. An Excel file with the failed rows is downloaded.
  2. Fix the cause — add a file to the rows with "There is no file in this row", or fix the CryptoPro error (for example, select another certificate). Rows with "Interrupted" need no fix.
  3. Upload — start the mass operation again and on Step 1 select Select from file in the gear menu. Rows are matched only by ID: the exported file keeps it in a hidden column, so the file does not need to be edited.
  4. Run again — select the certificates and go through the steps as usual.