ivymeter 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bc2746075be195abde3b585c1d422494b5abcec9091c794ff885680aa8b83336
4
+ data.tar.gz: 6a108ab4252785296d6cac5f8462deadd422f0a81acba0691fa2590d43a2e1a0
5
+ SHA512:
6
+ metadata.gz: bc1a26fd3db8906baaed1a249a4fb901857cdae1f5e2eb65c2b384f876a429e777a7b89a2299e357a1811e1212db065583a09766fb7e6fe6e2715f4e47e275ce
7
+ data.tar.gz: c3bb8f7e24844a3daa5b173e83b977bda541b9b54f02dde015a8e01f0c096164b747fd55dbc68aa6be3f9fe50fcd23f5351588208126c421afc2e46c1256e077
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: single_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: single_quotes
9
+
10
+ Style/PercentLiteralDelimiters:
11
+ PreferredDelimiters:
12
+ default: '()'
13
+ '%i': '()'
14
+
15
+ Layout/LineLength:
16
+ Enabled: false
17
+
18
+ Metrics:
19
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # changelog
2
+ ### v0.1.0 – 2025-04-14
3
+ initial release! should be maybe up-to-date-ish?
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # IVyMeTeR
2
+
3
+ a lil' gem for handling USPS IV-MTR event data
4
+
5
+ mainly focused on piece data bc i don't really do capital-C Commercial mail, but the other stuff might should work too?
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```bash
12
+ bundle add ivymeter
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```bash
18
+ gem install ivymeter
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ IvyMeter::Event::{Container,Tray,Bundle,Piece}Event
25
+ .from_json(<hash from USPS webhook>) or
26
+ .from_csv(<hash from USPS delimited file>)
27
+ ```
28
+
29
+ ## Development
30
+
31
+ `bundle exec exe/generate_operation_codes <xlsx>` whenever they come out with a new [opcode list](https://postalpro.usps.com/informedvisibility/OperationCodesList)
32
+
33
+ modify the `prop` statements in the event classes when a new version of the [external data dictionary](https://postalpro.usps.com/node/6307) is released
34
+ ## Contributing
35
+
36
+ pull requests welcome! bug reports less welcome...
37
+
38
+ this project is housed on GitHub at [24c02/ivymeter](https://github.com/24c02/ivymeter).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i(spec rubocop)
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'xsv'
5
+ require 'pathname'
6
+
7
+ module IvyMeter
8
+ class GenerateOperationCodes
9
+ SHEET_CONFIGS = {
10
+ 'OpCodes' => 1,
11
+ 'Unexpected OpCodes' => 2
12
+ }.freeze
13
+
14
+ def self.from_xlsx(xlsx_file)
15
+ workbook = Xsv.open(xlsx_file)
16
+ results = {}
17
+
18
+ SHEET_CONFIGS.each do |sheet_name, config|
19
+ sheet = workbook.sheets.find { |s| s.name == sheet_name }
20
+ next unless sheet
21
+
22
+ setup_sheet(sheet, sheet[config])
23
+ results[sheet_name] = process_sheet(sheet, config)
24
+ end
25
+
26
+ [results['OpCodes'] || {}, results['Unexpected OpCodes'] || {}]
27
+ end
28
+
29
+ def self.setup_sheet(sheet, headers)
30
+ sheet.instance_variable_set(:@headers, headers)
31
+ sheet.instance_variable_set(:@mode, :hash)
32
+ end
33
+
34
+ def self.process_sheet(sheet, config)
35
+ sheet.each_with_index.each_with_object({}) do |(row, index), codes|
36
+ # next if index <= config
37
+
38
+ code = row['Operation Code'].to_s
39
+ next if code.empty?
40
+ code = code.rjust(3, '0') if code.match?(/^\d+$/)
41
+
42
+ codes[code] = build_code_attributes(row)
43
+ end
44
+ end
45
+
46
+ def self.build_code_attributes(row)
47
+ {
48
+ code: row['Operation Code'].to_s.rjust(3, '0'),
49
+ machine_type: row['Machine Type'].to_s,
50
+ equipment_description: row['Equipment Description'].to_s,
51
+ stop_the_clock_scan: row['Stop-the-Clock Scan?'].to_s == 'Yes',
52
+ clearance_cutoff_time: row['Clearance Cutoff Time'].to_s,
53
+ handling_event_type: row['Piece'].to_s,
54
+ piece_mail_type: row['Piece Mail Type'].to_s,
55
+ mail_level: row['Mail Level'].to_s,
56
+ mail_phase: row['Mail Phase'].to_s,
57
+ process_description: row['Process Description'].to_s,
58
+ available_date: row['Available Date'].to_s,
59
+ last_updated: row['Last Updated'].to_s
60
+ }
61
+ end
62
+
63
+ def self.write_ruby_code(opcodes, unexpected_codes, output_file)
64
+ puts "writing #{opcodes.size} operation codes and #{unexpected_codes.size} unexpected codes..."
65
+
66
+ File.open(output_file, 'w') do |f|
67
+ f.write(<<~RUBY
68
+ # dis file is auto-generated from the xlsx file
69
+ # pwease do not edit it directly
70
+ module IvyMeter
71
+ class OperationCode
72
+
73
+ #{generate_constant('OPCODES', opcodes)}
74
+
75
+ #{generate_constant('UNEXPECTED_CODES', unexpected_codes)}
76
+ end
77
+ end
78
+ RUBY
79
+ )
80
+ end
81
+ end
82
+
83
+ def self.generate_constant(constant_name, codes)
84
+ sorted_codes = codes.sort_by do |code, _|
85
+ Integer(code)
86
+ rescue StandardError
87
+ 9999
88
+ end
89
+
90
+ code_entries = sorted_codes.map do |code, attributes|
91
+ attributes_str = attributes.map do |key, value|
92
+ " #{key}: #{value.inspect},"
93
+ end.join("\n")
94
+
95
+ <<RUBY
96
+ "#{code}" => new(
97
+ #{attributes_str}
98
+ ),
99
+ RUBY
100
+ end.join("\n")
101
+
102
+ <<RUBY
103
+ #{constant_name} = {
104
+ #{code_entries}
105
+ }.freeze
106
+ RUBY
107
+ end
108
+ end
109
+ end
110
+
111
+ if __FILE__ == $PROGRAM_NAME
112
+ if ARGV.length != 1
113
+ puts "Usage: #{$PROGRAM_NAME} <xlsx_file>"
114
+ exit 1
115
+ end
116
+
117
+ xlsx_file = ARGV[0]
118
+ opcodes, unexpected_codes = IvyMeter::GenerateOperationCodes.from_xlsx(xlsx_file)
119
+ output_file = Pathname.new(__dir__).join('..', 'lib', 'ivymeter', 'opcodes.rb')
120
+ IvyMeter::GenerateOperationCodes.write_ruby_code(opcodes, unexpected_codes, output_file)
121
+ puts "Generated operation codes in #{output_file}"
122
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'date'
5
+ require_relative '../prop'
6
+ require_relative '../opcode'
7
+
8
+ module IvyMeter
9
+ class Event
10
+ include Prop
11
+
12
+ class << self
13
+ def inherited(subclass)
14
+ subclass.instance_variable_set(:@property_mappings, property_mappings.dup)
15
+ end
16
+
17
+ def prop(name, json_name, csv_name, type: nil)
18
+ attr_accessor name
19
+
20
+ @property_mappings ||= {}
21
+ @property_mappings[name] = { json: json_name, csv: csv_name, type: type }
22
+ end
23
+
24
+ def property_mappings
25
+ @property_mappings || {}
26
+ end
27
+
28
+ def from_json(json_data)
29
+ event = new
30
+ property_mappings.each do |attr_name, mappings|
31
+ value = json_data[mappings[:json]]
32
+ next if value.nil?
33
+
34
+ event.send("#{attr_name}=", convert_value(value, mappings[:type]))
35
+ end
36
+ event
37
+ end
38
+
39
+ def from_csv(row)
40
+ event = new
41
+ property_mappings.each do |attr_name, mappings|
42
+ value = row[mappings[:csv]]
43
+ next if value.nil?
44
+
45
+ event.send("#{attr_name}=", convert_value(value, mappings[:type]))
46
+ end
47
+ event
48
+ end
49
+
50
+ private
51
+
52
+ def convert_value(value, type)
53
+ case type
54
+ when :date
55
+ Date.parse(value)
56
+ when :datetime
57
+ DateTime.parse(value)
58
+ else
59
+ value
60
+ end
61
+ end
62
+ end
63
+
64
+ def to_json(*_args)
65
+ result = {}
66
+ self.class.property_mappings.each do |attr_name, mappings|
67
+ value = send(attr_name)
68
+ if value.is_a?(DateTime) && mappings[:type] == :datetime
69
+ value = value.strftime('%Y-%m-%d %H:%M:%S%z')
70
+ elsif value.is_a?(Date) && mappings[:type] == :date
71
+ value = value.strftime('%Y-%m-%d')
72
+ end
73
+ result[mappings[:json]] = value
74
+ end
75
+ result.compact.to_json
76
+ end
77
+
78
+ # Common eDoc properties
79
+ prop :edoc_job_id, 'edocJobId', 'eDoc Job ID'
80
+ prop :edoc_mailing_group_id, 'edocMailingGroupId', 'eDoc Mailing Group ID'
81
+ prop :edoc_submitter_crid, 'edocSubmitterCrid', 'eDoc Submitter CRID'
82
+
83
+ # Common handling event properties
84
+ prop :scan_datetime, 'scanDatetime', 'Scan DateTime', type: :datetime
85
+ prop :scan_event_code, 'scanEventCode', 'Scan Event Code'
86
+ prop :scan_facility_city, 'scanFacilityCity', 'Scan Facility City'
87
+ prop :scan_facility_name, 'scanFacilityName', 'Scan Facility Name'
88
+ prop :scan_facility_state, 'scanFacilityState', 'Scan Facility State'
89
+ prop :scan_facility_zip, 'scanFacilityZip', 'Scan Facility ZIP'
90
+ prop :scan_locale_key, 'scanLocaleKey', 'Scan Locale Key'
91
+ prop :handling_event_type, 'handlingEventType', 'Handling Event Type'
92
+ prop :handling_event_type_description, 'handlingEventTypeDescription', 'Handling Event Type Description'
93
+
94
+ # Common mail properties
95
+ prop :mail_class_description, 'mailClassDescription', 'Mail Class Description'
96
+ prop :mail_shape_description, 'mailShapeDescription', 'Mail Shape Description'
97
+
98
+ # Common scanner properties
99
+ prop :scanner_type, 'scannerType', 'Scanner Type'
100
+ prop :machine_name, 'machineName', 'Machine Name'
101
+ prop :device_id, 'deviceId', 'Device ID'
102
+
103
+ # Common start the clock properties
104
+ prop :start_the_clock_date, 'startTheClockDate', 'Start the Clock Date', type: :date
105
+ prop :start_the_clock_facility_address, 'startTheClockFacilityAddress', 'Start the Clock Facility Address'
106
+ prop :start_the_clock_facility_city, 'startTheClockFacilityCity', 'Start the Clock Facility City'
107
+ prop :start_the_clock_facility_locale_key, 'startTheClockFacilityLocaleKey', 'Start the Clock Facility Locale Key'
108
+ prop :start_the_clock_facility_name, 'startTheClockFacilityName', 'Start the Clock Facility Name'
109
+ prop :start_the_clock_facility_state, 'startTheClockFacilityState', 'Start the Clock Facility State'
110
+ prop :start_the_clock_facility_zip, 'startTheClockFacilityZip', 'Start the Clock Facility ZIP'
111
+
112
+ # Common recipient CRID properties
113
+ prop :recipient_crid_of_mid_on_piece, 'recipientCridOfMidOnPiece', 'Recipient CRID of MID on Piece'
114
+ prop :recipient_crid_of_mid_on_piece_delegator, 'recipientCridOfMidOnPieceDelegator',
115
+ 'Recipient CRID of MID on Piece Delegator'
116
+ prop :recipient_mail_owner_crid, 'recipientMailOwnerCrid', 'Recipient Mail Owner CRID'
117
+ prop :recipient_mail_owner_delegator_crid, 'recipientMailOwnerDelegatorCrid', 'Recipient Mail Owner Delegator CRID'
118
+ prop :recipient_routing_code_authorized_crid, 'recipientRoutingCodeAuthorizedCrid',
119
+ 'Recipient Routing Code Authorized CRID'
120
+
121
+ # Common operational status
122
+ prop :operational_status, 'operationalStatus', 'Operational Status'
123
+
124
+ # Common handling event properties
125
+ prop :handling_event_datetime, 'handlingEventDateTime', 'Handling Event DateTime'
126
+ prop :handling_event_facility_city, 'handlingEventFacilityCity', 'Handling Event Facility City'
127
+ prop :handling_event_facility_name, 'handlingEventFacilityName', 'Handling Event Facility Name'
128
+ prop :handling_event_facility_state, 'handlingEventFacilityState', 'Handling Event Facility State'
129
+ prop :handling_event_facility_zip, 'handlingEventFacilityZip', 'Handling Event Facility ZIP Code'
130
+ prop :handling_event_locale_key, 'handlingEventLocaleKey', 'Handling Event Locale Key'
131
+ prop :handling_event_scan_state, 'handlingEventScanState', 'Handling Event Scan State'
132
+
133
+ # Common eDoc properties
134
+ prop :edoc_container_id, 'edocContainerId', 'eDoc Container ID'
135
+ prop :edoc_csa_agreement_id, 'edocCsaAgreementId', 'eDoc CSA Agreement ID'
136
+ prop :edoc_customer_group_id, 'edocCustomerGroupId', 'eDoc Customer Group ID'
137
+ prop :edoc_parent_container_id, 'edocParentContainerId', 'eDoc Parent Container ID'
138
+ prop :edoc_sibling_container_id, 'edocSiblingContainerId', 'eDoc Sibling Container ID'
139
+ prop :edoc_user_license_code, 'edocUserLicenseCode', 'eDoc User License Code'
140
+
141
+ # Common recipient CRID properties
142
+ prop :recipient_edoc_submitter_crid, 'recipientEdocSubmitterCrid', 'Recipient eDoc Submitter CRID'
143
+ prop :recipient_edoc_submitter_delegator_crid, 'recipientEdocSubmitterDelegatorCrid',
144
+ 'Recipient eDoc Submitter Delegator CRID'
145
+ prop :recipient_mail_preparer_crid, 'recipientMailPreparerCrid', 'Recipient Mail Preparer CRID'
146
+ prop :recipient_mail_preparer_delegator_crid, 'recipientMailPreparerDelegatorCrid',
147
+ 'Recipient Mail Preparer Delegator CRID'
148
+
149
+ # Common mail properties
150
+ prop :piece_count, 'pieceCount', 'Piece Count'
151
+
152
+ def actual?
153
+ handling_event_type == 'A'
154
+ end
155
+
156
+ def assumed_actual?
157
+ handling_event_type == 'AA'
158
+ end
159
+
160
+ def logical?
161
+ handling_event_type == 'L'
162
+ end
163
+
164
+ def assumed_logical?
165
+ handling_event_type == 'AL'
166
+ end
167
+
168
+ def assumed?
169
+ assumed_actual? || assumed_logical?
170
+ end
171
+
172
+ def opcode
173
+ @opcode ||= OpCode.find(scan_event_code)
174
+ end
175
+
176
+ def stop_the_clock_scan?
177
+ opcode&.stop_the_clock_scan?
178
+ end
179
+
180
+ def unexpected_opcode?
181
+ opcode&.unexpected?
182
+ end
183
+
184
+ def clearance_cutoff_time
185
+ opcode&.clearance_cutoff_time
186
+ end
187
+
188
+ def mail_phase
189
+ opcode&.mail_phase
190
+ end
191
+
192
+ def process_description
193
+ opcode&.process_description
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module IvyMeter
6
+ class Event
7
+ class BundleEvent < Event
8
+ # Bundle-specific properties
9
+ prop :bundle_id, 'bundleId', 'Bundle ID'
10
+ prop :edoc_container_id, 'edocContainerId', 'eDoc Container ID'
11
+ prop :edoc_container_zip, 'edocContainerZip', 'eDoc Container ZIP'
12
+ prop :edoc_csa_agreement_id, 'edocCsaAgreementId', 'eDoc CSA Agreement ID'
13
+ prop :edoc_customer_group_id, 'edocCustomerGroupId', 'eDoc Customer Group ID'
14
+ prop :edoc_parent_container_id, 'edocParentContainerId', 'eDoc Parent Container ID'
15
+ prop :edoc_sibling_container_id, 'edocSiblingContainerId', 'eDoc Sibling Container ID'
16
+ prop :edoc_user_license_code, 'edocUserLicenseCode', 'eDoc User License Code'
17
+ prop :piece_count, 'pieceCount', 'Piece Count'
18
+
19
+ # Bundle IMb properties
20
+ prop :imb, 'imb', 'IMb'
21
+ prop :imb_mid, 'imbMid', 'IMb MID'
22
+ prop :imb_routing_code, 'imbRoutingCode', 'IMb Routing Code'
23
+ prop :imb_serial_number, 'imbSerialNumber', 'IMb Serial Number'
24
+ prop :imb_stid, 'imbStid', 'IMb STID'
25
+ prop :imb_tracking_code, 'imbTrackingCode', 'IMb Tracking Code'
26
+
27
+ # Bundle-specific recipient CRID properties
28
+ prop :recipient_crid_of_mid_on_bundle, 'recipientCridOfMidOnBundle', 'Recipient CRID of MID on Bundle'
29
+ prop :recipient_crid_of_mid_on_bundle_delegator, 'recipientCridOfMidOnBundleDelegator',
30
+ 'Recipient CRID of MID on Bundle Delegator'
31
+
32
+ # Postage statement properties
33
+ prop :postage_statement_finalization_datetime, 'postageStatementFinalizationDatetime',
34
+ 'Postage Statement Finalization DateTime', type: :datetime
35
+ prop :postage_statement_finalization_facility_name, 'postageStatementFinalizationFacilityName',
36
+ 'Postage Statement Finalization Facility Name'
37
+
38
+ # Routing code
39
+ prop :routing_code_imb_matching_portion, 'routingCodeImbMatchingPortion', 'Routing Code (IMb Matching Portion)'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module IvyMeter
6
+ class Event
7
+ class ContainerEvent < Event
8
+ # Container-specific properties
9
+ prop :container_id, 'containerId', 'Container ID'
10
+ prop :bundle_count, 'bundleCount', 'Bundle Count'
11
+ prop :edoc_container_id, 'edocContainerId', 'eDoc Container ID'
12
+ prop :edoc_container_zip, 'edocContainerZip', 'eDoc Container ZIP'
13
+ prop :edoc_csa_agreement_id, 'edocCsaAgreementId', 'eDoc CSA Agreement ID'
14
+ prop :edoc_customer_group_id, 'edocCustomerGroupId', 'eDoc Customer Group ID'
15
+ prop :edoc_parent_container_id, 'edocParentContainerId', 'eDoc Parent Container ID'
16
+ prop :edoc_sibling_container_id, 'edocSiblingContainerId', 'eDoc Sibling Container ID'
17
+ prop :edoc_user_license_code, 'edocUserLicenseCode', 'eDoc User License Code'
18
+
19
+ # FAST appointment properties
20
+ prop :fast_appointment_scheduled_datetime, 'fastAppointmentScheduledDatetime',
21
+ 'FAST Appointment Scheduled DateTime', type: :datetime
22
+ prop :fast_appointment_unload_end_time, 'fastAppointmentUnloadEndTime', 'FAST Appointment Unload End Time',
23
+ type: :datetime
24
+ prop :fast_appointment_unload_start_time, 'fastAppointmentUnloadStartTime', 'FAST Appointment Unload Start Time',
25
+ type: :datetime
26
+
27
+ # Count properties
28
+ prop :piece_count, 'pieceCount', 'Piece Count'
29
+ prop :tray_count, 'trayCount', 'Tray Count'
30
+
31
+ # Container IMb properties
32
+ prop :imcb, 'imcb', 'IMcb'
33
+ prop :imcb_mid, 'imcbMid', 'IMcb MID'
34
+ prop :imcb_routing_code, 'imcbRoutingCode', 'IMcb Routing Code'
35
+ prop :imcb_serial_number, 'imcbSerialNumber', 'IMcb Serial Number'
36
+ prop :imcb_stid, 'imcbStid', 'IMcb STID'
37
+ prop :imcb_tracking_code, 'imcbTrackingCode', 'IMcb Tracking Code'
38
+
39
+ # Container-specific recipient CRID properties
40
+ prop :recipient_crid_of_mid_on_container, 'recipientCridOfMidOnContainer', 'Recipient CRID of MID on Container'
41
+ prop :recipient_crid_of_mid_on_container_delegator, 'recipientCridOfMidOnContainerDelegator',
42
+ 'Recipient CRID of MID on Container Delegator'
43
+ prop :recipient_fast_scheduler_crid, 'recipientFASTSchedulerCrid', 'Recipient FAST Scheduler CRID'
44
+ prop :recipient_fast_scheduler_delegator_crid, 'recipientFASTSchedulerDelegatorCrid',
45
+ 'Recipient FAST Scheduler Delegator CRID'
46
+
47
+ # Parent references
48
+ prop :parent_tray_edoc_container_id, 'parentTrayEdocContainerId', 'Parent Tray eDoc Container ID'
49
+ prop :parent_tray_edoc_imtb, 'parentTrayEdocImtb', 'Parent Tray eDoc IMtb'
50
+
51
+ # Postage statement properties
52
+ prop :postage_statement_finalization_datetime, 'postageStatementFinalizationDatetime',
53
+ 'Postage Statement Finalization DateTime', type: :datetime
54
+ prop :postage_statement_finalization_facility_name, 'postageStatementFinalizationFacilityName',
55
+ 'Postage Statement Finalization Facility Name'
56
+
57
+ # Routing code
58
+ prop :routing_code_imcb_matching_portion, 'routingCodeImcbMatchingPortion', 'Routing Code (IMcb Matching Portion)'
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module IvyMeter
6
+ class Event
7
+ class PieceEvent < Event
8
+ # Delivery date properties
9
+ prop :anticipated_delivery_date, 'anticipatedDeliveryDate', 'Anticipated Delivery Date', type: :date
10
+ prop :expected_delivery_date, 'expectedDeliveryDate', 'Expected Delivery Date', type: :date
11
+ prop :predicted_delivery_date, 'predictedDeliveryDate', 'Predicted Delivery Date', type: :date
12
+
13
+ # IMb properties
14
+ prop :imb, 'imb', 'IMb'
15
+ prop :imb_mid, 'imbMid', 'IMb MID'
16
+ prop :imb_routing_code, 'imbRoutingCode', 'IMb Routing Code'
17
+ prop :imb_serial_number, 'imbSerialNumber', 'IMb Serial Number'
18
+ prop :imb_stid, 'imbStid', 'IMb STID'
19
+ prop :imb_tracking_code, 'imbTrackingCode', 'IMb Tracking Code'
20
+
21
+ # IMpb properties
22
+ prop :impb, 'impb', 'IMpb'
23
+
24
+ # LDE properties
25
+ prop :lde_delivery_mode, 'ldeDeliveryMode', 'LDE Delivery Mode'
26
+ prop :lde_inventory_method, 'ldeInventoryMethod', 'LDE Inventory Method'
27
+ prop :lde_trigger_method, 'ldeTriggerMethod', 'LDE Trigger Method'
28
+
29
+ # Mail phase
30
+ prop :mail_phase, 'mailPhase', 'Mail Phase'
31
+
32
+ # Operational status
33
+ prop :operational_status, 'operationalStatus', 'Operational Status'
34
+
35
+ # Parent references
36
+ prop :parent_container_edoc_container_id, 'parentContainerEdocContainerId', 'Parent Container eDoc Container ID'
37
+ prop :parent_container_edoc_imtb, 'parentContainerEdocImtb', 'Parent Container eDoc IMcb'
38
+ prop :parent_tray_edoc_container_id, 'parentTrayEdocContainerId', 'Parent Tray eDoc Container ID'
39
+ prop :parent_tray_edoc_imtb, 'parentTrayEdocImtb', 'Parent Tray eDoc IMtb'
40
+
41
+ # Piece identifier
42
+ prop :piece_id, 'pieceId', 'Piece ID'
43
+
44
+ # Postage statement properties
45
+ prop :postage_statement_finalization_datetime, 'postageStatementFinalizationDatetime',
46
+ 'Postage Statement Finalization DateTime', type: :datetime
47
+ prop :postage_statement_finalization_facility_name, 'postageStatementFinalizationFacilityName',
48
+ 'Postage Statement Finalization Facility Name'
49
+
50
+ # Routing code
51
+ prop :routing_code_imb_matching_portion, 'routingCodeImbMatchingPortion', 'Routing Code (IMb Matching Portion)'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module IvyMeter
6
+ class Event
7
+ class TrayEvent < Event
8
+ # Tray-specific properties
9
+ prop :tray_id, 'trayId', 'Tray ID'
10
+ prop :edoc_container_id, 'edocContainerId', 'eDoc Container ID'
11
+ prop :edoc_container_zip, 'edocContainerZip', 'eDoc Container ZIP'
12
+ prop :edoc_csa_agreement_id, 'edocCsaAgreementId', 'eDoc CSA Agreement ID'
13
+ prop :edoc_customer_group_id, 'edocCustomerGroupId', 'eDoc Customer Group ID'
14
+ prop :edoc_parent_container_id, 'edocParentContainerId', 'eDoc Parent Container ID'
15
+ prop :edoc_sibling_container_id, 'edocSiblingContainerId', 'eDoc Sibling Container ID'
16
+ prop :edoc_user_license_code, 'edocUserLicenseCode', 'eDoc User License Code'
17
+ prop :piece_count, 'pieceCount', 'Piece Count'
18
+
19
+ # Tray IMb properties
20
+ prop :imtb, 'imtb', 'IMtb'
21
+ prop :imtb_mid, 'imtbMid', 'IMtb MID'
22
+ prop :imtb_routing_code, 'imtbRoutingCode', 'IMtb Routing Code'
23
+ prop :imtb_serial_number, 'imtbSerialNumber', 'IMtb Serial Number'
24
+ prop :imtb_stid, 'imtbStid', 'IMtb STID'
25
+ prop :imtb_tracking_code, 'imtbTrackingCode', 'IMtb Tracking Code'
26
+
27
+ # Tray-specific recipient CRID properties
28
+ prop :recipient_crid_of_mid_on_tray, 'recipientCridOfMidOnTray', 'Recipient CRID of MID on Tray'
29
+ prop :recipient_crid_of_mid_on_tray_delegator, 'recipientCridOfMidOnTrayDelegator',
30
+ 'Recipient CRID of MID on Tray Delegator'
31
+
32
+ # Parent references
33
+ prop :parent_bundle_edoc_container_id, 'parentBundleEdocContainerId', 'Parent Bundle eDoc Container ID'
34
+ prop :parent_bundle_edoc_imb, 'parentBundleEdocImb', 'Parent Bundle eDoc IMb'
35
+
36
+ # Postage statement properties
37
+ prop :postage_statement_finalization_datetime, 'postageStatementFinalizationDatetime',
38
+ 'Postage Statement Finalization DateTime', type: :datetime
39
+ prop :postage_statement_finalization_facility_name, 'postageStatementFinalizationFacilityName',
40
+ 'Postage Statement Finalization Facility Name'
41
+
42
+ # Routing code
43
+ prop :routing_code_imtb_matching_portion, 'routingCodeImtbMatchingPortion', 'Routing Code (IMtb Matching Portion)'
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IvyMeter
4
+ class OpCode
5
+ attr_reader :machine_type, :code, :equipment_description, :stop_the_clock_scan,
6
+ :clearance_cutoff_time, :handling_event_type, :piece_mail_type, :mail_level,
7
+ :mail_phase, :process_description, :available_date, :last_updated
8
+
9
+ def initialize(attributes = {})
10
+ @machine_type = attributes[:machine_type]
11
+ @code = attributes[:code]
12
+ @equipment_description = attributes[:equipment_description]
13
+ @stop_the_clock_scan = attributes[:stop_the_clock_scan]
14
+ @clearance_cutoff_time = attributes[:clearance_cutoff_time]
15
+ @handling_event_type = attributes[:handling_event_type]
16
+ @piece_mail_type = attributes[:piece_mail_type]
17
+ @mail_level = attributes[:mail_level]
18
+ @mail_phase = attributes[:mail_phase]
19
+ @process_description = attributes[:process_description]
20
+ @available_date = attributes[:available_date]
21
+ @last_updated = attributes[:last_updated]
22
+ end
23
+
24
+ def self.find(code)
25
+ code = code.to_s
26
+ OPCODES[code] || UNEXPECTED_CODES[code]
27
+ end
28
+
29
+ def unexpected?
30
+ UNEXPECTED_CODES.key?(code)
31
+ end
32
+
33
+ def stop_the_clock_scan?
34
+ !!stop_the_clock_scan
35
+ end
36
+ end
37
+ end