afd_parser 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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