Packages

This library follows the METRO 2 ® data reporting format, which is a data reporting format for consumer credit account data furnishers.

Current section

3 Versions

Jump to

Compare versions

12 files changed
+784 additions
-351 deletions
  @@ -12,27 +12,45 @@ It contains structs for
12 12
13 13 ```elixir
14 14 def deps do
15 - [{:metro_2, "~> 0.1.0"}]
15 + [{:metro_2, "~> 0.2.0"}]
16 16 end
17 17 ```
18 18
19 + ## Demo
20 +
21 + Try the interactive demo to see the library in action with realistic credit reporting data:
22 +
23 + ```bash
24 + mix run demo.exs
25 + ```
26 +
27 + The demo showcases:
28 + - **Character Validation**: Proper validation for names (with dashes), addresses (with dots/slashes), and regular fields
29 + - **Realistic Data**: 3 consumer accounts with different scenarios (current, past due, closed)
30 + - **METRO 2® Format**: Generates compliant format with header, base segments, and tailer
31 + - **Error Handling**: Demonstrates rejection of invalid characters
32 +
33 + **Output**: Creates `demo_output.metro2` with 200+ lines of properly formatted METRO 2® data.
34 +
35 + For detailed demo documentation, see [README_DEMO.md](README_DEMO.md).
36 +
19 37 ## Usage
20 - Every Segment stuct contains field structs, which contains field structs.
21 - Those field structs bear information about the individual field length, type and the allowed characters, wich are important for the
22 - serialisation process.
38 + Every segment struct contains field structs, which contain information about the individual field length, type and allowed characters, which are important for the serialization process.
23 39
24 - To access a field value in a segment struct, please use i.E.:
40 + To access a field value in a segment struct:
25 41 ```elixir
26 - # setter
27 - %Metro2.Records.BaseSegment{}
28 - |> Metro2.Fields.put(:first_name, "Max")
42 + # Create a new base segment with proper field initialization
43 + base_segment = Metro2.Records.BaseSegment.new()
29 44
30 - # getter
31 - %Metro2.Records.BaseSegment{}
32 - |> Metro2.Fields.get(:first_name)
45 + # Set field values
46 + base_segment = Metro2.Fields.put(base_segment, :first_name, "John-Smith") # Dashes allowed in names
47 + base_segment = Metro2.Fields.put(base_segment, :address_1, "123 Main St./Apt 2") # Dots/slashes allowed in addresses
48 +
49 + # Get field values
50 + first_name = Metro2.Fields.get(base_segment, :first_name)
33 51 ```
34 52 ### METRO 2® File Structure
35 - The Metro2 File Structure is the root structure and it has the following inital structure:
53 + The Metro2 File Structure is the root structure and it has the following initial structure:
36 54 ```elixir
37 55 defstruct [
38 56 header: %HeaderSegment{},
  @@ -40,32 +58,56 @@ defstruct [
40 58 tailer: %TailerSegment{}
41 59 ]
42 60 ```
43 - This file can be converted into a serialized Metro2 string via:
61 +
62 + Create and serialize a Metro2 file:
44 63
45 64 ```elixir
46 - my_file = %Metro2.File{}
47 - # update header segment
48 - # add base segments
49 - my_file |> Metro2.File.serialize
65 + # Create a new file with properly initialized segments
66 + my_file = Metro2.File.new()
67 +
68 + # Update header segment
69 + my_file = %{my_file | header: Metro2.Fields.put(my_file.header, :reporter_name, "My Credit Union")}
70 +
71 + # Add base segments
72 + base_segment = Metro2.Records.BaseSegment.new()
73 + |> Metro2.Fields.put(:surname, "Smith-Johnson")
74 + |> Metro2.Fields.put(:first_name, "John")
75 +
76 + my_file = Metro2.File.add_base_segment(my_file, base_segment)
77 +
78 + # Serialize to METRO 2® format
79 + metro2_content = Metro2.File.serialize(my_file)
50 80 ```
51 81 ### Header Segment
52 82 The header segment contains information about the data furnisher.
53 83 You should simply transform the header segment structure in the file structure.
54 84
55 85 ### Base Segment
56 - The base segment is stored in a list in the Metro2.File structure. Each Base segment represents one reportable loan.
86 + The base segment is stored in a list in the Metro2.File structure. Each base segment represents one reportable loan.
57 87 ```elixir
58 - base_segment =v%Metro2.Records.BaseSegment{}
59 - #
60 - # add information to the base_segment
61 - #
62 - %Metro2.File{}
88 + # Create a base segment with proper character validation
89 + base_segment = Metro2.Records.BaseSegment.new()
90 + |> Metro2.Fields.put(:surname, "Smith-Johnson") # Dashes allowed in names
91 + |> Metro2.Fields.put(:first_name, "John")
92 + |> Metro2.Fields.put(:address_1, "123 Main St./Apt 2") # Dots/slashes allowed in addresses
93 + |> Metro2.Fields.put(:account_status, "11") # Current account
94 + |> Metro2.Fields.put(:current_balance, 1500)
95 +
96 + # Add to file
97 + file = Metro2.File.new()
63 98 |> Metro2.File.add_base_segment(base_segment)
64 99 ```
100 +
65 101 ### Tailer Segment
66 102 The tailer segment contains counters for metrics like the SSN and the account statuses.
67 - It will be completly autogenerated.
103 + It will be completely auto-generated based on the base segments.
104 +
105 + ### Character Validation
106 + The library enforces METRO 2® character validation rules:
107 + - **Name fields** (surname, first_name, middle_name): Alphanumeric + dashes
108 + - **Address fields** (address_1, address_2, city): Alphanumeric + dots/dashes/slashes
109 + - **Regular fields**: Alphanumeric only
68 110
69 111 ### Limitations
70 - The Metro2 Standart contains beside the base segment( wich is the most common one) a coule of special segments, which are not supported yet.
112 + The Metro2 Standard contains, besides the base segment (which is the most common one), a couple of special segments which are not supported yet.
  @@ -0,0 +1,79 @@
