diff --git a/finnow-api/source/transaction/api.d b/finnow-api/source/transaction/api.d index 9955a55..cc590b2 100644 --- a/finnow-api/source/transaction/api.d +++ b/finnow-api/source/transaction/api.d @@ -224,8 +224,7 @@ immutable DEFAULT_DRAFT_PAGE = PageRequest(1, 10, [Sort("draft.id", SortDir.DESC void handleGetDrafts(ref ServerHttpRequest request, ref ServerHttpResponse response) { ProfileDataSource ds = getProfileDataSource(request); PageRequest pr = PageRequest.parse(request, DEFAULT_DRAFT_PAGE); - bool shouldFetchTemplates = request.getParamAs!bool("template", false); - Page!TransactionDraftListItem page = getDrafts(ds, pr, shouldFetchTemplates); + Page!TransactionDraftListItem page = getDrafts(ds, pr); writeJsonBody(response, page); } diff --git a/finnow-api/source/transaction/data.d b/finnow-api/source/transaction/data.d index 91edd66..df0d6d4 100644 --- a/finnow-api/source/transaction/data.d +++ b/finnow-api/source/transaction/data.d @@ -57,6 +57,7 @@ interface TransactionRepository { interface TransactionDraftRepository { Page!TransactionDraftListItem findAllDrafts(in PageRequest pr); Page!TransactionDraftListItem findAllTemplates(in PageRequest pr); + Page!TransactionDraftListItem findAll(in PageRequest pr); Optional!TransactionDraftResponse findById(ulong id); TransactionDraftResponse insert(in TransactionDraftPayload data); void linkAttachment(ulong draftId, ulong attachmentId); diff --git a/finnow-api/source/transaction/data_impl_sqlite.d b/finnow-api/source/transaction/data_impl_sqlite.d index bb2077f..bf10803 100644 --- a/finnow-api/source/transaction/data_impl_sqlite.d +++ b/finnow-api/source/transaction/data_impl_sqlite.d @@ -631,11 +631,15 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository { } Page!TransactionDraftListItem findAllDrafts(in PageRequest pr) { - return findAllInternal(pr, DraftType.DRAFT); + return findAllInternal(pr, DraftType.DRAFT.toOptional); } Page!TransactionDraftListItem findAllTemplates(in PageRequest pr) { - return findAllInternal(pr, DraftType.TEMPLATE); + return findAllInternal(pr, DraftType.TEMPLATE.toOptional); + } + + Page!TransactionDraftListItem findAll(in PageRequest pr) { + return findAllInternal(pr, Optional!DraftType.empty()); } private static enum DraftType { @@ -643,14 +647,16 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository { TEMPLATE } - private Page!TransactionDraftListItem findAllInternal(in PageRequest pr, DraftType type) { + private Page!TransactionDraftListItem findAllInternal(in PageRequest pr, Optional!DraftType type) { QueryBuilder qb = getBuilderForDraftsList(); addSelectsForDraftsList(qb); qb.groupBy("draft.id"); - if (type == DraftType.DRAFT) { - qb.where("template_name IS NULL"); - } else { - qb.where("template_name IS NOT NULL"); + if (type) { + if (type.value == DraftType.DRAFT) { + qb.where("template_name IS NULL"); + } else { + qb.where("template_name IS NOT NULL"); + } } string query = qb.build() ~ "\n" ~ pr.toSql(); TransactionDraftListItem[] results = util.sqlite.findAll(db, query, &parseDraftListItem); diff --git a/finnow-api/source/transaction/service.d b/finnow-api/source/transaction/service.d index cae94b6..15fccf8 100644 --- a/finnow-api/source/transaction/service.d +++ b/finnow-api/source/transaction/service.d @@ -16,6 +16,8 @@ import account.data; import util.money; import util.pagination; import util.data; +import util.validation.transaction; +import util.validation.draft; import attachment.data; import attachment.dto; @@ -182,76 +184,6 @@ void deleteTransaction(ProfileDataSource ds, ulong transactionId) { }); } -private void validateTransactionPayload( - TransactionVendorRepository vendorRepo, - TransactionCategoryRepository categoryRepo, - AccountRepository accountRepo, - in AddTransactionPayload payload -) { - if (!payload.creditedAccountId && !payload.debitedAccountId) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "At least one account must be linked."); - } - if ( - payload.creditedAccountId && - payload.debitedAccountId && - payload.creditedAccountId.value == payload.debitedAccountId.value - ) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Cannot link the same account as both credit and debit."); - } - if (payload.amount == 0) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Amount should be greater than 0."); - } - SysTime now = Clock.currTime(UTC()); - SysTime timestamp; - try { - timestamp = SysTime.fromISOExtString(payload.timestamp, UTC()); - } catch (TimeException e) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid timestamp format. Expected ISO-8601 datetime."); - } - if (timestamp > now) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Cannot create transaction in the future."); - } - if (payload.vendorId && !vendorRepo.existsById(payload.vendorId.value)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Vendor doesn't exist."); - } - if (payload.categoryId && !categoryRepo.existsById(payload.categoryId.value)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Category doesn't exist."); - } - if (payload.creditedAccountId && !accountRepo.existsById(payload.creditedAccountId.value)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Credited account doesn't exist."); - } - if (payload.debitedAccountId && !accountRepo.existsById(payload.debitedAccountId.value)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Debited account doesn't exist."); - } - foreach (tag; payload.tags) { - import std.regex; - auto r = ctRegex!(`^[a-z0-9-_]{3,32}$`); - if (!matchFirst(tag, r)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid tag: \"" ~ tag ~ "\"."); - } - } - if (payload.lineItems.length > 0) { - long lineItemsTotal = 0; - foreach (lineItem; payload.lineItems) { - if (lineItem.categoryId && !categoryRepo.existsById(lineItem.categoryId.value)) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Line item's category doesn't exist."); - } - if (lineItem.quantity == 0) { - throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Line item's quantity should greater than zero."); - } - for (ulong i = 0; i < lineItem.quantity; i++) { - lineItemsTotal += lineItem.valuePerItem; - } - } - if (lineItemsTotal != payload.amount) { - throw new HttpStatusException( - HttpStatus.BAD_REQUEST, - "Total of all line items doesn't equal the transaction's total." - ); - } - } -} - /** * Helper function to add / remove attachments for a transaction. */ @@ -501,13 +433,8 @@ void deleteCategory(ProfileDataSource ds, ulong categoryId) { // Draft services -Page!TransactionDraftListItem getDrafts(ProfileDataSource ds, in PageRequest pr, bool shouldFetchTemplates) { - if (shouldFetchTemplates) { - return ds.getTransactionDraftRepository() - .findAllTemplates(pr); - } - return ds.getTransactionDraftRepository() - .findAllDrafts(pr); +Page!TransactionDraftListItem getDrafts(ProfileDataSource ds, in PageRequest pr) { + return ds.getTransactionDraftRepository().findAll(pr); } TransactionDraftResponse getDraft(ProfileDataSource ds, ulong draftId) { @@ -530,7 +457,12 @@ TransactionDraftResponse addDraft(ProfileDataSource ds, in TransactionDraftPaylo TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository(); AttachmentRepository attachmentRepo = ds.getAttachmentRepository(); - validateDraftPayload(payload); + validateDraftPayload( + ds.getTransactionVendorRepository(), + ds.getTransactionCategoryRepository(), + ds.getAccountRepository(), + payload + ); SysTime now = Clock.currTime(UTC()); ulong draftId; @@ -552,7 +484,12 @@ TransactionDraftResponse updateDraft( TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository(); AttachmentRepository attachmentRepo = ds.getAttachmentRepository(); - validateDraftPayload(payload); + validateDraftPayload( + ds.getTransactionVendorRepository(), + ds.getTransactionCategoryRepository(), + ds.getAccountRepository(), + payload + ); SysTime now = Clock.currTime(UTC()); ds.doTransaction(() { @@ -577,10 +514,6 @@ void deleteDraft(ProfileDataSource ds, ulong draftId) { }); } -private void validateDraftPayload(in TransactionDraftPayload payload) { - // TODO! -} - private void updateDraftAttachments( ulong draftId, SysTime timestamp, diff --git a/web-app/src/components/TransactionDraftCard.vue b/web-app/src/components/TransactionDraftCard.vue index a0a4bab..ebab0f7 100644 --- a/web-app/src/components/TransactionDraftCard.vue +++ b/web-app/src/components/TransactionDraftCard.vue @@ -26,7 +26,15 @@ function goToDraft() {