@Transactional
  @Override
  public CommandProcessingResult create(final JsonCommand command) {

    try {
      this.fromApiJsonDataValidator.validateForCreate(command.json());

      final SavingsProduct product = this.savingsProductAssembler.assemble(command);

      this.savingProductRepository.save(product);

      // save accounting mappings
      this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(
          product.getId(), command, DepositAccountType.SAVINGS_DEPOSIT);

      // check if the office specific products are enabled. If yes, then
      // save this savings product against a specific office
      // i.e. this savings product is specific for this office.
      fineractEntityAccessUtil.checkConfigurationAndAddProductResrictionsForUserOffice(
          FineractEntityAccessType.OFFICE_ACCESS_TO_SAVINGS_PRODUCTS, product.getId());

      return new CommandProcessingResultBuilder() //
          .withEntityId(product.getId()) //
          .build();
    } catch (final DataAccessException e) {
      handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
      return CommandProcessingResult.empty();
    } catch (final PersistenceException dve) {
      Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
      handleDataIntegrityIssues(command, throwable, dve);
      return CommandProcessingResult.empty();
    }
  }
  @Transactional
  @Override
  public CommandProcessingResult update(final Long productId, final JsonCommand command) {

    try {
      this.context.authenticatedUser();
      final SavingsProduct product = this.savingProductRepository.findOne(productId);
      if (product == null) {
        throw new SavingsProductNotFoundException(productId);
      }

      this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);

      final Map<String, Object> changes = product.update(command);

      if (changes.containsKey(chargesParamName)) {
        final Set<Charge> savingsProductCharges =
            this.savingsProductAssembler.assembleListOfSavingsProductCharges(
                command, product.currency().getCode());
        final boolean updated = product.update(savingsProductCharges);
        if (!updated) {
          changes.remove(chargesParamName);
        }
      }

      if (changes.containsKey(taxGroupIdParamName)) {
        final TaxGroup taxGroup = this.savingsProductAssembler.assembleTaxGroup(command);
        product.setTaxGroup(taxGroup);
        if (product.withHoldTax() && product.getTaxGroup() == null) {
          final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
          final DataValidatorBuilder baseDataValidator =
              new DataValidatorBuilder(dataValidationErrors)
                  .resource(SAVINGS_PRODUCT_RESOURCE_NAME);
          final Long taxGroupId = null;
          baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank();
          throw new PlatformApiDataValidationException(dataValidationErrors);
        }
      }

      // accounting related changes
      final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
      final Map<String, Object> accountingMappingChanges =
          this.accountMappingWritePlatformService.updateSavingsProductToGLAccountMapping(
              product.getId(),
              command,
              accountingTypeChanged,
              product.getAccountingType(),
              DepositAccountType.SAVINGS_DEPOSIT);
      changes.putAll(accountingMappingChanges);

      if (!changes.isEmpty()) {
        this.savingProductRepository.saveAndFlush(product);
      }

      return new CommandProcessingResultBuilder() //
          .withEntityId(product.getId()) //
          .with(changes)
          .build();
    } catch (final DataAccessException e) {
      handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
      return CommandProcessingResult.empty();
    } catch (final PersistenceException dve) {
      Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
      handleDataIntegrityIssues(command, throwable, dve);
      return CommandProcessingResult.empty();
    }
  }