sunnyside 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1cb352e0ac28b56b294e7736a05ea8c3862c41b0
4
+ data.tar.gz: 8b00db12da4d56584417a1dc52e5b8cbb73226f8
5
+ SHA512:
6
+ metadata.gz: 5b8236a4f85643a031ecaed56c69d19bbc426c96443ce2d32624f85e32f27beb26b77a49866d2006d57b5e39ec65711d2ebe123a7c9fcf45d94c811f70c41c6f
7
+ data.tar.gz: af009aeedc0ecffc3f462233e5c628dd269d455366d84d4b809ba326fb8c3a53bcece65738d18b05870fcf72bf8513b4bea5acb23469ffe61cb7a6c22e1ebf51
data/bin/sunnyside ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'sequel'
4
+ require 'sunnyside'
5
+
6
+ Sunnyside::Menu.new.start
@@ -0,0 +1,169 @@
1
+ module Sunnyside
2
+
3
+ def payment_type
4
+ puts 'Type of Payment? (EDI or MANUAL)'
5
+ return gets.chomp.upcase == 'EDI' ? EdiPayment.new : ManualPayment.new
6
+ end
7
+
8
+ def check_number_and_date
9
+ puts 'Enter in check number followed by the post date (separated by a space - ex: 235345 10/12/2013): '
10
+ return gets.chomp.split(' ')
11
+ end
12
+
13
+ def invoice_numbers
14
+ puts 'Enter in invoices, each separated by a space. If an invoice contains any denials, flag it by typing in a "-d" right after the last number. '
15
+ return gets.chomp.split(' ')
16
+ end
17
+
18
+ class CashReceipt
19
+ include Sunnyside
20
+ attr_reader :type
21
+ def initialize
22
+ @type = self.payment_type
23
+ end
24
+
25
+ def process
26
+ type.collate
27
+ end
28
+ end
29
+
30
+ class EdiPayment
31
+ include Sunnyside
32
+ attr_reader :check_number, :post_date
33
+
34
+ def initialize
35
+ @check_number, @post_date = self.check_number_and_date
36
+ end
37
+
38
+ def invoices
39
+ Claim.where(check_number: check_number).map { |clm| clm.invoice_id }.uniq
40
+ end
41
+
42
+ def populated_data
43
+ invoices.map { |inv| Invoice[inv] }
44
+ end
45
+
46
+ def total
47
+ populated_data.map { |inv| inv.amount }.inject { |x, y| x + y }.round(2)
48
+ end
49
+
50
+ def payment_id
51
+ Payment.where(check_number: check_number).get(:id)
52
+ end
53
+
54
+ def collate
55
+ puts "Total Amount Paid for this check is: #{total}\nProcessing..."
56
+ populated_data.each { |inv| self.receivable_csv(inv, payment_id, check_number, post_date) if inv.amount > 0.0 }
57
+ puts "Check added to #{LOCAL_FILES}/EDI-citywide-import.csv"
58
+ puts "Please note that there are #{denied_services} service days with possible denials"
59
+ end
60
+
61
+ def denied_services
62
+ Service.where(check_number: check_number).exclude(denial_reason: [nil, '']).count
63
+ end
64
+ end
65
+
66
+ class ManualPayment < CashReceipt
67
+ attr_reader :check, :invoices, :post_date
68
+
69
+ def initialize
70
+ @check, @post_date = self.check_number_and_date
71
+ @invoices = self.invoice_numbers
72
+ end
73
+
74
+ def payment_id
75
+ if check_exists?
76
+ Payment.where(check_number: check).get(:id)
77
+ else
78
+ Payment.insert(check_number: check)
79
+ end
80
+ end
81
+
82
+ def check_exists?
83
+ Payment.where(check_number: check).count > 0
84
+ end
85
+
86
+ def date
87
+ Date.strptime(post_date, '%m/%d/%Y')
88
+ end
89
+
90
+ def collate
91
+ invoices.each { |inv|
92
+ invoice_data(inv) { |invoice|
93
+ claim_id = create_claim(invoice)
94
+ create_services(invoice, claim_id)
95
+ edit_services(inv) if denial_present?(inv)
96
+ self.receivable_csv(invoice, payment_id, check, post_date)
97
+ }
98
+ }
99
+ end
100
+
101
+ def invoice_data(inv)
102
+ invoice = inv.gsub(/-d/, '').to_i
103
+ yield Invoice[invoice]
104
+ end
105
+
106
+ def create_claim(invoice)
107
+ Claim.insert(
108
+ :invoice_id => invoice.invoice_number,
109
+ :client_id => invoice.client_id,
110
+ :billed => invoice.amount,
111
+ :paid => 0.0,
112
+ :payment_id => payment_id,
113
+ :check_number => check,
114
+ :provider_id => invoice.provider_id
115
+ )
116
+ end
117
+
118
+ def create_services(invoice, claim_id)
119
+ visits(invoice).each { |visit|
120
+ Service.insert(
121
+ :invoice_id => visit.invoice_id,
122
+ :payment_id => payment_id,
123
+ :claim_id => claim_id,
124
+ :service_code => visit.service_code,
125
+ :paid => visit.amount,
126
+ :billed => visit.amount,
127
+ :dos => visit.dos,
128
+ :units => visit.units
129
+ )
130
+ }
131
+ end
132
+
133
+ def visits(invoice)
134
+ Visit.where(invoice_id: invoice.invoice_number).all
135
+ end
136
+
137
+
138
+
139
+ def services(inv)
140
+ Service.where(payment_id: payment_id, invoice_id: inv)
141
+ end
142
+
143
+ def edit_services(inv)
144
+ invoice = inv.gsub(/-d/, '').to_i
145
+ print "Select the day you wish to edit by the corresponding number followed by the adjusted amount\n"
146
+ print "When you are finished, type 'done'."
147
+ print "(e.g. 3451 23.50) Enter in the number now: "
148
+ loop do
149
+ services(invoice).all.each { |svc| puts "#{svc.id} #{svc.dos} #{svc.service_code} #{svc.modifier} #{svc.paid}" }
150
+ id, adjusted_amt = gets.chomp.split
151
+ if !id.nil?
152
+ print "Type in the denial reason now: "
153
+ denial_reason = gets.chomp
154
+ Service[id].update(paid: adjusted_amt, denial_reason: denial_reason)
155
+ else
156
+ break
157
+ end
158
+ end
159
+ end
160
+
161
+ def denial_present?(invoice)
162
+ invoice.include?('-d')
163
+ end
164
+
165
+ def visits(invoice)
166
+ Visit.where(invoice_id: invoice.invoice_number).all
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,69 @@
1
+ # Not implemented yet
2
+
3
+ module Sunnyside
4
+ def self.ics_file
5
+ Dir['data/icseop/*.csv'].each { |file| ICS.new(file).process }
6
+ end
7
+
8
+ class ICS
9
+ attr_reader :rows
10
+
11
+ def initialize(file)
12
+ p "processing #{file}..."
13
+ @rows = CSV.read(file)
14
+ end
15
+
16
+ def checks
17
+ rows.map { |row| row[0] }.uniq
18
+ end
19
+
20
+ def invoices(check)
21
+ rows.select { |row| row[0] == check }.map { |row| row[1] }.uniq.map { |inv| Invoice[inv] }
22
+ end
23
+
24
+ def paid(inv, check)
25
+ rows.select { |row| row[1].to_i == inv.get(:invoice_number) && row[0] == check }.map { |i| i[3].to_f }.inject { |x, y| x + y}.round(2)
26
+ end
27
+
28
+ def seed_claims
29
+ checks.each do |check|
30
+ payment_id = Payment.insert(check_number: check)
31
+ invoices(check).each { |invoice|
32
+ Claim.insert(
33
+ :payment_id => payment_id,
34
+ :invoice_id => invoice,
35
+ :client_name => invoice.get(:client_name),
36
+ :amount_charged => invoice.get(:amount),
37
+ :amount_paid => paid(invoice, check),
38
+ :provider => invoice.get(:provider) )
39
+ }
40
+ end
41
+ end
42
+
43
+ def process
44
+ seed_claims
45
+ rows.each { |row| ICSEop.new(row).write }
46
+ end
47
+ end
48
+
49
+ class ICSEop < ICS
50
+ attr_reader :check, :invoice, :charged, :paid, :dos, :service_code, :provider
51
+
52
+ def initialize(row)
53
+ @check, @invoice, @charged, @paid, @dos, @service_code = row
54
+ @provider = Provider[13]
55
+ end
56
+
57
+ def write
58
+ Service.insert(claim_id: claim_id, service_code: service_code, amount_charged: charged, amount_paid: paid, dos: date, check_number: check)
59
+ end
60
+
61
+ def date
62
+ Date.strptime(dos, '%m/%d/%Y')
63
+ end
64
+
65
+ def claim_id
66
+ Claim.where(invoice_number: invoice, check_number: check).get(:id)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,101 @@
1
+ module Sunnyside
2
+ def self.expiring_auth
3
+ Dir["data/exp auth/*.txt"].each do |file|
4
+ file = File.open(file)
5
+ data = file.read.split("\n").reject { |line| line =~ /\f/}
6
+ data.each { |line|
7
+ if line =~ /^.{27}\d{7}\s+\d{7}\s/
8
+ client = line.slice!(0..26)
9
+ line = line.split(' ').keep_if { |elem| elem.length >= 4 }
10
+ data = ExpiringAuth.new(file, client, line)
11
+ print "#{line}\n"
12
+ end
13
+ }
14
+ file.close
15
+ FileUtils.mv(file, "data/exp auth/previous/#{File.basename(file)}")
16
+ end
17
+ clients = Authorization.map(:client_number).uniq
18
+ clients.each { |client|
19
+ most_recent_auth = Authorization.where(client_number: client).exclude(auth: 'Blanket').order(:end_date).last
20
+ # that gets the most recent auth that isnt a blanket.
21
+ # visits = Visit.where(member_id: client).where('dos > ?', most_recent_auth.end_date) if !most_recent_auth.nil?
22
+ # puts "#{client} #{visits.count}" if !visits.nil?
23
+ # that gets all the visits that has that particular client number that is also GREATER than the most_recent_auth's end date
24
+ # client.each { |x| puts x.client }
25
+ auth_data = Authorization.where(client_number: client)
26
+ auth = auth_data.order(:end_date).all.last
27
+ AuthReport.new(auth_data, auth).create_csv
28
+ }
29
+ end
30
+
31
+ class ExpiringAuth
32
+ attr_reader :file, :line, :client_id, :auth, :start_date, :end_date, :service_id, :client
33
+
34
+ def initialize(file, client, line)
35
+ @file = file
36
+ @client = client.strip
37
+ @service_id, @client_id, @auth, @start_date, @end_date = line
38
+ if Authorization.where(auth: auth, client: client.strip).count == 0
39
+ process
40
+ end
41
+ end
42
+
43
+ def provider
44
+ if invoice.count > 0
45
+ invoice.all.last.provider
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ def invoice
52
+ Invoice.where(service_number: client_id) || Invoice.where(service_number: service_id)
53
+ end
54
+
55
+ def process
56
+ Authorization.insert(auth: auth, provider: provider, client: client, start_date: begin_date, end_date: final_date, client_number: client_id, service_number: service_id)
57
+ end
58
+
59
+ def begin_date
60
+ if start_date.nil?
61
+ Date.new
62
+ else
63
+ Date.strptime(start_date, '%m/%d/%y')
64
+ end
65
+ end
66
+
67
+ def final_date
68
+ if end_date.nil?
69
+ Date.new
70
+ else
71
+ Date.strptime(end_date, '%m/%d/%y')
72
+ end
73
+ end
74
+ end
75
+
76
+ class AuthReport
77
+ attr_reader :client_auth, :invoices, :visits, :auth
78
+
79
+ def initialize(client, auth)
80
+ @auth = auth
81
+ @client_auth = client
82
+ @visits = Visit.where(member_id: client.get(:client_number)).all.select { |visit| auth.end_date < visit.dos }
83
+ end
84
+
85
+ def create_csv
86
+ if expired?
87
+ CSV.open('auth_report', 'a+') { |row| row << ['EXPIRED', auth.client, auth.provider, auth.start_date, auth.end_date, auth.auth] }
88
+ if visits.count > 0
89
+ # puts visits
90
+ visits.each { |visit| CSV.open('auth_report', 'a+') { |row| row << ['' , visit.dos, visit.service_code, visit.units, visit.amount] } }
91
+ end
92
+ else
93
+ CSV.open('auth_report', 'a+') { |row| row << ['PENDING', auth.client, auth.provider, auth.start_date, auth.end_date, auth.auth] }
94
+ end
95
+ end
96
+
97
+ def expired?
98
+ auth.end_date < Date.today
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,97 @@
1
+ require "net/ftp"
2
+ require 'rubygems'
3
+
4
+ module Sunnyside
5
+ PROVIDERS = [
6
+ { :username => '****', :password => '****', :name => 'GUILDNET'},
7
+ { :username => '****', :password => '****', :name => 'ELDERSERVE'},
8
+ { :username => '****', :password => '****', :name => 'CPHL'}
9
+ ]
10
+ def self.access_ftp(process)
11
+ PROVIDERS.each { |provider|
12
+ access = SunnyFTP.new(provider)
13
+ access.log_on
14
+ puts "Logged into #{provider[:name]}..."
15
+ if process == :download
16
+ access.download_files
17
+ elsif process == :upload
18
+ access.upload_files
19
+ end
20
+ }
21
+ end
22
+
23
+ class SunnyFTP
24
+ attr_reader :ftp, :username, :password, :name, :directory
25
+
26
+ def initialize(provider = {})
27
+ @ftp = Net::FTP.new('depot.per-se.com')
28
+ @username = provider[:username]
29
+ @password = provider[:password]
30
+ @name = provider[:name]
31
+ end
32
+
33
+ def log_on
34
+ ftp.login(username, password)
35
+ end
36
+
37
+ def empty?(folder)
38
+ ftp.list(folder) == ["total 0"]
39
+ end
40
+
41
+ def outgoing_contents
42
+ ftp.nlst('../outgoing') if empty?('../outgoing')
43
+ end
44
+
45
+ def download_folder
46
+ ftp.chdir('../outgoing')
47
+ if !empty?('.')
48
+ puts "No files found. Exiting..."
49
+ ftp.close
50
+ return []
51
+ else
52
+ return ftp.nlst
53
+ end
54
+ end
55
+
56
+ def up_files
57
+ Dir["edi/outgoing/*.txt"]
58
+ end
59
+
60
+ def upload_files
61
+ up_files.each { |file|
62
+ if file.include?(name)
63
+ puts "uploading #{file} for #{name}"
64
+ ftp.putbinaryfile(file)
65
+ puts "Upload complete."
66
+ puts "deleting #{file} in local folder."
67
+ FileUtils.mv(file, "edi/outgoing/uploaded/#{File.basename(file)}")
68
+ end
69
+ }
70
+ ftp.close
71
+ end
72
+
73
+ def provider_folder
74
+ Dir["edi/incoming/#{name}"].map { |file| File.basename(file) }
75
+ end
76
+
77
+ def new_file?(file)
78
+ provider_folder.include?(file)
79
+ end
80
+
81
+ def download_files
82
+ download_folder.each do |file|
83
+ if !provider_folder.include?(file)
84
+ puts "Downloading #{file}..."
85
+ case file
86
+ when file.include?('TA1') then ftp.getbinaryfile(file, "edi/incoming/#{name}/TA1/#{file}")
87
+ when file.include?('997') then ftp.getbinaryfile(file, "edi/incoming/#{name}/997/#{file}")
88
+ else
89
+ ftp.getbinaryfile(file, "edi/incoming/#{name}/#{file}")
90
+ end
91
+ puts "#{file} placed."
92
+ end
93
+ end
94
+ ftp.close
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,110 @@
1
+ module Sunnyside
2
+
3
+ # REG LOC is the only repeating element that would indicate that there's a new client being read. So instead of reading the pdf
4
+ # report page by page, it would be best to compress the text from every page into a single string and then parse from there.
5
+
6
+ def self.parse_pdf
7
+ Dir["#{LOCAL_FILES}/837/*.PDF"].select { |file| Filelib.where(filename: file).count == 0 }.each do |file|
8
+ puts "processing #{file}..."
9
+ data = PDF::Reader.new(file).pages.map { |page| page.raw_content.gsub(/^\(\s|\)'$/, '') }.join
10
+ data.split(/^\((?=REG\s+LOC)/).each { |entry| ParseInvoice.new(entry).process }
11
+ Filelib.insert(filename: file, purpose: '837')
12
+ end
13
+ end
14
+
15
+ class ParseInvoice
16
+ attr_reader :client_line, :visits
17
+
18
+ def initialize(entry)
19
+ @client_line = entry.split(/\n/).select { |line| line =~ /(NY|\s+)\s+001/ }
20
+ @visits = entry.split(/\n/).select { |line| line =~ /^\d{6}/ }
21
+ end
22
+
23
+ def invoice_lines
24
+ visits.map { |inv|
25
+ InvoiceDetail.new(
26
+ client_data,
27
+ { :invoice => inv[0..5],
28
+ :svc_code => inv[18..22],
29
+ :modifier => inv[25..30],
30
+ :dos => inv[57..66],
31
+ :units => inv[69..75],
32
+ :amount => inv[79..88] }
33
+ )
34
+ }
35
+ end
36
+
37
+
38
+ def client_data
39
+ client_line.map { |line| ( line.slice(9..28) + line.slice(66..120) ).split }[0]
40
+ end
41
+
42
+ def process
43
+ invoice_lines.each { |inv| inv.to_db }
44
+ end
45
+
46
+ # only the invoices lines and the client info line gets selected and passed onto the next object level
47
+ end
48
+ class ClientData < ParseInvoice
49
+ attr_reader :client_id, :service_id, :recipient_id, :authorization
50
+
51
+ def initialize(client)
52
+ @client_id, @service_id, @recipient_id, @authorization = client.map { |line| line.strip }
53
+ end
54
+
55
+ def show_me
56
+ puts "#{client_id} #{service_id} #{recipient_id} #{authorization}"
57
+ end
58
+
59
+ def client_number
60
+ if client_exists?
61
+ client_id
62
+ else
63
+ insert_client
64
+ end
65
+ end
66
+
67
+ def client_exists?
68
+ Client.where(client_number: client_id).count > 0
69
+ end
70
+
71
+ def insert_client
72
+ Client.insert(client_number: client_id)
73
+ end
74
+ end
75
+
76
+ class InvoiceDetail < ParseInvoice
77
+ attr_reader :invoice, :service_code, :modifier, :dos, :units, :amount, :client
78
+
79
+ def initialize(client, invoice_line = {})
80
+ @client = ClientData.new(client)
81
+ @invoice = invoice_line[:invoice]
82
+ @service_code = invoice_line[:svc_code]
83
+ @modifier = invoice_line[:modifier]
84
+ @dos = invoice_line[:dos]
85
+ @units = invoice_line[:units]
86
+ @amount = invoice_line[:amount]
87
+ end
88
+
89
+ def show
90
+ puts "#{invoice} #{service_code} #{modifier} #{dos} #{units} #{amount} #{client.show_me}"
91
+ end
92
+
93
+ def to_db
94
+ Visit.insert(
95
+ :client_id => client.client_number,
96
+ :modifier => modifier,
97
+ :invoice_id => invoice,
98
+ :amount => amount,
99
+ :service_code => service_code,
100
+ :dos => Date.strptime(dos, '%m/%d/%y'),
101
+ :units => units
102
+ )
103
+ update_invoice
104
+ end
105
+
106
+ def update_invoice
107
+ Invoice[invoice].update(:authorization => client.authorization, :recipient_id => client.recipient_id)
108
+ end
109
+ end
110
+ end