Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add InsightsAccountView #66

Merged
merged 5 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
9208242C2CA615B400388AB2 /* FieldRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9208242B2CA615B400388AB2 /* FieldRepository.swift */; };
9208242E2CA617FB00388AB2 /* FieldContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9208242D2CA617FB00388AB2 /* FieldContentRepository.swift */; };
920824322CA6E32800388AB2 /* RepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920824312CA6E32800388AB2 /* RepositoryProtocol.swift */; };
924352E02CA9CC5A0052E4BC /* InsightsAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924352DF2CA9CC5A0052E4BC /* InsightsAccountView.swift */; };
929EF65F2C9FF2DE0051A3E6 /* AssetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF65E2C9FF2DE0051A3E6 /* AssetData.swift */; };
929EF6612C9FF2FD0051A3E6 /* StockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6602C9FF2FD0051A3E6 /* StockData.swift */; };
929EF6632C9FF3ED0051A3E6 /* AssetRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */; };
Expand All @@ -53,8 +54,8 @@
A3363EE72C93249D004696C7 /* CategoryAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE62C93249D004696C7 /* CategoryAddView.swift */; };
A3363EE92C9326A1004696C7 /* CategoryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EE82C9326A1004696C7 /* CategoryListView.swift */; };
A3363EEB2C93BF62004696C7 /* CurrencyRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */; };
A33742882CA8E55400698466 /* IncomeAndExpense.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742872CA8E55400698466 /* IncomeAndExpense.swift */; };
A337428A2CA8E72C00698466 /* Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742892CA8E72C00698466 /* Summary.swift */; };
A33742882CA8E55400698466 /* IncomeExpenseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742872CA8E55400698466 /* IncomeExpenseView.swift */; };
A337428A2CA8E72C00698466 /* InsightsSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33742892CA8E72C00698466 /* InsightsSummary.swift */; };
A3462F642C9426F500F79145 /* InsightsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F632C9426F500F79145 /* InsightsViewModel.swift */; };
A3462F662C94854800F79145 /* ExportableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F652C94854800F79145 /* ExportableEntity.swift */; };
A3462F6A2C948CDB00F79145 /* ExportableEntityDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */; };
Expand Down Expand Up @@ -142,6 +143,7 @@
9208242B2CA615B400388AB2 /* FieldRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldRepository.swift; sourceTree = "<group>"; };
9208242D2CA617FB00388AB2 /* FieldContentRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FieldContentRepository.swift; sourceTree = "<group>"; };
920824312CA6E32800388AB2 /* RepositoryProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryProtocol.swift; sourceTree = "<group>"; };
924352DF2CA9CC5A0052E4BC /* InsightsAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsightsAccountView.swift; sourceTree = "<group>"; };
929EF65E2C9FF2DE0051A3E6 /* AssetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetData.swift; sourceTree = "<group>"; };
929EF6602C9FF2FD0051A3E6 /* StockData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StockData.swift; sourceTree = "<group>"; };
929EF6622C9FF3ED0051A3E6 /* AssetRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetRepository.swift; sourceTree = "<group>"; };
Expand All @@ -158,8 +160,8 @@
A3363EE62C93249D004696C7 /* CategoryAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryAddView.swift; sourceTree = "<group>"; };
A3363EE82C9326A1004696C7 /* CategoryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryListView.swift; sourceTree = "<group>"; };
A3363EEA2C93BF62004696C7 /* CurrencyRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyRepository.swift; sourceTree = "<group>"; };
A33742872CA8E55400698466 /* IncomeAndExpense.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomeAndExpense.swift; sourceTree = "<group>"; };
A33742892CA8E72C00698466 /* Summary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Summary.swift; sourceTree = "<group>"; };
A33742872CA8E55400698466 /* IncomeExpenseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomeExpenseView.swift; sourceTree = "<group>"; };
A33742892CA8E72C00698466 /* InsightsSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsSummary.swift; sourceTree = "<group>"; };
A3462F632C9426F500F79145 /* InsightsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsightsViewModel.swift; sourceTree = "<group>"; };
A3462F652C94854800F79145 /* ExportableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntity.swift; sourceTree = "<group>"; };
A3462F692C948CDB00F79145 /* ExportableEntityDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportableEntityDocument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -381,8 +383,9 @@
isa = PBXGroup;
children = (
A3C142AD2C9134DD00D3CEC0 /* InsightsView.swift */,
A33742872CA8E55400698466 /* IncomeAndExpense.swift */,
A33742892CA8E72C00698466 /* Summary.swift */,
924352DF2CA9CC5A0052E4BC /* InsightsAccountView.swift */,
A33742892CA8E72C00698466 /* InsightsSummary.swift */,
A33742872CA8E55400698466 /* IncomeExpenseView.swift */,
);
path = Insights;
sourceTree = "<group>";
Expand Down Expand Up @@ -582,14 +585,15 @@
929EF6652C9FF3FB0051A3E6 /* StockRepository.swift in Sources */,
A3C142502C8B366400D3CEC0 /* PayeeListView.swift in Sources */,
A379C1A72CA3B79E00CC8E2C /* AssetAddView.swift in Sources */,
A337428A2CA8E72C00698466 /* Summary.swift in Sources */,
A337428A2CA8E72C00698466 /* InsightsSummary.swift in Sources */,
A3C142672C8F2AF500D3CEC0 /* TransactionData.swift in Sources */,
920824042CA4ADC100388AB2 /* ScheduledSplitData.swift in Sources */,
A3462F642C9426F500F79145 /* InsightsViewModel.swift in Sources */,
A3C142AC2C909C1C00D3CEC0 /* TransactionEditView.swift in Sources */,
A3C142962C8FE15E00D3CEC0 /* TransactionRepository.swift in Sources */,
A39B1B342C99A0A8003E5562 /* CurrencyDetailView.swift in Sources */,
A3C1422D2C89751500D3CEC0 /* ContentView.swift in Sources */,
924352E02CA9CC5A0052E4BC /* InsightsAccountView.swift in Sources */,
929EF65F2C9FF2DE0051A3E6 /* AssetData.swift in Sources */,
920823FE2CA4A3F700388AB2 /* TagData.swift in Sources */,
A379C1A92CA3B9EB00CC8E2C /* AssetListView.swift in Sources */,
Expand Down Expand Up @@ -629,7 +633,7 @@
A3462F6A2C948CDB00F79145 /* ExportableEntityDocument.swift in Sources */,
920824122CA4C6A400388AB2 /* BudgetTableRepository.swift in Sources */,
A3C142982C8FE62200D3CEC0 /* TransactionListView.swift in Sources */,
A33742882CA8E55400698466 /* IncomeAndExpense.swift in Sources */,
A33742882CA8E55400698466 /* IncomeExpenseView.swift in Sources */,
A3C142542C8B381400D3CEC0 /* PayeeEditView.swift in Sources */,
920823F82CA498B500388AB2 /* CurrencyHistoryRepository.swift in Sources */,
A37E7D8A2C9AC30700B4ECFC /* HelpFAQView.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion MMEX/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ extension DataManager {

extension DataManager {
func loadCurrencyFormat() {
currencyFormat = CurrencyRepository(db)?.dictionaryRefFormat() ?? [:]
currencyFormat = CurrencyRepository(db)?.dictRefFormat() ?? [:]
}

func updateCurrencyFormat(id: Int64, value: CurrencyFormat) {
Expand Down
22 changes: 22 additions & 0 deletions MMEX/Models/AccountData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@ extension AccountData: DataProtocol {
}
}

struct AccountFlow {
let inflow : Double
let outflow : Double
}

extension AccountFlow {
var diff: Double { inflow - outflow }
}

typealias AccountFlowByStatus = [TransactionStatus: AccountFlow]

extension AccountFlowByStatus {
var diffVoid : Double { self[.void]? .diff ?? 0.0 }
var diffReconciled : Double { self[.reconciled]? .diff ?? 0.0 }
var diffTotal : Double {
(self[.none]? .diff ?? 0.0) +
(self[.reconciled]? .diff ?? 0.0) +
(self[.followUp]? .diff ?? 0.0) +
(self[.duplicate]? .diff ?? 0.0)
}
}

extension AccountData {
static let sampleData : [AccountData] = [
AccountData(
Expand Down
4 changes: 4 additions & 0 deletions MMEX/Models/TransactionData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,31 @@ extension TransactionData {
}
return transDate // If parsing fails, return original string
}

var income: Double {
// TODO: in base currency
if transCode == .deposit {
return transAmount
}
return 0.0
}

var expenses: Double {
// TODO: in base currency
if transCode == .withdrawal {
return transAmount
}
return 0.0
}

var actual: Double {
return switch transCode {
case .withdrawal: 0 - transAmount;
case .deposit: transAmount;
default: 0.0
}
}

var transfer: Double {
// TODO: in base currency
if transCode == .transfer {
Expand Down
119 changes: 117 additions & 2 deletions MMEX/Repositories/AccountRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,16 +156,131 @@ struct AccountRepository: RepositoryProtocol {
}

extension AccountRepository {
// load all accounts
// load all accounts, sorted by name
func load() -> [AccountData] {
return select(from: Self.table
.order(Self.col_name)
)
}

// select accounts by type
func selectByType(
from table: SQLite.Table = Self.table
) -> [AccountType: [AccountData]] {
do {
var dataByType: [AccountType: [AccountData]] = [:]
for row in try db.prepare(Self.selectData(from: table)) {
let type = AccountType(collateNoCase: row[Self.col_type])
if dataByType[type] == nil { dataByType[type] = [] }
dataByType[type]!.append(Self.fetchData(row))
}
print("Successfull select from \(Self.repositoryName): \(dataByType.count)")
return dataByType
} catch {
print("Failed select from \(Self.repositoryName): \(error)")
return [:]
}
}

// load account flow, indexed by id and transaction status
func dictFlowByStatus(
from table: SQLite.Table = Self.table,
minDate: String? = nil,
maxDate: String? = nil
) -> [Int64: AccountFlowByStatus] {
let minDate = minDate ?? ""
let supDate = (maxDate ?? "") + "z"

typealias T = TransactionRepository
let B_query = T.table.select(
T.col_accountId,
T.col_status,
T.col_transAmount, 0
)
.where(
T.col_transCode == "Deposit" &&
T.col_transDate ?? "" >= minDate &&
T.col_transDate ?? "" < supDate &&
T.col_deletedTime ?? "" == ""
)
.union(all: true, T.table.select(
T.col_accountId,
T.col_status,
0, T.col_transAmount
)
.where(
T.col_transCode == "Withdrawal" &&
T.col_transDate ?? "" >= minDate &&
T.col_transDate ?? "" < supDate &&
T.col_deletedTime ?? "" == ""
)
).union(all: true, T.table.select(
T.col_accountId,
T.col_status,
0, T.col_transAmount
)
.where(
T.col_transCode == "Transfer" &&
T.col_transDate ?? "" >= minDate &&
T.col_transDate ?? "" < supDate &&
T.col_deletedTime ?? "" == ""
)
).union(all: true, T.table.select(
T.col_toAccountId,
T.col_status,
T.col_toTransAmount, 0
)
.where(
T.col_transCode == "Transfer" &&
T.col_transDate ?? "" >= minDate &&
T.col_transDate ?? "" < supDate &&
T.col_deletedTime ?? "" == ""
)
)

typealias A = Self
let B_table = SQLite.Table("b")
let B_col_inflow = SQLite.Expression<Double>("INFLOW")
let B_col_outflow = SQLite.Expression<Double>("OUTFLOW")
let query = table.with(
B_table,
columns: [A.col_id, T.col_status, B_col_inflow, B_col_outflow],
recursive: false,
as: B_query
)
.join(B_table, on: B_table[A.col_id] == A.table[A.col_id])
.where(A.table[A.col_type] != "Investment")
.select(
A.table[A.col_id],
B_table[T.col_status],
B_table[B_col_inflow].total,
B_table[B_col_outflow].total
)
.group(A.table[A.col_id], B_table[T.col_status])

print("DEBUG: AccountRepository.dictFlowByStatus: \(query.expression.description)")
do {
var dict: [Int64: AccountFlowByStatus] = [:]
for row in try db.prepare(query) {
let id = row[A.table[A.col_id]]
let status = TransactionStatus(collateNoCase: row[B_table[T.col_status]])
if dict[id] == nil { dict[id] = [:] }
dict[id]![status] = AccountFlow(
inflow : row[B_table[B_col_inflow].total],
outflow : row[B_table[B_col_outflow].total]
)
}
print("Successfull dictionary from \(Self.repositoryName): \(dict.count)")
return dict
} catch {
print("Failed dictionary from \(Self.repositoryName): \(error)")
return [:]
}
}

// load currencyId for all accounts
func loadCurrencyId() -> [Int64] {
return Repository(db).select(from: Self.table
return select(from: Self.table
.select(distinct: Self.col_currencyId)
) { row in
row[Self.col_currencyId]
Expand Down
6 changes: 3 additions & 3 deletions MMEX/Repositories/CurrencyRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ extension CurrencyRepository {

// TODO: re-write in a more readable way (get the ids first, then pluck each currency)
// load all referred currency formats, indexed by currencyId
func dictionaryRefFormat() -> [Int64: CurrencyFormat] {
print("DEBUG: CurrencyRepository.dictionaryRefFormat()")
func dictRefFormat() -> [Int64: CurrencyFormat] {
print("DEBUG: CurrencyRepository.dictRefFormat()")
typealias C = CurrencyRepository
typealias A = AccountRepository
typealias E = AssetRepository
Expand All @@ -182,7 +182,7 @@ extension CurrencyRepository {
" union " +
"select 1 from \(E.repositoryName) where \(E.table[E.col_currencyId]) == \(C.table[C.col_id])" +
")"
return Repository(db).dictionary(
return Repository(db).dict(
query: query
) { row in CurrencyFormat(
name : row[1] as? String ?? "",
Expand Down
4 changes: 2 additions & 2 deletions MMEX/Repositories/Repository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ extension Repository {
}
}

func dictionary<Result>(
func dict<Result>(
query: String,
with result: (SQLite.Statement.Element) -> Result
) -> [Int64: Result] {
print("DEBUG: Repository.dictionary: \(query)")
print("DEBUG: Repository.dict: \(query)")
do {
var dict: [Int64: Result] = [:]
for row in try db.prepare(query) {
Expand Down
2 changes: 1 addition & 1 deletion MMEX/Repositories/RepositoryProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extension RepositoryProtocol {
}
}

func dictionary<Result>(
func dict<Result>(
from table: SQLite.Table,
with result: (SQLite.Row) -> Result = Self.fetchData
) -> [Int64: Result] {
Expand Down
Loading