1 + # Metro2 Library Demo
2 +
3 + This demo showcases the Metro2 library's functionality with realistic example data for credit bureau reporting.
4 +
5 + ## What the Demo Shows
6 +
7 + - **Character Validation**: Demonstrates proper validation for different field types:
8 + - Name fields (surname, first_name, middle_name) accept alphanumeric + dashes
9 + - Address fields accept alphanumeric + dots/dashes/slashes
10 + - Regular fields accept only alphanumeric characters
11 + - Invalid characters are properly rejected
12 +
13 + - **Realistic Data**: Creates 3 consumer accounts with different scenarios:
14 + - Account 1: John Smith-Johnson (Current account)
15 + - Account 2: Maria Garcia-Rodriguez (Past due account)
16 + - Account 3: Robert Williams-Brown (Closed account)
17 +
18 + - **METRO 2® Format**: Generates compliant METRO 2® format output with:
19 + - Header segment with reporter information
20 + - Base segments for each consumer account
21 + - Tailer segment with statistics
22 + - Proper field formatting and validation
23 +
24 + ## How to Run
25 +
26 + ### Using Mix (Recommended)
27 + ```bash
28 + mix run demo.exs
29 + ```
30 +
31 + ### Using Elixir directly
32 + ```bash
33 + elixir demo.exs
34 + ```
35 +
36 + ## Expected Output
37 +
38 + The demo will:
39 + 1. Create a Metro2 file structure
40 + 2. Add realistic consumer account data
41 + 3. Demonstrate character validation (both valid and invalid examples)
42 + 4. Generate a Metro2 format file
43 + 5. Save the output to `demo_output.metro2`
44 + 6. Display statistics and analysis
45 +
46 + ## Output Files
47 +
48 + - `demo_output.metro2` - The generated METRO 2® format file
49 + - Console output with detailed progress and validation examples
50 +
51 + ## Example Data Features
52 +
53 + - **Hyphenated Names**: Smith-Johnson, Garcia-Rodriguez, Williams-Brown
54 + - **Complex Addresses**: Street addresses with dots, slashes, and unit numbers
55 + - **Different Account Types**: Revolving, Installment, Mortgage
56 + - **Various Account Statuses**: Current, Past Due, Closed
57 + - **Proper SSN and Contact Information**
58 +
59 + ## Key Validation Examples
60 +
61 + **Valid Characters:**
62 + - Names: `Smith-Johnson` (dash allowed)
63 + - Addresses: `456 Oak Ave./Apt 2B` (dots and slashes allowed)
64 + - Cities: `St. Petersburg` (dots allowed)
65 +
66 + **Invalid Characters (properly rejected):**
67 + - Names: `Smith@Johnson` (@ not allowed)
68 + - Names: `O'Connor` (apostrophe not allowed)
69 + - Names: `García` (accented characters not allowed)
70 + - Addresses: `123 Main St%` (% not allowed)
71 + - Account Types: `ABC-123` (dash not allowed in regular fields)
72 +
73 + ## Requirements
74 +
75 + - Elixir 1.14+
76 + - Mix dependencies installed (`mix deps.get`)
77 + - Metro2 library compiled
78 +
79 + This demo validates that the library correctly handles METRO 2® format requirements and character validation rules.
\ No newline at end of file
  @@ -1,22 +1,23 @@
