This repository has been archived by the owner on Jun 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathameria_excel_parser.go
165 lines (149 loc) · 4.24 KB
/
ameria_excel_parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"fmt"
"slices"
"strconv"
"strings"
"time"
"github.com/tealeg/xlsx"
)
func (m *MoneyWith2DecimalPlaces) UnmarshalText(text []byte) error {
sanitizedText := strings.Replace(string(text), ",", "", -1)
floatVal, err := strconv.ParseFloat(sanitizedText, 64)
if err != nil {
return err
}
m.int = int(floatVal * 100)
return nil
}
const MyAmeriaDateFormat = "02/01/2006"
const giveUpFindHeaderAfterEmpty1Cells = 15
var (
xlsxHeaders = []string{
"Ամսաթիվ",
"Փաստ N",
"ԳՏ",
"Ելքագրվող հաշիվ",
"Շահառուի հաշիվ",
"Վճարող/Շահառու",
"Մանրամասներ",
"Կարգավիճակ",
"Մեկնաբանություն",
"Գումար",
"Արժույթ",
}
)
type MyAmeriaTransaction struct {
Date time.Time
FactN string
PO string
OutgoingAccount string
BeneficiaryAccount string
PayerOrBeneficiary string
Details string
Status string
Comment string
Amount MoneyWith2DecimalPlaces
Currency string
}
type MyAmeriaExcelFileParser struct {
MyAccounts []string
DetailsIncomeSubstrings []string
}
func (p MyAmeriaExcelFileParser) ParseRawTransactionsFromFile(
filePath string,
) ([]Transaction, error) {
f, err := xlsx.OpenFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
// Find first sheet.
firstSheet := f.Sheets[0]
fmt.Printf("%s: parsing first sheet '%s', total %d sheets.\n",
filePath, firstSheet.Name, len(f.Sheets))
// Parse myAmeriaTransactions.
var myAmeriaTransactions []MyAmeriaTransaction
var isHeaderRowFound bool
for i, row := range firstSheet.Rows {
cells := row.Cells
if len(cells) < len(xlsxHeaders) {
return nil,
fmt.Errorf(
"%s: %d row has only %d cells while need to find information for headers %v",
filePath, i, len(cells), xlsxHeaders,
)
}
// Find header row.
if !isHeaderRowFound {
if i > giveUpFindHeaderAfterEmpty1Cells {
return nil, fmt.Errorf(
"%s: after scanning %d rows can't find headers %v",
filePath, i, xlsxHeaders,
)
}
var isCellMatches = true
for cellIndex, header := range xlsxHeaders {
if strings.TrimSpace(cells[cellIndex].String()) != header {
isCellMatches = false
break
}
}
if isCellMatches {
isHeaderRowFound = true
}
// Skip this row anyway.
continue
}
// Stop if row doesn't have enough cells or first cell is empty.
if len(cells) < len(xlsxHeaders) || cells[0].String() == "" {
break
}
// Parse date and amount.
date, err := time.Parse(MyAmeriaDateFormat, cells[0].String())
if err != nil {
return nil, fmt.Errorf("failed to parse date from 1st cell of %d row: %w", i, err)
}
var amount MoneyWith2DecimalPlaces
if err := amount.UnmarshalText([]byte(cells[9].String())); err != nil {
return nil, fmt.Errorf("failed to parse amount from 10th cell of %d row: %w", i, err)
}
transaction := MyAmeriaTransaction{
Date: date,
FactN: cells[1].String(),
PO: cells[2].String(),
OutgoingAccount: cells[3].String(),
BeneficiaryAccount: cells[4].String(),
PayerOrBeneficiary: cells[5].String(),
Details: cells[6].String(),
Status: cells[7].String(),
Comment: cells[8].String(),
Amount: amount,
Currency: cells[10].String(),
}
myAmeriaTransactions = append(myAmeriaTransactions, transaction)
}
// Convert MyAmeria rows to unified transactions and separate expenses from incomes.
transactions := make([]Transaction, len(myAmeriaTransactions))
for i, transaction := range myAmeriaTransactions {
isExpense := true
if len(p.MyAccounts) > 0 {
if slices.Contains(p.MyAccounts, transaction.BeneficiaryAccount) {
isExpense = false
}
} else if len(p.DetailsIncomeSubstrings) > 0 {
for _, substring := range p.DetailsIncomeSubstrings {
if strings.Contains(transaction.Details, substring) {
isExpense = false
break
}
}
}
transactions[i] = Transaction{
IsExpense: isExpense,
Date: transaction.Date,
Details: transaction.Details,
Amount: transaction.Amount,
}
}
return transactions, nil
}