sunnyside 0.1.1 → 0.1.2
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 +7 -0
- data/bin/sunnyside +5 -5
- data/lib/sunnyside.rb +35 -35
- data/lib/sunnyside/advanced.rb +84 -84
- data/lib/sunnyside/cash_receipts/cash_receipt.rb +14 -11
- data/lib/sunnyside/expiring_auth.rb +0 -0
- data/lib/sunnyside/ftp.rb +0 -0
- data/lib/sunnyside/ledger/auth_report.rb +0 -0
- data/lib/sunnyside/ledger/edi.rb +174 -174
- data/lib/sunnyside/ledger/ledger.rb +189 -188
- data/lib/sunnyside/ledger/private.rb +0 -0
- data/lib/sunnyside/menu.rb +52 -52
- data/lib/sunnyside/models/db_setup.rb +196 -196
- data/lib/sunnyside/models/sequel_classes.rb +13 -13
- data/lib/sunnyside/query/query.rb +46 -28
- data/lib/sunnyside/reports/mco_mltc.rb +41 -41
- data/lib/sunnyside/reports/pdf_report.rb +1 -1
- data/lib/sunnyside/reports/pdf_report_draft.rb +0 -0
- data/lib/sunnyside/reports/private.rb +0 -0
- data/lib/sunnyside/reports/report.rb +0 -0
- data/lib/sunnyside/version.rb +3 -3
- metadata +21 -35
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8e0f90574f4539e1f9daa2488cbca27d81414ad3
|
4
|
+
data.tar.gz: 12ce71ebb22046f4d41c371ef3b5e984b04a25a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4cace35d98ac36f145edd1f29c32564fc56195d8711ee6ab4a941e721d4d2ecfcee4070aa7dd2d88ee5b0f119979cbc56d6309508dcfd7d44e1b10aac396bc48
|
7
|
+
data.tar.gz: 0ac9c8a12d46770f952cfc36e3072bad461572c843b0c24ff69d17888ebdd51c9aced11f7e59c5cd78587b2be5f24beca8227816d4e48c4824161a6809d1e88b
|
data/bin/sunnyside
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'sequel'
|
4
|
-
require 'sunnyside'
|
5
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'sequel'
|
4
|
+
require 'sunnyside'
|
5
|
+
|
6
6
|
Sunnyside::Menu.new.start
|
data/lib/sunnyside.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
|
-
require 'prawn'
|
2
|
-
require 'sequel'
|
3
|
-
require 'csv'
|
4
|
-
require 'fileutils'
|
5
|
-
require "sunnyside/version"
|
6
|
-
require 'sunnyside/cash_receipts/cash_receipt'
|
7
|
-
require 'sunnyside/ledger/ledger'
|
8
|
-
require 'sunnyside/ledger/edi'
|
9
|
-
require 'sunnyside/ledger/auth_report'
|
10
|
-
require 'sunnyside/ledger/private'
|
11
|
-
require 'sunnyside/reports/pdf_report'
|
12
|
-
require 'sunnyside/reports/private'
|
13
|
-
require 'sunnyside/reports/report'
|
14
|
-
require 'sunnyside/query/query'
|
15
|
-
require 'sunnyside/ftp'
|
16
|
-
require 'sunnyside/menu'
|
17
|
-
require 'sunnyside/expiring_auth'
|
18
|
-
require 'sunnyside/models/db_setup'
|
19
|
-
require 'sunnyside/advanced'
|
20
|
-
|
21
|
-
module Sunnyside
|
22
|
-
DRIVE = ENV["HOMEDRIVE"]
|
23
|
-
|
24
|
-
Sunnyside.create_folders if !Dir.exist?("#{DRIVE}/sunnyside-files")
|
25
|
-
Dir.chdir("R:/Departments/AR Department")
|
26
|
-
DB = Sequel.connect("sqlite://sunnyside.db")
|
27
|
-
|
28
|
-
if DB.tables.empty?
|
29
|
-
Sunnyside.create_tables
|
30
|
-
Sunnyside.add_providers
|
31
|
-
Sunnyside.add_denial_data
|
32
|
-
end
|
33
|
-
|
34
|
-
require 'sunnyside/models/sequel_classes'
|
35
|
-
end
|
1
|
+
require 'prawn'
|
2
|
+
require 'sequel'
|
3
|
+
require 'csv'
|
4
|
+
require 'fileutils'
|
5
|
+
require "sunnyside/version"
|
6
|
+
require 'sunnyside/cash_receipts/cash_receipt'
|
7
|
+
require 'sunnyside/ledger/ledger'
|
8
|
+
require 'sunnyside/ledger/edi'
|
9
|
+
require 'sunnyside/ledger/auth_report'
|
10
|
+
require 'sunnyside/ledger/private'
|
11
|
+
require 'sunnyside/reports/pdf_report'
|
12
|
+
require 'sunnyside/reports/private'
|
13
|
+
require 'sunnyside/reports/report'
|
14
|
+
require 'sunnyside/query/query'
|
15
|
+
require 'sunnyside/ftp'
|
16
|
+
require 'sunnyside/menu'
|
17
|
+
require 'sunnyside/expiring_auth'
|
18
|
+
require 'sunnyside/models/db_setup'
|
19
|
+
require 'sunnyside/advanced'
|
20
|
+
|
21
|
+
module Sunnyside
|
22
|
+
DRIVE = ENV["HOMEDRIVE"]
|
23
|
+
|
24
|
+
Sunnyside.create_folders if !Dir.exist?("#{DRIVE}/sunnyside-files")
|
25
|
+
Dir.chdir("R:/Departments/AR Department")
|
26
|
+
DB = Sequel.connect("sqlite://sunnyside.db")
|
27
|
+
|
28
|
+
if DB.tables.empty?
|
29
|
+
Sunnyside.create_tables
|
30
|
+
Sunnyside.add_providers
|
31
|
+
Sunnyside.add_denial_data
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'sunnyside/models/sequel_classes'
|
35
|
+
end
|
data/lib/sunnyside/advanced.rb
CHANGED
@@ -1,85 +1,85 @@
|
|
1
|
-
module Sunnyside
|
2
|
-
def self.advanced_opts
|
3
|
-
puts "1.) Add new provider"
|
4
|
-
puts "2.) Export A/R denials"
|
5
|
-
|
6
|
-
case gets.chomp
|
7
|
-
when '1'
|
8
|
-
print "Type in the provider name _EXACTLY_ how it appears on the SanData archive report files (e.g. Guildnet is always GUILDNET): "
|
9
|
-
provider = gets.chomp
|
10
|
-
print "Now type in the abbreviation (batch initials - e.g. MetroPlus Health is MPH): "
|
11
|
-
abbrev = gets.chomp
|
12
|
-
print "Now type in the CREDIT account that is used in FUND EZ: "
|
13
|
-
credit = gets.chomp
|
14
|
-
print "Now type in the DEBIT account that is used in FUND EZ: "
|
15
|
-
debit = gets.chomp
|
16
|
-
print "And finally, type in the FUND number that is used in FUND EZ: "
|
17
|
-
fund = gets.chomp
|
18
|
-
review = "--------Name: #{provider}, Credit Account: #{credit}, Debit Account: #{debit}, Fund: #{fund}, Abbreviation: #{abbrev}--------"
|
19
|
-
puts "Please review the below information."
|
20
|
-
puts '-' * review.length
|
21
|
-
puts review
|
22
|
-
puts '-' * review.length
|
23
|
-
print "Is this correct? (Y for yes, N for No): "
|
24
|
-
raise 'You have an empty field! Start over!' if [provider, credit, debit, fund, abbrev].any? { |elem| elem.empty? }
|
25
|
-
if gets.chomp.upcase == 'Y'
|
26
|
-
provider = Provider.insert(name: provider, credit_account: credit, debit_account: debit, fund: fund, abbreviation: abbrev)
|
27
|
-
puts "#{Provider[provider].name} added."
|
28
|
-
else
|
29
|
-
Sunnyside.advanced_opts
|
30
|
-
end
|
31
|
-
else
|
32
|
-
exit
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.rails_server
|
37
|
-
puts "Please wait..."
|
38
|
-
|
39
|
-
Dir.chdir("R:/Departments/AR Department/sunnyside-app")
|
40
|
-
|
41
|
-
%x(rails s)
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.determine_browser
|
45
|
-
if Dir.exist?("#{DRIVE}/Program Files (x86)")
|
46
|
-
Dir.chdir("#{DRIVE}/Program Files (x86)/Mozilla Firefox")
|
47
|
-
else
|
48
|
-
Dir.chdir("#{DRIVE}/Program Files/Mozilla Firefox")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.add_provider_to_ftp
|
53
|
-
Provider.all.each { |prov| puts "#{prov.id}: #{prov.name}"}
|
54
|
-
print "Type in the corresponding ID Number for the provider you would like to add to FTP: "
|
55
|
-
|
56
|
-
provider = Provider[gets.chomp].abbreviation
|
57
|
-
|
58
|
-
puts "You've selected #{provider}"
|
59
|
-
|
60
|
-
print "Type in the ftp address now: "
|
61
|
-
site = gets.chomp
|
62
|
-
|
63
|
-
print "Type in the username now: "
|
64
|
-
username = gets.chomp
|
65
|
-
|
66
|
-
print "Type in the password now: "
|
67
|
-
password = gets.chomp
|
68
|
-
|
69
|
-
review = "-------Provider: #{provider}, Site: #{site}, Username: #{username}, Password: #{password}--------------"
|
70
|
-
puts "Please review the following information: "
|
71
|
-
puts '-' * review.length
|
72
|
-
puts review
|
73
|
-
puts '-' * review.length
|
74
|
-
|
75
|
-
puts "Is this correct? Type Y or N."
|
76
|
-
if gets.chomp.downcase == 'y'
|
77
|
-
Login.insert(site: site, username: username, password: password, provider: provider)
|
78
|
-
Dir.mkdir("#{DRIVE}/sunnyside-files/ftp/835/#{provider}")
|
79
|
-
Dir.mkdir("#{DRIVE}/sunnyside-files/ftp/837/#{provider}")
|
80
|
-
else
|
81
|
-
puts 'Please try again.'
|
82
|
-
Sunnyside.add_provider_to_ftp
|
83
|
-
end
|
84
|
-
end
|
1
|
+
module Sunnyside
|
2
|
+
def self.advanced_opts
|
3
|
+
puts "1.) Add new provider"
|
4
|
+
puts "2.) Export A/R denials"
|
5
|
+
|
6
|
+
case gets.chomp
|
7
|
+
when '1'
|
8
|
+
print "Type in the provider name _EXACTLY_ how it appears on the SanData archive report files (e.g. Guildnet is always GUILDNET): "
|
9
|
+
provider = gets.chomp
|
10
|
+
print "Now type in the abbreviation (batch initials - e.g. MetroPlus Health is MPH): "
|
11
|
+
abbrev = gets.chomp
|
12
|
+
print "Now type in the CREDIT account that is used in FUND EZ: "
|
13
|
+
credit = gets.chomp
|
14
|
+
print "Now type in the DEBIT account that is used in FUND EZ: "
|
15
|
+
debit = gets.chomp
|
16
|
+
print "And finally, type in the FUND number that is used in FUND EZ: "
|
17
|
+
fund = gets.chomp
|
18
|
+
review = "--------Name: #{provider}, Credit Account: #{credit}, Debit Account: #{debit}, Fund: #{fund}, Abbreviation: #{abbrev}--------"
|
19
|
+
puts "Please review the below information."
|
20
|
+
puts '-' * review.length
|
21
|
+
puts review
|
22
|
+
puts '-' * review.length
|
23
|
+
print "Is this correct? (Y for yes, N for No): "
|
24
|
+
raise 'You have an empty field! Start over!' if [provider, credit, debit, fund, abbrev].any? { |elem| elem.empty? }
|
25
|
+
if gets.chomp.upcase == 'Y'
|
26
|
+
provider = Provider.insert(name: provider, credit_account: credit, debit_account: debit, fund: fund, abbreviation: abbrev)
|
27
|
+
puts "#{Provider[provider].name} added."
|
28
|
+
else
|
29
|
+
Sunnyside.advanced_opts
|
30
|
+
end
|
31
|
+
else
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.rails_server
|
37
|
+
puts "Please wait..."
|
38
|
+
|
39
|
+
Dir.chdir("R:/Departments/AR Department/sunnyside-app")
|
40
|
+
|
41
|
+
%x(rails s)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.determine_browser
|
45
|
+
if Dir.exist?("#{DRIVE}/Program Files (x86)")
|
46
|
+
Dir.chdir("#{DRIVE}/Program Files (x86)/Mozilla Firefox")
|
47
|
+
else
|
48
|
+
Dir.chdir("#{DRIVE}/Program Files/Mozilla Firefox")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.add_provider_to_ftp
|
53
|
+
Provider.all.each { |prov| puts "#{prov.id}: #{prov.name}"}
|
54
|
+
print "Type in the corresponding ID Number for the provider you would like to add to FTP: "
|
55
|
+
|
56
|
+
provider = Provider[gets.chomp].abbreviation
|
57
|
+
|
58
|
+
puts "You've selected #{provider}"
|
59
|
+
|
60
|
+
print "Type in the ftp address now: "
|
61
|
+
site = gets.chomp
|
62
|
+
|
63
|
+
print "Type in the username now: "
|
64
|
+
username = gets.chomp
|
65
|
+
|
66
|
+
print "Type in the password now: "
|
67
|
+
password = gets.chomp
|
68
|
+
|
69
|
+
review = "-------Provider: #{provider}, Site: #{site}, Username: #{username}, Password: #{password}--------------"
|
70
|
+
puts "Please review the following information: "
|
71
|
+
puts '-' * review.length
|
72
|
+
puts review
|
73
|
+
puts '-' * review.length
|
74
|
+
|
75
|
+
puts "Is this correct? Type Y or N."
|
76
|
+
if gets.chomp.downcase == 'y'
|
77
|
+
Login.insert(site: site, username: username, password: password, provider: provider)
|
78
|
+
Dir.mkdir("#{DRIVE}/sunnyside-files/ftp/835/#{provider}")
|
79
|
+
Dir.mkdir("#{DRIVE}/sunnyside-files/ftp/837/#{provider}")
|
80
|
+
else
|
81
|
+
puts 'Please try again.'
|
82
|
+
Sunnyside.add_provider_to_ftp
|
83
|
+
end
|
84
|
+
end
|
85
85
|
end
|
@@ -3,14 +3,18 @@ module Sunnyside
|
|
3
3
|
def self.cash_receipt
|
4
4
|
puts "1.) EDI PAYMENT"
|
5
5
|
puts "2.) MANUAL PAYMENT"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
puts "3.) RESET A/R SPREADSHEET"
|
7
|
+
case gets.chomp
|
8
|
+
when '1'
|
9
|
+
cash_receipt = CashReceipt.new(:electronic)
|
10
|
+
when '2'
|
11
|
+
cash_receipt = CashReceipt.new(:manual)
|
12
|
+
when '3'
|
13
|
+
CSV.open("#{DRIVE}/sunnyside-files/cash_receipts/EDI-citywide-import.csv", "w") { |row|
|
14
|
+
row << ['Seq','Receipt','post_date','other id','invoice','header memo','batch','doc date','detail memo','fund','account','cc1','cc2','cc3','debit','credit']
|
15
|
+
}
|
16
|
+
end
|
17
|
+
cash_receipt.collate if cash_receipt
|
14
18
|
end
|
15
19
|
|
16
20
|
class CashReceipt
|
@@ -39,9 +43,9 @@ module Sunnyside
|
|
39
43
|
print "You have typed out #{invoices.length} number of invoices. Do you wish to add more to the same check? (Y or N): "
|
40
44
|
if gets.chomp.upcase == 'Y'
|
41
45
|
more_invoices = gets.chomp.split
|
42
|
-
return (more_invoices + invoices)
|
46
|
+
return (more_invoices + invoices).uniq
|
43
47
|
else
|
44
|
-
return invoices
|
48
|
+
return invoices.uniq
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
@@ -61,7 +65,6 @@ module Sunnyside
|
|
61
65
|
manual_invoices
|
62
66
|
end
|
63
67
|
end
|
64
|
-
|
65
68
|
end
|
66
69
|
|
67
70
|
def invoices_exist?(invoices)
|
File without changes
|
data/lib/sunnyside/ftp.rb
CHANGED
File without changes
|
File without changes
|
data/lib/sunnyside/ledger/edi.rb
CHANGED
@@ -1,175 +1,175 @@
|
|
1
|
-
module Sunnyside
|
2
|
-
def self.edi_parser
|
3
|
-
print "checking for new files...\n"
|
4
|
-
Dir["#{DRIVE}/sunnyside-files/835/*.txt"].each do |file|
|
5
|
-
|
6
|
-
if Filelib.where(filename: file).count > 0
|
7
|
-
puts "This file has been processed already. File removed."
|
8
|
-
File.delete(file)
|
9
|
-
else
|
10
|
-
print "processing #{file}...\n"
|
11
|
-
file_data = File.open(file)
|
12
|
-
data = file_data.read
|
13
|
-
# Detect to see if the EDI file already has new lines inserted. If so, the newlines are removed before the file gets processed.
|
14
|
-
|
15
|
-
data.gsub!(/\n/, '')
|
16
|
-
|
17
|
-
data = data.split(/~CLP\*/)
|
18
|
-
|
19
|
-
edi_file = EdiReader.new(data)
|
20
|
-
edi_file.parse_claims
|
21
|
-
Filelib.insert(filename: file, purpose: '835')
|
22
|
-
file_data.close
|
23
|
-
FileUtils.mv(file, "#{DRIVE}/sunnyside-files/835/archive/#{File.basename(file)}")
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class EdiReader
|
29
|
-
attr_reader :data
|
30
|
-
|
31
|
-
def initialize(data)
|
32
|
-
@data = data
|
33
|
-
end
|
34
|
-
|
35
|
-
def claims
|
36
|
-
data.select { |clm| clm =~ /^\d+/ }.map { |clm| clm.split(/~(?=SVC)/) }
|
37
|
-
end
|
38
|
-
|
39
|
-
def check_number
|
40
|
-
check = data[0][/(?<=~TRN\*\d\*)\w+/]
|
41
|
-
return check.include?('E') ? check[/\d+$/] : check
|
42
|
-
end
|
43
|
-
|
44
|
-
def check_total
|
45
|
-
data[0][/(?<=~BPR\*\w\*)[0-9\.\-]+/, 0]
|
46
|
-
end
|
47
|
-
|
48
|
-
def parse_claims
|
49
|
-
payment_id = Payment.insert(check_number: check_number, check_total: check_total)
|
50
|
-
claims.each { |claim| ClaimParser.new(claim, payment_id).parse }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
class ClaimParser < EdiReader
|
55
|
-
attr_reader :claim_header, :service_data, :payment_id
|
56
|
-
|
57
|
-
def initialize(claim, payment_id)
|
58
|
-
@claim_header = claim[0].split(/\*/)
|
59
|
-
@service_data = claim.select { |clm| clm =~ /^SVC/ }
|
60
|
-
@payment_id = Payment[payment_id]
|
61
|
-
end
|
62
|
-
|
63
|
-
def header
|
64
|
-
{
|
65
|
-
:invoice => claim_header[0],
|
66
|
-
:response_code => claim_header[1],
|
67
|
-
:billed => claim_header[2],
|
68
|
-
:paid => claim_header[3],
|
69
|
-
:units => claim_header[5], # 4 is not used - that is the patient responsibility amount
|
70
|
-
:claim_number => claim_header[6][/^\d+/]
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
def parse
|
75
|
-
claim = ClaimEntry.new(header)
|
76
|
-
claim_id = claim.to_db(payment_id)
|
77
|
-
service_data.each { |service| ServiceParser.new(service, claim_id).parse }
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
class ClaimEntry < EdiReader
|
82
|
-
attr_reader :invoice, :response_code, :billed, :paid, :units, :claim_number
|
83
|
-
|
84
|
-
def initialize(header = {})
|
85
|
-
@invoice = header[:invoice].gsub(/[OLD]/, 'O' => '0', 'D' => '8', 'L' => '1').gsub(/^0/, '')[0..5].to_i # for the corrupt SHP EDI files
|
86
|
-
@response_code = header[:response_code]
|
87
|
-
@billed = header[:billed]
|
88
|
-
@paid = header[:paid]
|
89
|
-
@units = header[:units]
|
90
|
-
@claim_number = header[:claim_number]
|
91
|
-
end
|
92
|
-
|
93
|
-
def to_db(payment)
|
94
|
-
payment.update(provider_id: inv.provider_id) if payment.provider_id.nil?
|
95
|
-
Claim.insert(
|
96
|
-
:invoice_id => invoice,
|
97
|
-
:payment_id => payment.id,
|
98
|
-
:client_id => inv.client_id,
|
99
|
-
:control_number => claim_number,
|
100
|
-
:paid => paid,
|
101
|
-
:billed => billed,
|
102
|
-
:status => response_code,
|
103
|
-
:provider_id => inv.provider_id,
|
104
|
-
:recipient_id => inv.recipient_id
|
105
|
-
)
|
106
|
-
end
|
107
|
-
|
108
|
-
def inv
|
109
|
-
Invoice[invoice]
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
class ServiceParser < EdiReader
|
114
|
-
attr_reader :service_line, :claim
|
115
|
-
|
116
|
-
def initialize(service_line, claim_id)
|
117
|
-
@service_line = service_line.split(/~/)
|
118
|
-
@claim = Claim[claim_id]
|
119
|
-
end
|
120
|
-
|
121
|
-
def dos
|
122
|
-
service_line.detect { |svc| svc =~ /^DTM/ }[/\w+$/]
|
123
|
-
end
|
124
|
-
|
125
|
-
def service_header
|
126
|
-
line = service_line[0].split(/\*/).drop(1)
|
127
|
-
if line.length == 7 || line[1] != line[2]
|
128
|
-
return line.uniq
|
129
|
-
else
|
130
|
-
return line
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def error_codes
|
135
|
-
service_line.find_all { |section| section =~ /^CAS|^SE/ }.map { |code| code[/\w+\*\w+\*(\d+)/, 1] }
|
136
|
-
end
|
137
|
-
|
138
|
-
def parse
|
139
|
-
service = ServiceEntry.new(service_header, dos, error_codes)
|
140
|
-
service.to_db(claim)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
class ServiceEntry < EdiReader
|
145
|
-
attr_reader :paid, :billed, :service_code, :units, :res_code, :error_codes, :dos
|
146
|
-
|
147
|
-
def initialize(service_header, dos, error_codes)
|
148
|
-
@service_code, @billed, @paid, @res_code, @units = service_header
|
149
|
-
@dos = Date.parse(dos)
|
150
|
-
@error_codes = error_codes.map { |id| Denial[id].denial_explanation }
|
151
|
-
end
|
152
|
-
|
153
|
-
def to_db(claim)
|
154
|
-
Service.insert(
|
155
|
-
:claim_id => claim.id,
|
156
|
-
:invoice_id => claim.invoice_id,
|
157
|
-
:payment_id => claim.payment_id,
|
158
|
-
:denial_reason => denial_reason,
|
159
|
-
:service_code => service_code.gsub(/HC:/, ''),
|
160
|
-
:paid => paid,
|
161
|
-
:billed => billed,
|
162
|
-
:units => units,
|
163
|
-
:dos => dos
|
164
|
-
)
|
165
|
-
end
|
166
|
-
|
167
|
-
def denial_reason
|
168
|
-
error_codes.join("\n") if denied?
|
169
|
-
end
|
170
|
-
|
171
|
-
def denied?
|
172
|
-
paid != billed
|
173
|
-
end
|
174
|
-
end
|
1
|
+
module Sunnyside
|
2
|
+
def self.edi_parser
|
3
|
+
print "checking for new files...\n"
|
4
|
+
Dir["#{DRIVE}/sunnyside-files/835/*.txt"].each do |file|
|
5
|
+
|
6
|
+
if Filelib.where(filename: file).count > 0
|
7
|
+
puts "This file has been processed already. File removed."
|
8
|
+
File.delete(file)
|
9
|
+
else
|
10
|
+
print "processing #{file}...\n"
|
11
|
+
file_data = File.open(file)
|
12
|
+
data = file_data.read
|
13
|
+
# Detect to see if the EDI file already has new lines inserted. If so, the newlines are removed before the file gets processed.
|
14
|
+
|
15
|
+
data.gsub!(/\n/, '')
|
16
|
+
|
17
|
+
data = data.split(/~CLP\*/)
|
18
|
+
|
19
|
+
edi_file = EdiReader.new(data)
|
20
|
+
edi_file.parse_claims
|
21
|
+
Filelib.insert(filename: File.basename(file), purpose: '835')
|
22
|
+
file_data.close
|
23
|
+
FileUtils.mv(file, "#{DRIVE}/sunnyside-files/835/archive/#{File.basename(file)}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class EdiReader
|
29
|
+
attr_reader :data
|
30
|
+
|
31
|
+
def initialize(data)
|
32
|
+
@data = data
|
33
|
+
end
|
34
|
+
|
35
|
+
def claims
|
36
|
+
data.select { |clm| clm =~ /^\d+/ }.map { |clm| clm.split(/~(?=SVC)/) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_number
|
40
|
+
check = data[0][/(?<=~TRN\*\d\*)\w+/]
|
41
|
+
return check.include?('E') ? check[/\d+$/] : check
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_total
|
45
|
+
data[0][/(?<=~BPR\*\w\*)[0-9\.\-]+/, 0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_claims
|
49
|
+
payment_id = Payment.insert(check_number: check_number, check_total: check_total)
|
50
|
+
claims.each { |claim| ClaimParser.new(claim, payment_id).parse }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ClaimParser < EdiReader
|
55
|
+
attr_reader :claim_header, :service_data, :payment_id
|
56
|
+
|
57
|
+
def initialize(claim, payment_id)
|
58
|
+
@claim_header = claim[0].split(/\*/)
|
59
|
+
@service_data = claim.select { |clm| clm =~ /^SVC/ }
|
60
|
+
@payment_id = Payment[payment_id]
|
61
|
+
end
|
62
|
+
|
63
|
+
def header
|
64
|
+
{
|
65
|
+
:invoice => claim_header[0],
|
66
|
+
:response_code => claim_header[1],
|
67
|
+
:billed => claim_header[2],
|
68
|
+
:paid => claim_header[3],
|
69
|
+
:units => claim_header[5], # 4 is not used - that is the patient responsibility amount
|
70
|
+
:claim_number => claim_header[6][/^\d+/]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse
|
75
|
+
claim = ClaimEntry.new(header)
|
76
|
+
claim_id = claim.to_db(payment_id)
|
77
|
+
service_data.each { |service| ServiceParser.new(service, claim_id).parse }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class ClaimEntry < EdiReader
|
82
|
+
attr_reader :invoice, :response_code, :billed, :paid, :units, :claim_number
|
83
|
+
|
84
|
+
def initialize(header = {})
|
85
|
+
@invoice = header[:invoice].gsub(/[OLD]/, 'O' => '0', 'D' => '8', 'L' => '1').gsub(/^0/, '')[0..5].to_i # for the corrupt SHP EDI files
|
86
|
+
@response_code = header[:response_code]
|
87
|
+
@billed = header[:billed]
|
88
|
+
@paid = header[:paid]
|
89
|
+
@units = header[:units]
|
90
|
+
@claim_number = header[:claim_number]
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_db(payment)
|
94
|
+
payment.update(provider_id: inv.provider_id) if payment.provider_id.nil?
|
95
|
+
Claim.insert(
|
96
|
+
:invoice_id => invoice,
|
97
|
+
:payment_id => payment.id,
|
98
|
+
:client_id => inv.client_id,
|
99
|
+
:control_number => claim_number,
|
100
|
+
:paid => paid,
|
101
|
+
:billed => billed,
|
102
|
+
:status => response_code,
|
103
|
+
:provider_id => inv.provider_id,
|
104
|
+
:recipient_id => inv.recipient_id
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def inv
|
109
|
+
Invoice[invoice]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class ServiceParser < EdiReader
|
114
|
+
attr_reader :service_line, :claim
|
115
|
+
|
116
|
+
def initialize(service_line, claim_id)
|
117
|
+
@service_line = service_line.split(/~/)
|
118
|
+
@claim = Claim[claim_id]
|
119
|
+
end
|
120
|
+
|
121
|
+
def dos
|
122
|
+
service_line.detect { |svc| svc =~ /^DTM/ }[/\w+$/]
|
123
|
+
end
|
124
|
+
|
125
|
+
def service_header
|
126
|
+
line = service_line[0].split(/\*/).drop(1)
|
127
|
+
if line.length == 7 || line[1] != line[2]
|
128
|
+
return line.uniq
|
129
|
+
else
|
130
|
+
return line
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def error_codes
|
135
|
+
service_line.find_all { |section| section =~ /^CAS|^SE/ }.map { |code| code[/\w+\*\w+\*(\d+)/, 1] }
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse
|
139
|
+
service = ServiceEntry.new(service_header, dos, error_codes)
|
140
|
+
service.to_db(claim)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class ServiceEntry < EdiReader
|
145
|
+
attr_reader :paid, :billed, :service_code, :units, :res_code, :error_codes, :dos
|
146
|
+
|
147
|
+
def initialize(service_header, dos, error_codes)
|
148
|
+
@service_code, @billed, @paid, @res_code, @units = service_header
|
149
|
+
@dos = Date.parse(dos)
|
150
|
+
@error_codes = error_codes.map { |id| Denial[id].denial_explanation }
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_db(claim)
|
154
|
+
Service.insert(
|
155
|
+
:claim_id => claim.id,
|
156
|
+
:invoice_id => claim.invoice_id,
|
157
|
+
:payment_id => claim.payment_id,
|
158
|
+
:denial_reason => denial_reason,
|
159
|
+
:service_code => service_code.gsub(/HC:/, ''),
|
160
|
+
:paid => paid,
|
161
|
+
:billed => billed,
|
162
|
+
:units => units,
|
163
|
+
:dos => dos
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
def denial_reason
|
168
|
+
error_codes.join("\n") if denied?
|
169
|
+
end
|
170
|
+
|
171
|
+
def denied?
|
172
|
+
paid != billed
|
173
|
+
end
|
174
|
+
end
|
175
175
|
end
|