1 - {<<"app">>,<<"metro_2">>}.
2 - {<<"build_tools">>,[<<"mix">>]}.
1 + {<<"links">>,[{<<"GitHub">>,<<"https://github.com/dominikknafelj/metro2">>}]}.
2 + {<<"name">>,<<"metro2">>}.
3 + {<<"version">>,<<"0.2.0">>}.
3 4 {<<"description">>,
4 5 <<"This library follows the METRO 2 ® data reporting format, which is a data reporting format for consumer credit account data furnishers."/utf8>>}.
5 - {<<"elixir">>,<<"~> 1.4">>}.
6 + {<<"elixir">>,<<"~> 1.14">>}.
7 + {<<"app">>,<<"metro_2">>}.
6 8 {<<"files">>,
7 - [<<"lib/metro2/base.ex">>,<<"lib/metro2/fields.ex">>,
8 - <<"lib/metro2/file.ex">>,<<"lib/metro2/records/base_segment.ex">>,
9 + [<<"lib">>,<<"lib/metro2.ex">>,<<"lib/metro2">>,<<"lib/metro2/records">>,
10 + <<"lib/metro2/records/tailer_segment.ex">>,
11 + <<"lib/metro2/records/base_segment.ex">>,
9 12 <<"lib/metro2/records/header_segment.ex">>,
10 - <<"lib/metro2/records/segment.ex">>,
11 - <<"lib/metro2/records/tailer_segment.ex">>,<<"mix.exs">>,<<"README.md">>,
12 - <<"LICENSE.txt">>]}.
13 + <<"lib/metro2/records/segment.ex">>,<<"lib/metro2/fields.ex">>,
14 + <<"lib/metro2/base.ex">>,<<"lib/metro2/file.ex">>,<<"mix.exs">>,
15 + <<"README.md">>,<<"README_DEMO.md">>,<<"LICENSE.txt">>]}.
13 16 {<<"licenses">>,[<<"MIT">>]}.
14 - {<<"links">>,[{<<"GitHub">>,<<"https://github.com/dominikknafelj/metro2">>}]}.
15 - {<<"maintainers">>,[<<"Dominik Knafelj">>]}.
16 - {<<"name">>,<<"metro2">>}.
17 17 {<<"requirements">>,
18 - [[{<<"app">>,<<"timex">>},
19 - {<<"name">>,<<"timex">>},
18 + [[{<<"name">>,<<"timex">>},
19 + {<<"app">>,<<"timex">>},
20 20 {<<"optional">>,false},
21 - {<<"requirement">>,<<"~> 3.0">>}]]}.
22 - {<<"version">>,<<"0.1.1">>}.
21 + {<<"requirement">>,<<"~> 3.7">>},
22 + {<<"repository">>,<<"hexpm">>}]]}.
23 + {<<"build_tools">>,[<<"mix">>]}.
  @@ -0,0 +1,47 @@
