Skip to content

Commit

Permalink
Merge pull request #66 from moneymanagerex/insights_account
Browse files Browse the repository at this point in the history
Add InsightsAccountView
  • Loading branch information
guanlisheng authored Sep 30, 2024
2 parents 691201f + 8ddf134 commit 0f869c0
Show file tree
Hide file tree
Showing 13 changed files with 365 additions and 41 deletions.
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

0 comments on commit 0f869c0

Please sign in to comment.