-
Notifications
You must be signed in to change notification settings - Fork 13
/
efile_submission.rb
284 lines (243 loc) · 10.9 KB
/
efile_submission.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# == Schema Information
#
# Table name: efile_submissions
#
# id :bigint not null, primary key
# claimed_eitc :boolean
# data_source_type :string
# last_checked_for_ack_at :datetime
# message_tracker :jsonb
# created_at :datetime not null
# updated_at :datetime not null
# data_source_id :bigint
# irs_submission_id :string
# tax_return_id :bigint
#
# Indexes
#
# index_efile_submissions_on_created_at (created_at)
# index_efile_submissions_on_data_source (data_source_type,data_source_id)
# index_efile_submissions_on_irs_submission_id (irs_submission_id)
# index_efile_submissions_on_tax_return_id (tax_return_id)
# index_efile_submissions_on_tax_return_id_and_id (tax_return_id,id DESC)
#
class EfileSubmission < ApplicationRecord
belongs_to :tax_return, optional: true
belongs_to :data_source, polymorphic: true, optional: true
has_one :intake, through: :tax_return
has_one :client, through: :tax_return
has_one :fraud_score, class_name: "Fraud::Score"
has_many :qualifying_dependents, foreign_key: :efile_submission_id, class_name: "EfileSubmissionDependent"
has_one :verified_address, as: :record, dependent: :destroy, class_name: "Address"
has_many :efile_submission_transitions, -> { order(id: :asc) }, class_name: "EfileSubmissionTransition", autosave: false, dependent: :destroy
has_one_attached :submission_bundle
validates :irs_submission_id, format: { with: /\A[0-9]{6}[0-9]{7}[0-9a-z]{7}\z/ }, presence: true, uniqueness: true, allow_nil: true
include Statesman::Adapters::ActiveRecordQueries[
transition_class: EfileSubmissionTransition,
initial_state: EfileSubmissionStateMachine.initial_state,
]
scope :most_recent_by_current_year_tax_return, lambda {
joins(:tax_return).where("efile_submissions.id = (SELECT MAX(efile_submissions.id) FROM efile_submissions
WHERE efile_submissions.tax_return_id = tax_returns.id) AND year = ?", MultiTenantService.new(:ctc).current_tax_year)
}
scope :for_state_filing, lambda {
where(data_source_type: StateFile::StateInformationService.state_intake_class_names)
}
default_scope { order(id: :asc) }
def tax_year
return tax_return.year if tax_return
return data_source.tax_return_year if data_source
end
def state_machine
@state_machine ||= EfileSubmissionStateMachine.new(self, transition_class: EfileSubmissionTransition)
end
delegate :can_transition_to?, :current_state, :history, :last_transition, :last_transition_to,
:transition_to!, :transition_to, :in_state?, to: :state_machine
def self.state_counts(except: [])
result = {}
EfileSubmissionStateMachine.states.each { |state| result[state] = 0 }
ActiveRecord::Base.connection.execute(<<~SQL).each { |row| result[row['to_state']] = row['count'] }
SELECT to_state, COUNT(*) FROM "efile_submissions"
JOIN tax_returns ON ( efile_submissions.tax_return_id = tax_returns.id AND tax_returns.year = #{MultiTenantService.new(:ctc).current_tax_year} )
LEFT OUTER JOIN efile_submission_transitions AS most_recent_efile_submission_transition ON (
efile_submissions.id = most_recent_efile_submission_transition.efile_submission_id AND
most_recent_efile_submission_transition.most_recent = TRUE
)
WHERE most_recent_efile_submission_transition.to_state IS NOT NULL
GROUP BY to_state
SQL
result.except(*except)
end
def self.statefile_state_counts(except: [])
result = {}
EfileSubmissionStateMachine.states.each { |state| result[state] = 0 }
ActiveRecord::Base.connection.execute(<<~SQL).each { |row| result[row['to_state']] = row['count'] }
SELECT to_state, COUNT(*) FROM "efile_submissions"
LEFT OUTER JOIN efile_submission_transitions AS most_recent_efile_submission_transition ON (
efile_submissions.id = most_recent_efile_submission_transition.efile_submission_id AND
most_recent_efile_submission_transition.most_recent = TRUE
)
WHERE most_recent_efile_submission_transition.to_state IS NOT NULL
AND (
efile_submissions.data_source_type IN ('#{StateFile::StateInformationService.state_intake_class_names.join("','")}')
)
GROUP BY to_state
SQL
result.except(*except)
end
# If a federal tax return is rejected for a dependent SSN/Name Control mismatch,
# the return can be re-transmitted and accepted by the IRS if the Imperfect Return Election is made.
# This election can only be made if the original return rejected with reject code SEIC-F1040-501-02 or R0000-504-02.
def imperfect_return_resubmission?
return false unless previously_transmitted_submission.present?
previously_transmitted_submission.efile_submission_transitions.collect(&:efile_errors).flatten.any? { |error| ["SEIC-F1040-501-02", "R0000-504-02"].include? error.code }
end
def accepted_as_imperfect_return?
current_state == "accepted" && last_transition.metadata.key?("imperfect_return_acceptance")
end
def last_client_accessible_transition
# TODO: Simplify logic here so that we can show appropriate next steps for all cancelled returns
transitions = history.reverse
# Allow showing of cancelled state for clients who transitioned from fraud hold
if transitions[0]&.to_state == "cancelled" && transitions[1]&.to_state == "fraud_hold"
return transitions[0]
end
# We don't show the cancelled status HERE because it hides the instructions
# for how to handle your reject through cpaper filing.
transitions.find do |transition|
!EfileSubmissionStateMachine::CLIENT_INACCESSIBLE_STATUSES.include?(transition.to_state)
end
end
def resubmission?
previously_transmitted_submission.present?
end
def admin_resubmission?
reference_transition = last_transition_to("preparing")
reference_transition.present? && reference_transition.initiated_by.present? && reference_transition.initiated_by.admin?
end
def first_submission?
previous_submission_id.nil?
end
def previous_submission_id
transition = last_transition_to("preparing")
return unless transition.present?
transition.metadata["previous_submission_id"]
end
def previously_transmitted_submission
previous_submission = EfileSubmission.find(previous_submission_id) if previous_submission_id.present?
return nil unless previous_submission.present?
previous_submission if previous_submission.last_transition_to("transmitted").present?
end
def generate_verified_address(i = 0)
# TODO(state-file)
return OpenStruct.new(valid?: true) unless intake
return OpenStruct.new(valid?: true) if verified_address.present?
address_service = StandardizeAddressService.new(intake, read_timeout: 1500)
if address_service.timeout? && i < 2
generate_verified_address(i += 1)
end
if address_service.valid?
create_verified_address!(address_service.verified_address_attributes)
end
address_service # return the service object so that we can get errors if there are any
end
def benefits_eligibility
Efile::BenefitsEligibility.new(tax_return: tax_return, dependents: qualifying_dependents)
end
def pdf_bundle_filename
slug = irs_submission_id[6..] if irs_submission_id.present?
filename = "IRS 1040 - TY#{tax_return.year}"
filename += slug ? " - #{slug}.pdf" : ".pdf"
filename
end
def generate_filing_pdf
if is_for_federal_filing?
pdf_documents = SubmissionBuilder::Ty2021::Return1040.new(self).pdf_documents
output_file = Tempfile.new([pdf_bundle_filename, ".pdf"], "tmp/")
filled_out_documents = pdf_documents.map { |document| document.pdf.new(self, **document.kwargs).output_file }
PdfForms.new.cat(*filled_out_documents.push(output_file.path))
ClientPdfDocument.create_or_update(
output_file: output_file,
document_type: DocumentTypes::Form1040,
client: client,
filename: pdf_bundle_filename,
tax_return: tax_return
)
else
pdf_documents = bundle_class.new(self).pdf_documents
output_file = Tempfile.new(["tax_document", ".pdf"], "tmp/")
filled_out_documents = pdf_documents.map { |document| document.pdf.new(self, **document.kwargs).output_file }
PdfForms.new.cat(*filled_out_documents.push(output_file.path))
output_file
end
end
def manifest_class
if is_for_state_filing?
return SubmissionBuilder::StateManifest
end
SubmissionBuilder::FederalManifest
end
def bundle_class
if is_for_state_filing?
return StateFile::StateInformationService.submission_builder_class(data_source.state_code)
end
case tax_year
when 2020
SubmissionBuilder::Ty2020::Return1040
when 2021
SubmissionBuilder::Ty2021::Return1040
when 2022
SubmissionBuilder::Ty2021::Return1040
when 2023
SubmissionBuilder::Ty2021::Return1040
end
end
##
# Re-enqueue the submission to handle a temporary error.
#
# If the submission was enqueued more than 1 day ago, give up and transition to :failed.
# Always grabs the latest transition to queued so in cases where a user resubmits, we begin the 1 day expiration at that point.
#
# This method assumes the submission has already been transitioned to :queued. If the transition
# was never enqueued, transition to :failed to cover the programming error.
def retry_send_submission
now = DateTime.now.utc
queued_at = efile_submission_transitions.where(to_state: "queued").pluck(:created_at).max
if queued_at.nil?
transition_to!(:failed, error_code: "TRANSMISSION-SERVICE", raw_response: "Unable to retry_send_submission because submission was never queued.")
return
end
age = now.to_time - queued_at.to_time
if age > 1.days
transition_to!(:failed, error_code: "TRANSMISSION-SERVICE", raw_response: "Deadline exceeded when retrying send submission. Waited for about 1 day.")
return
end
max_backoff = 60.minutes
backoff =
if age > max_backoff
max_backoff
else
age ** 1.25
end
retry_wait = backoff + SecureRandom.rand(30)
StateFile::SendSubmissionJob.set(wait_until: now + retry_wait).perform_later(self)
end
def generate_irs_submission_id!(i = 0)
return if self.irs_submission_id.present?
raise "Max irs_submission_id attempts exceeded. Too many submissions today?" if i > 5
efin = EnvironmentCredentials.irs(:efin)
irs_submission_id = "#{efin}#{Date.current.strftime('%C%y%j')}#{SecureRandom.base36(7)}"
if self.class.find_by(irs_submission_id: irs_submission_id)
i += 1
generate_irs_submission_id!(i)
else
self.update!(irs_submission_id: irs_submission_id)
end
end
def is_for_federal_filing?
tax_return.present?
end
def is_for_state_filing?
data_source_type.in?(StateFile::StateInformationService.state_intake_class_names)
end
end