1 + defmodule Metro2 do
2 + @moduledoc """
3 + Metro2 is a library for generating METRO 2® format files for credit bureau reporting.
4 +
5 + METRO 2® is a data reporting format for consumer credit account data furnishers.
6 +
7 + ## Usage
8 +
9 + Create a Metro2 file with header, base segments, and tailer:
10 +
11 + file = %Metro2.File{}
12 + |> Metro2.File.add_base_segment(base_segment)
13 + |> Metro2.File.serialize()
14 +
15 + ## Modules
16 +
17 + - `Metro2.File` - Main file structure and serialization
18 + - `Metro2.Fields` - Field type definitions and operations
19 + - `Metro2.Records.HeaderSegment` - Header segment definition
20 + - `Metro2.Records.BaseSegment` - Base segment definition
21 + - `Metro2.Records.TailerSegment` - Tailer segment definition
22 + - `Metro2.Base` - Base constants and utility functions
23 + """
24 +
25 + alias Metro2.File
26 +
27 + @doc """
28 + Creates a new Metro2 file structure.
29 + """
30 + def new_file do
31 + %File{}
32 + end
33 +
34 + @doc """
35 + Adds a base segment to a Metro2 file.
36 + """
37 + def add_base_segment(file, base_segment) do
38 + File.add_base_segment(file, base_segment)
39 + end
40 +
41 + @doc """
42 + Serializes a Metro2 file to the METRO 2® format string.
43 + """
44 + def serialize(file) do
45 + File.serialize(file)
46 + end
47 + end
  @@ -1,17 +1,17 @@
