-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
331 lines (308 loc) · 11.5 KB
/
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package main
import (
"fmt"
"log"
"os"
"strconv"
"strings"
"github.com/dadosjusbr/proto/coleta"
"github.com/dadosjusbr/status"
"github.com/knieriem/odf/ods"
)
const (
INDENIZACOES = "indenizacoes"
REMUNERACOES = "membros"
INDENIZACOES_VERBAS_INDENIZATORIAS_1 = "VERBAS INDENIZATÓRIAS 1"
INDENIZACOES_OUTRAS_REMUNERACOES_TEMPORARIAS_2 = "OUTRAS REMUNERAÇÕES TEMPORÁRIAS 2"
REMUNERACAO_BASICA = "REMUNERAÇÃO BÁSICA"
REMUNERACAO_EVENTUAL_TEMPORARIA = "REMUNERAÇÃO EVENTUAL OU TEMPORÁRIA"
OBRIGATORIOS_LEGAIS = "OBRIGATÓRIOS/LEGAIS"
STATUS_DATA_UNAVAILABLE = 4
)
// Mapeia as categorias das planilhas.
var headersMap = map[string]map[string]int{
INDENIZACOES_VERBAS_INDENIZATORIAS_1: {
"ALIMENTAÇÃO": 4,
"SAÚDE": 5,
"PECÚNIA": 6,
"MORADIA": 7,
"LICENÇA COMPENSATÓRIA": 8,
"NATALIDADE": 9,
},
INDENIZACOES_OUTRAS_REMUNERACOES_TEMPORARIAS_2: {
"AJUDA DE CUSTO": 10,
"ADICIONAL DE INSALUBRIDADE": 11,
"SUBSTITUIÇÃO CUMULATIVA": 12,
"GRATIFICAÇÃO POR ATUAÇÃO EM COMARCA DIVERSA": 13,
"SUBSTITUIÇÃO DE CARGO": 14,
"SUBSTITUIÇÃO DE PROCURADOR DE JUSTIÇA": 15,
"DIFERENÇA DE ENTRÂNCIA": 16,
"DIFERENÇA DE 1/3 DE FÉRIAS": 17,
"DIFERENÇA DE PENSÃO": 18,
"DIFERENÇA ANTERIOR DENTRO DO EXERCÍCIO": 19,
"PARCELA DE GRATIFICAÇÃO ISONÔMICA": 20,
"SERVIÇO EXTRAORDINÁRIO": 21,
"DESPESA DE EXERCÍCIOS ANTERIORES": 22,
},
REMUNERACAO_BASICA: {
"CARGO EFETIVO": 4,
"OUTRAS VERBAS": 5,
},
REMUNERACAO_EVENTUAL_TEMPORARIA: {
"CARGO EM COMISSÃO": 6,
"GRATIFICAÇÃO NATALINA": 7,
"FÉRIAS": 8,
"PERMANÊNCIA": 9,
},
OBRIGATORIOS_LEGAIS: {
"PREVIDENCIÁRIA": 13,
"IMPOSTO": 14,
"RETENÇÃO": 15,
},
}
// Parse parses the ods tables.
func Parse(arquivos []string, chave_coleta string) (*coleta.FolhaDePagamento, error) {
var folha []*coleta.ContraCheque
var parseErr bool
indenizacoes, err := getDadosIndenizacoes(arquivos)
if err != nil {
return nil, status.NewError(status.InvalidInput, fmt.Errorf("erro tentando recuperar os dados de indenizações: %w", err))
}
mapIndenizacoes := map[string][]string{}
const INDENIZACOES_MATRICULA = 0
for _, f := range indenizacoes {
mapIndenizacoes[f[INDENIZACOES_MATRICULA]] = f
}
for _, f := range arquivos {
if tipoCSV(f) == INDENIZACOES {
continue
}
dados, err := dadosParaMatriz(f)
if err != nil {
return nil, status.NewError(status.InvalidInput, fmt.Errorf("erro na tentativa de transformar os dados em matriz (%s): %w", f, err))
}
if len(dados) == 0 {
fmt.Fprintf(os.Stderr, "o arquivo %s não contém dados\n", f)
os.Exit(STATUS_DATA_UNAVAILABLE)
}
contra_cheque, ok := getMembros(dados, mapIndenizacoes, chave_coleta, f)
if !ok {
parseErr = true
}
folha = append(folha, contra_cheque...)
}
if parseErr {
return &coleta.FolhaDePagamento{ContraCheque: folha}, status.NewError(status.Unknown, fmt.Errorf("parsing error: getMembros()"))
}
return &coleta.FolhaDePagamento{ContraCheque: folha}, nil
}
// getDadosIndenizacoes retorna a planilha de indenizações em forma de matriz
func getDadosIndenizacoes(files []string) ([][]string, error) {
for _, f := range files {
if tipoCSV(f) == INDENIZACOES {
return dadosParaMatriz(f)
}
}
return nil, nil
}
// getMembros retorna o array com a folha de pagamento da coleta.
func getMembros(membros [][]string, mapIndenizacoes map[string][]string, chaveColeta string, fileName string) ([]*coleta.ContraCheque, bool) {
ok := true
var contraCheque []*coleta.ContraCheque
counter := 1
for _, membro := range membros {
var err error
var novoMembro *coleta.ContraCheque
indenizacoesMembro := mapIndenizacoes[membro[0]]
if novoMembro, err = criaMembro(membro, indenizacoesMembro, chaveColeta, counter, fileName); err != nil {
ok = false
log.Fatalf("error na criação de um novo membro %s: %q", fileName, err)
continue
}
counter++
contraCheque = append(contraCheque, novoMembro)
}
return contraCheque, ok
}
// getIndenizacaoMembro busca as indenizacoes de um membro baseado na matrícula.
func getIndenizacaoMembro(regNum string, mapIndenizacoes map[string][]string) []string {
if val, ok := mapIndenizacoes[regNum]; ok {
return val
}
return nil
}
// criaMembro monta um contracheque de um único membro.
func criaMembro(membro []string, indenizacoes []string, chaveColeta string, counter int, fileName string) (*coleta.ContraCheque, error) {
var novoMembro coleta.ContraCheque
const REMUNERACOES_MATRICULA = 0
const REMUNERACOES_NOME = 1
const REMUNERACOES_CARGO = 2
const REMUNERACOES_LOTACAO = 3
novoMembro.IdContraCheque = fmt.Sprintf("%v/%v", chaveColeta, counter)
novoMembro.ChaveColeta = chaveColeta
novoMembro.Matricula = membro[REMUNERACOES_MATRICULA]
novoMembro.Nome = membro[REMUNERACOES_NOME]
novoMembro.Funcao = membro[REMUNERACOES_CARGO]
novoMembro.LocalTrabalho = membro[REMUNERACOES_LOTACAO]
novoMembro.Tipo = coleta.ContraCheque_MEMBRO
novoMembro.Ativo = true
remuneracoes, err := processaRemuneracao(membro, indenizacoes)
if err != nil {
return nil, status.NewError(status.InvalidInput, fmt.Errorf("error na transformação das remunerações: %w", err))
}
novoMembro.Remuneracoes = &coleta.Remuneracoes{Remuneracao: remuneracoes}
return &novoMembro, nil
}
// processaRemuneracao processa todas as remunerações de um único membro.
func processaRemuneracao(membro []string, indenizacoes []string) ([]*coleta.Remuneracao, error) {
var remuneracoes []*coleta.Remuneracao
temp, err := criaRemuneracao(indenizacoes, coleta.Remuneracao_R, INDENIZACOES_VERBAS_INDENIZATORIAS_1, coleta.Remuneracao_O)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("erro processando verbas indenizatorias 1: %w", err))
}
remuneracoes = append(remuneracoes, temp...)
temp, err = criaRemuneracao(indenizacoes, coleta.Remuneracao_R, INDENIZACOES_OUTRAS_REMUNERACOES_TEMPORARIAS_2, coleta.Remuneracao_O)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("erro processando outras remuneracoes temporarias 2: %w", err))
}
remuneracoes = append(remuneracoes, temp...)
temp, err = criaRemuneracao(membro, coleta.Remuneracao_R, REMUNERACAO_BASICA, coleta.Remuneracao_B)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("erro processando remuneracao básica: %w", err))
}
remuneracoes = append(remuneracoes, temp...)
temp, err = criaRemuneracao(membro, coleta.Remuneracao_R, REMUNERACAO_EVENTUAL_TEMPORARIA, coleta.Remuneracao_O)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("erro processando remuneracao eventual temporaria: %w", err))
}
remuneracoes = append(remuneracoes, temp...)
temp, err = criaRemuneracao(membro, coleta.Remuneracao_D, OBRIGATORIOS_LEGAIS, coleta.Remuneracao_O)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("erro processando erro processando obrigatório/legais: %w", err))
}
remuneracoes = append(remuneracoes, temp...)
return remuneracoes, nil
}
// criaRemuneracao monta as remuneracoes de um membro, a partir de cada categoria.
func criaRemuneracao(planilha []string, natureza coleta.Remuneracao_Natureza, categoria string, tipoReceita coleta.Remuneracao_TipoReceita) ([]*coleta.Remuneracao, error) {
var remuneracoes []*coleta.Remuneracao
var err error
for key := range headersMap[categoria] {
var remuneracao coleta.Remuneracao
remuneracao.Natureza = natureza
remuneracao.Categoria = categoria
remuneracao.Item = key
remuneracao.Valor, err = parseFloat(planilha, key, categoria)
remuneracao.TipoReceita = tipoReceita
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("error buscando o valor na planilha: %w", err))
}
if natureza == coleta.Remuneracao_D {
remuneracao.Valor = remuneracao.Valor * (-1)
}
remuneracoes = append(remuneracoes, &remuneracao)
}
return remuneracoes, nil
}
// dadosParaMatriz transforma os dados de determinado arquivo, em uma matriz
func dadosParaMatriz(file string) ([][]string, error) {
var result [][]string
var doc ods.Doc
f, err := ods.Open(file)
if err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("ods.Open error(%s): %w", file, err))
}
defer f.Close()
f.ParseContent(&doc)
fileType := tipoCSV(file)
if err := assertHeaders(doc, fileType); err != nil {
return nil, status.NewError(status.InvalidFile, fmt.Errorf("assertHeaders() for %s error: %w", file, err))
}
result = append(result, getEmployees(doc)...)
return result, nil
}
// tipoCSV checa se o arquivo é de indenizações ou membros.
func tipoCSV(nomeArquivo string) string {
if strings.Contains(nomeArquivo, INDENIZACOES) {
return INDENIZACOES
} else if strings.Contains(nomeArquivo, REMUNERACOES) {
return REMUNERACOES
}
return ""
}
// getEmployees varre a lista de membros e seleciona apenas as linhas que correspondem aos dados.
func getEmployees(doc ods.Doc) [][]string {
var lastLine int
for i, values := range doc.Table[0].Strings() {
if len(values) < 1 {
continue
}
if values[0] == "TOTAL GERAL" {
lastLine = i - 1
break
}
}
if lastLine == 0 {
return [][]string{}
}
return cleanStrings(doc.Table[0].Strings()[10:lastLine])
}
// getHeaders varre o documento e retorna o cabeçalho de cada arquivo.
func getHeaders(doc ods.Doc, fileType string) []string {
var headers []string
raw := cleanStrings(doc.Table[0].Strings()[5:8])
switch fileType {
case INDENIZACOES:
headers = append(headers, raw[0][:4]...)
headers = append(headers, raw[2][4:]...)
break
case REMUNERACOES:
headers = append(headers, raw[0][:4]...)
headers = append(headers, raw[2][4:10]...)
headers = append(headers, raw[1][10:13]...)
headers = append(headers, raw[2][13:]...)
break
}
return headers
}
// assertHeaders verifica se o cabeçalho existe.
func assertHeaders(doc ods.Doc, fileType string) error {
headers := getHeaders(doc, fileType)
for key, value := range headersMap[fileType] {
if err := containsHeader(headers, key, value); err != nil {
return err
}
}
return nil
}
// containsHeader verifica se é possível encontrar a chave buscada em alguma posição da planilha.
func containsHeader(headers []string, key string, value int) error {
if strings.Contains(headers[value], key) {
return nil
}
return status.NewError(status.Unknown, fmt.Errorf("couldn't find %s at position %d", key, value))
}
// parseFloat makes the string with format "xx.xx,xx" able to be parsed by the strconv.ParseFloat and return it parsed.
func parseFloat(emp []string, key, fileType string) (float64, error) {
valueStr := emp[headersMap[fileType][key]]
if valueStr == "" {
return 0.0, nil
} else {
valueStr = strings.Trim(valueStr, " ")
valueStr = strings.Replace(valueStr, ",", ".", 1)
if n := strings.Count(valueStr, "."); n > 1 {
valueStr = strings.Replace(valueStr, ".", "", n-1)
}
}
return strconv.ParseFloat(valueStr, 64)
}
// cleanStrings makes all strings to uppercase and removes N/D fields
func cleanStrings(raw [][]string) [][]string {
for row := range raw {
for col := range raw[row] {
raw[row][col] = strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(raw[row][col], "N/D", ""), "\n", " "))
raw[row][col] = strings.ReplaceAll(raw[row][col], " ", " ")
}
}
return raw
}