PS2Format 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/PS2Format/exception/InvalidAmountException.rb +8 -0
- data/lib/PS2Format/exception/InvalidNIBException.rb +8 -0
- data/lib/PS2Format/exception/InvalidOperationTypeException.rb +8 -0
- data/lib/PS2Format/exception/InvalidReferenceException.rb +8 -0
- data/lib/PS2Format/footer.rb +44 -0
- data/lib/PS2Format/header.rb +100 -0
- data/lib/PS2Format/operation.rb +122 -0
- data/lib/PS2Format/operation_type.rb +59 -0
- data/lib/PS2Format/ps2.rb +170 -0
- data/lib/PS2Format/record.rb +146 -0
- data/lib/PS2Format/version.rb +3 -0
- data/lib/locales/ps2.en.yml +24 -0
- data/lib/locales/ps2.pt-PT.yml +24 -0
- data/lib/ps2_format.rb +13 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5962014857f59b089dd934aec18c5945b46550fe
|
4
|
+
data.tar.gz: 1149120a9e49b39638af68e4cdafbce058210359
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8feb4232ac1c0b01999cee780662afe1d7d4592ebe9bcae8640bdfb9ac90924a5b99ab43ba86fc25116db02570b6f86715b5db0e365cb5b7d5aa5221aa9d1883
|
7
|
+
data.tar.gz: c87cddedd29933d204087b6f47fdadd01ddb31cd0c30e72f496b42743fda75e3a986f3ac606d6b12918f0ad99bfd963a46c6862df593b61f6d6ee3549b90317b
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PS2Format
|
2
|
+
class Footer < Record
|
3
|
+
FOOTER_RECORD_TYPE = '9'
|
4
|
+
|
5
|
+
def initialize(ps2)
|
6
|
+
@ps2 = ps2
|
7
|
+
super({format: FORMAT,
|
8
|
+
record_type: FOOTER_RECORD_TYPE,
|
9
|
+
operation_type: TRANSFER_OPCODE,
|
10
|
+
filler1: '00',
|
11
|
+
record_status: RECORD_STATUS,
|
12
|
+
filler2: ''.rjust(6, '0'),
|
13
|
+
num_operations: @ps2.operations.size.to_s.rjust(14, '0'),
|
14
|
+
total_amount: @ps2.total_amount.to_s.rjust(13, '0'),
|
15
|
+
filler3: ''.rjust(38, '0')})
|
16
|
+
process_options({operation_type: @ps2.operation_type})
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_amount
|
20
|
+
metadata.total_amount.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def num_operations
|
24
|
+
metadata.num_operations.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate
|
28
|
+
add_error(:format, value: format) if format != FORMAT
|
29
|
+
add_error(:record_type, should_be: FOOTER_RECORD_TYPE, is: record_type) if record_type != FOOTER_RECORD_TYPE
|
30
|
+
add_error(:record_status, value: record_status) if record_status.size != 1
|
31
|
+
add_error :num_operations, num_operations: num_operations if num_operations != @ps2.operations.size
|
32
|
+
add_error :total_amount, total_amount: total_amount if total_amount != @ps2.total_amount
|
33
|
+
add_error :line_size, component: 'Footer', size: data.length if data.length != 80
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def process_options(opts)
|
39
|
+
operation_type = opts.delete(:operation_type)
|
40
|
+
|
41
|
+
self.operation_type = operation_type if operation_type
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'citizenship'
|
3
|
+
require 'PS2Format/exception/InvalidOperationTypeException'
|
4
|
+
require 'PS2Format/exception/InvalidNIBException'
|
5
|
+
|
6
|
+
module PS2Format
|
7
|
+
class Header < Record
|
8
|
+
HEADER_RECORD_TYPE = '1'
|
9
|
+
COIN = 'EUR'
|
10
|
+
FILLER_SIZE = 19
|
11
|
+
|
12
|
+
def initialize(arg = nil)
|
13
|
+
case arg
|
14
|
+
when Hash, NilClass
|
15
|
+
super({format: FORMAT,
|
16
|
+
record_type: HEADER_RECORD_TYPE,
|
17
|
+
operation_type: TRANSFER_OPCODE,
|
18
|
+
account_status: ACCOUNT_STATUS,
|
19
|
+
record_status: RECORD_STATUS,
|
20
|
+
ordering_nib: ''.rjust(21, '0'),
|
21
|
+
coin: COIN,
|
22
|
+
processing_date: Record.date_to_str(Date.today),
|
23
|
+
reference: ''.rjust(REFERENCE_FIELD_SIZE, '0'),
|
24
|
+
filler: ''.rjust(FILLER_SIZE, '0')}.merge(arg || {}))
|
25
|
+
process_options(arg || {})
|
26
|
+
when String
|
27
|
+
super({format: arg[0..2],
|
28
|
+
record_type: arg[3..3],
|
29
|
+
operation_type: arg[4..5],
|
30
|
+
account_status: arg[6..7],
|
31
|
+
record_status: arg[8..8],
|
32
|
+
ordering_nib: arg[9..29],
|
33
|
+
coin: arg[30..32],
|
34
|
+
processing_date: arg[33..40],
|
35
|
+
reference: arg[41..60],
|
36
|
+
filler: arg[61..79]})
|
37
|
+
else
|
38
|
+
raise ArgumentError, "String or Hash expected"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reference=(ref)
|
43
|
+
metadata.reference = Record.remove_accents(String(ref)).rjust(REFERENCE_FIELD_SIZE, '0')
|
44
|
+
end
|
45
|
+
|
46
|
+
def reference
|
47
|
+
metadata.reference.lstrip
|
48
|
+
end
|
49
|
+
|
50
|
+
def ordering_nib=(ordering_nib)
|
51
|
+
metadata.ordering_nib = Record.pre_process_nib(ordering_nib)
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate
|
55
|
+
add_error(:format, value: format) if format != FORMAT
|
56
|
+
add_error(:record_type, should_be: HEADER_RECORD_TYPE, is: record_type) if record_type != HEADER_RECORD_TYPE
|
57
|
+
add_error(:record_status, value: record_status) if record_status.size != 1
|
58
|
+
add_error(:account_status, value: account_status) if account_status.size != 2
|
59
|
+
add_error(:coin, coin: coin) if coin != COIN
|
60
|
+
add_error(:date, date: processing_date) unless Record.valid_date?(processing_date)
|
61
|
+
add_error(:reference, size: reference.size) if reference.size != REFERENCE_FIELD_SIZE
|
62
|
+
add_error(:filler) if filler.size != 19 or filler.to_i != 0
|
63
|
+
add_error(:line_size, component: 'Header', size: data.length) if data.length != 80
|
64
|
+
|
65
|
+
begin
|
66
|
+
Citizenship.valid_nib!(ordering_nib)
|
67
|
+
rescue Citizenship::Error => e
|
68
|
+
add_error(:nib, error: e.message)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def process_options(opts)
|
75
|
+
|
76
|
+
if !opts.empty?
|
77
|
+
raise PS2Format::InvalidOperationTypeException.new I18n.t("ps2.exceptions.invalid_operation_type") if opts.delete(:operation_type).nil?
|
78
|
+
begin
|
79
|
+
Citizenship.valid_nib!(opts.delete(:ordering_nib))
|
80
|
+
ordering_nib = opts.delete(:ordering_nib)
|
81
|
+
rescue Citizenship::Error => e
|
82
|
+
raise PS2Format::InvalidNIBException.new e.message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
ref = opts.delete(:reference)
|
87
|
+
operation_type = opts.delete(:operation_type)
|
88
|
+
date = opts.delete(:date)
|
89
|
+
account_status = opts.delete(:account_status)
|
90
|
+
record_status = opts.delete(:record_status)
|
91
|
+
|
92
|
+
self.ordering_nib = ordering_nib if ordering_nib
|
93
|
+
self.reference = ref if ref
|
94
|
+
self.operation_type = operation_type if operation_type
|
95
|
+
self.date = date if date
|
96
|
+
self.account_status = account_status if account_status
|
97
|
+
self.record_status = record_status if record_status
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'citizenship'
|
2
|
+
require 'PS2Format/exception/InvalidAmountException'
|
3
|
+
|
4
|
+
module PS2Format
|
5
|
+
class Operation < Record
|
6
|
+
OPERATION_RECORD_TYPE = '2'
|
7
|
+
|
8
|
+
TRANSFER_REFERENCE_FIELD_SIZE = 15
|
9
|
+
AMOUNT_FIELD_SIZE = 13
|
10
|
+
COMPANY_REFERENCE_FIELD_SIZE = 20
|
11
|
+
|
12
|
+
def initialize(arg = nil)
|
13
|
+
case arg
|
14
|
+
when Hash, NilClass
|
15
|
+
super({format: FORMAT,
|
16
|
+
record_type: OPERATION_RECORD_TYPE,
|
17
|
+
operation_type: TRANSFER_OPCODE,
|
18
|
+
account_status: ACCOUNT_STATUS,
|
19
|
+
record_status: RECORD_STATUS,
|
20
|
+
nib: ''.rjust(21, '0'),
|
21
|
+
amount: ''.rjust(AMOUNT_FIELD_SIZE, '0'),
|
22
|
+
company_reference: ''.rjust(COMPANY_REFERENCE_FIELD_SIZE, '0'),
|
23
|
+
transfer_reference: ''.rjust(TRANSFER_REFERENCE_FIELD_SIZE, '0'),
|
24
|
+
filler: '00'})
|
25
|
+
process_options(arg || {})
|
26
|
+
when String
|
27
|
+
super({format: arg[0..2],
|
28
|
+
record_type: arg[3..3],
|
29
|
+
operation_type: arg[4..5],
|
30
|
+
account_status: arg[6..7],
|
31
|
+
record_status: arg[8..8],
|
32
|
+
nib: arg[9..29],
|
33
|
+
amount: arg[30..42],
|
34
|
+
company_reference: arg[43..62],
|
35
|
+
transfer_reference: arg[63..77],
|
36
|
+
filler: arg[78..79]})
|
37
|
+
else
|
38
|
+
raise ArgumentError, "String or Hash expected"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def amount=(value)
|
43
|
+
metadata.amount = String(value).rjust(AMOUNT_FIELD_SIZE, '0')
|
44
|
+
end
|
45
|
+
|
46
|
+
def amount
|
47
|
+
metadata.amount.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def transfer_reference=(ref)
|
51
|
+
metadata.transfer_reference = Record.remove_accents(String(ref)).rjust(TRANSFER_REFERENCE_FIELD_SIZE, ' ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def transfer_reference
|
55
|
+
metadata.transfer_reference.lstrip
|
56
|
+
end
|
57
|
+
|
58
|
+
def company_reference=(ref)
|
59
|
+
metadata.company_reference = Record.remove_accents(String(ref)).rjust(COMPANY_REFERENCE_FIELD_SIZE, ' ')
|
60
|
+
end
|
61
|
+
|
62
|
+
def company_reference
|
63
|
+
metadata.company_reference.lstrip
|
64
|
+
end
|
65
|
+
|
66
|
+
def nib=(nib)
|
67
|
+
metadata.nib = Record.pre_process_nib(nib)
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self
|
71
|
+
|
72
|
+
def validate_options(options)
|
73
|
+
raise PS2Format::InvalidOperationTypeException.new I18n.t("ps2.exceptions.invalid_operation_type") if options[:operation_type].nil?
|
74
|
+
raise InvalidAmountException.new I18n.t("ps2.exceptions.invalid_amount") if !(options[:amount].is_a? Numeric) or options[:amount].size > 11 or options[:amount] <= 0
|
75
|
+
raise InvalidReferenceException.new I18n.t("ps2.exceptions.invalid_reference") if options[:operation_type].to_i > 50 and options[:company_reference].nil?
|
76
|
+
|
77
|
+
begin
|
78
|
+
Citizenship.valid_nib!(options[:nib])
|
79
|
+
rescue Citizenship::Error => e
|
80
|
+
raise PS2Format::InvalidOperationTypeException.new e.message
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate(include_obj = false)
|
87
|
+
add_error(:format, value: format) if format != FORMAT
|
88
|
+
add_error(:record_type, should_be: OPERATION_RECORD_TYPE, is: record_type) if record_type != OPERATION_RECORD_TYPE
|
89
|
+
add_error(:record_status, value: record_status) if record_status.size != 1
|
90
|
+
add_error(:account_status, value: account_status) if account_status.size != 2
|
91
|
+
add_error(:amount, amount: amount) if !(amount.is_a? Numeric) or amount <= 0 or amount.size > 11
|
92
|
+
add_error(:line_size, component: 'Operation', size: data.length) if data.length != 80
|
93
|
+
|
94
|
+
begin
|
95
|
+
Citizenship.valid_nib!(nib)
|
96
|
+
rescue Citizenship::Error => e
|
97
|
+
add_error(:nib, error: e.message)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def process_options(opts)
|
104
|
+
|
105
|
+
nib = opts.delete(:nib)
|
106
|
+
amount = opts.delete(:amount)
|
107
|
+
transfer_reference = opts.delete(:transfer_reference)
|
108
|
+
company_reference = opts.delete(:company_reference)
|
109
|
+
operation_type = opts.delete(:operation_type)
|
110
|
+
account_status = opts.delete(:account_status)
|
111
|
+
record_status = opts.delete(:record_status)
|
112
|
+
|
113
|
+
self.nib = nib if nib
|
114
|
+
self.amount = amount if amount
|
115
|
+
self.transfer_reference = transfer_reference if transfer_reference
|
116
|
+
self.company_reference = company_reference if company_reference
|
117
|
+
self.operation_type = operation_type if operation_type
|
118
|
+
self.account_status = account_status if account_status
|
119
|
+
self.record_status = record_status if record_status
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module PS2Format
|
3
|
+
class OperationType
|
4
|
+
|
5
|
+
@@expense = {}
|
6
|
+
@@expense[:water] = "01"
|
7
|
+
@@expense[:gas] = "02"
|
8
|
+
@@expense[:electricity] = "03"
|
9
|
+
@@expense[:gas_electricity] = "04"
|
10
|
+
@@expense[:cellphone] = "05"
|
11
|
+
@@expense[:telex] = "06"
|
12
|
+
@@expense[:house_rent] = "07"
|
13
|
+
@@expense[:payroll] = "08"
|
14
|
+
@@expense[:suppliers] = "09"
|
15
|
+
@@expense[:transfer] = "12"
|
16
|
+
|
17
|
+
@@income = {}
|
18
|
+
@@income[:water] = "51"
|
19
|
+
@@income[:gas] = "52"
|
20
|
+
@@income[:electricity] = "53"
|
21
|
+
@@income[:gas_electricity] = "54"
|
22
|
+
@@income[:cellphone] = "55"
|
23
|
+
@@income[:telex] = "56"
|
24
|
+
@@income[:house_rent] = "57"
|
25
|
+
@@income[:land_public_service] = "58"
|
26
|
+
@@income[:suppliers] = "59"
|
27
|
+
@@income[:insurance] = "60"
|
28
|
+
@@income[:quota] = "61"
|
29
|
+
@@income[:transfer] = "62"
|
30
|
+
@@income[:data_com_public_service] = "63"
|
31
|
+
@@income[:collect] = "64"
|
32
|
+
@@income[:rented_circuits] = "65"
|
33
|
+
@@income[:various_services] = "66"
|
34
|
+
@@income[:sanitation] = "67"
|
35
|
+
@@income[:water_electricity] = "68"
|
36
|
+
@@income[:water_sanitation] = "69"
|
37
|
+
@@income[:telecom_ctt] = "70"
|
38
|
+
@@income[:iva_refund_iva_charge] = "71"
|
39
|
+
@@income[:tv_license] = "72"
|
40
|
+
@@income[:TELEBIP] = "73"
|
41
|
+
@@income[:VIDEOTEX] = "74"
|
42
|
+
@@income[:digital_phone] = "75"
|
43
|
+
@@income[:green_number] = "76"
|
44
|
+
@@income[:phone_porto] = "77"
|
45
|
+
@@income[:green_number_IN] = "78"
|
46
|
+
@@income[:blue_number] = "79"
|
47
|
+
@@income[:rented_circuits_porto] = "80"
|
48
|
+
@@income[:CLIP] = "81"
|
49
|
+
|
50
|
+
def self.expense
|
51
|
+
@@expense
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.income
|
55
|
+
@@income
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module PS2Format
|
4
|
+
class BatchTransfer
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@header, :operation_type, :ordering_nib
|
8
|
+
attr_accessor :header, :operations
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@header = Header.new(opts)
|
12
|
+
@operations = []
|
13
|
+
process_options(opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ordering_nib=(nib)
|
17
|
+
@header.ordering_nib = Record.pre_process_nib(nib)
|
18
|
+
end
|
19
|
+
|
20
|
+
def reference=(ref)
|
21
|
+
@header.reference = String(ref).rjust(Header::REFERENCE_FIELD_SIZE, ' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def reference
|
25
|
+
@header.reference.lstrip
|
26
|
+
end
|
27
|
+
|
28
|
+
def processing_date
|
29
|
+
Record.str_to_date(@header.processing_date)
|
30
|
+
end
|
31
|
+
|
32
|
+
def processing_date=(date)
|
33
|
+
@header.processing_date = Record.date_to_str(date)
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_operation!(options = {})
|
37
|
+
Operation.validate_options(options)
|
38
|
+
@operations << Operation.new(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_operation(options = {})
|
42
|
+
oper = Operation.new(options)
|
43
|
+
@operations << oper
|
44
|
+
oper.validate
|
45
|
+
oper.errors
|
46
|
+
end
|
47
|
+
|
48
|
+
def total_amount
|
49
|
+
@operations.map(&:amount).reduce(0, &:+)
|
50
|
+
end
|
51
|
+
|
52
|
+
def income_amount
|
53
|
+
@operations.map { |op|
|
54
|
+
if op.operation_type.to_i >= 50 then op.amount.to_i else 0 end
|
55
|
+
}.reduce(0, &:+)
|
56
|
+
end
|
57
|
+
|
58
|
+
def expense_amount
|
59
|
+
@operations.map { |op|
|
60
|
+
if op.operation_type.to_i < 50 then op.amount.to_i else 0 end
|
61
|
+
}.reduce(0, &:+)
|
62
|
+
end
|
63
|
+
|
64
|
+
def footer
|
65
|
+
Footer.new(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid?
|
69
|
+
validate
|
70
|
+
errors.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
def invalid?
|
74
|
+
not(valid?)
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate
|
78
|
+
oper_errors = @operations.each do |o|
|
79
|
+
o.validate
|
80
|
+
end
|
81
|
+
|
82
|
+
@header.validate
|
83
|
+
|
84
|
+
footer.validate
|
85
|
+
end
|
86
|
+
|
87
|
+
def data
|
88
|
+
to_a.map(&:to_s).join("\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
def errors
|
92
|
+
@header.errors + operation_errors + footer.errors
|
93
|
+
end
|
94
|
+
|
95
|
+
def structured_errors
|
96
|
+
{ header: @header.errors, operations: structured_operation_errors, footer: footer.errors }
|
97
|
+
end
|
98
|
+
|
99
|
+
def operation_errors
|
100
|
+
@operations.map(&:errors).reject(&:empty?)
|
101
|
+
end
|
102
|
+
|
103
|
+
def structured_operation_errors
|
104
|
+
error_hash = {}
|
105
|
+
@operations.each do |op|
|
106
|
+
error_hash[op] = op.errors unless op.errors.empty?
|
107
|
+
end
|
108
|
+
error_hash
|
109
|
+
end
|
110
|
+
|
111
|
+
def save(file_name)
|
112
|
+
stream = File.new(file_name, "w")
|
113
|
+
validate
|
114
|
+
if errors.empty?
|
115
|
+
marshall stream
|
116
|
+
stream.close
|
117
|
+
else
|
118
|
+
errors
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def marshall(stream)
|
123
|
+
stream << to_s
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_s
|
127
|
+
data
|
128
|
+
end
|
129
|
+
|
130
|
+
class << self
|
131
|
+
def read(file_name)
|
132
|
+
stream = File.new(file_name,'r')
|
133
|
+
file = unmarshall stream
|
134
|
+
stream.close
|
135
|
+
file
|
136
|
+
end
|
137
|
+
|
138
|
+
def unmarshall(stream)
|
139
|
+
lines = stream.readlines
|
140
|
+
|
141
|
+
header = Header.new(lines[0])
|
142
|
+
|
143
|
+
operations = lines[1..-2].reduce([]) { |array, line| array << Operation.new(line) }
|
144
|
+
|
145
|
+
ps2 = PS2Format::BatchTransfer.new
|
146
|
+
ps2.header = header
|
147
|
+
ps2.operations = operations
|
148
|
+
#NOTICE: Footer is built on the fly! This should not happen for unmarshalling
|
149
|
+
|
150
|
+
ps2
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def process_options(opts)
|
156
|
+
nib = opts.delete(:ordering_nib)
|
157
|
+
date = opts.delete(:processing_date)
|
158
|
+
ref = opts.delete(:reference)
|
159
|
+
|
160
|
+
self.ordering_nib = nib if nib
|
161
|
+
self.processing_date = date if date
|
162
|
+
self.reference = ref if ref
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_a
|
166
|
+
[@header.data, @operations.map(&:data), footer.data].flatten
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
require 'ostruct'
|
3
|
+
require 'delegate'
|
4
|
+
require 'i18n'
|
5
|
+
|
6
|
+
module PS2Format
|
7
|
+
# Compoents (Header, Operation and Footer) are validatable
|
8
|
+
# and delegate method calls to an OpenStruct containing
|
9
|
+
# the component's metadata
|
10
|
+
#
|
11
|
+
class Record < SimpleDelegator
|
12
|
+
alias :metadata :__getobj__
|
13
|
+
|
14
|
+
FORMAT = 'ps2'
|
15
|
+
TRANSFER_OPCODE = '12'
|
16
|
+
ACCOUNT_STATUS = '00'
|
17
|
+
RECORD_STATUS = '0'
|
18
|
+
REFERENCE_FIELD_SIZE = 20
|
19
|
+
|
20
|
+
#The error list
|
21
|
+
attr_reader :errors
|
22
|
+
|
23
|
+
def initialize(metadata)
|
24
|
+
super(OpenStruct.new(metadata))
|
25
|
+
@errors = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Validates the component
|
29
|
+
# Inheriting classes must implement #validate and use
|
30
|
+
# Componenet#add_error if errors found
|
31
|
+
def valid?
|
32
|
+
@errors.clear
|
33
|
+
validate
|
34
|
+
@errors.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def invalid?
|
38
|
+
not(valid?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def format
|
42
|
+
metadata.format
|
43
|
+
end
|
44
|
+
|
45
|
+
def ordering_nib
|
46
|
+
metadata.ordering_nib
|
47
|
+
end
|
48
|
+
|
49
|
+
def operation_type
|
50
|
+
metadata.operation_type
|
51
|
+
end
|
52
|
+
|
53
|
+
def operation_type=(operation_type)
|
54
|
+
metadata.operation_type = operation_type
|
55
|
+
end
|
56
|
+
|
57
|
+
def date=(date)
|
58
|
+
if date.is_a? Date
|
59
|
+
metadata.date = date_to_str(date)
|
60
|
+
end
|
61
|
+
|
62
|
+
if date.is_a? String
|
63
|
+
metadata.date = date
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def account_status
|
68
|
+
metadata.account_status
|
69
|
+
end
|
70
|
+
|
71
|
+
def account_status=(status)
|
72
|
+
metadata.account_status = status
|
73
|
+
end
|
74
|
+
|
75
|
+
def record_status
|
76
|
+
metadata.record_status
|
77
|
+
end
|
78
|
+
|
79
|
+
def record_status=(status)
|
80
|
+
metadata.record_status = status
|
81
|
+
end
|
82
|
+
|
83
|
+
#Raw component data
|
84
|
+
def data
|
85
|
+
metadata.marshal_dump.values.map(&:to_s).reduce('', :<<)
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
def add_error(key, values = {})
|
90
|
+
error(key, 'ps2.errors', values)
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_account_status_error(key, values = {})
|
94
|
+
error("status_#{key}", 'ps2.account_statuses', values)
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_status_error(key, values = {})
|
98
|
+
error("status_#{key}", 'ps2.statuses', values)
|
99
|
+
end
|
100
|
+
|
101
|
+
def error(key, scope, values = {})
|
102
|
+
@errors << I18n.t(key.to_sym, values.merge(scope: scope))
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.date_to_str(date)
|
106
|
+
date.strftime('%Y%m%d')
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.str_to_date(str)
|
110
|
+
Date.strptime(str, '%Y%m%d')
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.valid_date?(str)
|
114
|
+
str_to_date(str)
|
115
|
+
true
|
116
|
+
rescue ArgumentError
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.pre_process_nib(nib)
|
121
|
+
String(nib).delete(' ')
|
122
|
+
.delete('-')
|
123
|
+
.delete('.')
|
124
|
+
.rjust(21, '0')
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.remove_accents(text)
|
128
|
+
text = text.to_s
|
129
|
+
text = text.gsub(/[á|à|ã|â|ä]/, 'a').gsub(/(é|è|ê|ë)/, 'e').gsub(/(í|ì|î|ï)/, 'i').gsub(/(ó|ò|õ|ô|ö)/, 'o').gsub(/(ú|ù|û|ü)/, 'u')
|
130
|
+
text = text.gsub(/(Á|À|Ã|Â|Ä)/, 'A').gsub(/(É|È|Ê|Ë)/, 'E').gsub(/(Í|Ì|Î|Ï)/, 'I').gsub(/(Ó|Ò|Õ|Ô|Ö)/, 'O').gsub(/(Ú|Ù|Û|Ü)/, 'U')
|
131
|
+
text = text.gsub(/ñ/, 'n').gsub(/Ñ/, 'N')
|
132
|
+
text = text.gsub(/ç/, 'c').gsub(/Ç/, 'C')
|
133
|
+
text
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def ensure_options(options)
|
139
|
+
|
140
|
+
raise PS2Format::InvalidOperationTypeException.new I18n.t("ps2.exceptions.invalid_operation_type") if operation_type.nil?
|
141
|
+
raise PS2Format::InvalidNIBException.new I18n.t("ps2.exceptions.invalid_nib") if ordering_nib.nil? or ordering_nib.size != 19
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
en:
|
3
|
+
ps2:
|
4
|
+
errors:
|
5
|
+
format: "format should be 'PS2' and is %{value}"
|
6
|
+
nib: "Invalid NIB: %{error}"
|
7
|
+
record_type: "record_type should be %{should_be} and is %{is}"
|
8
|
+
operation_type: "operation type is inconsistent: %{value}"
|
9
|
+
account_status: "unexpected account_status: %{value}"
|
10
|
+
record_status: "unexpected record_status: %{value}"
|
11
|
+
coin: "coin must be 'EUR' but is %{coin}"
|
12
|
+
date: "invalid date: %{value}"
|
13
|
+
reference: "reference size show be 10 but is %{size}"
|
14
|
+
filler: "filler is misplaced or with wrong size"
|
15
|
+
line_size: "%{component} header data should be 80 bytes long but is: %{size}"
|
16
|
+
amount: "invalid amount: %{amount}"
|
17
|
+
num_operations: "unexpected number of operations: #{num_operations}"
|
18
|
+
total_amount: "unexpected total amount: #{total_amount}"
|
19
|
+
|
20
|
+
exceptions:
|
21
|
+
invalid_operation_type: "Invalid operation type"
|
22
|
+
invalid_nib: "Invalid NIB"
|
23
|
+
invalid_amount: "Invalid amount"
|
24
|
+
invalid_reference: "Invalid company reference"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
"pt-PT":
|
3
|
+
ps2:
|
4
|
+
errors:
|
5
|
+
format: "Formato devia ser 'PS2' mas é %{value}"
|
6
|
+
nib: "NIB Inválido: %{error}"
|
7
|
+
record_type: "Tipo de registo devias ser %{should_be} mas é %{is}"
|
8
|
+
operation_type: "Tipo de operação não suportada: %{value}"
|
9
|
+
account_status: "Erro na sutação da conta: %{value}"
|
10
|
+
record_status: "Erro na sutação do registo: %{value}"
|
11
|
+
coin: "moeda deve ser 'EUR' mas é %{coin}"
|
12
|
+
date: "Data de operação inválida: %{value}"
|
13
|
+
reference: "O tamanha da referência do ordenantes devia ter 10 bytes mas tem %{size}"
|
14
|
+
filler: "Filler is misplaced or with wrong size"
|
15
|
+
line_size: "%{component} devia ter 80 bytes mas tem: %{size}"
|
16
|
+
amount: "Importância inválida: %{amount}"
|
17
|
+
num_operations: "Número de operações inesperado: #{num_operations}"
|
18
|
+
total_amount: "Importância total inválida: #{total_amount}"
|
19
|
+
|
20
|
+
exceptions:
|
21
|
+
invalid_operation_type: "Tipo de operação inválida"
|
22
|
+
invalid_nib: "NIB inválido"
|
23
|
+
invalid_amount: "Montante introduziodo inválido"
|
24
|
+
invalid_reference: "Referência da empresa inválida"
|
data/lib/ps2_format.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "PS2Format/version"
|
2
|
+
require "PS2Format/ps2"
|
3
|
+
require "PS2Format/record"
|
4
|
+
require "PS2Format/header"
|
5
|
+
require "PS2Format/operation"
|
6
|
+
require "PS2Format/operation_type"
|
7
|
+
require "PS2Format/footer"
|
8
|
+
require "PS2Format/exception/InvalidOperationTypeException"
|
9
|
+
require "PS2Format/exception/InvalidNIBException"
|
10
|
+
require "PS2Format/exception/InvalidAmountException"
|
11
|
+
require "PS2Format/exception/InvalidReferenceException"
|
12
|
+
|
13
|
+
I18n.load_path += Dir.glob( File.join(File.dirname(__FILE__), 'locales'.freeze, '*.yml'.freeze) )
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: PS2Format
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Runtime Revolution
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: i18n
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.7.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: citizenship
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: test-unit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.1.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.2
|
83
|
+
description: PS2 provides you the ability of creating PS2 type files used to make
|
84
|
+
financial transfers with a bank.
|
85
|
+
email: info@runtime-revolution.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/PS2Format/exception/InvalidAmountException.rb
|
91
|
+
- lib/PS2Format/exception/InvalidNIBException.rb
|
92
|
+
- lib/PS2Format/exception/InvalidOperationTypeException.rb
|
93
|
+
- lib/PS2Format/exception/InvalidReferenceException.rb
|
94
|
+
- lib/PS2Format/footer.rb
|
95
|
+
- lib/PS2Format/header.rb
|
96
|
+
- lib/PS2Format/operation.rb
|
97
|
+
- lib/PS2Format/operation_type.rb
|
98
|
+
- lib/PS2Format/ps2.rb
|
99
|
+
- lib/PS2Format/record.rb
|
100
|
+
- lib/PS2Format/version.rb
|
101
|
+
- lib/locales/ps2.en.yml
|
102
|
+
- lib/locales/ps2.pt-PT.yml
|
103
|
+
- lib/ps2_format.rb
|
104
|
+
homepage: https://github.com/runtimerevolution/ps2
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.4.6
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: PS2 provides you the ability of creating PS2 type files used to make financial
|
128
|
+
transfers with a bank.
|
129
|
+
test_files: []
|