1 1 defmodule Metro2.Base do
2 2 @moduledoc """
3 - This module contains all the elementar parts of the metro2 generation, like:
3 + This module contains all the elementar parts of the metro2 generation, like:
4 4 * Maps wich translates the humanized states into metro2 conform codes
5 5 * Regular expressions for valid numerics and alphanumerics
6 - * Functions which are converting datatypes in to a metro2 compatible format
6 + * Functions which are converting datatypes in to a metro2 compatible format
7 7 """
8 8 @portfolio_type %{
9 - line_of_credit: "C",
10 - installment: "I",
11 - mortgage: "M",
12 - open_account: "O",
13 - revolving: "R"
14 - }
9 + line_of_credit: "C",
10 + installment: "I",
11 + mortgage: "M",
12 + open_account: "O",
13 + revolving: "R"
14 + }
15 15
16 16 @account_type %{
17 17 unsecured: "01",
  @@ -21,15 +21,15 @@ defmodule Metro2.Base do
21 21
22 22 @ecoa_code %{
23 23 individual: "1",
24 - deceased: "X"
24 + deceased: "X"
25 25 # TODO: add other ECOA codes
26 26 }
27 27
28 28 @special_comment_code %{
29 - partial_payment_agreement: "AC",
30 - paid_in_full_less_than_full_balance: "AU",
31 - loan_modified: "CO",
32 - forbearance: "CP"
29 + partial_payment_agreement: "AC",
30 + paid_in_full_less_than_full_balance: "AU",
31 + loan_modified: "CO",
32 + forbearance: "CP"
33 33 # TODO: add other special comment codes
34 34 }
35 35
  @@ -39,140 +39,139 @@ defmodule Metro2.Base do
39 39 }
40 40
41 41 @interest_type_indicator %{
42 - fixed: "F",
42 + fixed: "F",
43 43 variable: "V"
44 44 }
45 45
46 46 @correction_indicator "1"
47 47
48 48 @terms_frequency %{
49 - deferred: "D",
49 + deferred: "D",
50 50 single_payment: "P",
51 - weekly: "W",
52 - biweekly: "B",
53 - semimonthly: "S",
54 - monthly: "M",
55 - bimonthly: "L",
56 - quarterly: "Q",
57 - triannually: "T",
58 - semiannually: "S",
59 - annually: "Y"
51 + weekly: "W",
52 + biweekly: "B",
53 + semimonthly: "S",
54 + monthly: "M",
55 + bimonthly: "L",
56 + quarterly: "Q",
57 + triannually: "T",
58 + semiannually: "S",
59 + annually: "Y"
60 60 }
61 61
62 62 @account_status %{
63 - account_transferred: "05",
64 - current: "11",
65 - closed: "13",
63 + account_transferred: "05",
64 + current: "11",
65 + closed: "13",
66 66 paid_in_full_voluntary_surrender: "61",
67 - paid_in_full_collection_account: "62",
68 - paid_in_full_repossession: "63",
69 - paid_in_full_charge_off: "64",
70 - paid_in_full_foreclosure: "65",
71 - past_due_30_59: "71",
72 - past_due_60_89: "78",
73 - past_due_90_119: "80",
74 - past_due_120_149: "82",
75 - past_due_150_179: "83",
76 - past_due_180_plus: "84",
77 - govt_insurance_claim_filed: "88",
78 - deed_received: "89",
79 - collections: "93",
80 - foreclosure_completed: "94",
81 - voluntary_surrender: "95",
82 - merch_repossessed: "96",
83 - charge_off: "97",
84 - delete_account: "DA",
85 - delete_account_fraud: "DF"
67 + paid_in_full_collection_account: "62",
68 + paid_in_full_repossession: "63",
69 + paid_in_full_charge_off: "64",
70 + paid_in_full_foreclosure: "65",
71 + past_due_30_59: "71",
72 + past_due_60_89: "78",
73 + past_due_90_119: "80",
74 + past_due_120_149: "82",
75 + past_due_150_179: "83",
76 + past_due_180_plus: "84",
77 + govt_insurance_claim_filed: "88",
78 + deed_received: "89",
79 + collections: "93",
80 + foreclosure_completed: "94",
81 + voluntary_surrender: "95",
82 + merch_repossessed: "96",
83 + charge_off: "97",
84 + delete_account: "DA",
85 + delete_account_fraud: "DF"
86 86 }
87 87
88 88 @payment_history_profile %{
89 - current: "0",
90 - past_due_30_59: "1",
91 - past_due_60_89: "2",
92 - past_due_90_119: "3",
93 - past_due_120_149: "4",
94 - past_due_150_179: "5",
95 - past_due_180_plus: "6",
96 - no_history_prior: "B",
97 - no_history_available: "D",
98 - zero_balance: "E",
99 - collection: "G",
100 - foreclosure_completed: "H",
101 - voluntary_surrender: "J",
102 - repossession: "K",
103 - charge_off: "L"
89 + current: "0",
90 + past_due_30_59: "1",
91 + past_due_60_89: "2",
92 + past_due_90_119: "3",
93 + past_due_120_149: "4",
94 + past_due_150_179: "5",
95 + past_due_180_plus: "6",
96 + no_history_prior: "B",
97 + no_history_available: "D",
98 + zero_balance: "E",
99 + collection: "G",
100 + foreclosure_completed: "H",
101 + voluntary_surrender: "J",
102 + repossession: "K",
103 + charge_off: "L"
104 104 }
105 105
106 106 @consumer_transaction_type %{
107 - new_account_or_new_borrower: "1",
108 - name_change: "2",
109 - address_change: "3",
110 - ssn_change: "5",
111 - name_and_address_change: "6",
112 - name_and_ssn_change: "8",
113 - address_and_ssn_change: "9",
114 - name_address_and_ssn_change: "A"
107 + new_account_or_new_borrower: "1",
108 + name_change: "2",
109 + address_change: "3",
110 + ssn_change: "5",
111 + name_and_address_change: "6",
112 + name_and_ssn_change: "8",
113 + address_and_ssn_change: "9",
114 + name_address_and_ssn_change: "A"
115 115 }
116 116
117 117 @address_indicator %{
118 - confirmed: "C",
119 - known: "Y",
120 - not_confirmed: "N",
121 - military: "M",
122 - secondary: "S",
123 - business: "B",
124 - non_deliverable: "U",
118 + confirmed: "C",
119 + known: "Y",
120 + not_confirmed: "N",
121 + military: "M",
122 + secondary: "S",
123 + business: "B",
124 + non_deliverable: "U",
125 125 data_reporters_default: "D",
126 - bill_payer_service: "P"
126 + bill_payer_service: "P"
127 127 }
128 128
129 129 @residence_code %{
130 - owns: "O",
131 - rents: "R"
130 + owns: "O",
131 + rents: "R"
132 132 }
133 133
134 134 @generation_code %{
135 - junior: "J",
136 - senior: "S",
137 - ii: "2",
138 - iii: "3",
139 - iv: "4",
140 - v: "5",
141 - vi: "6",
142 - vii: "7",
143 - viii: "8",
144 - ix: "9"
135 + junior: "J",
136 + senior: "S",
137 + ii: "2",
138 + iii: "3",
139 + iv: "4",
140 + v: "5",
141 + vi: "6",
142 + vii: "7",
143 + viii: "8",
144 + ix: "9"
145 145 }
146 146
147 147 @consumer_information_indicator %{
148 - petition_ch7: "A",
149 - petition_ch11: "B",
150 - petition_ch12: "C",
151 - petition_ch13: "D",
152 - discharged_ch7: "E",
153 - discharged_ch11: "F",
154 - discharged_ch12: "G",
155 - discharged_ch13: "H",
156 - dismissed_ch7: "I",
157 - dismissed_ch11: "J",
158 - dismissed_ch12: "K",
159 - dismissed_ch13: "L",
160 - withdrawn_ch7: "M",
161 - withdrawn_ch11: "N",
162 - withdrawn_ch12: "O",
163 - withdrawn_ch13: "P",
148 + petition_ch7: "A",
149 + petition_ch11: "B",
150 + petition_ch12: "C",
151 + petition_ch13: "D",
152 + discharged_ch7: "E",
153 + discharged_ch11: "F",
154 + discharged_ch12: "G",
155 + discharged_ch13: "H",
156 + dismissed_ch7: "I",
157 + dismissed_ch11: "J",
158 + dismissed_ch12: "K",
159 + dismissed_ch13: "L",
160 + withdrawn_ch7: "M",
161 + withdrawn_ch11: "N",
162 + withdrawn_ch12: "O",
163 + withdrawn_ch13: "P"
164 164 }
165 165
166 - @alphanumeric ~r/\A([[:alnum:]]|\s)+\z/
167 - @alphanumeric_plus_dash ~r/\A([[:alnum:]]|\s|\-)+\z/
168 - @alphanumeric_plus_dot_dash_slash ~r/\A([[:alnum:]]|\s|\-|\.|\\|\/)+\z/
169 - @numeric ~r/\A\d+\.?\d*\z/
170 - @integer ~r/\d+\z/
166 + @alphanumeric ~r/\A([[:alnum:]]|\s)+\z/
167 + @alphanumeric_plus_dash ~r/\A([[:alnum:]]|\s|\-)+\z/
168 + @alphanumeric_plus_dot_dash_slash ~r/\A([[:alnum:]]|\s|\-|\.|\\|\/)+\z/
169 + @numeric ~r/\A\d+\.?\d*\z/
170 + @integer ~r/\d+\z/
171 171 @fixed_length 426
172 - @decimal_seperator "."
172 + @decimal_separator "."
173 173 @version_string "01"
174 174
175 -
176 175 def portfolio_type, do: @portfolio_type
177 176 def account_type, do: @account_type
178 177 def ecoa_code, do: @ecoa_code
  @@ -195,31 +194,36 @@ defmodule Metro2.Base do
