Compare commits
No commits in common. "115a79a5c0ab99cc26364625694fc48b3158f8e8" and "23cfe0b1a976b270fe27325285284dfd3d2e6f96" have entirely different histories.
115a79a5c0
...
23cfe0b1a9
|
|
@ -224,7 +224,8 @@ 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);
|
||||
Page!TransactionDraftListItem page = getDrafts(ds, pr);
|
||||
bool shouldFetchTemplates = request.getParamAs!bool("template", false);
|
||||
Page!TransactionDraftListItem page = getDrafts(ds, pr, shouldFetchTemplates);
|
||||
writeJsonBody(response, page);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -631,15 +631,11 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
}
|
||||
|
||||
Page!TransactionDraftListItem findAllDrafts(in PageRequest pr) {
|
||||
return findAllInternal(pr, DraftType.DRAFT.toOptional);
|
||||
return findAllInternal(pr, DraftType.DRAFT);
|
||||
}
|
||||
|
||||
Page!TransactionDraftListItem findAllTemplates(in PageRequest pr) {
|
||||
return findAllInternal(pr, DraftType.TEMPLATE.toOptional);
|
||||
}
|
||||
|
||||
Page!TransactionDraftListItem findAll(in PageRequest pr) {
|
||||
return findAllInternal(pr, Optional!DraftType.empty());
|
||||
return findAllInternal(pr, DraftType.TEMPLATE);
|
||||
}
|
||||
|
||||
private static enum DraftType {
|
||||
|
|
@ -647,17 +643,15 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
TEMPLATE
|
||||
}
|
||||
|
||||
private Page!TransactionDraftListItem findAllInternal(in PageRequest pr, Optional!DraftType type) {
|
||||
private Page!TransactionDraftListItem findAllInternal(in PageRequest pr, DraftType type) {
|
||||
QueryBuilder qb = getBuilderForDraftsList();
|
||||
addSelectsForDraftsList(qb);
|
||||
qb.groupBy("draft.id");
|
||||
if (type) {
|
||||
if (type.value == DraftType.DRAFT) {
|
||||
if (type == 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);
|
||||
ulong totalCount = util.sqlite.count(db, "SELECT COUNT(DISTINCT id) FROM transaction_draft");
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ 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;
|
||||
|
||||
|
|
@ -184,6 +182,76 @@ 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.
|
||||
*/
|
||||
|
|
@ -433,8 +501,13 @@ void deleteCategory(ProfileDataSource ds, ulong categoryId) {
|
|||
|
||||
// Draft services
|
||||
|
||||
Page!TransactionDraftListItem getDrafts(ProfileDataSource ds, in PageRequest pr) {
|
||||
return ds.getTransactionDraftRepository().findAll(pr);
|
||||
Page!TransactionDraftListItem getDrafts(ProfileDataSource ds, in PageRequest pr, bool shouldFetchTemplates) {
|
||||
if (shouldFetchTemplates) {
|
||||
return ds.getTransactionDraftRepository()
|
||||
.findAllTemplates(pr);
|
||||
}
|
||||
return ds.getTransactionDraftRepository()
|
||||
.findAllDrafts(pr);
|
||||
}
|
||||
|
||||
TransactionDraftResponse getDraft(ProfileDataSource ds, ulong draftId) {
|
||||
|
|
@ -457,12 +530,7 @@ TransactionDraftResponse addDraft(ProfileDataSource ds, in TransactionDraftPaylo
|
|||
TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository();
|
||||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
|
||||
validateDraftPayload(
|
||||
ds.getTransactionVendorRepository(),
|
||||
ds.getTransactionCategoryRepository(),
|
||||
ds.getAccountRepository(),
|
||||
payload
|
||||
);
|
||||
validateDraftPayload(payload);
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
|
||||
ulong draftId;
|
||||
|
|
@ -484,12 +552,7 @@ TransactionDraftResponse updateDraft(
|
|||
TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository();
|
||||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
|
||||
validateDraftPayload(
|
||||
ds.getTransactionVendorRepository(),
|
||||
ds.getTransactionCategoryRepository(),
|
||||
ds.getAccountRepository(),
|
||||
payload
|
||||
);
|
||||
validateDraftPayload(payload);
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
|
||||
ds.doTransaction(() {
|
||||
|
|
@ -514,6 +577,10 @@ void deleteDraft(ProfileDataSource ds, ulong draftId) {
|
|||
});
|
||||
}
|
||||
|
||||
private void validateDraftPayload(in TransactionDraftPayload payload) {
|
||||
// TODO!
|
||||
}
|
||||
|
||||
private void updateDraftAttachments(
|
||||
ulong draftId,
|
||||
SysTime timestamp,
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
module util.validation.common;
|
||||
|
||||
import handy_http_primitives.optional;
|
||||
import std.datetime;
|
||||
|
||||
struct ValidationError {
|
||||
string field;
|
||||
string message;
|
||||
}
|
||||
|
||||
interface ValidationRule(T) {
|
||||
Optional!ValidationError validate(in T payload);
|
||||
}
|
||||
|
||||
ValidationError[] applyValidationRules(T)(ValidationRule!(T)[] rules, in T payload) {
|
||||
import std.array;
|
||||
auto app = appender!(ValidationError[]);
|
||||
foreach (rule; rules) {
|
||||
auto result = rule.validate(payload);
|
||||
if (result) app ~= result.value;
|
||||
}
|
||||
return app[];
|
||||
}
|
||||
|
||||
// Helper functions:
|
||||
|
||||
void validateTags(in string[] tags) {
|
||||
import std.regex;
|
||||
import handy_http_primitives: HttpStatus, HttpStatusException;
|
||||
foreach (tag; 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 ~ "\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SysTime validateTimestampFormat(string timestampStr) {
|
||||
import handy_http_primitives: HttpStatus, HttpStatusException;
|
||||
try {
|
||||
return SysTime.fromISOExtString(timestampStr, UTC());
|
||||
} catch (TimeException e) {
|
||||
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid timestamp format. Expected ISO-8601 datetime.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
module util.validation.draft;
|
||||
|
||||
import handy_http_primitives;
|
||||
import std.datetime;
|
||||
|
||||
import transaction.data;
|
||||
import transaction.dto;
|
||||
import util.validation.common;
|
||||
import account.data;
|
||||
|
||||
void validateDraftPayload(
|
||||
TransactionVendorRepository vendorRepo,
|
||||
TransactionCategoryRepository categoryRepo,
|
||||
AccountRepository accountRepo,
|
||||
in TransactionDraftPayload payload
|
||||
) {
|
||||
if (payload.amount && !payload.currencyCode) {
|
||||
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Currency is required when saving an amount.");
|
||||
}
|
||||
if (payload.amount && payload.amount.value == 0) {
|
||||
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Amount should be greater than 0.");
|
||||
}
|
||||
if (payload.timestamp) {
|
||||
validateTimestampFormat(payload.timestamp.value);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
validateTags(payload.tags);
|
||||
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 (payload.amount && lineItemsTotal != payload.amount.value) {
|
||||
throw new HttpStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Total of all line items doesn't equal the transaction's total."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
module util.validation.transaction;
|
||||
|
||||
import handy_http_primitives;
|
||||
import std.array;
|
||||
import std.datetime;
|
||||
|
||||
import util.validation.common;
|
||||
import transaction.dto;
|
||||
import transaction.data;
|
||||
import account.data;
|
||||
|
||||
// class AtLeastOneLinkedAccountRule : ValidationRule!AddTransactionPayload {
|
||||
// Optional!ValidationError validate(in AddTransactionPayload payload) {
|
||||
// if (!payload.creditedAccountId && !payload.debitedAccountId) {
|
||||
// return ValidationError("creditedAccountId", "At least one account must be linked.").toOptional();
|
||||
// }
|
||||
// return Optional!ValidationError.empty();
|
||||
// }
|
||||
// }
|
||||
|
||||
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 = validateTimestampFormat(payload.timestamp);
|
||||
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.");
|
||||
}
|
||||
validateTags(payload.tags);
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,15 +26,7 @@ function goToDraft() {
|
|||
<!-- Top row contains timestamp and amount. -->
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<div>
|
||||
<div class="font-mono font-size-xsmall text-normal">
|
||||
Draft #{{ draft.id }}
|
||||
<AppBadge
|
||||
v-if="draft.templateName"
|
||||
size="sm"
|
||||
>
|
||||
Template: {{ draft.templateName }}
|
||||
</AppBadge>
|
||||
</div>
|
||||
<div class="font-mono font-size-xsmall text-normal">Draft #{{ draft.id }}</div>
|
||||
<div
|
||||
class="text-muted font-mono font-size-xsmall"
|
||||
v-if="draft.timestamp"
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ async function onVendorClicked() {
|
|||
<AppButton
|
||||
icon="wrench"
|
||||
@click="
|
||||
router.push(`/profiles/${getSelectedProfile(route)}/transaction-drafts/${draft?.id}/edit`)
|
||||
router.push(`/profiles/${getSelectedProfile(route)}/transaction-drafts/${draft.id}/edit`)
|
||||
"
|
||||
>
|
||||
Edit
|
||||
|
|
|
|||
|
|
@ -102,7 +102,6 @@ onMounted(async () => {
|
|||
v-model="formData.templateName"
|
||||
:disabled="loading"
|
||||
style="max-width: 200px"
|
||||
maxlength="32"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,10 @@ export interface TransactionEditorContextBase {
|
|||
export class NewTransactionEditorContext implements TransactionEditorContextBase {
|
||||
isFormDataValid(formData: TransactionEditorFormFields): boolean {
|
||||
return (
|
||||
isFormDataValidForDraftSave(formData) || isFormDataValidForTransactionSubmission(formData)
|
||||
formData.amount !== null &&
|
||||
formData.amount > 0 &&
|
||||
formData.timestamp !== null &&
|
||||
formData.currency !== null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -123,9 +126,7 @@ export class NewTransactionEditorContext implements TransactionEditorContextBase
|
|||
return [
|
||||
{
|
||||
name: 'Save',
|
||||
disabled: !(
|
||||
this.areChangesPresent(formData) && isFormDataValidForTransactionSubmission(formData)
|
||||
),
|
||||
disabled: !(this.areChangesPresent(formData) && this.isFormDataValid(formData)),
|
||||
callback: async (formData, route, router) => {
|
||||
const api = new TransactionApiClient(getSelectedProfile(route))
|
||||
// Assume that form data is valid!
|
||||
|
|
@ -136,7 +137,7 @@ export class NewTransactionEditorContext implements TransactionEditorContextBase
|
|||
},
|
||||
{
|
||||
name: 'Save Draft',
|
||||
disabled: !this.areChangesPresent(formData) || !isFormDataValidForDraftSave(formData),
|
||||
disabled: !this.areChangesPresent(formData),
|
||||
callback: async (formData, route, router) => {
|
||||
const api = new TransactionApiClient(getSelectedProfile(route))
|
||||
const data = toDraftPayload(formData)
|
||||
|
|
@ -168,7 +169,17 @@ export class TransactionEditorContext implements TransactionEditorContextBase {
|
|||
}
|
||||
|
||||
isFormDataValid(formData: TransactionEditorFormFields): boolean {
|
||||
return isFormDataValidForTransactionSubmission(formData)
|
||||
return (
|
||||
formData.timestamp !== null &&
|
||||
formData.timestamp.length > 0 &&
|
||||
formData.amount !== null &&
|
||||
formData.amount > 0 &&
|
||||
formData.currency !== null &&
|
||||
formData.description !== null &&
|
||||
formData.description.length > 0 &&
|
||||
(formData.creditedAccountId !== null || formData.debitedAccountId !== null) &&
|
||||
formData.creditedAccountId !== formData.debitedAccountId
|
||||
)
|
||||
}
|
||||
|
||||
areChangesPresent(formData: TransactionEditorFormFields): boolean {
|
||||
|
|
@ -267,16 +278,9 @@ export class DraftEditorContext implements TransactionEditorContextBase {
|
|||
this.existingDraft = existingDraft
|
||||
}
|
||||
|
||||
isFormDataValid(formData: TransactionEditorFormFields): boolean {
|
||||
const result =
|
||||
(formData.amount === null || formData.currency !== null) &&
|
||||
(formData.amount === null || formData.amount > 0) &&
|
||||
(formData.creditedAccountId === null ||
|
||||
formData.debitedAccountId === null ||
|
||||
formData.creditedAccountId !== formData.debitedAccountId) &&
|
||||
(formData.templateName === null || formData.templateName.length <= 32)
|
||||
console.log('Checking draft editor valid:', formData, result)
|
||||
return result
|
||||
isFormDataValid(): boolean {
|
||||
// TODO: What validation is needed client-side for draft data?
|
||||
return true
|
||||
}
|
||||
|
||||
areChangesPresent(): boolean {
|
||||
|
|
@ -321,11 +325,11 @@ export class DraftEditorContext implements TransactionEditorContextBase {
|
|||
return fields
|
||||
}
|
||||
|
||||
getActions(formData: TransactionEditorFormFields): TransactionEditorAction[] {
|
||||
getActions(): TransactionEditorAction[] {
|
||||
return [
|
||||
{
|
||||
name: 'Save Draft',
|
||||
disabled: !this.areChangesPresent() || !this.isFormDataValid(formData),
|
||||
name: 'Save',
|
||||
disabled: !this.areChangesPresent() || !this.isFormDataValid(),
|
||||
callback: async (formData, route, router) => {
|
||||
const api = new TransactionApiClient(getSelectedProfile(route))
|
||||
const data = toDraftPayload(formData)
|
||||
|
|
@ -339,21 +343,6 @@ export class DraftEditorContext implements TransactionEditorContextBase {
|
|||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Submit Transaction',
|
||||
disabled: !this.areChangesPresent() || !isFormDataValidForTransactionSubmission(formData),
|
||||
callback: async (formData, route, router) => {
|
||||
const api = new TransactionApiClient(getSelectedProfile(route))
|
||||
// First call the normal "Save" callback from the NewTransactionEditorContext.
|
||||
const tec = new NewTransactionEditorContext()
|
||||
const saveTxnAction = tec.getActions(formData).find((a) => a.name === 'Save')!
|
||||
await saveTxnAction.callback(formData, route, router)
|
||||
// Then if this is not a template draft, delete it.
|
||||
if (formData.templateName === null || formData.templateName.length === 0) {
|
||||
await api.deleteDraft(this.existingDraft.id)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
disabled: false,
|
||||
|
|
@ -365,8 +354,6 @@ export class DraftEditorContext implements TransactionEditorContextBase {
|
|||
}
|
||||
}
|
||||
|
||||
// Helper functions below here!
|
||||
|
||||
/**
|
||||
* Obtains an editor context by determining what the user is doing based on the
|
||||
* route they've navigated to.
|
||||
|
|
@ -468,26 +455,3 @@ async function goBackOrHome(router: Router, route: RouteLocation) {
|
|||
await router.replace(`/profiles/${getSelectedProfile(route)}`)
|
||||
}
|
||||
}
|
||||
|
||||
function isFormDataValidForTransactionSubmission(formData: TransactionEditorFormFields): boolean {
|
||||
return (
|
||||
formData.timestamp !== null &&
|
||||
formData.timestamp.length > 0 &&
|
||||
formData.amount !== null &&
|
||||
formData.amount > 0 &&
|
||||
formData.currency !== null &&
|
||||
formData.description !== null &&
|
||||
formData.description.length > 0 &&
|
||||
(formData.creditedAccountId !== null || formData.debitedAccountId !== null) &&
|
||||
formData.creditedAccountId !== formData.debitedAccountId
|
||||
)
|
||||
}
|
||||
|
||||
function isFormDataValidForDraftSave(formData: TransactionEditorFormFields): boolean {
|
||||
return (
|
||||
formData.amount !== null &&
|
||||
formData.amount > 0 &&
|
||||
formData.timestamp !== null &&
|
||||
formData.currency !== null
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue