FINERACT-2268: Fix GL account deletion logic

This commit is contained in:
Adam Saghy
2025-04-30 14:04:12 +02:00
parent 354f1bdc93
commit aaf1351c4f
12 changed files with 216 additions and 15 deletions
@@ -213,7 +213,7 @@ public class GLAccountsApiResource {
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(tags = { "General Ledger Account" }, summary = "Delete a GL Account", description = "Deletes a GL Account")
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.DeleteGLAccountsRequest.class)))
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = GLAccountsApiResourceSwagger.DeleteGLAccountsResponse.class)))
public CommandProcessingResult deleteGLAccount(
@PathParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteGLAccount(glAccountId).build();
@@ -203,23 +203,23 @@ final class GLAccountsApiResourceSwagger {
public PutGLAccountsResponsechangesSwagger changes;
}
@Schema(description = "DeleteGLAccountsRequest")
public static final class DeleteGLAccountsRequest {
@Schema(description = "DeleteGLAccountsResponse")
public static final class DeleteGLAccountsResponse {
private DeleteGLAccountsRequest() {
private DeleteGLAccountsResponse() {
}
private static final class DeleteGLAccountsRequestchangesSwagger {
private static final class DeleteGLAccountsResponseChangesSwagger {
private DeleteGLAccountsRequestchangesSwagger() {}
private DeleteGLAccountsResponseChangesSwagger() {}
}
@Schema(example = "1")
public Long resourceId;
private DeleteGLAccountsRequestchangesSwagger changes;
private DeleteGLAccountsResponseChangesSwagger changes;
}
}
@@ -28,13 +28,15 @@ public class GLAccountInvalidDeleteException extends AbstractPlatformDomainRuleE
/*** Enum of reasons for invalid delete **/
public enum GlAccountInvalidDeleteReason {
TRANSACTIONS_LOGGED, HAS_CHILDREN;
TRANSACTIONS_LOGGED, HAS_CHILDREN, PRODUCT_MAPPING;
public String errorMessage() {
if (name().equalsIgnoreCase("TRANSACTIONS_LOGGED")) {
return "This GL Account cannot be deleted as it has transactions logged against it";
} else if (name().equalsIgnoreCase("HAS_CHILDREN")) {
return "Cannot delete this Header GL Account without first deleting or reassigning its children";
} else if (name().equalsIgnoreCase("PRODUCT_MAPPING")) {
return "Cannot delete this GL Account as it is mapped to a Product";
}
return name();
}
@@ -44,6 +46,8 @@ public class GLAccountInvalidDeleteException extends AbstractPlatformDomainRuleE
return "error.msg.glaccount.glcode.invalid.delete.transactions.logged";
} else if (name().equalsIgnoreCase("HAS_CHILDREN")) {
return "error.msg.glaccount.glcode.invalid.delete.has.children";
} else if (name().equalsIgnoreCase("PRODUCT_MAPPING")) {
return "error.msg.glaccount.glcode.invalid.delete.product.mapping";
}
return name();
}
@@ -37,6 +37,7 @@ import org.apache.fineract.accounting.glaccount.exception.GLAccountNotFoundExcep
import org.apache.fineract.accounting.glaccount.exception.InvalidParentGLAccountHeadException;
import org.apache.fineract.accounting.glaccount.serialization.GLAccountCommandFromApiJsonDeserializer;
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -61,6 +62,7 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
private final GLAccountRepository glAccountRepository;
private final JournalEntryRepository glJournalEntryRepository;
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
private final GLAccountCommandFromApiJsonDeserializer fromApiJsonDeserializer;
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final JdbcTemplate jdbcTemplate;
@@ -147,7 +149,7 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
if (changesOnly.containsKey(GLAccountJsonInputParams.USAGE.getValue())) {
if (glAccount.isHeaderAccount()) {
final boolean journalEntriesForAccountExist = this.glJournalEntryRepository
.exists((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("glAccountId"), glAccountId));
.exists((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("glAccount").get("id"), glAccountId));
if (journalEntriesForAccountExist) {
throw new GLAccountInvalidUpdateException(GlAccountInvalidUpdateReason.TRANSANCTIONS_LOGGED, glAccountId);
}
@@ -186,16 +188,22 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
.orElseThrow(() -> new GLAccountNotFoundException(glAccountId));
// validate this isn't a header account that has children
if (glAccount.isHeaderAccount() && glAccount.getChildren().size() > 0) {
if (glAccount.isHeaderAccount() && !glAccount.getChildren().isEmpty()) {
throw new GLAccountInvalidDeleteException(GlAccountInvalidDeleteReason.HAS_CHILDREN, glAccountId);
}
// does this account have transactions logged against it
final boolean journalEntriesForAccountExist = this.glJournalEntryRepository
.exists((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("glAccountId"), glAccountId));
.exists((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("glAccount").get("id"), glAccountId));
if (journalEntriesForAccountExist) {
throw new GLAccountInvalidDeleteException(GlAccountInvalidDeleteReason.TRANSACTIONS_LOGGED, glAccountId);
}
// does this account mapped to product
final boolean accountMappingForAccountExists = this.productToGLAccountMappingRepository
.exists((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("glAccount").get("id"), glAccountId));
if (accountMappingForAccountExists) {
throw new GLAccountInvalidDeleteException(GlAccountInvalidDeleteReason.PRODUCT_MAPPING, glAccountId);
}
this.glAccountRepository.delete(glAccount);
return new CommandProcessingResultBuilder().withEntityId(glAccountId).build();
@@ -52,6 +52,8 @@ public class JournalEntryCommand {
private final SingleDebitOrCreditEntryCommand[] credits;
private final SingleDebitOrCreditEntryCommand[] debits;
private final String locale;
private final String dateFormat;
public void validateForCreate() {
@@ -73,7 +73,7 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr
element);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final String localeStr = locale.getLanguage() + "_" + locale.getCountry() + '_' + locale.getVariant();
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(JournalEntryJsonInputParams.AMOUNT.getValue(), element,
locale);
final Long paymentTypeId = this.fromApiJsonHelper.extractLongNamed(JournalEntryJsonInputParams.PAYMENT_TYPE_ID.getValue(), element);
@@ -97,8 +97,9 @@ public final class JournalEntryCommandFromApiJsonDeserializer extends AbstractFr
debits = populateCreditsOrDebitsArray(topLevelJsonElement, locale, JournalEntryJsonInputParams.DEBITS.getValue());
}
}
String dateFormat = this.fromApiJsonHelper.extractStringNamed(JournalEntryJsonInputParams.DATE_FORMAT.getValue(), element);
return new JournalEntryCommand(officeId, currencyCode, transactionDate, comments, referenceNumber, accountingRuleId, amount,
paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits);
paymentTypeId, accountNumber, checkNumber, receiptNumber, bankNumber, routingCode, credits, debits, localeStr, dateFormat);
}
/**