195 194 def integer, do: @integer
196 195 def fixed_length, do: @fixed_length
197 196 def version_string, do: @version_string
198 - def decimal_seperator, do: @decimal_seperator
197 + def decimal_separator, do: @decimal_separator
199 198
200 199 @doc """
201 200 function to check if a certain account status needs a payment rating
202 201 """
203 202 def account_status_needs_payment_rating?(account_status) do
204 - account_status in [@account_status[:account_transferred], @account_status[:closed],
205 - @account_status[:paid_in_full_foreclosure], @account_status[:govt_insurance_claim_filed],
206 - @account_status[:deed_received], @account_status[:foreclosure_completed],
207 - @account_status[:voluntary_surrender]]
203 + account_status in [
204 + @account_status[:account_transferred],
205 + @account_status[:closed],
206 + @account_status[:paid_in_full_foreclosure],
207 + @account_status[:govt_insurance_claim_filed],
208 + @account_status[:deed_received],
209 + @account_status[:foreclosure_completed],
210 + @account_status[:voluntary_surrender]
211 + ]
208 212 end
209 213
210 - #converts a nil value to an alphanumeric metro2 format
214 + # converts a nil value to an alphanumeric metro2 format
211 215 @doc false
212 - def alphanumeric_to_metro2(nil, required_length, _ ) do
216 + def alphanumeric_to_metro2(nil, required_length, _) do
213 217 String.duplicate(" ", required_length)
214 218 end
215 219
216 - #converts a string to an alphanumeric metro2 format
220 + # converts a string to an alphanumeric metro2 format
217 221 @doc false
218 - def alphanumeric_to_metro2("", required_length, _ ) do
222 + def alphanumeric_to_metro2("", required_length, _) do
219 223 String.duplicate(" ", required_length)
220 224 end
221 225
222 - #converts a numeral to an alphanumeric metro2 format
226 + # converts a numeral to an alphanumeric metro2 format
223 227 @doc false
224 228 def alphanumeric_to_metro2(val, required_length, permitted_chars) when is_number(val) do
225 229 case val do
  @@ -230,15 +234,16 @@ defmodule Metro2.Base do
