afd_parser 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 23281239730f2fc9a56515c9cc307eca390d957a
4
+ data.tar.gz: 4919382b66f53e7796168b3cc3bdf8f71aeac03d
5
+ SHA512:
6
+ metadata.gz: 4498b18497c30a5c580090b3f751bf0f450148a775ef0dd82d152e1e0a84488c96cf7bddb9615093333ab28c032b69da1ec1280ad7ad6d8b400edd87349f895a
7
+ data.tar.gz: '038906e8404160933b602f429259a72e83c98bb08573ac494721b7e4c283ade240fc767c4a0149113073c5838cf0fa73831fca62fd703a5977816b074393fbd6'
@@ -0,0 +1,257 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Controle de Horas - Sistema para gestão de horas trabalhadas
3
+ # Copyright (C) 2009 O.S. Systems Softwares Ltda.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as
7
+ # published by the Free Software Foundation, either version 3 of the
8
+ # License, or (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Rua Clóvis Gularte Candiota 132, Pelotas-RS, Brasil.
19
+ # e-mail: contato@ossystems.com.br
20
+
21
+ require 'date'
22
+
23
+ # Parser para a PORTARIA No 1.510, DE 21 DE AGOSTO DE 2009, do
24
+ # Ministério do Trabalho;
25
+
26
+ class AfdParser
27
+ require 'afd_parser/clock_in_out'
28
+ require 'afd_parser/header'
29
+ require 'afd_parser/set_employee'
30
+ require 'afd_parser/set_employer'
31
+ require 'afd_parser/set_time'
32
+ require 'afd_parser/trailer'
33
+
34
+ class AfdParserException < Exception; end
35
+ attr_reader :records, :header, :trailer
36
+
37
+ def initialize(*args)
38
+ if args.size == 1
39
+ initialize_variables(args[0])
40
+ elsif args.size == 2
41
+ initialize_variables(args[1])
42
+ File.open(args[0], "r") do |file|
43
+ @raw_data = file.readlines
44
+ end
45
+ else
46
+ raise AfdParser::AfdParserException.new("wrong number of arguments, should be 1 or 2")
47
+ end
48
+ end
49
+
50
+ def parse
51
+ @raw_data.each_with_index do |line, index|
52
+ parse_line(line, index)
53
+ end
54
+
55
+ if @validate_structure and not trailer_found?
56
+ raise AfdParser::AfdParserException.new("AFD ended without a trailer record")
57
+ end
58
+ end
59
+
60
+ def parse_line(line, index)
61
+ line_id, record_type_id = line.unpack("A9A").collect{|id| id.to_i}
62
+ record_type = get_record_type(line_id, record_type_id, line)
63
+
64
+ @previous_line_id = @current_line_id unless @current_line_id == 0
65
+ @current_line_id = line_id
66
+
67
+ if @validate_structure
68
+ validate_afd(line, line_id, @previous_line_id, index, record_type)
69
+ end
70
+
71
+ case record_type
72
+ when :header
73
+ @header = Header.new(line)
74
+ return @header
75
+ when :set_employer
76
+ @records << SetEmployer.new(line)
77
+ when :clock_in_out
78
+ @records << ClockInOut.new(line)
79
+ when :set_time
80
+ @records << SetTime.new(line)
81
+ when :set_employee
82
+ @records << SetEmployee.new(line)
83
+ when :trailer
84
+ @trailer = Trailer.new(line, count_records)
85
+ return @trailer
86
+ else
87
+ if @validate_structure
88
+ raise AfdParser::AfdParserException.new("Unknown record type found in AFD file, line #{index.to_s}: '#{line}'")
89
+ end
90
+ end
91
+
92
+ return @records.last
93
+ end
94
+
95
+ def create_header(employer_type, employer_document, employer_cei, employer_name, rep_serial_number, afd_start_date,afd_end_date, afd_creation_time)
96
+ if header_found?
97
+ raise AfdParser::AfdParserException.new("Cannot add a second AFD header")
98
+ else
99
+ @header = Header.new(employer_type, employer_document, employer_cei, employer_name, rep_serial_number, afd_start_date,afd_end_date, afd_creation_time)
100
+ end
101
+ end
102
+
103
+ def create_trailer
104
+ if trailer_found?
105
+ raise AfdParser::AfdParserException.new("Cannot add a second AFD trailer")
106
+ else
107
+ @trailer = Trailer.new(count_records)
108
+ end
109
+ end
110
+
111
+ def export
112
+ exported_data = ""
113
+ (exported_data += @header.export + "\r\n") if @header
114
+ @records.each do |record|
115
+ exported_data += record.export + "\r\n"
116
+ end
117
+ (exported_data += @trailer.export + "\r\n") if @trailer
118
+
119
+ exported_data
120
+ end
121
+
122
+ def first_creation_date
123
+ record = @records[0]
124
+ if record
125
+ time = record.creation_time
126
+ return Date.civil(time.year, time.month, time.day)
127
+ end
128
+ end
129
+
130
+ def last_creation_date
131
+ record = @records[-1]
132
+ if record
133
+ time = record.creation_time
134
+ return Date.civil(time.year, time.month, time.day)
135
+ end
136
+ end
137
+
138
+ def ==(other)
139
+ return self.class == other.class && @records == other.records
140
+ end
141
+
142
+ # get the first id after the AFD header
143
+ def first_id
144
+ first_record = @records[0]
145
+ first_record ? first_record.line_id : nil
146
+ end
147
+
148
+ # get the last id before the AFD trailer
149
+ def last_id
150
+ last_record = @records[-1]
151
+ last_record ? last_record.line_id : nil
152
+ end
153
+
154
+ def merge(other)
155
+ other_first_id, other_last_id = other.first_id, other.last_id
156
+ if other_first_id.nil? || other_last_id.nil?
157
+ raise AfdParser::AfdParserException.new("Cannot merge with a empty parser")
158
+ end
159
+
160
+ @header = other.header if other.header
161
+
162
+ # merge is done by grouping all the records by line id, and replacing
163
+ # the ones in "self" by duplicates of the ones in "other".
164
+ this_records_by_line_id = @records.group_by{|record| record.line_id}
165
+ other_records_by_line_id = other.records.group_by{|record| record.line_id}
166
+ other_records_by_line_id.keys.each do |key|
167
+ this_records_by_line_id[key] = other_records_by_line_id[key].dup
168
+ end
169
+ @records = this_records_by_line_id.keys.sort.collect{|key| this_records_by_line_id[key]}.flatten
170
+
171
+ @trailer = nil
172
+ create_trailer
173
+ end
174
+
175
+ private
176
+ def initialize_variables(options)
177
+ @records = []
178
+ @current_line_id = nil
179
+ @previous_line_id = nil
180
+ if options.is_a?(Hash)
181
+ @validate_structure = options[:validate_structure]
182
+ @validate_expected_afd_records_ids = options[:validate_expected_afd_records_ids]
183
+ else
184
+ @validate_expected_afd_records_ids = @validate_structure = !!options
185
+ end
186
+ end
187
+
188
+ def get_record_type(line_id, record_type_id, line)
189
+ if record_type_id == 1
190
+ return :header
191
+ elsif line_id == 999999999 and line.unpack("x45A").first.to_i == 9
192
+ return :trailer
193
+ elsif line_id != 0
194
+ case record_type_id
195
+ when 2
196
+ return :set_employer
197
+ when 3
198
+ return :clock_in_out
199
+ when 4
200
+ return :set_time
201
+ when 5
202
+ return :set_employee
203
+ end
204
+ end
205
+
206
+ return nil
207
+ end
208
+
209
+ def validate_afd(line, line_id, previous_line_id, index, record_type)
210
+ raise AfdParser::AfdParserException.new("Line #{index.to_s} is blank") if line.nil? || line.empty?
211
+
212
+ if trailer_found?
213
+ raise AfdParser::AfdParserException.new("Unexpected AFD record found after trailer, line #{index.to_s}: '#{line}'")
214
+ end
215
+
216
+ if not header_found? and record_type != :header
217
+ raise AfdParser::AfdParserException.new("Unexpected AFD record found before header, line #{index.to_s}: '#{line}'")
218
+ end
219
+
220
+ if header_found? and record_type == :header
221
+ raise AfdParser::AfdParserException.new("Unexpected second AFD header found, line #{index.to_s}: '#{line}'")
222
+ end
223
+
224
+ if @validate_expected_afd_records_ids and previous_line_id.nil? and index == 1 and line_id != index
225
+ raise AfdParser::AfdParserException.new("AFD records starts at an unexpected line ID; expected '1', got '#{line_id.to_s}'.")
226
+ end
227
+
228
+ if not previous_line_id.nil? and line_id != previous_line_id + 1 and not (line_id == 999999999 and not trailer_found?)
229
+ raise AfdParser::AfdParserException.new("Out-of-order line id on line 1; expected '#{index.to_s}', got '#{line_id.to_s}'")
230
+ end
231
+ end
232
+
233
+ def header_found?
234
+ !@header.nil?
235
+ end
236
+
237
+ def trailer_found?
238
+ !@trailer.nil?
239
+ end
240
+
241
+ def count_records
242
+ counter = {:set_employer => 0, :clock_in_out => 0, :set_time => 0, :set_employee => 0}
243
+ @records.each do |record|
244
+ case record
245
+ when *SetEmployer
246
+ counter[:set_employer] += 1
247
+ when *ClockInOut
248
+ counter[:clock_in_out] += 1
249
+ when *SetTime
250
+ counter[:set_time] += 1
251
+ when *SetEmployee
252
+ counter[:set_employee] += 1
253
+ end
254
+ end
255
+ return counter
256
+ end
257
+ end
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Controle de Horas - Sistema para gestão de horas trabalhadas
3
+ # Copyright (C) 2009 O.S. Systems Softwares Ltda.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as
7
+ # published by the Free Software Foundation, either version 3 of the
8
+ # License, or (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Rua Clóvis Gularte Candiota 132, Pelotas-RS, Brasil.
19
+ # e-mail: contato@ossystems.com.br
20
+
21
+ require 'afd_parser/record_parser'
22
+
23
+ class AfdParser::ClockInOut < AfdParser::RecordParser
24
+ attr_reader :line_id, :record_type_id, :creation_time, :pis
25
+
26
+ def initialize(line)
27
+ self.line_id, self.record_type_id, self.creation_time, self.pis =
28
+ line.unpack("A9AA12A12").collect{|str| _clean!(str)}
29
+ end
30
+
31
+ def export
32
+ line_export = ""
33
+ line_export += line_id.to_s.rjust(9,"0")
34
+ line_export += record_type_id.to_s
35
+ line_export += format_time(creation_time)
36
+ line_export += pis.to_s.rjust(11,"0")
37
+ line_export
38
+ end
39
+
40
+ def self.size
41
+ 34
42
+ end
43
+
44
+ def ==(other)
45
+ return self.class == other.class && [:line_id, :record_type_id, :creation_time, :pis].all? do |reader|
46
+ self.send(reader) == other.send(reader)
47
+ end
48
+ end
49
+
50
+ private
51
+ def line_id=(data)
52
+ @line_id = well_formed_number_string?(data) ? data.to_i : data
53
+ end
54
+
55
+ def record_type_id=(data)
56
+ @record_type_id = well_formed_number_string?(data) ? data.to_i : data
57
+ end
58
+
59
+ def pis=(data)
60
+ @pis = well_formed_number_string?(data.strip) ? data.to_i : data
61
+ end
62
+
63
+ def creation_time=(raw_time)
64
+ begin
65
+ parsed_time = parse_time(raw_time)
66
+ @creation_time = parsed_time
67
+ rescue
68
+ @creation_time = ""
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,144 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Controle de Horas - Sistema para gestão de horas trabalhadas
3
+ # Copyright (C) 2009 O.S. Systems Softwares Ltda.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as
7
+ # published by the Free Software Foundation, either version 3 of the
8
+ # License, or (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # Rua Clóvis Gularte Candiota 132, Pelotas-RS, Brasil.
19
+ # e-mail: contato@ossystems.com.br
20
+
21
+ require 'afd_parser/record_parser'
22
+
23
+ class AfdParser::Header < AfdParser::RecordParser
24
+ attr_reader :line_id, :record_type_id, :employer_type, :employer_document, :employer_cei, :employer_name, :rep_serial_number, :afd_start_date, :afd_end_date, :afd_creation_time
25
+
26
+ EMPLOYER_TYPES = {1 => :cnpj, 2 => :cpf}
27
+
28
+ def initialize(*args)
29
+ if args.size == 1
30
+ line = args[0]
31
+
32
+ self.line_id, self.record_type_id, self.employer_type, self.employer_document,
33
+ self.employer_cei, self.employer_name, self.rep_serial_number, self.afd_start_date,
34
+ self.afd_end_date, self.afd_creation_time =
35
+ line.unpack("A9AAA14A12A150A17A8A8A12").collect{|str| _clean!(str)}
36
+ elsif args.size == 8
37
+ @line_id = 0
38
+ @record_type_id = 1
39
+ @employer_type = args[0]
40
+ @employer_document = args[1]
41
+ @employer_cei = args[2]
42
+ @employer_name = args[3]
43
+ @rep_serial_number = args[4]
44
+ @afd_start_date = args[5]
45
+ @afd_end_date = args[6]
46
+ @afd_creation_time = args[7]
47
+ else
48
+ raise AfdParser::AfdParserException.new("wrong number of arguments for header object, should be 1 or 8")
49
+ end
50
+ end
51
+
52
+ def export
53
+ line_export = ""
54
+ line_export += @line_id.to_s.rjust(9,"0")
55
+ line_export += @record_type_id.to_s
56
+ line_export += get_employer_type_number(@employer_type).to_s
57
+ line_export += @employer_document.to_s.rjust(14, "0")
58
+ line_export += @employer_cei.to_s.rjust(12, "0")
59
+ line_export += @employer_name.to_s.ljust(150, " ")
60
+ line_export += @rep_serial_number
61
+ line_export += format_date(@afd_start_date)
62
+ line_export += format_date(@afd_end_date)
63
+ line_export += format_time(@afd_creation_time)
64
+ line_export
65
+ end
66
+
67
+ def self.size
68
+ 232
69
+ end
70
+
71
+ def ==(other)
72
+ return self.class == other.class && [:line_id, :record_type_id, :employer_type, :employer_document, :employer_cei, :employer_name, :rep_serial_number, :afd_start_date, :afd_end_date, :afd_creation_time].all? do |reader|
73
+ self.send(reader) == other.send(reader)
74
+ end
75
+ end
76
+
77
+ private
78
+ def line_id=(data)
79
+ @line_id = well_formed_number_string?(data) ? data.to_i : data
80
+ end
81
+
82
+ def record_type_id=(data)
83
+ @record_type_id = well_formed_number_string?(data) ? data.to_i : data
84
+ end
85
+
86
+ def employer_type=(data)
87
+ @employer_type = get_employer_type(data.to_i)
88
+ end
89
+
90
+ def employer_document=(data)
91
+ @employer_document = well_formed_number_string?(data) ? data.to_i : data
92
+ end
93
+
94
+ def employer_cei=(data)
95
+ @employer_cei = well_formed_number_string?(data) ? data.to_i : data
96
+ end
97
+
98
+ def employer_name=(data)
99
+ @employer_name = data.rstrip
100
+ end
101
+
102
+ def rep_serial_number=(data)
103
+ @rep_serial_number = data
104
+ end
105
+
106
+ def afd_start_date=(raw_date)
107
+ begin
108
+ parsed_date = parse_date(raw_date)
109
+ @afd_start_date = parsed_date
110
+ rescue
111
+ @afd_start_date = raw_date
112
+ end
113
+ end
114
+
115
+ def afd_end_date=(raw_date)
116
+ begin
117
+ parsed_date = parse_date(raw_date)
118
+ @afd_end_date = parsed_date
119
+ rescue
120
+ @afd_end_date = raw_date
121
+ end
122
+ end
123
+
124
+ def afd_creation_time=(raw_time)
125
+ begin
126
+ parsed_time = parse_time(raw_time)
127
+ @afd_creation_time = parsed_time
128
+ rescue
129
+ @afd_creation_time = raw_time
130
+ end
131
+ end
132
+
133
+ def get_employer_type(employer_type_id)
134
+ type = EMPLOYER_TYPES[employer_type_id]
135
+ if type.nil?
136
+ raise AfdParser::AfdParserException.new("Unknown employer type id '#{employer_type_id.to_s}' found in AFD header")
137
+ end
138
+ type
139
+ end
140
+
141
+ def get_employer_type_number(employer_type)
142
+ EMPLOYER_TYPES.invert[employer_type]
143
+ end
144
+ end