424 lines
15 KiB
D
424 lines
15 KiB
D
module transaction.data_impl_sqlite;
|
|
|
|
import handy_http_primitives : Optional;
|
|
import std.datetime;
|
|
import std.typecons;
|
|
import d2sqlite3;
|
|
|
|
import transaction.model;
|
|
import transaction.data;
|
|
import transaction.dto;
|
|
import util.sqlite;
|
|
import util.money;
|
|
import util.pagination;
|
|
import util.data;
|
|
|
|
class SqliteTransactionVendorRepository : TransactionVendorRepository {
|
|
private Database db;
|
|
this(Database db) {
|
|
this.db = db;
|
|
}
|
|
|
|
Optional!TransactionVendor findById(ulong id) {
|
|
return util.sqlite.findById(db, "transaction_vendor", &parseVendor, id);
|
|
}
|
|
|
|
TransactionVendor[] findAll() {
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT * FROM transaction_vendor ORDER BY name ASC",
|
|
&parseVendor
|
|
);
|
|
}
|
|
|
|
bool existsByName(string name) {
|
|
return util.sqlite.exists(db, "SELECT id FROM transaction_vendor WHERE name = ?", name);
|
|
}
|
|
|
|
bool existsById(ulong id) {
|
|
return util.sqlite.exists(db, "SELECT id FROM transaction_vendor WHERE id = ?", id);
|
|
}
|
|
|
|
TransactionVendor insert(string name, string description) {
|
|
util.sqlite.update(
|
|
db,
|
|
"INSERT INTO transaction_vendor (name, description) VALUES (?, ?)",
|
|
name, description
|
|
);
|
|
ulong id = db.lastInsertRowid();
|
|
return findById(id).orElseThrow();
|
|
}
|
|
|
|
void deleteById(ulong id) {
|
|
util.sqlite.deleteById(db, "transaction_vendor", id);
|
|
}
|
|
|
|
TransactionVendor updateById(ulong id, string name, string description) {
|
|
util.sqlite.update(
|
|
db,
|
|
"UPDATE transaction_vendor SET name = ?, description = ? WHERE id = ?",
|
|
name, description, id
|
|
);
|
|
return findById(id).orElseThrow();
|
|
}
|
|
|
|
private static TransactionVendor parseVendor(Row row) {
|
|
return TransactionVendor(
|
|
row.peek!ulong(0),
|
|
row.peek!string(1),
|
|
row.peek!string(2)
|
|
);
|
|
}
|
|
}
|
|
|
|
class SqliteTransactionCategoryRepository : TransactionCategoryRepository {
|
|
private Database db;
|
|
this(Database db) {
|
|
this.db = db;
|
|
}
|
|
|
|
Optional!TransactionCategory findById(ulong id) {
|
|
return util.sqlite.findById(db, "transaction_category", &parseCategory, id);
|
|
}
|
|
|
|
bool existsById(ulong id) {
|
|
return util.sqlite.exists(db, "SELECT id FROM transaction_category WHERE id = ?", id);
|
|
}
|
|
|
|
TransactionCategory[] findAll() {
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT * FROM transaction_category ORDER BY parent_id, name",
|
|
&parseCategory
|
|
);
|
|
}
|
|
|
|
TransactionCategory[] findAllByParentId(Optional!ulong parentId) {
|
|
if (parentId) {
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT * FROM transaction_category WHERE parent_id = ? ORDER BY name ASC",
|
|
&parseCategory,
|
|
parentId.value
|
|
);
|
|
}
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT * FROM transaction_category WHERE parent_id IS NULL ORDER BY name ASC",
|
|
&parseCategory
|
|
);
|
|
}
|
|
|
|
TransactionCategory insert(Optional!ulong parentId, string name, string description, string color) {
|
|
util.sqlite.update(
|
|
db,
|
|
"INSERT INTO transaction_category
|
|
(parent_id, name, description, color)
|
|
VALUES (?, ?, ?, ?)",
|
|
toNullable(parentId), name, description, color
|
|
);
|
|
ulong id = db.lastInsertRowid();
|
|
return findById(id).orElseThrow();
|
|
}
|
|
|
|
void deleteById(ulong id) {
|
|
util.sqlite.deleteById(db, "transaction_category", id);
|
|
}
|
|
|
|
TransactionCategory updateById(ulong id, string name, string description, string color) {
|
|
util.sqlite.update(
|
|
db,
|
|
"UPDATE transaction_category
|
|
SET name = ?, description = ?, color = ?
|
|
WHERE id = ?",
|
|
name, description, color, id
|
|
);
|
|
return findById(id).orElseThrow();
|
|
}
|
|
|
|
private static TransactionCategory parseCategory(Row row) {
|
|
import std.typecons;
|
|
return TransactionCategory(
|
|
row.peek!ulong(0),
|
|
toOptional(row.peek!(Nullable!ulong)(1)),
|
|
row.peek!string(2),
|
|
row.peek!string(3),
|
|
row.peek!string(4)
|
|
);
|
|
}
|
|
}
|
|
|
|
class SqliteTransactionTagRepository : TransactionTagRepository {
|
|
private Database db;
|
|
this(Database db) {
|
|
this.db = db;
|
|
}
|
|
|
|
string[] findAllByTransactionId(ulong transactionId) {
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT tag FROM transaction_tag WHERE transaction_id = ? ORDER BY tag",
|
|
r => r.peek!string(0),
|
|
transactionId
|
|
);
|
|
}
|
|
|
|
void updateTags(ulong transactionId, in string[] tags) {
|
|
util.sqlite.update(
|
|
db,
|
|
"DELETE FROM transaction_tag WHERE transaction_id = ?",
|
|
transactionId
|
|
);
|
|
foreach (tag; tags) {
|
|
util.sqlite.update(
|
|
db,
|
|
"INSERT INTO transaction_tag (transaction_id, tag) VALUES (?, ?)",
|
|
transactionId, tag
|
|
);
|
|
}
|
|
}
|
|
|
|
string[] findAll() {
|
|
return util.sqlite.findAll(
|
|
db,
|
|
"SELECT DISTINCT tag FROM transaction_tag ORDER BY tag",
|
|
r => r.peek!string(0)
|
|
);
|
|
}
|
|
}
|
|
|
|
class SqliteTransactionRepository : TransactionRepository {
|
|
private const TABLE_NAME = "\"transaction\"";
|
|
private Database db;
|
|
this(Database db) {
|
|
this.db = db;
|
|
}
|
|
|
|
Page!TransactionsListItem findAll(PageRequest pr) {
|
|
const BASE_QUERY = import("sql/get_transactions.sql");
|
|
// TODO: Implement filtering or something!
|
|
import std.array;
|
|
const string countQuery = "SELECT COUNT(ID) FROM " ~ TABLE_NAME;
|
|
auto sqlBuilder = appender!string;
|
|
sqlBuilder ~= BASE_QUERY;
|
|
sqlBuilder ~= " ";
|
|
sqlBuilder ~= pr.toSql();
|
|
string query = sqlBuilder[];
|
|
TransactionsListItem[] results = util.sqlite.findAll(db, query, (row) {
|
|
TransactionsListItem item;
|
|
item.id = row.peek!ulong(0);
|
|
item.timestamp = row.peek!string(1);
|
|
item.addedAt = row.peek!string(2);
|
|
item.amount = row.peek!ulong(3);
|
|
item.currency = Currency.ofCode(row.peek!(string, PeekMode.slice)(4));
|
|
item.description = row.peek!string(5);
|
|
|
|
Nullable!ulong vendorId = row.peek!(Nullable!ulong)(6);
|
|
if (!vendorId.isNull) {
|
|
string vendorName = row.peek!string(7);
|
|
item.vendor = Optional!(TransactionsListItem.Vendor).of(
|
|
TransactionsListItem.Vendor(vendorId.get, vendorName));
|
|
}
|
|
Nullable!ulong categoryId = row.peek!(Nullable!ulong)(8);
|
|
if (!categoryId.isNull) {
|
|
string categoryName = row.peek!string(9);
|
|
string categoryColor = row.peek!string(10);
|
|
item.category = Optional!(TransactionsListItem.Category).of(
|
|
TransactionsListItem.Category(categoryId.get, categoryName, categoryColor));
|
|
}
|
|
Nullable!ulong creditedAccountId = row.peek!(Nullable!ulong)(11);
|
|
if (!creditedAccountId.isNull) {
|
|
ulong id = creditedAccountId.get;
|
|
string name = row.peek!string(12);
|
|
string type = row.peek!string(13);
|
|
string suffix = row.peek!string(14);
|
|
item.creditedAccount = Optional!(TransactionsListItem.Account).of(
|
|
TransactionsListItem.Account(id, name, type, suffix));
|
|
}
|
|
Nullable!ulong debitedAccountId = row.peek!(Nullable!ulong)(15);
|
|
if (!debitedAccountId.isNull) {
|
|
ulong id = debitedAccountId.get;
|
|
string name = row.peek!string(16);
|
|
string type = row.peek!string(17);
|
|
string suffix = row.peek!string(18);
|
|
item.debitedAccount = Optional!(TransactionsListItem.Account).of(
|
|
TransactionsListItem.Account(id, name, type, suffix));
|
|
}
|
|
string tagsStr = row.peek!string(19);
|
|
if (tagsStr !is null && tagsStr.length > 0) {
|
|
import std.string : split;
|
|
item.tags = tagsStr.split(",");
|
|
} else {
|
|
item.tags = [];
|
|
}
|
|
return item;
|
|
});
|
|
ulong totalCount = util.sqlite.count(db, countQuery);
|
|
return Page!(TransactionsListItem).of(results, pr, totalCount);
|
|
}
|
|
|
|
Optional!TransactionDetail findById(ulong id) {
|
|
Optional!TransactionDetail item = util.sqlite.findOne(
|
|
db,
|
|
import("sql/get_transaction.sql"),
|
|
(row) {
|
|
TransactionDetail item;
|
|
item.id = row.peek!ulong(0);
|
|
item.timestamp = row.peek!string(1);
|
|
item.addedAt = row.peek!string(2);
|
|
item.amount = row.peek!ulong(3);
|
|
item.currency = Currency.ofCode(row.peek!(string, PeekMode.slice)(4));
|
|
item.description = row.peek!string(5);
|
|
|
|
Nullable!ulong vendorId = row.peek!(Nullable!ulong)(6);
|
|
if (!vendorId.isNull) {
|
|
item.vendor = Optional!(TransactionDetail.Vendor).of(
|
|
TransactionDetail.Vendor(
|
|
vendorId.get,
|
|
row.peek!string(7),
|
|
row.peek!string(8)
|
|
)).toNullable;
|
|
}
|
|
Nullable!ulong categoryId = row.peek!(Nullable!ulong)(9);
|
|
if (!categoryId.isNull) {
|
|
item.category = Optional!(TransactionDetail.Category).of(
|
|
TransactionDetail.Category(
|
|
categoryId.get,
|
|
row.peek!(Nullable!ulong)(10),
|
|
row.peek!string(11),
|
|
row.peek!string(12),
|
|
row.peek!string(13)
|
|
)).toNullable;
|
|
}
|
|
Nullable!ulong creditedAccountId = row.peek!(Nullable!ulong)(14);
|
|
if (!creditedAccountId.isNull) {
|
|
item.creditedAccount = Optional!(TransactionDetail.Account).of(
|
|
TransactionDetail.Account(
|
|
creditedAccountId.get,
|
|
row.peek!string(15),
|
|
row.peek!string(16),
|
|
row.peek!string(17)
|
|
)).toNullable;
|
|
}
|
|
Nullable!ulong debitedAccountId = row.peek!(Nullable!ulong)(18);
|
|
if (!debitedAccountId.isNull) {
|
|
item.debitedAccount = Optional!(TransactionDetail.Account).of(
|
|
TransactionDetail.Account(
|
|
debitedAccountId.get,
|
|
row.peek!string(19),
|
|
row.peek!string(20),
|
|
row.peek!string(21)
|
|
)).toNullable;
|
|
}
|
|
string tagsStr = row.peek!string(22);
|
|
if (tagsStr !is null && tagsStr.length > 0) {
|
|
import std.string : split;
|
|
item.tags = tagsStr.split(",");
|
|
} else {
|
|
item.tags = [];
|
|
}
|
|
return item;
|
|
},
|
|
id
|
|
);
|
|
if (item.isNull) return item;
|
|
item.value.lineItems = util.sqlite.findAll(
|
|
db,
|
|
import("sql/get_line_items.sql"),
|
|
(row) {
|
|
TransactionDetail.LineItem li;
|
|
li.idx = row.peek!uint(0);
|
|
li.valuePerItem = row.peek!long(1);
|
|
li.quantity = row.peek!ulong(2);
|
|
li.description = row.peek!string(3);
|
|
Nullable!ulong categoryId = row.peek!(Nullable!ulong)(4);
|
|
if (!categoryId.isNull) {
|
|
li.category = Optional!(TransactionDetail.Category).of(
|
|
TransactionDetail.Category(
|
|
categoryId.get,
|
|
row.peek!(Nullable!ulong)(5),
|
|
row.peek!string(6),
|
|
row.peek!string(7),
|
|
row.peek!string(8)
|
|
)).toNullable;
|
|
}
|
|
return li;
|
|
},
|
|
id
|
|
);
|
|
return item;
|
|
}
|
|
|
|
TransactionDetail insert(in AddTransactionPayload data) {
|
|
util.sqlite.update(
|
|
db,
|
|
import("sql/insert_transaction.sql"),
|
|
data.timestamp,
|
|
Clock.currTime(UTC()).toISOExtString(),
|
|
data.amount,
|
|
data.currencyCode,
|
|
data.description,
|
|
data.vendorId,
|
|
data.categoryId
|
|
);
|
|
ulong transactionId = db.lastInsertRowid();
|
|
insertLineItems(transactionId, data);
|
|
return findById(transactionId).orElseThrow();
|
|
}
|
|
|
|
TransactionDetail update(ulong transactionId, in AddTransactionPayload data) {
|
|
util.sqlite.update(
|
|
db,
|
|
import("sql/update_transaction.sql"),
|
|
data.timestamp,
|
|
data.amount,
|
|
data.currencyCode,
|
|
data.description,
|
|
data.vendorId,
|
|
data.categoryId,
|
|
transactionId
|
|
);
|
|
// Re-write all line items:
|
|
util.sqlite.update(
|
|
db,
|
|
"DELETE FROM transaction_line_item WHERE transaction_id = ?",
|
|
transactionId
|
|
);
|
|
insertLineItems(transactionId, data);
|
|
return findById(transactionId).orElseThrow();
|
|
}
|
|
|
|
void deleteById(ulong id) {
|
|
util.sqlite.deleteById(db, TABLE_NAME, id);
|
|
}
|
|
|
|
static Transaction parseTransaction(Row row) {
|
|
import std.typecons : Nullable;
|
|
return Transaction(
|
|
row.peek!ulong(0),
|
|
SysTime.fromISOExtString(row.peek!string(1)),
|
|
SysTime.fromISOExtString(row.peek!string(2)),
|
|
row.peek!ulong(3),
|
|
Currency.ofCode(row.peek!(string, PeekMode.slice)(4)),
|
|
row.peek!string(5),
|
|
toOptional(row.peek!(Nullable!ulong)(6)),
|
|
toOptional(row.peek!(Nullable!ulong)(7))
|
|
);
|
|
}
|
|
|
|
private void insertLineItems(ulong transactionId, in AddTransactionPayload data) {
|
|
foreach (size_t idx, lineItem; data.lineItems) {
|
|
util.sqlite.update(
|
|
db,
|
|
import("sql/insert_line_item.sql"),
|
|
transactionId,
|
|
idx,
|
|
lineItem.valuePerItem,
|
|
lineItem.quantity,
|
|
lineItem.description,
|
|
lineItem.categoryId
|
|
);
|
|
}
|
|
}
|
|
}
|