ruby-hotfile 0.0.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,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Ticket/Document Identification Record
6
+ class BKS24 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ coupon, conjunction, agent_code, issuance_reason, tour, transaction_code, destination, pnr, time, turnaround,
11
+ reserved =
12
+ line.scan(/
13
+ ([A-Z0-9 ]{4})
14
+ ([A-Z0-9 ]{3})
15
+ (\d{8})
16
+ ([A-Z0-9 ])
17
+ ([A-Z0-9 ]{15})
18
+ ([A-Z0-9]{4})
19
+ ([A-Z0-9 ]{10})
20
+ (.{13})
21
+ ([\d ]{4})
22
+ ([A-Z0-9 ]{5})
23
+ (.{29})
24
+ /x).flatten
25
+ time = nil if time.strip == ''
26
+
27
+ @data = {
28
+ transaction_code: transaction_code,
29
+ time: time&.insert(2, ':'),
30
+ coupon: coupon.strip,
31
+ conjunction: conjunction.strip,
32
+ agent_code: agent_code.to_i,
33
+ issuance_reason: issuance_reason.strip,
34
+ tour: tour.strip,
35
+ destination: destination.strip,
36
+ pnr: pnr.strip,
37
+ turnaround: turnaround.strip,
38
+ reserved: reserved.strip
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## STD/Document Amounts Record
6
+ class BKS30 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ commissionable, net_fare, misc1_type, misc1_amount, misc2_type, misc2_amount, misc3_type, misc3_amount, amount,
11
+ reserved, currency =
12
+ line.scan(/
13
+ (\d{10}.)
14
+ ([\d ]{10}.)
15
+ ([A-Z0-9 ]{8})
16
+ ([\d ]{10}.)
17
+ ([A-Z0-9 ]{8})
18
+ ([\d ]{10}.)
19
+ ([A-Z0-9 ]{8})
20
+ ([\d ]{10}.)
21
+ (\d{10}.)
22
+ (.{2})
23
+ ([A-Z0-9]{4})
24
+ /x).flatten
25
+
26
+ @data = {
27
+ currency: currency.strip,
28
+ amounts: {
29
+ total: Hotfile::Integer.new(amount).to_i,
30
+ net: Hotfile::Integer.new(net_fare).to_i,
31
+ commissionable: Hotfile::Integer.new(commissionable).to_i,
32
+ tax_misc: [
33
+ { type: misc1_type.strip, amount: Hotfile::Integer.new(misc1_amount).to_i },
34
+ { type: misc2_type.strip, amount: Hotfile::Integer.new(misc2_amount).to_i },
35
+ { type: misc3_type.strip, amount: Hotfile::Integer.new(misc3_amount).to_i }
36
+ ].reject { |x| x[:type] == '' }
37
+ },
38
+ reserved: reserved.strip
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Coupon Tax Information Record
6
+ # ! not tested, I hope this code works at all...
7
+ class BKS31 < Record
8
+ def initialize(line)
9
+ super
10
+
11
+ segment1, coupon_airport1, segment_airport1, tax_code1, tax_type1, tax_amount1, tax_currency1, tax_amount_a1,
12
+ segment2, coupon_airport2, segment_airport2, tax_code2, tax_type2, tax_amount2, tax_currency2,
13
+ tax_amount_a2, reserved, currency =
14
+ line.scan(/
15
+ (\d)
16
+ ([A-Z ]{5})
17
+ ([A-Z ]{6})
18
+ ([A-Z0-9]{2})
19
+ ([A-Z0-9 ]{3})
20
+ (\d{10}.)
21
+ ([A-Z0-9 ]{4})
22
+ ([\d ]{10}.)
23
+ (\d)
24
+ ([A-Z ]{5})
25
+ ([A-Z ]{6})
26
+ ([A-Z0-9]{2})
27
+ ([A-Z0-9 ]{3})
28
+ (\d{10}.)
29
+ ([A-Z0-9 ]{4})
30
+ ([\d ]{10}.)
31
+ (.{6})
32
+ ([A-Z0-9]{4})
33
+ /x).flatten
34
+
35
+ @data = {
36
+ currency: currency.strip,
37
+ coupons: [
38
+ {
39
+ segment: segment1.strip,
40
+ segment_airport: segment_airport1.strip,
41
+ coupon_tax: {
42
+ currency: tax_currency1.strip,
43
+ airport: coupon_airport1.strip,
44
+ code: tax_code1.strip,
45
+ type: tax_type1.strip,
46
+ amount: Hotfile::Integer.new(tax_amount1).to_i,
47
+ applicable_amount: Hotfile::Integer.new(tax_amount_a1).to_i
48
+ }
49
+ },
50
+ {
51
+ segment: segment2.strip,
52
+ segment_airport: segment_airport2.strip,
53
+ coupon_tax: {
54
+ currency: tax_currency2.strip,
55
+ airport: coupon_airport2.strip,
56
+ code: tax_code2.strip,
57
+ type: tax_type2.strip,
58
+ amount: Hotfile::Integer.new(tax_amount2).to_i,
59
+ applicable_amount: Hotfile::Integer.new(tax_amount_a2).to_i
60
+ }
61
+ }
62
+ ].reject { |x| x[:segment] == '' },
63
+ reserved: reserved.strip
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Commission Record
6
+ class BKS39 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ stat_code, type, rate, amount, type2, rate2, amount2, effective_rate, effective_amount, paid, rdi_indicator,
11
+ cca_indicator, reserved, currency =
12
+ line.scan(/
13
+ ([A-Z0-9 ]{3})
14
+ ([A-Z0-9 ]{6})
15
+ (\d{5})
16
+ (\d{10}.)
17
+ ([A-Z0-9 ]{6})
18
+ ([\d ]{5})
19
+ ([\d ]{10}.)
20
+ (\d{5})
21
+ (\d{10}.)
22
+ ([\d ]{10}.)
23
+ ([A-Z0-9 ])
24
+ ([A-Z0-9 ])
25
+ (.{16})
26
+ ([A-Z0-9]{4})
27
+ /x).flatten
28
+
29
+ @data = {
30
+ currency: currency.strip,
31
+ stat_code: stat_code.strip,
32
+ types: [type.strip, type2.strip].reject { |x| x == '' },
33
+ rates: [Hotfile::Integer.new(rate).to_i, Hotfile::Integer.new(rate2).to_i].reject(&:zero?),
34
+ amounts: [Hotfile::Integer.new(amount).to_i, Hotfile::Integer.new(amount2).to_i].reject(&:zero?),
35
+ effective_rate: Hotfile::Integer.new(effective_rate).to_i,
36
+ effective_amount: Hotfile::Integer.new(effective_amount).to_i,
37
+ paid_amount: Hotfile::Integer.new(paid).to_i,
38
+ rdi_indicator: rdi_indicator.strip,
39
+ cca_indicator: cca_indicator.strip,
40
+ reserved: reserved.strip
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Tax on Commission Record
6
+ # ! not tested, I hope this code works at all...
7
+ class BKS42 < Record
8
+ def initialize(line)
9
+ super
10
+
11
+ type1, amount1, type2, amount2, type3, amount3, type4, amount4, reserved, currency =
12
+ line.scan(/
13
+ ([A-Z0-9 ]{6})
14
+ ([\d ]{10}.)
15
+ ([A-Z0-9 ]{6})
16
+ ([\d ]{10}.)
17
+ ([A-Z0-9 ]{6})
18
+ ([\d ]{10}.)
19
+ ([A-Z0-9 ]{6})
20
+ ([\d ]{10}.)
21
+ (.{24})
22
+ ([A-Z0-9]{4})
23
+ /x).flatten
24
+
25
+ @data = {
26
+ currency: currency.strip,
27
+ taxes: [
28
+ { type: type1.strip, amount: Hotfile::Integer.new(amount1).to_i },
29
+ { type: type2.strip, amount: Hotfile::Integer.new(amount2).to_i },
30
+ { type: type3.strip, amount: Hotfile::Integer.new(amount3).to_i },
31
+ { type: type4.strip, amount: Hotfile::Integer.new(amount4).to_i }
32
+ ].reject { |x| x[:type] == '' },
33
+ reserved: reserved.strip
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Related Ticket/Document Information Record
6
+ # ! not tested, I hope this code works at all...
7
+ class BKS45 < Record
8
+ def initialize(line)
9
+ super
10
+
11
+ waiver, reason, identifier, date_of_issue, reserved =
12
+ line.scan(/
13
+ (.{14})
14
+ (.{5})
15
+ ([\d ]{4})
16
+ ([\d ]{6})
17
+ (.{67})
18
+ /x).flatten
19
+
20
+ @data = {
21
+ waiver: waiver.strip,
22
+ reason_code: reason.strip,
23
+ identifier: identifier.strip,
24
+ date_of_issue: Hotfile::Date.new(date_of_issue).to_date,
25
+ reserved: reserved.strip
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Qualifying Issue Information for Sales Transactions Record
6
+ class BKS46 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ original_doc_nr, original_location, original_date, original_agent_code, e_r, reserved =
11
+ line.scan(/
12
+ (.{14})
13
+ ([A-Z ]{3})
14
+ ([A-Z0-9 ]{7})
15
+ ([\d ]{8})
16
+ (.{49})
17
+ (.{15})
18
+ /x).flatten
19
+
20
+ @data = {
21
+ original_issue: {
22
+ doc_nr: original_doc_nr.strip,
23
+ location: original_location.strip,
24
+ date: Hotfile::Date.new(original_date).to_date,
25
+ agent_code: original_agent_code.to_i
26
+ },
27
+ endorsements_restrictions: e_r.strip,
28
+ reserved: reserved.strip
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Netting Values Record
6
+ # ! not tested, I hope this code works at all...
7
+ class BKS47 < Record
8
+ def initialize(line)
9
+ super
10
+
11
+ type1, code1, amount1, type2, code2, amount2, type3, code3, amount3, type4, code4, amount4, reserved, currency =
12
+ line.scan(/
13
+ ([A-Z])
14
+ ([A-Z0-9 ]{8})
15
+ (\d{10}.)
16
+ ([A-Z])
17
+ ([A-Z0-9 ]{8})
18
+ (\d{10}.)
19
+ ([A-Z])
20
+ ([A-Z0-9 ]{8})
21
+ (\d{10}.)
22
+ ([A-Z])
23
+ ([A-Z0-9 ]{8})
24
+ (\d{10}.)
25
+ (.{12})
26
+ ([A-Z0-9]{4})
27
+ /x).flatten
28
+
29
+ @data = {
30
+ currency: currency.strip,
31
+ netting_values: [
32
+ { type: type1.strip, code: code1.strip, amount: Hotfile::Integer.new(amount1).to_i },
33
+ { type: type2.strip, code: code2.strip, amount: Hotfile::Integer.new(amount2).to_i },
34
+ { type: type3.strip, code: code3.strip, amount: Hotfile::Integer.new(amount3).to_i },
35
+ { type: type4.strip, code: code4.strip, amount: Hotfile::Integer.new(amount4).to_i }
36
+ ].reject { |x| x[:type] == '' },
37
+ reserved: reserved.strip
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Transaction Header Record
6
+ class BKT06 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ transaction, net_report, record, airline_code, agreement_ref, file_ref, system_id, auth_code, status,
11
+ net_report_method, net_report_calc_type, are_indicator, reserved =
12
+ line.scan(/
13
+ (\d{6})
14
+ ([A-Z0-9 ]{2})
15
+ (\d{3})
16
+ ([A-Z0-9]{3})
17
+ ([A-Z0-9 ]{10})
18
+ ([A-Z0-9 ]{27})
19
+ ([A-Z0-9]{4})
20
+ ([A-Z0-9 ]{14})
21
+ ([A-Z0-9 ])
22
+ ([A-Z0-9 ])
23
+ ([A-Z0-9 ])
24
+ ([A-Z0-9 ])
25
+ (.{50})
26
+ /x).flatten
27
+
28
+ @data = {
29
+ transaction: transaction.to_i,
30
+ net_reporting: {
31
+ indicator: net_report.strip,
32
+ method: net_report_method.strip,
33
+ calc_type: net_report_calc_type.strip
34
+ },
35
+ record: record.to_i,
36
+ airline_code: airline_code,
37
+ agreement_ref: agreement_ref.strip,
38
+ file_ref: file_ref.strip,
39
+ system_id: system_id,
40
+ auth_code: auth_code.strip,
41
+ input_status: status.strip,
42
+ are_indicator: are_indicator.strip,
43
+ reserved: reserved.strip
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## (Reporting Agent) Office Header Record
6
+ class BOH03 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ agent_code, remittance_end_date, currency, location_id, reserved =
11
+ line.scan(/
12
+ (\d{8})
13
+ (\d{6})
14
+ ([A-Z0-9]{4})
15
+ ([A-Z0-9 ]{3})
16
+ (.{102})
17
+ /x).flatten
18
+
19
+ @data = {
20
+ currency: currency.strip,
21
+ agent_code: agent_code.to_i,
22
+ remittance_end_date: Hotfile::Date.new(remittance_end_date).to_date,
23
+ location_id: location_id.strip,
24
+ reserved: reserved.strip
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Office Subtotals per Transaction Code and Currency Type Record
6
+ class BOT93 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ agent_code, remittance_end_date, gross, remittance, commission, tax, transaction_code, commission_tax, reserved,
11
+ currency =
12
+ line.scan(/
13
+ (\d{8})
14
+ (\d{6})
15
+ (\d{14}.)
16
+ (\d{14}.)
17
+ (\d{14}.)
18
+ (\d{14}.)
19
+ ([A-Z0-9 ]{4})
20
+ (\d{14}.)
21
+ (.{26})
22
+ ([A-Z0-9]{4})
23
+ /x).flatten
24
+
25
+ @data = {
26
+ currency: currency.strip,
27
+ transaction_code: transaction_code.strip,
28
+ agent_code: agent_code.to_i,
29
+ remittance_end_date: Hotfile::Date.new(remittance_end_date).to_date,
30
+ gross: Hotfile::Integer.new(gross).to_i,
31
+ remittance: Hotfile::Integer.new(remittance).to_i,
32
+ commission: Hotfile::Integer.new(commission).to_i,
33
+ tax: Hotfile::Integer.new(tax).to_i,
34
+ commission_tax: Hotfile::Integer.new(commission_tax).to_i,
35
+ reserved: reserved.strip
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Office Totals per Currency Type Record
6
+ class BOT94 < Record
7
+ def initialize(line)
8
+ super
9
+
10
+ agent_code, remittance_end_date, gross, remittance, commission, tax, commission_tax, reserved, currency =
11
+ line.scan(/
12
+ (\d{8})
13
+ (\d{6})
14
+ (\d{14}.)
15
+ (\d{14}.)
16
+ (\d{14}.)
17
+ (\d{14}.)
18
+ (\d{14}.)
19
+ (.{30})
20
+ ([A-Z0-9]{4})
21
+ /x).flatten
22
+
23
+ @data = {
24
+ currency: currency.strip,
25
+ agent_code: agent_code.to_i,
26
+ remittance_end_date: Hotfile::Date.new(remittance_end_date).to_date,
27
+ gross: Hotfile::Integer.new(gross).to_i,
28
+ remittance: Hotfile::Integer.new(remittance).to_i,
29
+ commission: Hotfile::Integer.new(commission).to_i,
30
+ tax: Hotfile::Integer.new(tax).to_i,
31
+ commission_tax: Hotfile::Integer.new(commission_tax).to_i,
32
+ reserved: reserved.strip
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ class Record
5
+ ## Records parent class
6
+ class Record
7
+ def initialize(line)
8
+ raise 'invalid line' if line.nil? || line.empty?
9
+ end
10
+
11
+ def parse
12
+ @data
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ ## Metaclass for loading all parser modules
5
+ class Record
6
+ require 'hotfile/record/record'
7
+ require 'hotfile/record/BAR64'
8
+ require 'hotfile/record/BAR65'
9
+ require 'hotfile/record/BAR66'
10
+ require 'hotfile/record/BAR67'
11
+ require 'hotfile/record/BCC82'
12
+ require 'hotfile/record/BCX83'
13
+ require 'hotfile/record/BCH02'
14
+ require 'hotfile/record/BCT95'
15
+ require 'hotfile/record/BFH01'
16
+ require 'hotfile/record/BFT99'
17
+ require 'hotfile/record/BKF81'
18
+ require 'hotfile/record/BKI61'
19
+ require 'hotfile/record/BKI62'
20
+ require 'hotfile/record/BKI63'
21
+ require 'hotfile/record/BKP84'
22
+ require 'hotfile/record/BKS24'
23
+ require 'hotfile/record/BKS30'
24
+ require 'hotfile/record/BKS31'
25
+ require 'hotfile/record/BKS39'
26
+ require 'hotfile/record/BKS42'
27
+ require 'hotfile/record/BKS45'
28
+ require 'hotfile/record/BKS46'
29
+ require 'hotfile/record/BKS47'
30
+ require 'hotfile/record/BKT06'
31
+ require 'hotfile/record/BOH03'
32
+ require 'hotfile/record/BOT93'
33
+ require 'hotfile/record/BOT94'
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ ## String data type
5
+ class String
6
+ def initialize(string)
7
+ @raw_value = string
8
+ end
9
+
10
+ def to_s
11
+ @raw_value.strip
12
+ end
13
+
14
+ def to_i
15
+ Hotfile::Integer.new(@raw_value).to_i
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hotfile
4
+ ## Gathers transaction information from parsed data.
5
+ module Transaction
6
+ def transactions
7
+ record_groups = parse[:records].select { |x| x[:transaction].to_i.positive? }.group_by { |x| x[:transaction] }
8
+ record_groups.each_value.map { |x| summarize_transaction(x) }
9
+ end
10
+
11
+ def summarize_transaction(records)
12
+ types = records.group_by { |x| x[:code][:number] }
13
+ {
14
+ transaction: records.first[:transaction],
15
+ payment: {
16
+ total: types[30].map { |x| x[:data][:amounts][:total] }.sum,
17
+ net: types[30].map { |x| x[:data][:amounts][:net] }.sum,
18
+ commissionable: types[30].map { |x| x[:data][:amounts][:commissionable] }.sum
19
+ },
20
+ flight_schedule: flight_schedule(types[63]),
21
+ passengers: passengers(types[65]),
22
+ pnr: find_pnr(types[24])
23
+ }
24
+ end
25
+
26
+ def flight_schedule(flight_records)
27
+ flight_records.map do |record|
28
+ d = record[:data]
29
+ {
30
+ carrier: d[:carrier],
31
+ flight_number: d[:flight_number],
32
+ departure: d[:departure],
33
+ arrival: d[:arrival],
34
+ baggage: d[:baggage],
35
+ status: d[:booking][:status]
36
+ }
37
+ end
38
+ end
39
+
40
+ def passengers(passenger_records)
41
+ passenger_records.map do |record|
42
+ full_name = record[:data][:name]
43
+ surname, first_name = full_name.split('/')
44
+ gender = first_name.end_with?('MS') || first_name.end_with?('MRS') ? :f : :m
45
+ first_name = first_name.scan(/(.*)(MS|MR|MRS)$/).flatten.first
46
+ type = case record[:data][:type]
47
+ when 'ADT' then :adult
48
+ when 'CHD' then :child
49
+ when 'INF' then :infant
50
+ else :unknown
51
+ end
52
+ {
53
+ first_name: first_name,
54
+ surname: surname,
55
+ gender: gender,
56
+ info: record[:data][:passenger_data],
57
+ date_of_birth: record[:data][:date_of_birth],
58
+ type: type
59
+ }
60
+ end
61
+ end
62
+
63
+ def find_pnr(records)
64
+ records.first&.dig(:data, :pnr)&.split('/')&.first
65
+ end
66
+ end
67
+ end
data/lib/hotfile.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ ## Main class
4
+ class Hotfile
5
+ require 'hotfile/date'
6
+ require 'hotfile/integer'
7
+ require 'hotfile/string'
8
+
9
+ require 'hotfile/parser'
10
+ require 'hotfile/record'
11
+ require 'hotfile/transaction'
12
+
13
+ include Hotfile::Parser
14
+ include Hotfile::Transaction
15
+
16
+ LINE_LENGTH = 136
17
+
18
+ def initialize(file = nil)
19
+ @file = if file.respond_to? :read
20
+ file.read
21
+ elsif file
22
+ File.read file
23
+ end
24
+ @file = @file.delete("\r") if @file
25
+ end
26
+
27
+ def size
28
+ @file&.size
29
+ end
30
+ alias length size
31
+ alias count size
32
+
33
+ def records
34
+ @file&.lines(chomp: true)&.count
35
+ end
36
+ alias lines records
37
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hotfile'