230 234 end
231 235
232 236 # converts val into an alphanumeric metro2 format with the required length
233 - # it checks if val contains just permitted characters
237 + # it checks if val contains just permitted characters
234 238 @doc false
235 239 def alphanumeric_to_metro2(val, required_length, permitted_chars) do
236 240 unless Regex.match?(permitted_chars, val) do
237 241 raise ArgumentError, message: "Content (#{val}) contains invalid characters"
238 242 end
243 +
239 244 # if val is too long, just cut it down, else fil it up with leading spaces
240 245 if String.length(val) > required_length do
241 - String.slice(val, 0..(required_length-1))
246 + String.slice(val, 0..(required_length - 1))
242 247 else
243 248 val |> String.pad_trailing(required_length)
244 249 end
  @@ -246,13 +251,13 @@ defmodule Metro2.Base do
246 251
247 252 # converts a nil value to a numeric metro2 format
248 253 @doc false
249 - def numeric_to_metro2( nil , required_length, _) do
254 + def numeric_to_metro2(nil, required_length, _) do
250 255 String.duplicate("0", required_length)
251 256 end
252 257
253 258 # converts an empty string to a numeric metro2 format
254 259 @doc false
255 - def numeric_to_metro2( "" , required_length, _) do
260 + def numeric_to_metro2("", required_length, _) do
256 261 String.duplicate("0", required_length)
257 262 end
258 263
  @@ -262,13 +267,18 @@ defmodule Metro2.Base do
262 267 # non monetary fields will raise an ArgunmentError for being too long
263 268 @doc false
264 269 def numeric_to_metro2(val, required_length, is_monetary) do
265 - case normalize_numeric(val) |> Tuple.append(is_monetary) do
270 + case normalize_numeric(val) |> Tuple.insert_at(2, is_monetary) do
266 271 # when we have a monetary value and we exceed the billion we limit to 999,999,999
267 - {x, _, true} when x >= 1_000_000_000 -> String.duplicate("9", required_length)
272 + {x, _, true} when x >= 1_000_000_000 ->
273 + String.duplicate("9", required_length)
274 +
268 275 # normal case, we return the value with leading 0 as fillup
269 - {x, length, _ } when length <= required_length -> x |> Integer.to_string |> String.pad_leading( required_length, "0" )
276 + {x, length, _} when length <= required_length ->
277 + x |> Integer.to_string() |> String.pad_leading(required_length, "0")
278 +
270 279 # when we don't have a monetary value and we exceed the required_length
271 - _ -> raise ArgumentError, message: "numeric field (#{val}) is too long (max #{required_length})"
280 + _ ->
281 + raise ArgumentError, message: "numeric field (#{val}) is too long (max #{required_length})"
272 282 end
273 283 end
274 284
  @@ -283,11 +293,11 @@ defmodule Metro2.Base do
283 293 # returns the interger numeric value and the figures in a tupel
284 294 @doc false
285 295 defp normalize_numeric(val) when is_integer(val) do
286 - {val, (Integer.to_string(val) |> String.length())}
296 + {val, Integer.to_string(val) |> String.length()}
287 297 end
288 298
289 299 @doc false
290 300 defp normalize_numeric(val) when is_float(val) do
291 - val |> Float.floor |> round() |> normalize_numeric()
301 + val |> Float.floor() |> round() |> normalize_numeric()
292 302 end
293 303 end
Loading more files…