Compare commits
3 Commits
df4737d0a5
...
13aadc2358
| Author | SHA1 | Date |
|---|---|---|
|
|
13aadc2358 | |
|
|
69c3079174 | |
|
|
3b35f8f16d |
|
|
@ -9,7 +9,6 @@ import std.algorithm;
|
|||
import std.array;
|
||||
import std.conv;
|
||||
import slf4d;
|
||||
import asdf;
|
||||
|
||||
import profile.data;
|
||||
import profile.model;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ interface AttachmentRepository {
|
|||
Attachment[] findAllByLinkedEntity(string subquery, ulong entityId);
|
||||
Attachment[] findAllByTransactionId(ulong transactionId);
|
||||
Attachment[] findAllByValueRecordId(ulong valueRecordId);
|
||||
Attachment[] findAllByTransactionDraftId(ulong draftId);
|
||||
ulong save(SysTime uploadedAt, string filename, string contentType, in ubyte[] content);
|
||||
void remove(ulong id);
|
||||
Optional!(ubyte[]) getContent(ulong id);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ class SqliteAttachmentRepository : AttachmentRepository {
|
|||
);
|
||||
}
|
||||
|
||||
Attachment[] findAllByTransactionDraftId(ulong draftId) {
|
||||
return findAllByLinkedEntity(
|
||||
"SELECT attachment_id FROM transaction_draft_attachment WHERE draft_id = ?",
|
||||
draftId
|
||||
);
|
||||
}
|
||||
|
||||
ulong save(SysTime uploadedAt, string filename, string contentType, in ubyte[] content) {
|
||||
util.sqlite.update(
|
||||
db,
|
||||
|
|
|
|||
|
|
@ -224,20 +224,12 @@ immutable DEFAULT_DRAFT_PAGE = PageRequest(1, 10, [Sort("txn.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);
|
||||
writeJsonBody(response, page);
|
||||
}
|
||||
|
||||
@GetMapping(PROFILE_PATH ~ "/transaction-templates")
|
||||
void handleGetTemplates(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||
ProfileDataSource ds = getProfileDataSource(request);
|
||||
PageRequest pr = PageRequest.parse(request, DEFAULT_DRAFT_PAGE);
|
||||
Page!TransactionDraftListItem page = getTemplates(ds, pr);
|
||||
bool shouldFetchTemplates = request.getParamAs!bool("template", false);
|
||||
Page!TransactionDraftListItem page = getDrafts(ds, pr, shouldFetchTemplates);
|
||||
writeJsonBody(response, page);
|
||||
}
|
||||
|
||||
@GetMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
|
||||
@GetMapping(PROFILE_PATH ~ "/transaction-templates/:draftId:ulong")
|
||||
void handleGetDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||
ProfileDataSource ds = getProfileDataSource(request);
|
||||
TransactionDraftResponse draft = getDraft(ds, getDraftId(request));
|
||||
|
|
@ -246,6 +238,33 @@ void handleGetDraft(ref ServerHttpRequest request, ref ServerHttpResponse respon
|
|||
response.writeBodyString(jsonStr, "application/json");
|
||||
}
|
||||
|
||||
@PostMapping(PROFILE_PATH ~ "/transaction-drafts")
|
||||
void handleAddDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||
auto fullPayload = parseMultipartFilesAndBody!TransactionDraftPayload(request);
|
||||
ProfileDataSource ds = getProfileDataSource(request);
|
||||
TransactionDraftResponse draft = addDraft(ds, fullPayload.payload, fullPayload.files);
|
||||
import asdf : serializeToJson;
|
||||
string jsonStr = serializeToJson(draft);
|
||||
response.writeBodyString(jsonStr, "application/json");
|
||||
}
|
||||
|
||||
@PutMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
|
||||
void handleUpdateDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||
ProfileDataSource ds = getProfileDataSource(request);
|
||||
auto fullPayload = parseMultipartFilesAndBody!TransactionDraftPayload(request);
|
||||
TransactionDraftResponse draft = updateDraft(ds, getDraftId(request), fullPayload.payload, fullPayload.files);
|
||||
import asdf : serializeToJson;
|
||||
string jsonStr = serializeToJson(draft);
|
||||
response.writeBodyString(jsonStr, "application/json");
|
||||
}
|
||||
|
||||
@DeleteMapping(PROFILE_PATH ~ "/transaction-drafts/:draftId:ulong")
|
||||
void handleDeleteDraft(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||
ProfileDataSource ds = getProfileDataSource(request);
|
||||
ulong draftId = getDraftId(request);
|
||||
deleteDraft(ds, draftId);
|
||||
}
|
||||
|
||||
private ulong getDraftId(in ServerHttpRequest request) {
|
||||
return getPathParamOrThrow!ulong(request, "draftId");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ interface TransactionCategoryRepository {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: Migrate into transaction repo, similar to drafts!
|
||||
interface TransactionTagRepository {
|
||||
string[] findAllByTransactionId(ulong transactionId);
|
||||
void updateTags(ulong transactionId, in string[] tags);
|
||||
|
|
@ -61,4 +62,7 @@ interface TransactionDraftRepository {
|
|||
void linkAttachment(ulong draftId, ulong attachmentId);
|
||||
TransactionDraftResponse update(ulong draftId, in TransactionDraftPayload data);
|
||||
void deleteById(ulong id);
|
||||
|
||||
void updateTags(ulong draftId, in string[] tags);
|
||||
string[] findAllTags();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -659,18 +659,75 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
}
|
||||
|
||||
Optional!TransactionDraftResponse findById(ulong id) {
|
||||
// First fetch the draft list item (contains all basic properties).
|
||||
QueryBuilder qb = getBuilderForDraftsList();
|
||||
addSelectsForDraftsList(qb);
|
||||
qb.groupBy("draft.id");
|
||||
qb.where("draft.id = ?");
|
||||
string query = qb.build();
|
||||
// return util.sqlite.findOne(db, query, &parseDraft, id);
|
||||
// TODO!
|
||||
return Optional!TransactionDraftResponse.empty();
|
||||
Optional!TransactionDraftListItem li = util.sqlite.findOne(db, query, &parseDraftListItem, id);
|
||||
if (li.isNull) return Optional!TransactionDraftResponse.empty();
|
||||
TransactionDraftListItem draft = li.value;
|
||||
// Then fetch line items.
|
||||
TransactionDraftResponse response;
|
||||
response.id = draft.id;
|
||||
response.addedAt = draft.addedAt;
|
||||
response.templateName = draft.templateName;
|
||||
response.timestamp = draft.timestamp;
|
||||
response.amount = draft.amount;
|
||||
response.currency = draft.currency;
|
||||
response.description = draft.description;
|
||||
response.internalTransfer = draft.internalTransfer;
|
||||
response.vendor = draft.vendor;
|
||||
response.category = draft.category;
|
||||
response.creditedAccount = draft.creditedAccount;
|
||||
response.debitedAccount = draft.debitedAccount;
|
||||
response.lineItems = util.sqlite.findAll(
|
||||
db,
|
||||
import("sql/query/get_line_items_draft.sql"),
|
||||
(row) {
|
||||
TransactionLineItemResponse item;
|
||||
item.idx = row.peek!uint(0);
|
||||
item.valuePerItem = row.peek!long(1);
|
||||
item.quantity = row.peek!ulong(2);
|
||||
item.description = row.peek!string(3);
|
||||
Optional!ulong categoryId = row.parseOptional!ulong(4);
|
||||
if (categoryId) {
|
||||
item.category = Optional!TransactionCategory.of(
|
||||
TransactionCategory(
|
||||
categoryId.value,
|
||||
row.parseOptional!ulong(5),
|
||||
row.peek!string(6),
|
||||
row.peek!string(7),
|
||||
row.peek!string(8)
|
||||
));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
);
|
||||
// Return the response, excluding attachments (they are fetched using the attachment repo).
|
||||
return Optional!TransactionDraftResponse.of(response);
|
||||
}
|
||||
|
||||
TransactionDraftResponse insert(in TransactionDraftPayload data) {
|
||||
// TODO
|
||||
return TransactionDraftResponse.init;
|
||||
util.sqlite.update(
|
||||
db,
|
||||
import("sql/insert_transaction_draft.sql"),
|
||||
Clock.currTime(UTC()).toISOExtString(),
|
||||
data.templateName.toNullable(),
|
||||
data.timestamp.toNullable(),
|
||||
data.amount.toNullable(),
|
||||
data.currencyCode.toNullable(),
|
||||
data.description.toNullable(),
|
||||
data.internalTransfer.toNullable(),
|
||||
data.vendorId.toNullable(),
|
||||
data.categoryId.toNullable(),
|
||||
data.creditedAccountId.toNullable(),
|
||||
data.debitedAccountId.toNullable()
|
||||
);
|
||||
ulong draftId = db.lastInsertRowid();
|
||||
insertLineItems(draftId, data);
|
||||
return findById(draftId).orElseThrow();
|
||||
}
|
||||
|
||||
void linkAttachment(ulong draftId, ulong attachmentId) {
|
||||
|
|
@ -683,8 +740,29 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
}
|
||||
|
||||
TransactionDraftResponse update(ulong draftId, in TransactionDraftPayload data) {
|
||||
// TODO
|
||||
return TransactionDraftResponse.init;
|
||||
util.sqlite.update(
|
||||
db,
|
||||
import("sql/update_transaction_draft.sql"),
|
||||
data.templateName.toNullable(),
|
||||
data.timestamp.toNullable(),
|
||||
data.amount.toNullable(),
|
||||
data.currencyCode.toNullable(),
|
||||
data.description.toNullable(),
|
||||
data.internalTransfer.toNullable(),
|
||||
data.vendorId.toNullable(),
|
||||
data.categoryId.toNullable(),
|
||||
data.creditedAccountId.toNullable(),
|
||||
data.debitedAccountId.toNullable(),
|
||||
draftId
|
||||
);
|
||||
// Re-write all line items:
|
||||
util.sqlite.update(
|
||||
db,
|
||||
"DELETE FROM transaction_draft_line_item WHERE draft_id = ?",
|
||||
draftId
|
||||
);
|
||||
insertLineItems(draftId, data);
|
||||
return findById(draftId).orElseThrow();
|
||||
}
|
||||
|
||||
void deleteById(ulong id) {
|
||||
|
|
@ -698,6 +776,31 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
util.sqlite.deleteById(db, "transaction_draft", id);
|
||||
}
|
||||
|
||||
void updateTags(ulong draftId, in string[] tags) {
|
||||
util.sqlite.update(
|
||||
db,
|
||||
"DELETE FROM transaction_draft_tag WHERE draft_id = ?",
|
||||
draftId
|
||||
);
|
||||
foreach (tag; tags) {
|
||||
util.sqlite.update(
|
||||
db,
|
||||
"INSERT INTO transaction_draft_tag (draft_id, tag) VALUES (?, ?)",
|
||||
draftId, tag
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
string[] findAllTags() {
|
||||
return util.sqlite.findAll(
|
||||
db,
|
||||
"SELECT DISTINCT tag FROM transaction_draft_tag ORDER BY tag",
|
||||
r => r.peek!string(0)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private QueryBuilder getBuilderForDraftsList() {
|
||||
return QueryBuilder("transaction_draft draft")
|
||||
.join("LEFT JOIN transaction_vendor vendor ON vendor.id = draft.vendor_id")
|
||||
|
|
@ -784,4 +887,19 @@ class SqliteTransactionDraftRepository : TransactionDraftRepository {
|
|||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private void insertLineItems(ulong draftId, in TransactionDraftPayload payload) {
|
||||
foreach (size_t idx, lineItem; payload.lineItems) {
|
||||
util.sqlite.update(
|
||||
db,
|
||||
import("sql/insert_line_item_draft.sql"),
|
||||
draftId,
|
||||
idx,
|
||||
lineItem.valuePerItem,
|
||||
lineItem.quantity,
|
||||
lineItem.description,
|
||||
lineItem.categoryId.toNullable()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,5 +182,26 @@ struct TransactionDraftResponse {
|
|||
|
||||
/// Data provided by users when creating or updating drafts.
|
||||
struct TransactionDraftPayload {
|
||||
// TODO.
|
||||
Optional!string templateName;
|
||||
Optional!string timestamp;
|
||||
Optional!ulong amount;
|
||||
Optional!string currencyCode;
|
||||
Optional!string description;
|
||||
Optional!bool internalTransfer;
|
||||
|
||||
Optional!ulong vendorId;
|
||||
Optional!ulong categoryId;
|
||||
Optional!ulong creditedAccountId;
|
||||
Optional!ulong debitedAccountId;
|
||||
|
||||
string[] tags;
|
||||
LineItemPayload[] lineItems;
|
||||
ulong[] attachmentIdsToRemove;
|
||||
|
||||
static struct LineItemPayload {
|
||||
long valuePerItem;
|
||||
ulong quantity;
|
||||
string description;
|
||||
Optional!ulong categoryId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ TransactionDetail addTransaction(ProfileDataSource ds, in AddTransactionPayload
|
|||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
|
||||
validateTransactionPayload(vendorRepo, categoryRepo, accountRepo, payload);
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
SysTime timestamp = SysTime.fromISOExtString(payload.timestamp, UTC());
|
||||
|
||||
// Add the transaction:
|
||||
|
|
@ -74,7 +75,7 @@ TransactionDetail addTransaction(ProfileDataSource ds, in AddTransactionPayload
|
|||
}
|
||||
TransactionTagRepository tagRepo = ds.getTransactionTagRepository();
|
||||
tagRepo.updateTags(txn.id, payload.tags);
|
||||
updateAttachments(txn.id, timestamp, payload.attachmentIdsToRemove, files, attachmentRepo, txnRepo);
|
||||
updateAttachments(txn.id, now, payload.attachmentIdsToRemove, files, attachmentRepo, txnRepo);
|
||||
txnId = txn.id;
|
||||
});
|
||||
return getTransaction(ds, txnId);
|
||||
|
|
@ -95,6 +96,7 @@ TransactionDetail updateTransaction(
|
|||
|
||||
validateTransactionPayload(vendorRepo, categoryRepo, accountRepo, payload);
|
||||
SysTime timestamp = SysTime.fromISOExtString(payload.timestamp, UTC());
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
|
||||
const TransactionDetail prev = transactionRepo.findById(transactionId)
|
||||
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
||||
|
|
@ -104,7 +106,7 @@ TransactionDetail updateTransaction(
|
|||
TransactionDetail curr = transactionRepo.update(transactionId, payload);
|
||||
updateLinkedAccountJournalEntries(prev, curr, payload, ds, timestamp);
|
||||
tagRepo.updateTags(transactionId, payload.tags);
|
||||
updateAttachments(curr.id, timestamp, payload.attachmentIdsToRemove, files, attachmentRepo, transactionRepo);
|
||||
updateAttachments(curr.id, now, payload.attachmentIdsToRemove, files, attachmentRepo, transactionRepo);
|
||||
});
|
||||
return getTransaction(ds, transactionId);
|
||||
}
|
||||
|
|
@ -499,18 +501,99 @@ void deleteCategory(ProfileDataSource ds, ulong categoryId) {
|
|||
|
||||
// Draft services
|
||||
|
||||
Page!TransactionDraftListItem getDrafts(ProfileDataSource ds, in PageRequest pr) {
|
||||
return ds.getTransactionDraftRepository()
|
||||
.findAllDrafts(pr);
|
||||
}
|
||||
|
||||
Page!TransactionDraftListItem getTemplates(ProfileDataSource ds, in PageRequest 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) {
|
||||
return ds.getTransactionDraftRepository()
|
||||
.findById(draftId)
|
||||
// Populate the list of attachments for the draft.
|
||||
.mapIfPresent!((draft) {
|
||||
import std.algorithm : map;
|
||||
import std.array : array;
|
||||
draft.attachments = ds.getAttachmentRepository()
|
||||
.findAllByTransactionDraftId(draft.id)
|
||||
.map!(AttachmentResponse.of)
|
||||
.array;
|
||||
return draft;
|
||||
})
|
||||
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
TransactionDraftResponse addDraft(ProfileDataSource ds, in TransactionDraftPayload payload, in MultipartFile[] files) {
|
||||
TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository();
|
||||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
|
||||
validateDraftPayload(payload);
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
|
||||
ulong draftId;
|
||||
ds.doTransaction(() {
|
||||
TransactionDraftResponse draft = draftRepo.insert(payload);
|
||||
draftRepo.updateTags(draft.id, payload.tags);
|
||||
updateDraftAttachments(draft.id, now, payload.attachmentIdsToRemove, files, attachmentRepo, draftRepo);
|
||||
draftId = draft.id;
|
||||
});
|
||||
return getDraft(ds, draftId);
|
||||
}
|
||||
|
||||
TransactionDraftResponse updateDraft(
|
||||
ProfileDataSource ds,
|
||||
ulong draftId,
|
||||
in TransactionDraftPayload payload,
|
||||
in MultipartFile[] files
|
||||
) {
|
||||
TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository();
|
||||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
|
||||
validateDraftPayload(payload);
|
||||
SysTime now = Clock.currTime(UTC());
|
||||
|
||||
ds.doTransaction(() {
|
||||
draftRepo.update(draftId, payload);
|
||||
draftRepo.updateTags(draftId, payload.tags);
|
||||
updateDraftAttachments(draftId, now, payload.attachmentIdsToRemove, files, attachmentRepo, draftRepo);
|
||||
});
|
||||
return getDraft(ds, draftId);
|
||||
}
|
||||
|
||||
void deleteDraft(ProfileDataSource ds, ulong draftId) {
|
||||
TransactionDraftRepository draftRepo = ds.getTransactionDraftRepository();
|
||||
AttachmentRepository attachmentRepo = ds.getAttachmentRepository();
|
||||
TransactionDraftResponse draft = draftRepo.findById(draftId)
|
||||
.orElseThrow(() => new HttpStatusException(HttpStatus.NOT_FOUND));
|
||||
ds.doTransaction(() {
|
||||
// First delete all attachments.
|
||||
foreach (a; attachmentRepo.findAllByTransactionDraftId(draft.id)) {
|
||||
attachmentRepo.remove(a.id);
|
||||
}
|
||||
draftRepo.deleteById(draft.id);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateDraftPayload(in TransactionDraftPayload payload) {
|
||||
// TODO!
|
||||
}
|
||||
|
||||
private void updateDraftAttachments(
|
||||
ulong draftId,
|
||||
SysTime timestamp,
|
||||
in ulong[] attachmentIdsToRemove,
|
||||
in MultipartFile[] attachmentsToAdd,
|
||||
AttachmentRepository attachmentRepo,
|
||||
TransactionDraftRepository draftRepo
|
||||
) {
|
||||
foreach (file; attachmentsToAdd) {
|
||||
ulong attachmentId = attachmentRepo.save(timestamp, file.name, file.contentType, file.content);
|
||||
draftRepo.linkAttachment(draftId, attachmentId);
|
||||
}
|
||||
foreach (idToRemove; attachmentIdsToRemove) {
|
||||
attachmentRepo.remove(idToRemove);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
INSERT INTO transaction_draft_line_item (
|
||||
draft_id,
|
||||
idx,
|
||||
value_per_item,
|
||||
quantity,
|
||||
description,
|
||||
category_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
INSERT INTO transaction_draft (
|
||||
added_at,
|
||||
template_name,
|
||||
timestamp,
|
||||
amount,
|
||||
currency,
|
||||
description,
|
||||
internal_transfer,
|
||||
vendor_id,
|
||||
category_id,
|
||||
credited_account_id,
|
||||
debited_account_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
SELECT
|
||||
i.idx,
|
||||
i.value_per_item,
|
||||
i.quantity,
|
||||
i.description,
|
||||
|
||||
i.category_id,
|
||||
category.parent_id,
|
||||
category.name,
|
||||
category.description,
|
||||
category.color
|
||||
|
||||
FROM transaction_draft_line_item i
|
||||
LEFT JOIN transaction_category category
|
||||
ON category.id = i.category_id
|
||||
WHERE i.draft_id = ?
|
||||
ORDER BY idx;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
UPDATE transaction_draft
|
||||
SET
|
||||
template_name = ?,
|
||||
timestamp = ?,
|
||||
amount = ?,
|
||||
currency = ?,
|
||||
description = ?,
|
||||
internal_transfer = ?,
|
||||
vendor_id = ?,
|
||||
category_id = ?,
|
||||
credited_account_id = ?,
|
||||
debited_account_id = ?
|
||||
WHERE id = ?
|
||||
|
|
@ -59,6 +59,13 @@ export interface Account {
|
|||
currentBalance: number | null
|
||||
}
|
||||
|
||||
export interface SimpleAccountResponse {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
numberSuffix: string
|
||||
}
|
||||
|
||||
export interface AccountCreationPayload {
|
||||
type: string
|
||||
numberSuffix: string
|
||||
|
|
|
|||
|
|
@ -1,8 +1,28 @@
|
|||
import type { SimpleAccountResponse } from './account'
|
||||
import type { Attachment } from './attachment'
|
||||
import { ApiClient } from './base'
|
||||
import type { Currency } from './data'
|
||||
import { type Page, type PageRequest } from './pagination'
|
||||
|
||||
export interface SimpleVendorResponse {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface SimpleCategoryResponse {
|
||||
id: number
|
||||
name: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface TransactionLineItemResponse {
|
||||
idx: number
|
||||
valuePerItem: number
|
||||
quantity: number
|
||||
description: string
|
||||
category: TransactionCategory | null
|
||||
}
|
||||
|
||||
export interface TransactionVendor {
|
||||
id: number
|
||||
name: string
|
||||
|
|
@ -47,10 +67,10 @@ export interface TransactionsListItem {
|
|||
currency: Currency
|
||||
description: string
|
||||
internalTransfer: boolean
|
||||
vendor: TransactionsListItemVendor | null
|
||||
category: TransactionsListItemCategory | null
|
||||
creditedAccount: TransactionsListItemAccount | null
|
||||
debitedAccount: TransactionsListItemAccount | null
|
||||
vendor: SimpleVendorResponse | null
|
||||
category: SimpleCategoryResponse | null
|
||||
creditedAccount: SimpleAccountResponse | null
|
||||
debitedAccount: SimpleAccountResponse | null
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
|
|
@ -82,28 +102,13 @@ export interface TransactionDetail {
|
|||
internalTransfer: boolean
|
||||
vendor: TransactionVendor | null
|
||||
category: TransactionCategory | null
|
||||
creditedAccount: TransactionDetailAccount | null
|
||||
debitedAccount: TransactionDetailAccount | null
|
||||
creditedAccount: SimpleAccountResponse | null
|
||||
debitedAccount: SimpleAccountResponse | null
|
||||
tags: string[]
|
||||
lineItems: TransactionDetailLineItem[]
|
||||
lineItems: TransactionLineItemResponse[]
|
||||
attachments: Attachment[]
|
||||
}
|
||||
|
||||
export interface TransactionDetailAccount {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
numberSuffix: string
|
||||
}
|
||||
|
||||
export interface TransactionDetailLineItem {
|
||||
idx: number
|
||||
valuePerItem: number
|
||||
quantity: number
|
||||
description: string
|
||||
category: TransactionCategory | null
|
||||
}
|
||||
|
||||
export interface AddTransactionPayload {
|
||||
timestamp: string
|
||||
amount: number
|
||||
|
|
@ -144,6 +149,56 @@ export interface AggregateTransactionData {
|
|||
currencies: AggregateTransactionCurrencyData[]
|
||||
}
|
||||
|
||||
export interface TransactionDraftListItem {
|
||||
id: number
|
||||
addedAt: string
|
||||
templateName: string | null
|
||||
timestamp: string | null
|
||||
amount: number | null
|
||||
currency: Currency | null
|
||||
description: string | null
|
||||
internalTransfer: boolean | null
|
||||
vendor: SimpleVendorResponse | null
|
||||
category: SimpleCategoryResponse | null
|
||||
creditedAccount: SimpleAccountResponse | null
|
||||
debitedAccount: SimpleAccountResponse | null
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface TransactionDraftResponse {
|
||||
id: number
|
||||
addedAt: string
|
||||
templateName: string | null
|
||||
timestamp: string | null
|
||||
amount: number | null
|
||||
currency: Currency | null
|
||||
description: string | null
|
||||
internalTransfer: boolean | null
|
||||
vendor: SimpleVendorResponse | null
|
||||
category: SimpleCategoryResponse | null
|
||||
creditedAccount: SimpleAccountResponse | null
|
||||
debitedAccount: SimpleAccountResponse | null
|
||||
tags: string[]
|
||||
lineItems: TransactionLineItemResponse[]
|
||||
attachments: Attachment[]
|
||||
}
|
||||
|
||||
export interface TransactionDraftPayload {
|
||||
templateName: string | null
|
||||
timestamp: string | null
|
||||
amount: number | null
|
||||
currencyCode: string | null
|
||||
description: string | null
|
||||
internalTransfer: boolean | null
|
||||
vendorId: number | null
|
||||
categoryId: number | null
|
||||
creditedAccountId: number | null
|
||||
debitedAccountId: number | null
|
||||
tags: string[]
|
||||
lineItems: AddTransactionPayloadLineItem[]
|
||||
attachmentIdsToRemove: number[]
|
||||
}
|
||||
|
||||
export class TransactionApiClient extends ApiClient {
|
||||
readonly path: string
|
||||
|
||||
|
|
@ -277,4 +332,57 @@ export class TransactionApiClient extends ApiClient {
|
|||
getAllTags(): Promise<string[]> {
|
||||
return super.getJson(this.path + '/transaction-tags')
|
||||
}
|
||||
|
||||
// Drafts:
|
||||
|
||||
getDrafts(
|
||||
paginationOptions: PageRequest | undefined = undefined,
|
||||
): Promise<Page<TransactionDraftListItem>> {
|
||||
return super.getJsonPage(this.path + '/transaction-drafts', paginationOptions)
|
||||
}
|
||||
|
||||
getTemplateDrafts(
|
||||
paginationOptions: PageRequest | undefined = undefined,
|
||||
): Promise<Page<TransactionDraftListItem>> {
|
||||
const params = new URLSearchParams()
|
||||
params.append('template', 'true')
|
||||
if (paginationOptions !== undefined) {
|
||||
params.append('page', paginationOptions.page + '')
|
||||
params.append('size', paginationOptions.size + '')
|
||||
for (const sort of paginationOptions.sorts) {
|
||||
params.append('sort', sort.attribute + ',' + sort.dir)
|
||||
}
|
||||
}
|
||||
return super.getJson(this.path + '/transaction-drafts?' + params.toString())
|
||||
}
|
||||
|
||||
getDraft(id: number): Promise<TransactionDraftResponse> {
|
||||
return super.getJson(this.path + '/transaction-drafts/' + id)
|
||||
}
|
||||
|
||||
addDraft(data: TransactionDraftPayload, files: File[] = []): Promise<TransactionDraftResponse> {
|
||||
const formData = new FormData()
|
||||
formData.append('payload', JSON.stringify(data))
|
||||
for (const file of files) {
|
||||
formData.append('file', file)
|
||||
}
|
||||
return super.postFormData(this.path + '/transaction-drafts', formData)
|
||||
}
|
||||
|
||||
updateDraft(
|
||||
id: number,
|
||||
data: TransactionDraftPayload,
|
||||
files: File[] = [],
|
||||
): Promise<TransactionDraftResponse> {
|
||||
const formData = new FormData()
|
||||
formData.append('payload', JSON.stringify(data))
|
||||
for (const file of files) {
|
||||
formData.append('file', file)
|
||||
}
|
||||
return super.putFormData(this.path + '/transaction-drafts/' + id, formData)
|
||||
}
|
||||
|
||||
deleteDraft(id: number): Promise<void> {
|
||||
return super.delete(this.path + '/transaction-drafts/' + id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import type { TransactionDetailLineItem } from '@/api/transaction'
|
||||
import type { TransactionLineItemResponse } from '@/api/transaction'
|
||||
import AppButton from './common/AppButton.vue'
|
||||
import { formatMoney, type Currency } from '@/api/data'
|
||||
|
||||
defineProps<{
|
||||
lineItem: TransactionDetailLineItem
|
||||
lineItem: TransactionLineItemResponse
|
||||
currency: Currency
|
||||
totalCount?: number
|
||||
editable?: boolean
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ transaction. This editor shows a table of current line items, and includes a
|
|||
modal for adding a new one.
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import { type TransactionCategoryTree, type TransactionDetailLineItem } from '@/api/transaction'
|
||||
import { type TransactionCategoryTree, type TransactionLineItemResponse } from '@/api/transaction'
|
||||
import AppButton from '@/components/common/AppButton.vue'
|
||||
import FormGroup from '@/components/common/form/FormGroup.vue'
|
||||
import { floatMoneyToInteger, formatMoney, type Currency } from '@/api/data'
|
||||
|
|
@ -15,7 +15,7 @@ import CategorySelect from './CategorySelect.vue'
|
|||
import LineItemCard from './LineItemCard.vue'
|
||||
import AppBadge from './common/AppBadge.vue'
|
||||
|
||||
const model = defineModel<TransactionDetailLineItem[]>({ required: true })
|
||||
const model = defineModel<TransactionLineItemResponse[]>({ required: true })
|
||||
const props = defineProps<{
|
||||
transactionAmount: number
|
||||
currency: Currency
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
TransactionApiClient,
|
||||
type AddTransactionPayload,
|
||||
type TransactionDetail,
|
||||
type TransactionDetailLineItem,
|
||||
type TransactionLineItemResponse,
|
||||
type TransactionVendor,
|
||||
} from '@/api/transaction'
|
||||
import AppPage from '@/components/common/AppPage.vue'
|
||||
|
|
@ -144,7 +144,7 @@ const vendor: Ref<TransactionVendor | null> = ref(null)
|
|||
const categoryId: Ref<number | null> = ref(null)
|
||||
const creditedAccountId: Ref<number | null> = ref(null)
|
||||
const debitedAccountId: Ref<number | null> = ref(null)
|
||||
const lineItems: Ref<TransactionDetailLineItem[]> = ref([])
|
||||
const lineItems: Ref<TransactionLineItemResponse[]> = ref([])
|
||||
const tags: Ref<string[]> = ref([])
|
||||
const customTagInput = ref('')
|
||||
const customTagInputValid = ref(false)
|
||||
|
|
|
|||
Loading…
Reference in New Issue