· 9 years ago · Sep 30, 2016, 11:04 PM
1package com.hummingbird.ejb;
2
3import java.io.File;
4import java.io.IOException;
5import java.io.StringReader;
6import java.math.BigDecimal;
7import java.math.BigInteger;
8import java.nio.file.Paths;
9import java.sql.CallableStatement;
10import java.sql.Connection;
11import java.sql.ResultSet;
12import java.sql.SQLException;
13import java.sql.Types;
14import java.text.DateFormat;
15import java.text.ParseException;
16import java.text.SimpleDateFormat;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Calendar;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.Date;
24import java.util.EnumSet;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31import java.util.TimeZone;
32import java.util.concurrent.Callable;
33import java.util.concurrent.ConcurrentLinkedQueue;
34import java.util.concurrent.ExecutionException;
35import java.util.concurrent.ExecutorService;
36import java.util.concurrent.Executors;
37import java.util.concurrent.Future;
38import java.util.logging.Level;
39import java.util.logging.Logger;
40
41import javax.annotation.Resource;
42import javax.ejb.EJB;
43import javax.ejb.LocalBean;
44import javax.ejb.SessionContext;
45import javax.ejb.Stateless;
46import javax.ejb.TransactionAttribute;
47import javax.ejb.TransactionAttributeType;
48import javax.mail.MessagingException;
49import javax.persistence.EntityManager;
50import javax.persistence.NoResultException;
51import javax.persistence.PersistenceContext;
52import javax.persistence.Query;
53import javax.persistence.TypedQuery;
54import javax.sql.DataSource;
55import javax.transaction.TransactionSynchronizationRegistry;
56import javax.xml.XMLConstants;
57import javax.xml.bind.JAXB;
58import javax.xml.bind.JAXBContext;
59import javax.xml.bind.JAXBElement;
60import javax.xml.bind.JAXBException;
61import javax.xml.bind.Marshaller;
62import javax.xml.bind.ValidationEvent;
63import javax.xml.bind.ValidationEventHandler;
64import javax.xml.namespace.QName;
65import javax.xml.transform.stream.StreamSource;
66import javax.xml.validation.Schema;
67import javax.xml.validation.SchemaFactory;
68
69import org.apache.commons.collections.CollectionUtils;
70import org.apache.commons.io.FileUtils;
71import org.apache.commons.lang.StringUtils;
72import org.apache.commons.lang.time.DateUtils;
73import org.hibernate.SQLQuery;
74import org.hibernate.transform.Transformers;
75import org.hibernate.type.BigIntegerType;
76import org.hibernate.type.TimestampType;
77import org.joda.time.LocalDate;
78import org.springframework.security.core.Authentication;
79import org.springframework.security.core.context.SecurityContext;
80import org.springframework.security.core.context.SecurityContextHolder;
81import org.xml.sax.SAXException;
82import org.xml.sax.helpers.DefaultHandler;
83
84import com.github.raycw.edidom.common.Document;
85import com.github.raycw.edidom.common.Segment;
86import com.github.raycw.edidom.input.WrongDocuemntFormatException;
87import com.github.raycw.edidom.input.X12Builder;
88import com.google.common.annotations.VisibleForTesting;
89import com.google.common.collect.ImmutableSet;
90import com.hummingbird.ejb.accessability.AccessAbilityService;
91import com.hummingbird.ejb.pasbe.client.PASBillingReportException;
92import com.hummingbird.ejb.pasbe.client.PasbeServiceClient;
93import com.hummingbird.ejb.resources.jaxb.crossbill.CarrierRate;
94import com.hummingbird.ejb.resources.jaxb.crossbill.ClaimBatch;
95import com.hummingbird.ejb.resources.jaxb.crossbill.Diagnosis;
96import com.hummingbird.ejb.resources.jaxb.crossbill.IndexRate;
97import com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory;
98import com.hummingbird.ejb.resources.jaxb.crossbill.Patient.RoomBoardRecs;
99import com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim;
100import com.hummingbird.ejb.resources.jaxb.crossbill.RoomAndBoard;
101import com.hummingbird.ejb.resources.jaxb.crossbill.ServiceFacility;
102import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport;
103import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill;
104import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.CoveredDays;
105import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.Footer;
106import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.Footer.PrimeCarrier;
107import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.Footer.SecondaryCarrier;
108import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.MedLine;
109import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.Occurrences.Occurrence;
110import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.RevLine;
111import com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.SvcLine;
112import com.hummingbird.ejb.security.UserService;
113import com.hummingbird.ejb.system.SystemDataService;
114import com.hummingbird.ejb.utils.ClaimUtil;
115import com.hummingbird.ejb.utils.ClaimsGenerationCache;
116import com.hummingbird.ejb.utils.PersonUtil;
117import com.hummingbird.jpa.BillLog;
118import com.hummingbird.jpa.DischargeDayBillingRule;
119import com.hummingbird.jpa.DischargeReason;
120import com.hummingbird.jpa.ElectionPeriod;
121import com.hummingbird.jpa.Eligibility;
122import com.hummingbird.jpa.FileScheduler;
123import com.hummingbird.jpa.Hospice;
124import com.hummingbird.jpa.InsuranceCarrier;
125import com.hummingbird.jpa.InsuranceCarrierRate;
126import com.hummingbird.jpa.InsuranceCarrierRates;
127import com.hummingbird.jpa.InsuranceCarrierSiteCredentials;
128import com.hummingbird.jpa.InsuranceCoverage;
129import com.hummingbird.jpa.MedicareIntermediaryInfo;
130import com.hummingbird.jpa.Noa;
131import com.hummingbird.jpa.Office;
132import com.hummingbird.jpa.Patient;
133import com.hummingbird.jpa.PatientPersonRelation;
134import com.hummingbird.jpa.PatientPhysician;
135import com.hummingbird.jpa.Person;
136import com.hummingbird.jpa.Phone;
137import com.hummingbird.jpa.PriorAuthorization;
138import com.hummingbird.jpa.ProgramSite;
139import com.hummingbird.jpa.Role;
140import com.hummingbird.jpa.RoleGroup;
141import com.hummingbird.jpa.RoleRate;
142import com.hummingbird.jpa.Site;
143import com.hummingbird.jpa.claims.Claim;
144import com.hummingbird.jpa.claims.ClaimMedication;
145import com.hummingbird.jpa.claims.ClaimPatientVisit;
146import com.hummingbird.jpa.claims.ClaimRevenueDetail;
147import com.hummingbird.jpa.claims.ClaimTransaction;
148import com.hummingbird.jpa.interaction.PatientDiagnoses;
149import com.hummingbird.jpa.interaction.PatientFacility;
150import com.hummingbird.jpa.interaction.PatientInteraction;
151import com.hummingbird.jpa.interaction.PatientLevelOfCare;
152import com.hummingbird.jpa.interaction.PatientStatus;
153import com.hummingbird.jpa.interaction.PatientVisit;
154import com.hummingbird.jpa.interaction.PhysicianService;
155import com.hummingbird.jpa.masters.Facility;
156import com.hummingbird.jpa.masters.FacilityType;
157import com.hummingbird.jpa.masters.MedicareIntermediary;
158import com.hummingbird.jpa.medication.PatientMedication;
159import com.hummingbird.jpa.reference.BillingRules;
160import com.hummingbird.jpa.reference.RateCode;
161import com.hummingbird.jpa.reference.RateCodeGroup;
162import com.hummingbird.jpa.reference.StateBillingRules;
163import com.hummingbird.jpa.reference.WageIndex;
164import com.hummingbird.jpa.reference.Zipcode;
165import com.hummingbird.jpa.util.HBMultiTenantConnectionProvider;
166import com.hummingbird.server.HospiceClaimInfo;
167import com.hummingbird.server.PatientClaimInfo;
168import com.hummingbird.server.util.io.HBFileUtils;
169import com.hummingbird.shared.dto.Address;
170import com.hummingbird.shared.dto.CareType;
171import com.hummingbird.shared.dto.ClaimsHeaderDTO;
172import com.hummingbird.shared.dto.DateRange;
173import com.hummingbird.shared.dto.FileArchiveDTO;
174import com.hummingbird.shared.dto.FileProcessType;
175import com.hummingbird.shared.dto.InsurancePriorityType;
176import com.hummingbird.shared.dto.InteractionType;
177import com.hummingbird.shared.dto.LevelOfCareDTO;
178import com.hummingbird.shared.dto.LevelOfCareType;
179import com.hummingbird.shared.dto.NoaStatus;
180import com.hummingbird.shared.dto.NoaTypeOfBill;
181import com.hummingbird.shared.dto.OfficeDTO;
182import com.hummingbird.shared.dto.PatientLocation;
183import com.hummingbird.shared.dto.PatientPhysicianType;
184import com.hummingbird.shared.dto.PayerType;
185import com.hummingbird.shared.dto.PhoneType;
186import com.hummingbird.shared.dto.ProgramDTO;
187import com.hummingbird.shared.dto.ProgramEnum;
188import com.hummingbird.shared.dto.SystemRoleGroups;
189import com.hummingbird.shared.dto.SystemRoles;
190import com.hummingbird.shared.dto.TypeOfCare;
191import com.hummingbird.shared.dto.VisitBillingType;
192import com.hummingbird.shared.dto.bill.AdmissionArcDTO;
193import com.hummingbird.shared.dto.bill.BatchStatus;
194import com.hummingbird.shared.dto.bill.BillDTO;
195import com.hummingbird.shared.dto.bill.BillMedicationDTO;
196import com.hummingbird.shared.dto.bill.BillPatientDTO;
197import com.hummingbird.shared.dto.bill.BillRevenueLineDTO;
198import com.hummingbird.shared.dto.bill.BillingExportOptions;
199import com.hummingbird.shared.dto.bill.BillingOptions;
200import com.hummingbird.shared.dto.bill.ClaimBatchDTO;
201import com.hummingbird.shared.dto.bill.ClaimStatus;
202import com.hummingbird.shared.dto.bill.EdiBatchDTO;
203import com.hummingbird.shared.dto.bill.EdiClaimDTO;
204import com.hummingbird.shared.dto.bill.IndividualClaimDTO;
205import com.hummingbird.shared.dto.bill.OccurrenceDTO;
206import com.hummingbird.shared.dto.bill.PatientFacilityArcDTO;
207import com.hummingbird.shared.dto.bill.ProcedureDTO;
208import com.hummingbird.shared.dto.bill.RepostType;
209import com.hummingbird.shared.dto.bill.RevenueDetailDTO;
210import com.hummingbird.shared.dto.bill.ReviewClaimReason;
211import com.hummingbird.shared.dto.bill.VisitLineDTO;
212import com.hummingbird.shared.dto.certifications.ElectionPeriodDTO;
213import com.hummingbird.shared.dto.timers.TimerServiceResponse;
214import com.hummingbird.shared.util.HBUtil;
215import com.hummingbird.util.CommonUtil;
216import com.hummingbird.util.CurrencyUtil;
217import com.hummingbird.util.date.DateEncoder;
218import com.hummingbird.util.date.DateUtil;
219import com.hummingbird.util.edi.EdiUtil;
220
221@Stateless
222@LocalBean
223public class BillingService implements BillingServiceLocal {
224 private static final Logger log = Logger.getLogger(BillingService.class.getName());
225 private static final DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy H:mm:ss.S zZZ");
226
227 private static final String Q5006 = "Q5006";
228 private static List<String> qCodes = Arrays.asList(new String[] { "Q5003", "Q5004", "Q5005", "Q5006", "Q5007",
229 "Q5008" });
230 private static List<String> ipuCodes = Arrays.asList(new String[] { "Q5006", "Q5008" });
231
232 @PersistenceContext(unitName = "hummingbird-jta")
233 private EntityManager em;
234
235 @PersistenceContext(unitName = "hummingbird-reference-jta")
236 private EntityManager emRef;
237
238 @Resource
239 private SessionContext sessionContext;
240
241 @Resource
242 TransactionSynchronizationRegistry tsr;
243
244 @EJB
245 Auth auth;
246
247 @EJB
248 InteractionService interactionService;
249
250 @EJB
251 MultiTenantService multiTenantService;
252
253 @EJB
254 LevelOfCareService locService;
255
256 @EJB
257 PatientService patientService;
258
259 @EJB
260 ListManagementService adminService;
261
262 @EJB
263 RateCodeService rateCodeService;
264
265 @EJB
266 InsuranceCoverageService insService;
267
268 @EJB
269 ElectionService electionService;
270
271 @EJB
272 SystemDataService systemDataService;
273
274 @EJB
275 FacilityService facilityService;
276
277 @EJB
278 ConfigService configService;
279
280 @EJB
281 FinanceModuleService financeService;
282
283 @EJB
284 UserService userService;
285
286 @EJB
287 PersistenceService persistenceService;
288
289 @EJB
290 HospiceService hospiceService;
291
292 @EJB
293 SearchService searchService;
294
295 @EJB
296 BillingService self;
297
298 @EJB
299 FileSchedulerService fileSchedulerService;
300
301 @EJB
302 AccessAbilityService accessAbilityService;
303
304 /**
305 * All billing operations should be using the patient's site tz
306 *
307 * @return
308 */
309 @Deprecated
310 private TimeZone getUserTz() {
311 return (auth.getCurrentUser() == null ? DateUtil.UTC_ZONE : auth.getUserTimeZone());
312 }
313
314 @Deprecated
315 public List<BillRevenueLineDTO> createRevLines(Date monthDate, List<LevelOfCareDTO> locList) {
316 List<BillRevenueLineDTO> revLines = new ArrayList<BillRevenueLineDTO>();
317 DateFormat df = SimpleDateFormat.getDateInstance();
318
319 // Set to the first day of the month
320 Calendar cal = Calendar.getInstance(DateUtil.UTC_ZONE);
321 cal.setTime(DateUtil.dateOnlyUtc(monthDate));
322 cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
323 Date lastDayOfMonth = cal.getTime();
324
325 if (lastDayOfMonth.after(DateUtil.getTodayDateOnly(DateUtil.UTC_ZONE))) {
326 lastDayOfMonth = DateUtil.getTodayDateOnly(DateUtil.UTC_ZONE);
327 }
328
329 cal.set(Calendar.DAY_OF_MONTH, 1);
330 Date firstDayOfMonth = cal.getTime();
331
332 log.fine("CRL monthDate: " + dateFormat.format(monthDate) + ", results in FirstDayOfMonth: "
333 + dateFormat.format(firstDayOfMonth) + ", lastDayOfMonth: " + dateFormat.format(lastDayOfMonth));
334
335 // Sort the LOC records by start date
336 List<LevelOfCareDTO> sLocList = new ArrayList<LevelOfCareDTO>(locList);
337 Collections.sort(sLocList, new Comparator<LevelOfCareDTO>() {
338
339 @Override
340 public int compare(LevelOfCareDTO o1, LevelOfCareDTO o2) {
341 return o1.getStartDate().compareTo(o2.getStartDate());
342 }
343 });
344
345 // Consume all the LOCs that end before the beginning of the month
346 while (sLocList.size() > 0 && sLocList.get(0).getEndDate() != null
347 && DateUtil.dateOnlyUtc(sLocList.get(0).getEndDate()).before(firstDayOfMonth)) {
348 log.fine("Trimming early LOC: " + df.format(sLocList.get(0).getStartDate()) + " -- "
349 + df.format(sLocList.get(0).getEndDate()));
350 sLocList.remove(0);
351 }
352
353 // if there is no LOC, then there are no rev lines.
354 if (sLocList.size() <= 0) {
355 log.fine("No valid LOCs for patient during selected month");
356 return revLines;
357 }
358
359 // if the first LOC doesn't start until later in the month, then start
360 // then, not on the 1st
361 int day = 1;
362 if (DateUtil.dateOnlyUtc(sLocList.get(0).getStartDate()).after(firstDayOfMonth)) {
363 Calendar stCal = Calendar.getInstance(DateUtil.UTC_ZONE);
364 stCal.setTime(sLocList.get(0).getStartDate());
365
366 log.fine("late start: " + df.format(stCal.getTime()));
367 day = stCal.get(Calendar.DAY_OF_MONTH);
368 cal.set(Calendar.DAY_OF_MONTH, day);
369 }
370
371 // Deal with the possibility that the patient died or was discharged
372 // during this month
373 int lastLocDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH) + 1;
374
375 Date lastLocDate = sLocList.get(sLocList.size() - 1).getEndDate();
376 if (lastLocDate != null && lastLocDate.before(lastDayOfMonth)) {
377 log.fine("LastLoc day is: " + df.format(lastLocDate));
378
379 // We will have to end before the eom
380 Calendar locCal = Calendar.getInstance(DateUtil.UTC_ZONE);
381 locCal.setTime(lastLocDate);
382 lastLocDay = locCal.get(Calendar.DAY_OF_MONTH) + 1;
383 }
384
385 // make sure we do not show rev lines from future dates
386 Calendar endCal = Calendar.getInstance(DateUtil.UTC_ZONE);
387 endCal.setTime(DateUtil.dateOnlyUtc(monthDate));
388 endCal.set(Calendar.DAY_OF_MONTH, lastLocDay);
389 if (endCal.getTime().after(DateUtil.getTodayDateOnly(DateUtil.UTC_ZONE))) {
390 endCal.setTime(DateUtil.getTodayDateOnly(DateUtil.UTC_ZONE));
391 lastLocDay = endCal.get(Calendar.DAY_OF_MONTH) + 1;
392 }
393
394 // Save the "first" LOC and pop it.
395 LevelOfCareDTO curLoc = sLocList.remove(0);
396
397 Date revStartDate = cal.getTime();
398 int weekNum = cal.get(Calendar.WEEK_OF_MONTH);
399
400 // This scan intentionally runs one day ahead so it can see whether
401 // the next week or the next LOC has started. That is why the end
402 // date must be calculated by backing off a day.
403 Date revEndDate = null;
404 log.fine("Running Rev Scan in month: " + dateFormat.format(cal.getTime()) + " from day: " + day + " to "
405 + lastLocDay);
406 while (day <= lastLocDay) {
407 cal.set(Calendar.DAY_OF_MONTH, day);
408
409 boolean endOfRev = false;
410 boolean endOfLoc = false;
411 if (sLocList.size() > 0 && DateUtil.shortFormatEquals(sLocList.get(0).getStartDate(), cal.getTime())) {
412 // the next LOC has started
413 endOfRev = true;
414 endOfLoc = true;
415 } else if (day == lastLocDay) // this is the end of the period
416 endOfRev = true;
417
418 if (cal.get(Calendar.WEEK_OF_MONTH) > weekNum) {
419 // End of week
420 endOfRev = true;
421 weekNum = cal.get(Calendar.WEEK_OF_MONTH);
422 }
423
424 if (endOfRev) {
425 // It is the end of the revenue period. Create a rev line
426 // and reset
427
428 cal.add(Calendar.DAY_OF_MONTH, -1); // back off a day
429 revEndDate = cal.getTime();
430 cal.add(Calendar.DAY_OF_MONTH, 1); // restore from back off
431
432 BillRevenueLineDTO revLine = new BillRevenueLineDTO();
433 revLine.setStartDate(revStartDate);
434 revLine.setEndDate(revEndDate);
435 revLine.setUnits(calculateRevLineServiceUnits(curLoc.getTypeOfCare(), revStartDate, revEndDate));
436
437 // if there is no curLoc, then there is no level of care for
438 // this rev line
439 if (curLoc != null) {
440 revLine.setTypeOfCare(curLoc.getTypeOfCare());
441 revLine.setFacilityName(CommonUtil.scrubNames(curLoc.getLocation()));
442 revLine.setFacilityId(curLoc.getFacilityId());
443 if (curLoc.getTypeOfCare() == LevelOfCareType.GENERAL_IN_PATIENT) {
444 revLine.setFacTypeQ5006(isFacTypeQ5006(curLoc.getFacilityId()));
445 }
446 }
447
448 revLines.add(revLine);
449
450 // reset and continue
451 cal.set(Calendar.DAY_OF_MONTH, day);
452 revStartDate = cal.getTime();
453 revEndDate = null;
454
455 // If we have moved into a new LOC, then pop the next one
456 if (endOfLoc)
457 curLoc = sLocList.remove(0);
458 }
459
460 day++;
461 }
462
463 return revLines;
464 }
465
466 @VisibleForTesting
467 @Deprecated
468 public double calculateRevLineServiceUnits(LevelOfCareType typeOfCare, Date startDate, Date endDate) {
469 double returnValue = 0;
470
471 if (startDate == null)
472 return 0;
473
474 if (endDate == null)
475 endDate = new Date();
476
477 if (LevelOfCareType.CONTINUOUS_CARE.equals(typeOfCare)) {
478 long timeDiffInMilliSeconds = endDate.getTime() - startDate.getTime();
479 returnValue = Math.round(timeDiffInMilliSeconds / 60000.0 / 15.0);
480 } else {
481 returnValue = DateUtil.daysBetween(startDate, endDate) + 1;
482 }
483
484 return returnValue;
485 }
486
487 public BigDecimal calculateTotalReceivable(BillPatientDTO billPatientDto) {
488 BigDecimal totalReceivable = BigDecimal.ZERO;
489
490 List<BillRevenueLineDTO> revLines = billPatientDto.getRevenueLines();
491
492 if (revLines != null && !revLines.isEmpty())
493 for (BillRevenueLineDTO revLine : revLines)
494 totalReceivable = totalReceivable.add(revLine.getCharges());
495
496 return totalReceivable;
497 }
498
499 @Deprecated
500 public List<RevenueDetailDTO> createBillVisitLines(LevelOfCareType loc, Long facilityId,
501 List<PatientVisit> patVisitList, BillPatientDTO billPat) {
502 List<RevenueDetailDTO> billVisList = new ArrayList<RevenueDetailDTO>();
503
504 VisitLineDTO billVisGIP = new VisitLineDTO();
505
506 BigDecimal visits = BigDecimal.ZERO, sumOfCharges = BigDecimal.ZERO;
507
508 RoleGroup drRoleGroup = systemDataService.findSystemRoleGroup(SystemRoleGroups.DOCTOR);
509 Role lpnRole = systemDataService.findSystemRole(SystemRoles.NURSE_PRACTITIONER);
510
511 for (PatientVisit patVisit : patVisitList) {
512 VisitLineDTO billVis = null;
513 double units = 0.0;
514 Date startDate = DateUtil.convertDateTz(patVisit.getInteraction().getEffectiveDate(), getUserTz(),
515 DateUtil.UTC_ZONE);
516
517 Role actedRole = findActedRole(patVisit);
518 boolean isDrVisit = (actedRole != null && actedRole.getRoleGroup().getId().equals(drRoleGroup.getId()));
519 boolean isLPNVisit = (actedRole != null && actedRole.getId().equals(lpnRole.getId()));
520
521 // Check if this is a Postmortem visit
522 boolean postMortem = billPat.getDeathDateTime() != null
523 && patVisit.getInteraction().getEffectiveDate().after(billPat.getDeathDateTime());
524
525 if (loc == LevelOfCareType.GENERAL_IN_PATIENT && !postMortem && !isDrVisit) {
526 billVis = billVisGIP;
527 if (visits.compareTo(BigDecimal.ZERO) == 0) {
528 billVisList.add(billVis);
529 } else {
530 // reuse start date if we don't create a new vis.
531 startDate = billVisGIP.getStartDate();
532 }
533 visits = visits.add(BigDecimal.valueOf(1));
534 units = visits.doubleValue();
535 } else {
536 billVis = new VisitLineDTO();
537 // Take the time difference and divide by 15 minute increments
538 if (patVisit.getDuration() != null && patVisit.getDuration() > 0) {
539 units = patVisit.getDuration() / 15.0;
540 }
541
542 billVisList.add(billVis);
543 billVis.setPostMortemVisit(postMortem);
544 }
545
546 billVis.setStartDate(startDate);
547 billVis.setUnits(Math.round(units));
548 billVis.setDrVisit(isDrVisit);
549 billVis.setLpnVisit(isLPNVisit);
550
551 RoleRate rate = null;
552 if (actedRole != null) {
553 String revCode = null;
554 if (patVisit.getInteraction().getType() == InteractionType.PHONE_CALL)
555 revCode = actedRole.getRoleGroup().getPhoneCallRevCode();
556 else if (patVisit.getInteraction().getType() == InteractionType.DIRECT_CARE)
557 revCode = actedRole.getRoleGroup().getDirectCareRevCode();
558
559 billVis.setDescription(actedRole.getRoleGroup().getName());
560 billVis.setHcpcsCode(actedRole.getRoleGroup().getHcpcsCode());
561 billVis.setRevenueCode(revCode);
562 rate = findRateForRole(actedRole, revCode, patVisit.getInteraction().getType(), patVisit
563 .getInteraction().getEffectiveDate());
564 if (rate != null) {
565 BigDecimal charges = rate.getHourlyRate().multiply(BigDecimal.valueOf(billVis.getUnits() / 4.0));
566 if (loc == LevelOfCareType.GENERAL_IN_PATIENT && !postMortem && !billVis.isDrVisit()) {
567 sumOfCharges = sumOfCharges.add(charges);
568 billVis.setCharges(sumOfCharges);
569 } else
570 billVis.setCharges(charges);
571 }
572 }
573 if (billVis.isPostMortemVisit())
574 billVis.setHcpcsCode(billVis.getHcpcsCode() + " PM");
575 setProcedures(patVisit, billVis, rate);
576 }
577
578 return billVisList;
579 }
580
581 @Deprecated
582 private Role findActedRole(PatientVisit patVisit) {
583 Role actedRole = patVisit.getInteraction().getRole();
584
585 if (actedRole == null && patVisit.getInteraction().getCareProvider() != null) {
586 actedRole = patVisit.getInteraction().getCareProvider().getPrimaryRole();
587 if (actedRole == null && patVisit.getInteraction().getCareProvider().getPersonRoles().size() > 0) {
588 actedRole = patVisit.getInteraction().getCareProvider().getPersonRoles().get(0).getRole();
589 }
590 }
591 return actedRole;
592 }
593
594 @Deprecated
595 public void setProcedures(PatientVisit visit, VisitLineDTO visitLine, RoleRate rate) {
596
597 List<PhysicianService> services = (visit != null && visit.getId() != null) ? interactionService
598 .findPhysicianServices(visit.getId()) : new ArrayList<PhysicianService>();
599
600 // if the visit has procedures/services, zero out the visit's charges;
601 // they'll be the accumulation of the charges of all the procedures
602 // instead
603 if (!services.isEmpty()) {
604 visitLine.setCharges(BigDecimal.ZERO);
605 }
606
607 for (PhysicianService service : services) {
608
609 ProcedureDTO proc = new ProcedureDTO();
610
611 boolean npFlag = (rate != null && rate.getRole() != null && (SystemRoles.NURSE_PRACTITIONER.toString()
612 .equals(rate.getRole().getName()) || SystemRoles.LICENSED_PRACTICAL_NURSE.toString().equals(
613 rate.getRole().getName())));
614
615 proc.setCharges(service.getRate());
616 proc.setProcedureCode(service.getCode(), npFlag);
617 proc.setDescription(service.getDescription());
618 proc.setStartDate(visitLine.getStartDate());
619
620 visitLine.getNestedLines().add(proc);
621 visitLine.setCharges(visitLine.getCharges().add(service.getRate()));
622 }
623 }
624
625 @Deprecated
626 public RoleRate findRateForRole(Role role, String revCode, InteractionType encType, Date effectiveDate) {
627 CareType careType;
628 if (encType == InteractionType.DIRECT_CARE)
629 careType = CareType.Direct;
630 else if (encType == InteractionType.PHONE_CALL)
631 careType = CareType.Phone;
632 else
633 return null;
634
635 TypedQuery<RoleRate> query = em.createNamedQuery("RoleRate.findEffectiveRate", RoleRate.class);
636 query.setParameter("roleId", role.getId());
637 query.setParameter("revCode", revCode);
638 query.setParameter("effectiveDate", effectiveDate);
639 query.setParameter("careType", careType);
640
641 List<RoleRate> rates = query.getResultList();
642 if (rates != null && rates.size() > 0)
643 return rates.get(0);
644 else
645 return null;
646 }
647
648 /**
649 * Get the service facility NPI based on a given patient, and billing dates
650 *
651 * @param billPat
652 * @param startDate
653 * @param endDate
654 * @return
655 */
656 public String getServiceFacilityNPI(BillPatientDTO billPat, Date startDate, Date endDate) {
657 String serviceFacilityNPI = "";
658
659 TypedQuery<PatientFacility> query = em.createNamedQuery("PatientFacility.findRecentFacilitiesByDate",
660 PatientFacility.class);
661 query.setParameter("patientId", billPat.getId());
662 query.setParameter("date", endDate);
663 List<PatientFacility> recentFacs = query.getResultList();
664
665 Patient pat = null;
666 boolean beforeStartDate = false;
667 if (recentFacs != null && !recentFacs.isEmpty()) {
668 // iterate through each facility. we stop once we get to the first
669 // one before the start date
670
671 for (PatientFacility recentFac : recentFacs) {
672
673 // we've already gotten the facility prior to the bill period
674 if (beforeStartDate)
675 break;
676
677 // if this is the last facility before the billing period
678 if (recentFac.getInteraction().getEffectiveDate().before(startDate))
679 beforeStartDate = true;
680
681 Facility fac = em.find(Facility.class, recentFac.getFacility().getId());
682
683 // Patient's home - don't use
684 if (PatientLocation.HOME.equals(fac.getName())) {
685 continue;
686 } else {
687 // not patient's home. see if it has the Q-code we need
688 List<FacilityType> types = fac.getFacilityTypes();
689 boolean acceptableType = false;
690 boolean ipu = false;
691
692 List<String> facQcodes = new ArrayList<String>();
693 for (FacilityType t : types) {
694 facQcodes.add(t.getQCode());
695 }
696
697 List<String> facIPUcodes = new ArrayList<String>(facQcodes);
698 facIPUcodes.retainAll(ipuCodes);
699 ipu = !facIPUcodes.isEmpty();
700 facQcodes.retainAll(qCodes);
701 if (!facQcodes.isEmpty())
702 acceptableType = true;
703
704 // It's an IPU - see if it's not hospice-owned
705 if (ipu) {
706
707 if (pat == null)
708 pat = em.find(Patient.class, billPat.getId());
709 if (pat.getProgramSite() == null) {
710 // This shouldn't happen - log a warning
711 log.warning("Patient " + pat.getId() + " has a programSite that's null");
712 continue;
713 }
714 if (!pat.getProgramSite().getNpi().equals(fac.getNpi())) {
715 // Facility is not hospice owned - use it
716 serviceFacilityNPI = fac.getNpi();
717 break;
718 }
719 } else if (acceptableType) {
720 // has the Q-codes we need - use it
721 serviceFacilityNPI = fac.getNpi();
722 break;
723 }
724
725 }
726 }
727 }
728 return serviceFacilityNPI;
729 }
730
731 @Override
732 public List<String> getValueCodes(Long patientId, Date startDate, Date endDate, TimeZone userTz) {
733
734 Set<String> valueCodes = new HashSet<String>();
735 List<String> resultList = new ArrayList<String>();
736
737 String cbsaCodeG8 = null;
738 String cbsaCode61 = null;
739
740 /*
741 * We will go thru the interactions most-recent first.
742 *
743 * 1) If a patient was in routine or CC, document where the patient was
744 * at the end of the billing period
745 *
746 * 2) If a patient was in GIP or respite, document where the patient
747 * last received that care.
748 */
749
750 List<PatientFacility> pfs = interactionService.findInteractionDetailsByPatientAndDate(patientId,
751 PatientFacility.class, startDate, endDate, userTz);
752 List<PatientLevelOfCare> locs = interactionService.findInteractionDetailsByPatientAndDate(patientId,
753 PatientLevelOfCare.class, startDate, endDate, userTz);
754
755 // If there are no PatientFacility's in the date range, get the most
756 // current for the patient
757 if (pfs == null || pfs.isEmpty()) {
758 pfs = interactionService.findLatestInteractionDetailsByPatientBeforeDate(patientId, PatientFacility.class,
759 startDate, userTz);
760 }
761
762 for (PatientFacility pf : pfs) {
763 if (cbsaCode61 == null) {
764 Facility facility = pf.getFacility();
765
766 if (facility.getName().equalsIgnoreCase(PatientLocation.HOME)) {
767 Patient p = patientService.findPatient(patientId);
768 cbsaCode61 = p.getPrimaryAddress().getCbsa();
769 } else {
770 cbsaCode61 = facility.getPrimaryAddress().getCbsa();
771 }
772 }
773 }
774
775 for (PatientLevelOfCare loc : locs) {
776 switch (loc.getType()) {
777
778 case ROUTINE:
779 case CONTINUOUS_CARE:
780 valueCodes.add("61");
781 break;
782 case GENERAL_IN_PATIENT:
783 case RESPITE:
784 valueCodes.add("G8");
785 PatientFacility pf = findMatchingFacility(loc, pfs);
786
787 if (cbsaCodeG8 == null && pf != null) {
788 cbsaCodeG8 = pf.getFacility().getPrimaryAddress().getCbsa();
789 }
790 break;
791 default:
792 break;
793 }
794 }
795
796 for (String value : valueCodes) {
797 if (value.equals("61")) {
798 resultList.add(value);
799 resultList.add(cbsaCode61);
800 } else if (value.equals("G8")) {
801 resultList.add(value);
802 resultList.add(cbsaCodeG8);
803 }
804 }
805
806 return resultList;
807 }
808
809 public List<OccurrenceDTO> getM2OccurrenceCodes(Long patientId, Date startDate, Date endDate) {
810 List<LevelOfCareDTO> locList = locService.findBasicPatientLevelsOfCare(patientId, auth.getUserTimeZone());
811 return getM2OccurrenceCodes(locList, patientId, startDate, endDate);
812 }
813
814 private List<OccurrenceDTO> getM2OccurrenceCodes(List<LevelOfCareDTO> locList, Long patientId, Date startDate,
815 Date endDate) {
816 List<OccurrenceDTO> occurrencesList = new ArrayList<OccurrenceDTO>();
817
818 OccurrenceDTO occurrenceDto;
819 List<String> respiteDates = new ArrayList<String>();
820 LevelOfCareDTO previousLOC = null;
821 for (LevelOfCareDTO loc : locList) {
822 if (loc != null) {
823 if (loc.getEndDate() == null)
824 loc.setEndDate(DateUtil.getTodayDateOnly(DateUtil.UTC_ZONE));
825 if (previousLOC == null) {
826 // first LOC, can't see if it's non-consecutive yet
827 previousLOC = loc;
828 if (loc.getTypeOfCare() == LevelOfCareType.RESPITE
829 && DateUtil.areInDateRange(loc.getStartDate(), loc.getEndDate(), startDate, endDate))
830 respiteDates.add(getRespiteDates(loc, startDate, endDate));
831 continue;
832 }
833
834 // if Respite & in billing range & previous LOC wasn't respite
835 if (loc.getTypeOfCare() == LevelOfCareType.RESPITE
836 && DateUtil.areInDateRange(loc.getStartDate(), loc.getEndDate(), startDate, endDate)
837 && previousLOC.getTypeOfCare() != LevelOfCareType.RESPITE) {
838 respiteDates.add(getRespiteDates(loc, startDate, endDate));
839 }
840
841 previousLOC = loc;
842 }
843
844 }
845
846 if (respiteDates.size() > 1) {
847 for (String date : respiteDates) {
848 occurrenceDto = new OccurrenceDTO();
849 occurrenceDto.setType("M2");
850 occurrenceDto.setCode(date);
851 occurrencesList.add(occurrenceDto);
852 }
853 }
854
855 // We worked backwards, so reverse so codes are in correct order
856 Collections.reverse(occurrencesList);
857 return occurrencesList;
858 }
859
860 private String getRespiteDates(LevelOfCareDTO loc, Date billStartDate, Date billEndDate) {
861 String result = "";
862
863 Date startDate = loc.getStartDate().before(billStartDate) ? billStartDate : loc.getStartDate();
864 Date endDate = loc.getEndDate().after(billEndDate) ? billEndDate : loc.getEndDate();
865 result += DateUtil.UTC_MMDDYYYY.format(startDate) + "-" + DateUtil.UTC_MMDDYYYY.format(endDate);
866
867 return result;
868 }
869
870 public ClaimsHeaderDTO getPrepareClaimsHeader() {
871
872 ClaimsHeaderDTO claimsHeaderDTO = new ClaimsHeaderDTO();
873 claimsHeaderDTO.setProgramNames(getProgramNames());
874 claimsHeaderDTO.setOfficeNames(getOfficeNames());
875
876 return claimsHeaderDTO;
877 }
878
879 protected List<ProgramDTO> getProgramNames() {
880 List<ProgramDTO> programNames = new ArrayList<ProgramDTO>();
881 TypedQuery<ProgramSite> query = em.createNamedQuery("ProgramSite.findProgramsBySite", ProgramSite.class);
882 query.setParameter("id", auth.getCurrentUserSiteId());
883 List<ProgramSite> programSites = query.getResultList();
884 Iterator<ProgramSite> programSiteIT = programSites.iterator();
885 while (programSiteIT.hasNext()) {
886 ProgramSite programSite = programSiteIT.next();
887 ProgramDTO prgm = new ProgramDTO();
888 prgm.setName(programSite.getProgram().getName());
889 prgm.setId(programSite.getProgram().getId());
890 prgm.setCode(programSite.getProgram().getCode());
891 programNames.add(prgm);
892 }
893 return programNames;
894 }
895
896 protected List<OfficeDTO> getOfficeNames() {
897 List<OfficeDTO> officeNames = new ArrayList<OfficeDTO>();
898 TypedQuery<Office> query = em.createNamedQuery("Office.findBySite", Office.class);
899 query.setParameter("siteId", auth.getCurrentUserSiteId());
900
901 List<Office> offices = query.getResultList();
902 Iterator<Office> officesIT = offices.iterator();
903 while (officesIT.hasNext()) {
904 OfficeDTO officeDto = new OfficeDTO();
905 Office office = officesIT.next();
906 officeDto.setId(office.getId());
907 officeDto.setName(office.getName());
908 officeDto.setNickName(office.getNickName());
909 officeNames.add(officeDto);
910 }
911 return officeNames;
912 }
913
914 private boolean isFacTypeQ5006(Long facilityId) {
915 boolean result = false;
916 Facility fac = em.find(Facility.class, facilityId);
917
918 if (fac.getFacilityTypes() != null && !fac.getFacilityTypes().isEmpty())
919 for (FacilityType facType : fac.getFacilityTypes())
920 if (Q5006.equals(facType.getQCode())) {
921 result = true;
922 break;
923 }
924
925 return result;
926 }
927
928 private PatientFacility findMatchingFacility(PatientLevelOfCare loc, List<PatientFacility> pfs) {
929
930 PatientFacility ret = null;
931 Date locDate = loc.getInteraction().getEffectiveDate();
932
933 // assumes facility interactions are in reverse chronological order
934 for (PatientFacility pf : pfs) {
935
936 // find the facility that was active at the time of this LOC
937 // interaction
938 if (locDate.compareTo(pf.getInteraction().getEffectiveDate()) >= 0) {
939
940 ret = pf;
941 break;
942 }
943 }
944
945 return ret;
946 }
947
948 /**
949 * This method updates an existing Claim using claimKey
950 */
951 public void updateClaimStatus(String claimKey, ClaimStatus claimStatus, String reason) {
952 TypedQuery<Claim> query = em.createNamedQuery("Claim.findClaimByClaimKey", Claim.class);
953 query.setParameter("claimKey", claimKey);
954
955 List<Claim> claimResult = query.getResultList();
956 if (claimResult.size() != 1) {
957 log.warning(claimResult.size() + " Claim Keys in the claim table for: " + claimKey);
958 return;
959 }
960 Claim result = claimResult.get(0);
961
962 // if the claim WAS sent, but they're reseting the status, clear the
963 // sent date
964 if (result.getSent() != null && ClaimStatus.notSentClaimValues.contains(claimStatus))
965 result.setSent(null);
966
967 result.setClaimStatus(claimStatus);
968 result.setStatusReason(reason);
969 persistenceService.update(result);
970 }
971
972 /**
973 * Fetch the most recent patientphysician; before or during effectiveDate
974 *
975 * @param physicianType
976 * @param patientId
977 * @param effectiveDate
978 * Date is expected to be in date only format. (i.e. time set to
979 * 00:00:00 UTC)
980 * @return
981 */
982 private Person getPatientPhysician(PatientPhysicianType physicianType, Long patientId, Date effectiveDate) {
983 List<PatientPhysician> results;
984 TypedQuery<PatientPhysician> query = em.createQuery(
985 "select pp from PatientPhysician pp where pp.patient.id = :patientId "
986 + "and pp.effectiveDate <= :effectiveDate " + "and pp.physicianType = :physicianType "
987 + "order by pp.effectiveDate desc, pp.lastUpdateTime desc", PatientPhysician.class);
988
989 // Note: patientphysician.effectivedate is persisted in date only
990 // format.
991 // (Thus no time conversion is needed)
992 Date ppEndDate = effectiveDate;
993
994 query.setParameter("patientId", patientId);
995 query.setParameter("physicianType", physicianType);
996 query.setParameter("effectiveDate", ppEndDate);
997 results = query.getResultList();
998 if (results.size() > 0)
999 return results.get(0).getPhysician();
1000 else
1001 return null;
1002 }
1003
1004 /**
1005 *
1006 * @param hospiceId
1007 * @param siteTz
1008 * @param siteId
1009 * @param officeIdList
1010 * @param programIdList
1011 * @param payerType
1012 * @param processOnlyRB
1013 * @param separateState
1014 * @param runAsync
1015 * @param claimsGenerationCache
1016 * @param patientId
1017 * @param dateRange
1018 * dateRange is expected to be in date only format (i.e. time is
1019 * set to 00:00:00 UTC)
1020 * @return
1021 * @throws SAXException
1022 * @throws JAXBException
1023 * @throws ParseException
1024 */
1025 public ClaimBatchDTO generateBillingXMLForPAS(Long hospiceId, TimeZone siteTz, ProgramSite billingSite,
1026 PayerType payerType, boolean processOnlyRB, boolean separateState, boolean runAsync,
1027 ClaimsGenerationCache claimsGenerationCache, Long patientId, Date[] dateRange,
1028 com.hummingbird.jpa.claims.ClaimBatch batchEntity) throws SAXException, JAXBException, ParseException {
1029
1030 // Get list of patients who were active, the date range to
1031 // look at is: start date = first of previous month, end date = last
1032 // date of previous month
1033 Date[] activeDateRange = null;
1034 if (dateRange == null) {
1035 activeDateRange = DateUtil.calculatePreviousMonthDateRangeForBilling(siteTz);
1036 } else {
1037 activeDateRange = dateRange;
1038 }
1039
1040 List<Patient> activePatientList = patientService.findPatientsForBilling(activeDateRange[0], activeDateRange[1],
1041 billingSite.getId(), null, payerType, siteTz);
1042 // Emptying the list if a specific patient was requested
1043 if (patientId != null) {
1044 Patient pat = em.find(Patient.class, patientId);
1045 if (activePatientList.contains(pat)) {
1046 activePatientList.clear();
1047 activePatientList.add(pat);
1048 } else {
1049 activePatientList.clear();
1050 String r0 = DateUtil.getDateOnlyFormat(DateUtil.UTC_ZONE).format(activeDateRange[0]);
1051 String r1 = DateUtil.getDateOnlyFormat(DateUtil.UTC_ZONE).format(activeDateRange[1]);
1052 log.info("Patient " + patientId + " not on service for dates " + r0 + " - " + r1);
1053 }
1054 }
1055
1056 log.info(String.format(
1057 "[ClaimGeneration] Hospice: %s -- Program: %s -- PayerType: %s -- Patient Count: %d -- %s", billingSite
1058 .getSite().getHospice().getBin(), billingSite.getProgram().getCode(), payerType,
1059 activePatientList.size(), processOnlyRB ? "RB Batch" : "HSP Batch"));
1060
1061 if (activePatientList.size() == 0)
1062 return null;
1063
1064 List<IndividualClaimDTO> patientClaimDTOList = createClaimDTOsFromRawData(activePatientList, activeDateRange,
1065 runAsync, siteTz, payerType, processOnlyRB, separateState, claimsGenerationCache, billingSite);
1066
1067 Long batchCarrierId = null;
1068 switch (payerType) {
1069 case COMMERCIAL:
1070 case MEDICAID:
1071 // TODO This is a kludge but it will work until we can figure out a
1072 // better way.
1073 List<InsuranceCoverage> coverages = insService.findPatientInsuranceCoverageInPeriod(activePatientList
1074 .get(0).getId(), activeDateRange[0], activeDateRange[1], EnumSet.of(payerType));
1075 batchCarrierId = coverages.get(0).getInsurancecarrier().getId();
1076 break;
1077 case MEDICARE:
1078 batchCarrierId = systemDataService.getSystemMedicareCarrier().getId();
1079 break;
1080 case SELF_PAY:
1081 batchCarrierId = systemDataService.getSelfPayInsuranceCarrier().getId();
1082 break;
1083 case NON_FUNDED:
1084 batchCarrierId = systemDataService.getFreeCareInsuranceCarrier().getId();
1085 break;
1086 default:
1087 break;
1088 }
1089 log.fine("** Creating claim batch from patient list ...");
1090 ClaimBatchDTO claimbatch = createClaimBatchDTO(patientClaimDTOList, billingSite, batchCarrierId,
1091 activeDateRange, batchEntity, payerType, claimsGenerationCache);
1092
1093 return claimbatch;
1094 }
1095
1096 public ClaimBatchDTO createIndividualClaimBatch(IndividualClaimDTO claimDTO,
1097 com.hummingbird.jpa.claims.ClaimBatch batchEntity, ProgramSite billingSite, Long batchCarrierId,
1098 PayerType batchPayerType, ClaimsGenerationCache claimsGenerationCache) {
1099 List<IndividualClaimDTO> claimList = new ArrayList<IndividualClaimDTO>();
1100 claimList.add(claimDTO);
1101 Date[] dateRange = new Date[] { claimDTO.getStartDate(), claimDTO.getEndDate() };
1102 return createClaimBatchDTO(claimList, billingSite, batchCarrierId, dateRange, batchEntity, batchPayerType,
1103 claimsGenerationCache);
1104 }
1105
1106 /**
1107 *
1108 * @param individualClaimDTOList
1109 * @param siteId
1110 * @param batchCarrierId
1111 * @param dateRange
1112 * Dates are expected to be in date only format (i.e. time is set
1113 * to 00:00:00 UTC)
1114 * @return
1115 */
1116 private ClaimBatchDTO createClaimBatchDTO(List<IndividualClaimDTO> individualClaimDTOList, ProgramSite programSite,
1117 Long batchCarrierId, Date[] dateRange, com.hummingbird.jpa.claims.ClaimBatch batchEntity,
1118 PayerType batchPayerType, ClaimsGenerationCache claimsGenerationCache) {
1119 // Create object factory
1120 com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory objFactory = new ObjectFactory();
1121 // Create claim batch
1122 com.hummingbird.ejb.resources.jaxb.crossbill.ClaimBatch claimBatch = objFactory.createClaimBatch();
1123 ClaimBatchDTO claimBatchDTO = new ClaimBatchDTO();
1124
1125 Long billingSiteId = null;
1126 if (programSite == null) {
1127 billingSiteId = auth.getCurrentUserSiteId();
1128 } else {
1129 billingSiteId = programSite.getSite().getId();
1130 }
1131
1132 claimBatchDTO.setSiteId(billingSiteId);
1133
1134 List<IndividualClaimDTO> errorIndividualClaimDtoList = new ArrayList<IndividualClaimDTO>();
1135 claimBatch.setBatchStartDate(convertToOutgoingClaimDateOnlyFormat(dateRange[0]));
1136 claimBatch.setBatchStopDate(convertToOutgoingClaimDateOnlyFormat(dateRange[1]));
1137 for (IndividualClaimDTO individualClaimDTO : individualClaimDTOList) {
1138 // NOTE: Since we are no longer validating the xmls for required
1139 // fields, isValid() will always return true.
1140 if (individualClaimDTO.isValid()) {
1141 claimBatch.getPatientClaim().add(individualClaimDTO.getJaxbPatientClaim());
1142 } else {
1143 // Add invalid claims to a different list which will NOT be sent
1144 // to PASBe and added later to the final BillDTO
1145 errorIndividualClaimDtoList.add(individualClaimDTO);
1146 }
1147 }
1148
1149 addRequestMetaData(claimBatch, batchEntity, batchCarrierId, batchPayerType, programSite, claimsGenerationCache);
1150
1151 claimBatchDTO.setJaxbClaimBatch(claimBatch);
1152 claimBatchDTO.setErrorIndividualClaimDTOList(errorIndividualClaimDtoList);
1153
1154 return claimBatchDTO;
1155 }
1156
1157 private Schema getPatientClaimSchema() throws SAXException {
1158 SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
1159 File xmlSchema = Paths.get(
1160 configService.getDataRoot() + "/"
1161 + configService.getConfigProperties().getProperty("billingService.patientClaim.xsd")).toFile();
1162 return sf.newSchema(xmlSchema);
1163 }
1164
1165 private IndividualClaimDTO createClaimDTOsFromRawClaimData(Claim claim, boolean runAsync,
1166 ClaimsGenerationCache claimsGenerationCache) throws SAXException, JAXBException, ParseException {
1167 TimeZone siteTz = TimeZone.getDefault(); // in case of emergency
1168 if (claim.getProgramSite() != null && claim.getProgramSite().getSite() != null) {
1169 siteTz = TimeZone.getTimeZone(claim.getProgramSite().getSite().getTimezone());
1170 }
1171 List<Claim> claimList = new ArrayList<Claim>(1);
1172 claimList.add(claim);
1173 List<IndividualClaimDTO> dtoList = createClaimDTOsFromClaims(claimList, siteTz, runAsync, claimsGenerationCache);
1174 // re-calculating claim correctly should only ever make one claim.
1175 if (dtoList != null && dtoList.size() == 1) {
1176 return dtoList.get(0);
1177 } else {
1178 log.warning("Single claim not updated");
1179 return null;
1180 }
1181
1182 }
1183
1184 public List<PatientFacilityArcDTO> findPatientFacilitiesForPatientClaim(Date claimStartDate, Date claimEndDate,
1185 Long patientId) {
1186 List<PatientFacilityArcDTO> results = new ArrayList<PatientFacilityArcDTO>();
1187 if (claimStartDate != null && claimEndDate != null && patientId != null) {
1188 String queryStr = "SELECT "
1189 + "patientfacility_id as patientFacilityId, insurancecoverage_id as insuranceCoverageId, startdate as startUtcDateOnly, enddate as stopUtcDateOnly "
1190 + "from patientfacility_with_enddate_v3_vw "
1191 + "where patient_id = :patientId and billroomandboard = 't' "
1192 + "and startdate <= :claimEndDate and (enddate is null or enddate >= :claimStartDate) order by startdate, enddate";
1193 Query query = em.createNativeQuery(queryStr);
1194 query.setParameter("claimStartDate", claimStartDate);
1195 query.setParameter("claimEndDate", claimEndDate);
1196 query.setParameter("patientId", patientId);
1197 query.unwrap(SQLQuery.class).addScalar("patientFacilityId", BigIntegerType.INSTANCE)
1198 .addScalar("insuranceCoverageId", BigIntegerType.INSTANCE)
1199 .addScalar("startUtcDateOnly", TimestampType.INSTANCE)
1200 .addScalar("stopUtcDateOnly", TimestampType.INSTANCE)
1201 .setResultTransformer(Transformers.aliasToBean(PatientFacilityArcDTO.class));
1202 results.addAll(query.getResultList());
1203 }
1204 return results;
1205 }
1206
1207 /**
1208 * We suppress warnings because this is a native query.
1209 *
1210 * @param startDate
1211 * Date is expected to be in date only format (i.e. time is set
1212 * to 00:00:00 UTC). Example 2016-01-01 00:00:00 UTC
1213 * @param endDate
1214 * Date is expected to be in date only format (i.e. time is set
1215 * to 00:00:00 UTC). Example 2016-01-31 00:00:00 UTC
1216 * @param carrierId
1217 * : If null, query results will include all carriers
1218 * @param payerType
1219 * : If null, query results will include all payertypes
1220 * @param patientId
1221 * : Cannot be null
1222 * @return Returns list of zero or more admission arcs.
1223 */
1224 @SuppressWarnings("unchecked")
1225 public List<AdmissionArcDTO> findAdmitsDischargesForPatientClaim(Date startDate, Date endDate, Long carrierId,
1226 PayerType payerType, Long patientId, Long programSiteId) {
1227 if (startDate == null || endDate == null)
1228 return new ArrayList<AdmissionArcDTO>();
1229
1230 TimeZone patTz = patientService.getPatientTimeZone(patientId);
1231
1232 // The end of the range is actually the *end* of the end date
1233 // Therefore, adding 1 day to the end date
1234 Calendar utcCal = Calendar.getInstance(DateUtil.UTC_ZONE);
1235 utcCal.setTime(endDate);
1236 utcCal.add(Calendar.DATE, 1);
1237 Date trueEndDate = utcCal.getTime();
1238
1239 // Note: insuranceCoverage.effectiveDate is persisted in date only
1240 // format
1241 // (So there is no need for a timezone conversion here)
1242 Date coverageEnd = trueEndDate;
1243
1244 // Note: admitDate and dischargeDate are persisted with time
1245 // (So we must do a timezone conversion here)
1246 // Convert to patient's time zone when searching
1247 // (00:00:00 UTC => 00:00:00 <siteTz>)
1248 Date admissionArcStart = DateUtil.convertDateTz(startDate, DateUtil.UTC_ZONE, patTz);
1249 Date admissionArcEnd = DateUtil.convertDateTz(trueEndDate, DateUtil.UTC_ZONE, patTz);
1250
1251 List<AdmissionArcDTO> results = new ArrayList<AdmissionArcDTO>();
1252
1253 StringBuilder query = new StringBuilder();
1254 query.append("SELECT "
1255 + "case when mits.admitdate < cv.effectivedate and cv.insuranceprioritytype = 'PRIMARY' then cv.effectivedate else mits.admitdate end as admitdateonly, "
1256 + "case when mits.admitdate < cv.effectivedate and cv.insuranceprioritytype = 'PRIMARY' then mits.admitdatetime_utc + integer '1' * (cv.effectivedate - mits.admitdate) "
1257 + " else mits.admitdatetime_utc end as utcadmitdatetime, "
1258 + "case when mits.dischargedate > cv.enddate and cv.insuranceprioritytype = 'PRIMARY' then cv.enddate "
1259 + " when mits.dischargedate is null and cv.enddate is not null and cv.enddate > mits.admitdate and cv.insuranceprioritytype = 'PRIMARY' then cv.enddate "
1260 + "else mits.dischargedate end as dischargedateonly, "
1261 + "case when mits.dischargedate > cv.enddate and cv.insuranceprioritytype = 'PRIMARY' then mits.dischargedatetime_utc - integer '1' * (mits.dischargedate - cv.enddate)"
1262 + " when mits.dischargedate is null and cv.enddate is not null and cv.enddate > mits.admitdate and cv.insuranceprioritytype = 'PRIMARY' then cv.enddate "
1263 + " else mits.dischargedatetime_utc end as utcdischargedatetime, "
1264 + "mits.admitinteraction_id AS admissioninteraction_id, mits.dischargeinteraction_id, mits.programsite_id, "
1265 + "mits.admitdate as realadmitdate " + "FROM patient_admissionperiods_v3_vw mits "
1266 + "LEFT JOIN insurancecoverage cv ON cv.patient_id = mits.patient_id "
1267 + "AND (((mits.dischargedate IS NULL " + " AND cv.effectiveDate <= :coverageDate) "
1268 + " OR (cv.effectivedate <= mits.dischargedate)) " + " AND ((cv.endDate > mits.admitdate) "
1269 + " OR (cv.endDate IS NULL " + " AND mits.dischargedate IS NULL) "
1270 + " OR (cv.endDate = mits.admitdate "
1271 + " AND cv.effectiveDate = mits.admitdate))) "
1272 + "WHERE ( mits.admitdatetime_utc < :patTzEndDate "
1273 + "AND (mits.dischargedatetime_utc IS NULL OR mits.dischargedatetime_utc >= :patTzStartDate)) "
1274 + "AND cv.deleted = false ");
1275 if (carrierId != null) {
1276 query.append("AND cv.insurancecarrier_id = :carrierId ");
1277 }
1278 if (payerType != null) {
1279 query.append("AND cv.payertype = :payerType ");
1280 }
1281 if (programSiteId != null) {
1282 query.append("and mits.programsite_id = :programSiteId ");
1283 }
1284 query.append(" AND mits.patient_id = :patientId ");
1285 query.append(" ORDER BY admitdateonly ");
1286 Query patientQuery = em.createNativeQuery(query.toString());
1287
1288 patientQuery.setParameter("patTzStartDate", admissionArcStart);
1289 patientQuery.setParameter("patTzEndDate", admissionArcEnd);
1290 patientQuery.setParameter("coverageDate", coverageEnd);
1291
1292 patientQuery.setParameter("patientId", patientId);
1293
1294 if (carrierId != null)
1295 patientQuery.setParameter("carrierId", carrierId);
1296 if (payerType != null)
1297 patientQuery.setParameter("payerType", payerType.name());
1298 if (programSiteId != null)
1299 patientQuery.setParameter("programSiteId", programSiteId);
1300
1301 // Convert query results into AdmissionArcDTO objects
1302 List<Object[]> resultsObjs = patientQuery.getResultList();
1303 for (Object[] resultObj : resultsObjs) {
1304
1305 Date admitDateOnly = ((Date) resultObj[0]);
1306 Date utcAdmitDateTime = ((Date) resultObj[1]);
1307 BigInteger admissionInteractionId = ((BigInteger) resultObj[4]);
1308
1309 AdmissionArcDTO arc = new AdmissionArcDTO();
1310 arc.setAdmitDateOnly(admitDateOnly);
1311 arc.setUtcAdmitDateTime(utcAdmitDateTime);
1312 arc.setAdmissionInteraction_id(admissionInteractionId);
1313 arc.setRealAdmitDateOnly((Date) resultObj[7]);
1314
1315 if (resultObj[2] != null) {
1316 Date dischargeDateOnly = ((Date) resultObj[2]);
1317 arc.setDischargeDateOnly(dischargeDateOnly);
1318 }
1319 if (resultObj[3] != null) {
1320 Date utcDischargeDateTime = ((Date) resultObj[3]);
1321 arc.setUtcDischargeDateTime(utcDischargeDateTime);
1322 }
1323
1324 if (resultObj[5] != null) {
1325 BigInteger dischargeInteractionId = ((BigInteger) resultObj[5]);
1326 arc.setDischargeInteraction_id(dischargeInteractionId);
1327 }
1328
1329 results.add(arc);
1330 }
1331 return results;
1332 }
1333
1334 /**
1335 * findStatusRecordsForPatientClaim
1336 *
1337 * Returns list of patient statuses for relevant admits/discharges
1338 *
1339 * @param interactionId
1340 * @return
1341 */
1342 @SuppressWarnings("unchecked")
1343 private List<PatientStatus> findStatusRecordsForPatientClaim(BigInteger interactionId) {
1344 List<PatientStatus> result = new ArrayList<PatientStatus>();
1345 if (interactionId != null) {
1346 StringBuilder query = new StringBuilder();
1347 query.append("SELECT psa.* FROM patientstatus psa" + " WHERE psa.interaction_id = :interactionId");
1348
1349 Query patientQuery = em.createNativeQuery(query.toString(), PatientStatus.class);
1350
1351 patientQuery.setParameter("interactionId", interactionId);
1352
1353 List<PatientStatus> resultlist = patientQuery.getResultList();
1354 if (resultlist != null && resultlist.size() > 0) {
1355 result.add(resultlist.get(0));
1356 }
1357 }
1358
1359 return result;
1360
1361 }
1362
1363 /**
1364 * This method fetches data from various tables for each patient. We need to
1365 * optimize this method as much as possible.
1366 *
1367 * @param patientClaimList
1368 * @param dateRange
1369 * Dates are expected to be in date only format (i.e. time is set
1370 * to 00:00:00 UTC)
1371 * @param runAsync
1372 * @param siteTz
1373 * @param payerType
1374 * @param processOnlyRB
1375 * @param separateState
1376 * @param claimsGenerationCache
1377 * @return
1378 * @throws SAXException
1379 * @throws JAXBException
1380 * @throws ParseException
1381 */
1382 private List<IndividualClaimDTO> createClaimDTOsFromRawData(List<Patient> patientClaimList, Date[] dateRange,
1383 boolean runAsync, final TimeZone siteTz, PayerType payerType, final boolean processOnlyRB,
1384 boolean separateState, final ClaimsGenerationCache claimsGenerationCache, ProgramSite billingProgramSite)
1385 throws SAXException, JAXBException, ParseException {
1386 final BillingService billingService = (runAsync) ? sessionContext.getBusinessObject(BillingService.class)
1387 : this;
1388
1389 List<IndividualClaimDTO> individualClaimDTOList = new ArrayList<IndividualClaimDTO>();
1390 Date todayUTC = DateUtil.getTodayDateOnly(siteTz);
1391 Date[] currentRange = DateUtil.calculateDateOnlyArbitraryMonthRange(new LocalDate(todayUTC));
1392 boolean currentMonth = false;
1393 if (currentRange[0].equals(dateRange[0]) && currentRange[1].equals(dateRange[1]))
1394 currentMonth = true;
1395 if (patientClaimList == null)
1396 return individualClaimDTOList;
1397
1398 final Schema patientClaimSchema = getPatientClaimSchema();
1399
1400 Map<String, Future<IndividualClaimDTO>> indClaimCalcFutures = new HashMap<String, Future<IndividualClaimDTO>>();
1401 ExecutorService claimCalcExecutor = Executors.newFixedThreadPool(Integer.parseInt(configService
1402 .lookupEnvProperty("claim.gen.threads")));
1403
1404 boolean currentMonthDischarge = false;
1405 for (Patient patient : patientClaimList) {
1406 // Here we get the actual dates on the claim that we are going to
1407 // bill for. Eg: 1/6/15 - 1/31/15
1408 List<AdmissionArcDTO> admitRanges = findAdmitsDischargesForPatientClaim(dateRange[0], dateRange[1], null,
1409 payerType, patient.getId(), billingProgramSite.getId());
1410 for (AdmissionArcDTO arc : admitRanges) {
1411 currentMonthDischarge = false;
1412 final AdmissionArcDTO rawArc = new AdmissionArcDTO();
1413 rawArc.setAdmitDateOnly(arc.getAdmitDateOnly());
1414 rawArc.setUtcAdmitDateTime(arc.getUtcAdmitDateTime());
1415 rawArc.setRealAdmitDateOnly(arc.getRealAdmitDateOnly());
1416 rawArc.setAdmissionInteraction_id(arc.getAdmissionInteraction_id());
1417 rawArc.setDischargeDateOnly(arc.getDischargeDateOnly());
1418 rawArc.setUtcDischargeDateTime(arc.getUtcDischargeDateTime());
1419 rawArc.setDischargeInteraction_id(arc.getDischargeInteraction_id());
1420
1421 // These dates are in date only format (i.e. time is set to
1422 // 00:00:00 UTC)
1423 Date claimStartDateOnly;
1424 Date claimEndDateOnly;
1425 Date roomBoardStart;
1426 Date roomBoardStop;
1427
1428 if (arc.getAdmitDateOnly().before(dateRange[0])) {
1429 claimStartDateOnly = dateRange[0];
1430 } else {
1431 claimStartDateOnly = arc.getAdmitDateOnly();
1432 }
1433 if (arc.getDischargeDateOnly() == null)
1434 arc.setDischargeDateOnly(dateRange[1]);
1435 if (arc.getDischargeDateOnly().after(dateRange[1]))
1436 claimEndDateOnly = dateRange[1];
1437 else {
1438 claimEndDateOnly = arc.getDischargeDateOnly();
1439 if (currentMonth && arc.getDischargeInteraction_id() != null)
1440 currentMonthDischarge = true;
1441 }
1442
1443 if (billingProgramSite.getProgram().getCode().equals("PC")) {
1444 final List<InsuranceCoverage> allCoverages = insService.findPatientInsuranceCoverageInPeriod(
1445 patient.getId(), arc.getAdmitDateOnly(), claimEndDateOnly, PayerType.billablePayerTypes);
1446
1447 Long coverageId = allCoverages.size() == 0 ? null : allCoverages.get(0).getId();
1448 // Primary only from this point forward.
1449 if (coverageId == null) {
1450 log.severe("Palliative claim does not have insurance coverage " + patient.getId());
1451 continue;
1452 }
1453 if (allCoverages.get(0).getPayerType() != payerType
1454 || allCoverages.get(0).getInsurancePriorityType() != InsurancePriorityType.PRIMARY) {
1455 log.severe("Palliative claim does not have primarycoverage match patient " + patient.getId());
1456 continue;
1457 }
1458 final Claim activeClaim = fetchNewOrExistingClaim(patient.getId(), billingProgramSite.getId(),
1459 claimStartDateOnly, claimEndDateOnly, coverageId, null, ProgramEnum.PALLIATIVE, false);
1460
1461 if (activeClaim == null) {
1462 log.severe("Palliative claim is null, not creating claim, patient " + patient.getId());
1463 continue;
1464 }
1465 Future<IndividualClaimDTO> indClaimRBCalcFut = claimCalcExecutor
1466 .submit(new Callable<IndividualClaimDTO>() {
1467
1468 @Override
1469 public IndividualClaimDTO call() throws Exception {
1470 return billingService.calculatePalliativeClaim(activeClaim, allCoverages.get(0),
1471 rawArc, siteTz, SecurityContextHolder.getContext(), claimsGenerationCache);
1472 }
1473
1474 });
1475 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimRBCalcFut);
1476 continue;
1477 }
1478 // for each patient If patient has medicaid or a R&B patient
1479 // facility and the insurance on the coverage is in a state
1480 // where we can't include it
1481 roomBoardStart = claimStartDateOnly;
1482 boolean doRBandHSPsameClaim = false;
1483 if (payerType == PayerType.MEDICAID) {
1484 final List<InsuranceCoverage> coverages = insService.findPatientInsuranceCoverageInPeriod(
1485 patient.getId(), arc.getAdmitDateOnly(), claimEndDateOnly, EnumSet.of(PayerType.MEDICAID));
1486 final List<PatientFacility> rbFacilities = interactionService.findFacilitiesForRB(patient.getId(),
1487 arc.getAdmitDateOnly(), claimEndDateOnly);
1488 final List<PatientFacilityArcDTO> rbFacilityArcs = findPatientFacilitiesForPatientClaim(
1489 arc.getRealAdmitDateOnly(), claimEndDateOnly, patient.getId());
1490
1491 if (coverages.size() > 0) {
1492 if (coverages.get(0).getInsurancePriorityType() == InsurancePriorityType.PRIMARY) {
1493 // regular bill + R&B if applicable
1494 doRBandHSPsameClaim = roomAndBoardSameClaim(patient, coverages, rbFacilityArcs);
1495 }
1496 // If same-claim does not apply, or if medicaid is
1497 // second/supplemental
1498
1499 if (processOnlyRB) {
1500 if (rbFacilityArcs.size() > 0) {
1501 Long medicaidCoverage = coverages.get(0).getId();
1502
1503 for (PatientFacilityArcDTO pfStay : rbFacilityArcs) {
1504 if (pfStay.getStartUtcDateOnly().after(roomBoardStart)) {
1505 roomBoardStart = pfStay.getStartUtcDateOnly();
1506 } else {
1507 roomBoardStart = claimStartDateOnly;
1508 }
1509
1510 if (pfStay.getStopUtcDateOnly() != null
1511 && pfStay.getStopUtcDateOnly().before(claimEndDateOnly)) {
1512 roomBoardStop = pfStay.getStopUtcDateOnly();
1513 } else {
1514 roomBoardStop = claimEndDateOnly;
1515 }
1516
1517 if (currentMonthDischarge) {
1518 deleteCurrentMonthClaims(patient.getId(), roomBoardStart, roomBoardStop,
1519 medicaidCoverage);
1520 }
1521
1522 final Claim activeClaim = fetchNewOrExistingClaim(patient.getId(),
1523 billingProgramSite.getId(), roomBoardStart, roomBoardStop,
1524 medicaidCoverage, null, ProgramEnum.ROOM_BOARD, false);
1525 if ((activeClaim == null)) {
1526 log.warning("Unable to fetch or create R&B claim entity for pat#"
1527 + patient.getId());
1528 } else {
1529
1530 if (currentMonth && !currentMonthDischarge) {
1531 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1532 } else if (activeClaim.getClaimStatus() == ClaimStatus.SEQH
1533 || activeClaim.getClaimStatus() == ClaimStatus.ERROR) {
1534 activeClaim.setClaimStatus(ClaimStatus.READY);
1535 activeClaim.setStatusReason(null);
1536 }
1537
1538 Future<IndividualClaimDTO> indClaimRBCalcFut = claimCalcExecutor
1539 .submit(new Callable<IndividualClaimDTO>() {
1540
1541 @Override
1542 public IndividualClaimDTO call() throws Exception {
1543 return billingService.calculateRBClaimSync(activeClaim,
1544 coverages.get(0), rbFacilityArcs, rawArc, siteTz,
1545 SecurityContextHolder.getContext(),
1546 claimsGenerationCache);
1547 }
1548
1549 });
1550 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimRBCalcFut);
1551 }
1552 }
1553 }
1554 } else if (!separateState
1555 && (doRBandHSPsameClaim != true || coverages.get(0).getInsurancePriorityType() != InsurancePriorityType.PRIMARY)) {
1556 if (rbFacilities.size() > 0) {
1557
1558 for (PatientFacilityArcDTO pfStay : rbFacilityArcs) {
1559 if (pfStay.getStartUtcDateOnly().after(roomBoardStart)) {
1560 roomBoardStart = pfStay.getStartUtcDateOnly();
1561 } else {
1562 roomBoardStart = claimStartDateOnly;
1563 }
1564
1565 if (pfStay.getStopUtcDateOnly() != null
1566 && pfStay.getStopUtcDateOnly().before(claimEndDateOnly)) {
1567 roomBoardStop = pfStay.getStopUtcDateOnly();
1568 } else {
1569 roomBoardStop = claimEndDateOnly;
1570 }
1571
1572 Long medicaidCoverage = coverages.get(0).getId();
1573
1574 if (currentMonthDischarge) {
1575 deleteCurrentMonthClaims(patient.getId(), roomBoardStart, roomBoardStop,
1576 medicaidCoverage);
1577 }
1578
1579 final Claim activeClaim = fetchNewOrExistingClaim(patient.getId(),
1580 billingProgramSite.getId(), roomBoardStart, roomBoardStop,
1581 medicaidCoverage, null, ProgramEnum.ROOM_BOARD, false);
1582 if (activeClaim == null) {
1583 log.warning("Unable to fetch or create R&B claim entity for pat#"
1584 + patient.getId());
1585 } else {
1586 if (currentMonth && !currentMonthDischarge) {
1587 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1588 } else if (PayerType.MEDICARE != activeClaim.getPayerType()
1589 && (activeClaim.getClaimStatus() == ClaimStatus.SEQH || activeClaim
1590 .getClaimStatus() == ClaimStatus.ERROR)) {
1591 activeClaim.setClaimStatus(ClaimStatus.READY);
1592 activeClaim.setStatusReason(null);
1593 }
1594
1595 Future<IndividualClaimDTO> indClaimRBCalcFut = claimCalcExecutor
1596 .submit(new Callable<IndividualClaimDTO>() {
1597
1598 @Override
1599 public IndividualClaimDTO call() throws Exception {
1600 return billingService.calculateRBClaimSync(activeClaim,
1601 coverages.get(0), rbFacilityArcs, rawArc, siteTz,
1602 SecurityContextHolder.getContext(),
1603 claimsGenerationCache);
1604 }
1605
1606 });
1607 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimRBCalcFut);
1608 }
1609 }
1610 }
1611 }
1612 }
1613 }
1614
1615 if (!processOnlyRB) {
1616
1617 // This is a retrofit, realistically, we want to generate a
1618 // separate claim for every coverage. Except in certain
1619 // cases.
1620 List<InsuranceCoverage> allCoverages = insService.findPatientInsuranceCoverageInPeriod(
1621 patient.getId(), arc.getAdmitDateOnly(), claimEndDateOnly, PayerType.billablePayerTypes);
1622
1623 Long coverageId = allCoverages.size() == 0 ? null : allCoverages.get(0).getId();
1624 InsuranceCoverage prim = allCoverages.size() == 0 ? null : allCoverages.get(0);
1625 // Primary only from this point forward.
1626 if (coverageId == null) {
1627 log.severe("Claim does not have insurance coverage " + patient.getId());
1628 continue;
1629 }
1630 if (allCoverages.get(0).getPayerType() != payerType) {
1631 for (InsuranceCoverage cov : allCoverages) {
1632 if (cov.getPayerType() == payerType
1633 && cov.getInsurancePriorityType() == InsurancePriorityType.PRIMARY) {
1634 coverageId = cov.getId();
1635 prim = cov;
1636 break;
1637 }
1638 }
1639 }
1640 if (prim.getPayerType() != payerType
1641 || prim.getInsurancePriorityType() != InsurancePriorityType.PRIMARY) {
1642 // Room and board claims will usually have this error,
1643 // which is fine
1644 if (payerType != PayerType.MEDICAID)
1645 log.severe("Claim does not have primarycoverage match patient " + patient.getId());
1646 continue;
1647 }
1648
1649 InsurancePriorityType priority = InsurancePriorityType.SECONDARY;
1650 List<InsuranceCoverage> secondaryCoverages = insService
1651 .findPatientInsuranceCoverageInPeriodByPriorityType(patient.getId(),
1652 arc.getAdmitDateOnly(), claimEndDateOnly, PayerType.billablePayerTypes, priority);
1653 Long secCoverageId = secondaryCoverages != null && secondaryCoverages.size() > 0 ? secondaryCoverages
1654 .get(0).getId() : null;
1655 if (currentMonthDischarge) {
1656 deleteCurrentMonthClaims(patient.getId(), claimStartDateOnly, claimEndDateOnly, coverageId);
1657 }
1658
1659 final Claim activeClaim = fetchNewOrExistingClaim(patient.getId(), billingProgramSite.getId(),
1660 claimStartDateOnly, claimEndDateOnly, coverageId, secCoverageId, ProgramEnum.HOSPICE, false);
1661
1662 if (activeClaim == null) {
1663 log.severe("Claim is null, not creating claim, patient " + patient.getId());
1664 continue;
1665 }
1666
1667 Noa noa = findNoaForCoverage(patient.getId(), coverageId, payerType);
1668 if (noa == null)
1669 noa = findNoaForAdmission(patient.getId(), arc.getAdmitDateOnly(), payerType);
1670 // if still null, try the "real" admit date
1671 if (noa == null)
1672 noa = findNoaForAdmission(patient.getId(), arc.getRealAdmitDateOnly(), payerType);
1673 final Noa patientNoa = noa;
1674
1675 if (activeClaim.getClaimStatus() == ClaimStatus.READY
1676 || activeClaim.getClaimStatus() == ClaimStatus.SEQH
1677 || activeClaim.getClaimStatus() == ClaimStatus.ERROR) {
1678 activeClaim.setClaimStatus(fetchReadyOrSeqh(activeClaim));
1679 activeClaim.setStatusReason(null);
1680
1681 if (currentMonth && !currentMonthDischarge) {
1682 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1683 } else if (PayerType.MEDICARE != activeClaim.getPayerType()) {
1684 activeClaim.setClaimStatus(ClaimStatus.READY);
1685 activeClaim.setStatusReason(null);
1686 }
1687 if ((patientNoa == null || patientNoa.getStatus() != NoaStatus.PAID)
1688 && PayerType.MEDICARE == activeClaim.getPayerType()) {
1689 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1690 if (activeClaim.getStatusReason() != null)
1691 activeClaim.setStatusReason("NOA not processed");
1692 else
1693 activeClaim.setStatusReason(activeClaim.getStatusReason() + ", NOA not processed");
1694 }
1695 }
1696 Future<IndividualClaimDTO> indClaimCalcFut = claimCalcExecutor
1697 .submit(new Callable<IndividualClaimDTO>() {
1698
1699 @Override
1700 public IndividualClaimDTO call() throws Exception {
1701 return billingService.calculateClaimSync(activeClaim, rawArc, siteTz,
1702 patientClaimSchema, SecurityContextHolder.getContext(), patientNoa,
1703 processOnlyRB, claimsGenerationCache);
1704 }
1705 });
1706 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimCalcFut);
1707 }
1708 }
1709 }
1710
1711 for (Map.Entry<String, Future<IndividualClaimDTO>> entry : indClaimCalcFutures.entrySet()) {
1712 try {
1713 Future<IndividualClaimDTO> indClaimCalcFut = entry.getValue();
1714 if (indClaimCalcFut.get() == null) {
1715 log.severe("Null Future for claimKey: " + entry.getKey());
1716 revertNewlyCreatedClaim(entry.getKey());
1717 continue;
1718 }
1719 individualClaimDTOList.add(indClaimCalcFut.get());
1720 } catch (InterruptedException | ExecutionException e) {
1721 String hospiceBin = billingProgramSite.getSite().getHospice().getBin();
1722 log.log(Level.SEVERE, "[ClaimGeneration] " + hospiceBin + " -- Error while processing claim -- "
1723 + entry.getKey(), e);
1724 }
1725 }
1726 claimCalcExecutor.shutdown();
1727
1728 return individualClaimDTOList;
1729 }
1730
1731 /**
1732 * Function for cleaning out no-longer valid current month claims on a
1733 * discharge
1734 *
1735 * We may want to expand the use of this logic to handle all discharge
1736 * claims, as long as they are in a pre-sent status. Which is out of scope
1737 * at the moment.
1738 *
1739 * @param patientId
1740 * @param claimStartDateOnly
1741 * @param claimEndDateOnly
1742 * @param coverageId
1743 */
1744 private void deleteCurrentMonthClaims(Long patientId, Date claimStartDateOnly, Date claimEndDateOnly,
1745 Long coverageId) {
1746 // Query finds claims with start date but not matching end date, and
1747 // deletes them (if it can.)
1748 // If it can't it's probably related to payment posting and it should be
1749 // amended etc anyway.
1750 TypedQuery<Claim> query = em.createNamedQuery("Claim.findClaimsByStartDatePatient", Claim.class);
1751 query.setParameter("patientid", patientId);
1752 query.setParameter("startDate", claimStartDateOnly);
1753 query.setParameter("endDate", claimEndDateOnly);
1754 query.setParameter("coverageId", coverageId);
1755 query.setParameter("excludedStatuses", ClaimStatus.excludedFromClaimSearch);
1756 query.setParameter("noaTypeOfBill", NoaTypeOfBill.getAllTypeOfBills());
1757
1758 List<Claim> deleteList = query.getResultList();
1759 if (deleteList != null && deleteList.size() > 0) {
1760 for (Claim cl : deleteList) {
1761 List<ClaimRevenueDetail> oldRevLines = cl.getRevenueDetails();
1762 for (ClaimRevenueDetail rev : oldRevLines) {
1763 boolean deleted = persistenceService.tryDelete(rev);
1764 if (!deleted) {
1765 // oh no! couldn't delete. probably due to a
1766 // ClaimTransaction, which is bad
1767 log.warning("Couldn't delete revenue line id:" + rev.getId() + ", revcode:" + rev.getRevCode()
1768 + ", claim_id:" + cl.getId() + ", date:" + rev.getDate());
1769 }
1770 }
1771 boolean deleted = persistenceService.tryDelete(cl);
1772 if (!deleted) {
1773 log.warning("Could not delete old claim " + cl.getId());
1774 }
1775 }
1776 }
1777 }
1778
1779 protected Noa findNoaForCoverage(Long patientId, Long coverageId, PayerType payerType) {
1780
1781 Noa patientNoa = null;
1782
1783 if (payerType != null && payerType == PayerType.MEDICARE && patientId != null) {
1784 TypedQuery<ElectionPeriod> electionIdQuery = em
1785 .createQuery(
1786 "select new com.hummingbird.jpa.ElectionPeriod(ep.id) from ElectionPeriod ep where ep.insuranceCoverage.id = :coverageId",
1787 ElectionPeriod.class);
1788 electionIdQuery.setParameter("coverageId", coverageId);
1789 List<ElectionPeriod> electionIds = electionIdQuery.getResultList();
1790 ElectionPeriod firstElectionRecord = null;
1791
1792 if (electionIds != null && electionIds.size() > 0)
1793 firstElectionRecord = electionIds.get(electionIds.size() - 1);
1794
1795 if (firstElectionRecord != null)
1796 patientNoa = financeService.findNoeByElection(patientId, firstElectionRecord.getId());
1797
1798 }
1799
1800 return patientNoa;
1801 }
1802
1803 /**
1804 * Concession to imported data, we may not always -have- the insurance
1805 * coverage linkage on readmits, so this is for fallback only.
1806 *
1807 * @param patientId
1808 * @param admissionDate
1809 * @param payerType
1810 * @return
1811 */
1812 private Noa findNoaForAdmission(Long patientId, Date admissionDate, PayerType payerType) {
1813 Noa patientNoa = null;
1814 if (payerType == PayerType.MEDICARE && patientId != null) {
1815 TypedQuery<Noa> query = em.createNamedQuery("Noa.findByPatientandAdmit", Noa.class);
1816 query.setParameter("patientId", patientId);
1817 query.setParameter("admitDate", admissionDate);
1818
1819 List<Noa> noas = query.getResultList();
1820 if (noas != null && noas.size() > 0)
1821 patientNoa = noas.get(0);
1822
1823 }
1824 return patientNoa;
1825 }
1826
1827 /**
1828 * This method takes a list of Claims, and processes them into a collection
1829 * of IndividualClaimDTO.
1830 *
1831 * @param patientClaimList
1832 * @param userTz
1833 * @return
1834 * @throws SAXException
1835 * @throws JAXBException
1836 * @throws ParseException
1837 */
1838 private List<IndividualClaimDTO> createClaimDTOsFromClaims(List<Claim> patientClaimList, final TimeZone userTz,
1839 boolean runAsync, final ClaimsGenerationCache claimsGenerationCache) throws SAXException, JAXBException,
1840 ParseException {
1841 final BillingService billingService = (runAsync) ? sessionContext.getBusinessObject(BillingService.class)
1842 : this;
1843
1844 List<IndividualClaimDTO> individualClaimDTOList = new ArrayList<IndividualClaimDTO>();
1845
1846 if (patientClaimList == null)
1847 return individualClaimDTOList;
1848
1849 Date todayUTC = DateUtil.getTodayDateOnly(userTz);
1850 Date[] currentRange = DateUtil.calculateDateOnlyArbitraryMonthRange(new LocalDate(todayUTC));
1851 boolean currentMonth = false;
1852 if (patientClaimList == null)
1853 return individualClaimDTOList;
1854
1855 final Schema patientClaimSchema = getPatientClaimSchema();
1856
1857 Map<String, Future<IndividualClaimDTO>> indClaimCalcFutures = new HashMap<String, Future<IndividualClaimDTO>>();
1858 ExecutorService claimCalcExecutor = Executors.newFixedThreadPool(Integer.parseInt(configService
1859 .lookupEnvProperty("claim.gen.threads")));
1860
1861 boolean currentMonthDischarge = false;
1862 for (Claim claim : patientClaimList) {
1863 final Claim activeClaim = claim;
1864 Patient patient = activeClaim.getPatient();
1865 List<AdmissionArcDTO> admitRanges = findAdmitsDischargesForPatientClaim(activeClaim.getServiceStart(),
1866 activeClaim.getServiceEnd(), null, activeClaim.getPayerType(), patient.getId(), activeClaim
1867 .getProgramSite().getId());
1868 AdmissionArcDTO arc = null;
1869 if (admitRanges != null && admitRanges.size() > 0)
1870 arc = admitRanges.get(0);
1871 // the UI only allows billing of now, so this should not go future
1872 // any further
1873 if (currentRange[0].equals(activeClaim.getServiceStart()))
1874 currentMonth = true;
1875 if (currentRange[0].before(activeClaim.getServiceStart()))
1876 currentMonth = true;
1877 currentMonthDischarge = false;
1878 // If the discharge date matches the end date then we're a current
1879 // month discharge
1880 if (currentMonth && arc.getDischargeDateOnly() != null
1881 && arc.getDischargeDateOnly().equals(activeClaim.getServiceEnd())
1882 && arc.getDischargeInteraction_id() != null)
1883 currentMonthDischarge = true;
1884 if (activeClaim.getClaimStatus() == ClaimStatus.READY || activeClaim.getClaimStatus() == ClaimStatus.SEQH
1885 || activeClaim.getClaimStatus() == ClaimStatus.ERROR) {
1886 activeClaim.setClaimStatus(fetchReadyOrSeqh(activeClaim));
1887 activeClaim.setStatusReason(null);
1888
1889 // If we've discharged, the claim may not have been updated with
1890 // end dates already.
1891
1892 // ugly: if discharged and discharge date is not end date, and
1893 // the discharge
1894 // date is in the month of the claim, change the end date.
1895 if (arc != null && arc.getDischargeDateOnly() != null
1896 && activeClaim.getServiceEnd().compareTo(arc.getDischargeDateOnly()) != 0
1897 && arc.getDischargeDateOnly().before(activeClaim.getServiceEnd())
1898 && activeClaim.getServiceStart().compareTo(arc.getDischargeDateOnly()) <= 0) {
1899 activeClaim.setServiceEnd(arc.getDischargeDateOnly());
1900 }
1901 }
1902 if (activeClaim.getClaimKey().startsWith("MDRB")) {
1903 final List<InsuranceCoverage> coverages = insService.findPatientInsuranceCoverageInPeriod(
1904 patient.getId(), arc.getAdmitDateOnly(), activeClaim.getServiceEnd(),
1905 EnumSet.of(PayerType.MEDICAID));
1906 final List<PatientFacilityArcDTO> rbFacilityArcs = findPatientFacilitiesForPatientClaim(
1907 arc.getRealAdmitDateOnly(), activeClaim.getServiceEnd(), patient.getId());
1908
1909 if (currentMonth && !currentMonthDischarge)
1910 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1911 else if (PayerType.MEDICARE != activeClaim.getPayerType()
1912 && (activeClaim.getClaimStatus() == ClaimStatus.SEQH || activeClaim.getClaimStatus() == ClaimStatus.ERROR)) {
1913 activeClaim.setClaimStatus(ClaimStatus.READY);
1914 activeClaim.setStatusReason(null);
1915 }
1916 final AdmissionArcDTO admitArc = arc;
1917 Future<IndividualClaimDTO> indClaimCalcFut = claimCalcExecutor
1918 .submit(new Callable<IndividualClaimDTO>() {
1919
1920 @Override
1921 public IndividualClaimDTO call() throws Exception {
1922 return billingService.calculateRBClaimSync(activeClaim, coverages.get(0),
1923 rbFacilityArcs, admitArc, userTz, SecurityContextHolder.getContext(),
1924 claimsGenerationCache);
1925 }
1926 });
1927 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimCalcFut);
1928 } else {
1929 final Noa patientNoa;
1930 if (patient != null && activeClaim.getInsuranceCoverage() != null) {
1931 Noa noa = findNoaForCoverage(patient.getId(), activeClaim.getInsuranceCoverage().getId(),
1932 activeClaim.getPayerType());
1933
1934 if (activeClaim.getPayerType() == PayerType.MEDICARE) {
1935 // fallback for imported data
1936 if (noa == null)
1937 noa = findNoaForAdmission(patient.getId(), arc.getAdmitDateOnly(),
1938 activeClaim.getPayerType());
1939 // if still null, try the "real" admit date
1940 if (noa == null)
1941 noa = findNoaForAdmission(patient.getId(), arc.getRealAdmitDateOnly(),
1942 activeClaim.getPayerType());
1943 }
1944 patientNoa = noa;
1945
1946 if (activeClaim.getClaimStatus() == ClaimStatus.READY
1947 || activeClaim.getClaimStatus() == ClaimStatus.SEQH
1948 || activeClaim.getClaimStatus() == ClaimStatus.ERROR) {
1949 activeClaim.setClaimStatus(fetchReadyOrSeqh(activeClaim));
1950 activeClaim.setStatusReason(null);
1951 if (currentMonth && !currentMonthDischarge)
1952 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1953 else if (PayerType.MEDICARE != activeClaim.getPayerType()) {
1954 activeClaim.setClaimStatus(ClaimStatus.READY);
1955 activeClaim.setStatusReason(null);
1956 }
1957 if ((patientNoa == null || patientNoa.getStatus() != NoaStatus.PAID)
1958 && PayerType.MEDICARE == activeClaim.getPayerType()) {
1959 activeClaim.setClaimStatus(ClaimStatus.SEQH);
1960 if (activeClaim.getStatusReason() != null)
1961 activeClaim.setStatusReason("NOA not processed");
1962 else
1963 activeClaim.setStatusReason(activeClaim.getStatusReason() + ", NOA not processed");
1964 }
1965 }
1966 } else
1967 patientNoa = null;
1968
1969 final AdmissionArcDTO admitArc = arc;
1970 Future<IndividualClaimDTO> indClaimCalcFut = claimCalcExecutor
1971 .submit(new Callable<IndividualClaimDTO>() {
1972
1973 @Override
1974 public IndividualClaimDTO call() throws Exception {
1975 return billingService.calculateClaimSync(activeClaim, admitArc, userTz,
1976 patientClaimSchema, SecurityContextHolder.getContext(), patientNoa, false,
1977 claimsGenerationCache);
1978 }
1979 });
1980 indClaimCalcFutures.put(activeClaim.getClaimKey(), indClaimCalcFut);
1981 }
1982 }
1983
1984 for (Map.Entry<String, Future<IndividualClaimDTO>> entry : indClaimCalcFutures.entrySet()) {
1985 try {
1986 Future<IndividualClaimDTO> indClaimCalcFut = entry.getValue();
1987 if (indClaimCalcFut == null) {
1988 log.severe("Null Future for claimKey: " + entry.getKey());
1989 continue;
1990 }
1991 individualClaimDTOList.add(indClaimCalcFut.get());
1992 } catch (InterruptedException | ExecutionException e) {
1993 String hospiceBin = claimsGenerationCache.getClaimSite().getHospice().getBin();
1994 log.log(Level.SEVERE, "[ClaimGeneration] " + hospiceBin + " -- Error while processing claim -- "
1995 + entry.getKey(), e);
1996 }
1997 }
1998 claimCalcExecutor.shutdown();
1999
2000 return individualClaimDTOList;
2001 }
2002
2003 /**
2004 * This Method adds the Additional meta data along with Patient Bill details
2005 * , required to make the HTTP REST service call
2006 *
2007 * TODO This method should eventually put the appropriate NPI, If we're
2008 * billing Hospice, then fetch the NPI for the Hospice Program of the site
2009 * etc.
2010 *
2011 * @param claimsGenerationCache
2012 */
2013 private void addRequestMetaData(ClaimBatch claimBatch, com.hummingbird.jpa.claims.ClaimBatch batchEntity,
2014 Long carrierId, PayerType batchPayerType, ProgramSite billingProgramSite,
2015 ClaimsGenerationCache claimsGenerationCache) {
2016 InsuranceCarrier batchCarrier = null;
2017 MedicareIntermediary intermediary = null;
2018 ProgramSite billingProgram = billingProgramSite;
2019 Site billingSite = billingProgram.getSite();
2020
2021 if (carrierId != null) {
2022 batchCarrier = em.find(InsuranceCarrier.class, carrierId);
2023 } else {
2024 log.warning("Attempting to add PASBE request metaData for a claim batch with no specified carrier information.");
2025 return;
2026 }
2027
2028 if (batchPayerType == PayerType.MEDICARE && billingSite.getMedicareIntermediaryInfo() != null
2029 && billingSite.getMedicareIntermediaryInfo().getMedicareIntermediary() != null) {
2030 intermediary = billingSite.getMedicareIntermediaryInfo().getMedicareIntermediary();
2031 }
2032
2033 if (batchPayerType == PayerType.MEDICARE) {
2034 // All medicare A claims go on the ProgramSite intermediary.
2035 if (intermediary != null) {
2036 claimBatch.setReceiverID(intermediary.getReceiverId());
2037 claimBatch.setIntermediary(intermediary.getName());
2038 } else {
2039 log.severe("Medicare Intermediary missing for selected site!");
2040 }
2041 } else {
2042 // Other payers(state medicaids) use different data
2043 if (batchCarrier.getReceiverId() != null)
2044 claimBatch.setReceiverID(batchCarrier.getReceiverId().trim());
2045 // TODO This should probably be configurable too.
2046 if (batchCarrier.getPayerType() == PayerType.SELF_PAY) {
2047 claimBatch.setIntermediary("SELFPAY");
2048 } else if (batchCarrier.getPayerType() == PayerType.NON_FUNDED) {
2049 claimBatch.setIntermediary("NONFUNDED");
2050 } else {
2051 claimBatch.setIntermediary("ZIRMED");
2052 claimBatch.setZirmedLogin(billingSite.getZirmedlogin());
2053 claimBatch.setZirmedPassw(billingSite.getZirmedpassword());
2054 }
2055 }
2056
2057 com.hummingbird.ejb.resources.jaxb.crossbill.Address address = new com.hummingbird.ejb.resources.jaxb.crossbill.Address();
2058 com.hummingbird.ejb.resources.jaxb.crossbill.HospiceProvider facility = new com.hummingbird.ejb.resources.jaxb.crossbill.HospiceProvider();
2059
2060 if (billingProgram != null) {
2061 if (batchPayerType == PayerType.MEDICARE) {
2062 claimBatch.setSubmitterID(billingProgram.getSubmitterID());
2063 if (billingProgram.getPtan() != null) {
2064 claimBatch.setMedicarePTAN((billingProgram.getPtan()).trim());
2065 }
2066 if (billingSite.getTestingMedicare()) {
2067 claimBatch.setTestingFlag(true);
2068 }
2069 } else {
2070 // Use site-specific submitter id *if* available
2071 InsuranceCarrierSiteCredentials credentials = claimsGenerationCache.findCoverageSiteCredentials(em,
2072 batchCarrier.getId());
2073 if (credentials != null && credentials.getSubmitterId() != null
2074 && !credentials.getSubmitterId().isEmpty()) {
2075 claimBatch.setSubmitterID(credentials.getSubmitterId().trim());
2076 } else if (batchCarrier.getSubmitterId() != null) {
2077 // Otherwise use the submitter ID from the carrier
2078 claimBatch.setSubmitterID(batchCarrier.getSubmitterId().trim());
2079 } else if (batchPayerType == PayerType.COMMERCIAL) {
2080 claimBatch.setSubmitterID("COMMERCIAL_" + billingSite.getPasBin());
2081 } else if (batchCarrier.getPayerType() == PayerType.SELF_PAY
2082 || batchCarrier.getPayerType() == PayerType.NON_FUNDED) {
2083 // For selfpay, using a unique site identifier instead of
2084 // submitter ID
2085 String siteIdentifier = billingSite.getHospice().getBin();
2086 if (billingSite.getNickName() == null) {
2087 log.severe("Site " + billingSite.getName()
2088 + " has no nickname. Claim XML may be generated in wrong dir.");
2089 } else {
2090 siteIdentifier += "_" + billingSite.getNickName();
2091 }
2092 claimBatch.setSubmitterID(siteIdentifier);
2093 }
2094
2095 }
2096 if (batchPayerType == PayerType.MEDICAID) {
2097 if (billingSite.getTestingMedicaid()) {
2098 claimBatch.setTestingFlag(true);
2099 }
2100 }
2101 if (billingProgram.getNpi() != null) {
2102 facility.setFacilityNPI((billingProgram.getNpi()).trim());
2103 }
2104 }
2105
2106 if (billingSite.getBillingAddress().getCity() != null) {
2107 address.setCity((billingSite.getBillingAddress().getCity()).trim());
2108 }
2109 if (billingSite.getBillingAddress().getState() != null) {
2110 address.setState((billingSite.getBillingAddress().getState()).trim());
2111 }
2112 if (billingSite.getBillingAddress().getStreet1() != null) {
2113 address.setStreet1((billingSite.getBillingAddress().getStreet1()).trim());
2114 }
2115 if (billingSite.getBillingAddress().getZip() != null) {
2116 address.setZip(billingSite.getBillingAddress().getZip() + billingSite.getBillingAddress().getZip4());
2117 }
2118
2119 facility.setFacilityAddress(address);
2120 if (billingSite.getName() != null) {
2121 facility.setFacilityName(CommonUtil.scrubNames(billingSite.getName()).trim());
2122 }
2123 facility.setHospitalBased(billingSite.getHospitalBased());
2124 if (billingProgramSite.getFederalTaxId() != null) {
2125 facility.setFederalID((billingProgramSite.getFederalTaxId()).trim());
2126 } else if (billingSite.getFederalIdEin() != null) {
2127 facility.setFederalID((billingSite.getFederalIdEin()).trim());
2128 }
2129
2130 claimBatch.setHospiceProvider(facility);
2131 claimBatch.setBillingRep(billingSite.getBillingRep());
2132 String sitePhone = "";
2133 for (Phone phone : billingSite.getPhones()) {
2134 if (phone.getPhoneType() == PhoneType.OFFICE) {
2135 sitePhone = phone.getNumber();
2136 break;
2137 }
2138 }
2139 claimBatch.setSitePhone(sitePhone);
2140 if (batchEntity != null)
2141 claimBatch.setBatchID(batchEntity.getBatchKey());
2142 }
2143
2144 /**
2145 * This method checks whether the claim to be submitted has all the required
2146 * fields filled out. Claims that have missing fields will show up on as an
2147 * error on the report.
2148 *
2149 * @param jaxbPatientClaim
2150 * @param patientClaimSchema
2151 * @param singleClaim
2152 * @throws JAXBException
2153 */
2154
2155 public IndividualClaimDTO validateRequestClaimXml(IndividualClaimDTO individualClaimDTO, Schema patientClaimSchema) {
2156 final List<String> errorMessages = new ArrayList<String>();
2157 PatientClaim jaxbPatientClaim = individualClaimDTO.getJaxbPatientClaim();
2158 boolean isValid = true;
2159
2160 // Set this to TRUE if you need list of missing fields.
2161 boolean detailErrorToggle = true;
2162
2163 try {
2164 JAXBContext jc = JAXBContext.newInstance(com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory.class);
2165
2166 Marshaller marshaller = jc.createMarshaller();
2167 marshaller.setSchema(patientClaimSchema);
2168 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
2169
2170 if (patientClaimSchema != null) {
2171 marshaller.setEventHandler(new ValidationEventHandler() {
2172
2173 @Override
2174 public boolean handleEvent(ValidationEvent event) {
2175 errorMessages.add(event.getMessage().substring(event.getMessage().indexOf(":") + 1).trim());
2176 return true;
2177 }
2178
2179 });
2180
2181 marshaller.marshal(new JAXBElement<PatientClaim>(new QName("patient-claim"), PatientClaim.class,
2182 jaxbPatientClaim), new DefaultHandler());
2183 if (errorMessages.size() > 0)
2184 throw new JAXBException("Validation Failed");
2185 } else {
2186 log.warning("patientClient Schema is not present in the place");
2187 // This Exception will be caught and application specific
2188 // PASBillingReportException will be throw from WEB Layer
2189 throw new RuntimeException();
2190 }
2191 } catch (JAXBException jxe) {
2192 StringBuilder builder = new StringBuilder();
2193
2194 if (detailErrorToggle)
2195 for (String errMsg : errorMessages)
2196 builder.append(errMsg + "\n");
2197 else
2198 builder.append("ERROR - missing required fields" + "\n");
2199
2200 individualClaimDTO.setErrorStatusMessage(builder.toString());
2201 isValid = false;
2202
2203 /*
2204 * Update the Claim status for the ERROR claim
2205 */
2206 updateClaimStatus(individualClaimDTO.getClaimKey(), ClaimStatus.ERROR, null);
2207 }
2208 individualClaimDTO.setValid(isValid);
2209 return individualClaimDTO;
2210 }
2211
2212 /**
2213 * This method is used to convert the response from PASBe to BillDTO
2214 *
2215 * @param preBillReport
2216 * @param isSubmit
2217 * @return
2218 * @throws ParseException
2219 */
2220 public BillDTO convertPasbeToDTO(PreBillReport preBillReport, boolean isSubmit, boolean isLiveBill,
2221 ClaimsGenerationCache claimsCache) throws ParseException {
2222 BillDTO billDto = new BillDTO();
2223 BigDecimal amountSubmitted = BigDecimal.ZERO;
2224 BigDecimal amountNotSubmitted = BigDecimal.ZERO;
2225
2226 if (preBillReport != null) {
2227
2228 // BillPatient info
2229 List<PatientBill> jaxbBillPatients = preBillReport.getPatientBill();
2230 List<BillPatientDTO> billPatientDTOList = new ArrayList<BillPatientDTO>();
2231 BillPatientDTO billPatientDto;
2232 Claim prevClaim = null;
2233 ClaimStatus newClaimStatus = null;
2234 boolean isUpdateStatus = false;
2235 String reason;
2236
2237 billDto.setStartDate(DateUtil.UTC_MMDDYY.parse(preBillReport.getHeader().getReportingPeriod()
2238 .getStartDate()));
2239 billDto.setEndDate(DateUtil.UTC_MMDDYY.parse(preBillReport.getHeader().getReportingPeriod().getEndDate()));
2240
2241 for (PatientBill jaxbPatientBill : jaxbBillPatients) {
2242
2243 // Convert PatientBill to BillPatientDTO
2244 billPatientDto = convertPatientBillToBillPatientDTO(jaxbPatientBill, claimsCache);
2245
2246 newClaimStatus = null;
2247 isUpdateStatus = false;
2248 reason = "";
2249
2250 log.fine("Received for patient - " + billPatientDto.getFirstName());
2251
2252 prevClaim = findClaim(billPatientDto.getClaimKey());
2253
2254 // Set claim status
2255 if (jaxbPatientBill.getError() == null) {
2256 // Get the correct claim status from BillingStatus table
2257 if (isSubmit && isLiveBill && prevClaim != null && prevClaim.getClaimStatus() != null
2258 && prevClaim.getClaimStatus().isGoodToSubmit()) {
2259 newClaimStatus = ClaimStatus.SENT;
2260 isUpdateStatus = true;
2261 } else if (isSubmit && !isLiveBill && prevClaim != null && prevClaim.getClaimStatus() != null
2262 && prevClaim.getClaimStatus().isGoodToSubmit()) {
2263 newClaimStatus = ClaimStatus.SENTEXT;
2264 isUpdateStatus = true;
2265 } else if (prevClaim != null && prevClaim.getClaimStatus() == ClaimStatus.ERROR) {
2266 /*
2267 * If the claim previously had an ERROR status, we need
2268 * to set it back to READY or SEQH
2269 */
2270 newClaimStatus = fetchReadyOrSeqh(prevClaim);
2271 isUpdateStatus = true;
2272 }
2273
2274 else
2275 newClaimStatus = prevClaim != null ? prevClaim.getClaimStatus() : null;
2276
2277 if (prevClaim != null) {
2278 if (billPatientDto.getAddress() != null) {
2279 if (prevClaim.getPatientAddress() == null) {
2280 prevClaim.setPatientAddress(new com.hummingbird.jpa.Address());
2281 }
2282 prevClaim.getPatientAddress().setStreet1(billPatientDto.getAddress().getStreet1());
2283 prevClaim.getPatientAddress().setStreet2(billPatientDto.getAddress().getStreet2());
2284 prevClaim.getPatientAddress().setCity(billPatientDto.getAddress().getCity());
2285 prevClaim.getPatientAddress().setState(billPatientDto.getAddress().getState());
2286 prevClaim.getPatientAddress().setZip(billPatientDto.getAddress().getZip());
2287 }
2288
2289 prevClaim.setTypeOfBill(billPatientDto.getTypeOfBill());
2290 prevClaim.setStatusCode(billPatientDto.getCurrentStatusCode());
2291 // prevClaim.setPatientAdmitDate(billPatientDto.getAdmitDate());
2292 prevClaim.setAttendingPhysicianName(billPatientDto.getAttendingPhysicianName());
2293 prevClaim.setAttendingPhysicianNpi(billPatientDto.getAttendingPhysicianNpi());
2294 prevClaim.setCertifyingPhysicianName(billPatientDto.getCertifyingPhysicianName());
2295 prevClaim.setCertifyingPhysicianNpi(billPatientDto.getCertifyingPhysicianNpi());
2296 prevClaim.setDiagnosisCodes(billPatientDto.getDiagnosisCodes());
2297 prevClaim.setConditionCodes(encodeListOfCodes(billPatientDto.getConditionCodes()));
2298 prevClaim.setOccurrenceCodes(encodeOccurrenceCodes(billPatientDto.getOccurrenceCodes()));
2299 prevClaim.setValueCodes(encodeListOfCodes(billPatientDto.getValueCodes()));
2300 prevClaim.setCbsa(billPatientDto.getCbsaCode());
2301 prevClaim.setServiceFacilityNpi(billPatientDto.getFacilityNpi());
2302
2303 if (jaxbPatientBill.getCoveredDays() != null) {
2304 CoveredDays coveredDays = jaxbPatientBill.getCoveredDays();
2305
2306 prevClaim.setDaysRtn(Integer.valueOf(coveredDays.getRoutine() != null ? coveredDays
2307 .getRoutine() : 0));
2308 prevClaim.setDaysCc(Integer.valueOf(coveredDays.getCc() != null ? coveredDays.getCc() : 0));
2309 prevClaim.setDaysResp(Integer.valueOf(coveredDays.getRespit() != null ? coveredDays
2310 .getRespit() : 0));
2311 prevClaim.setDaysGip(Integer.valueOf(coveredDays.getGip() != null ? coveredDays.getGip()
2312 : 0));
2313
2314 prevClaim.setCoveredDays(Integer.valueOf(coveredDays.getTotalDays() != null ? coveredDays
2315 .getTotalDays() : 0));
2316 }
2317 prevClaim.setTotalWithVisits(billPatientDto.getTotalWithBillables());
2318
2319 // persist visit lines
2320 createOrUpdateClaimVisits(jaxbPatientBill.getSvcLine(), prevClaim);
2321
2322 // persist meds
2323 createOrUpdateClaimMedication(jaxbPatientBill.getMedLine(), prevClaim);
2324
2325 }
2326 } else {
2327 // Update the existing BillingStatus with ERROR status
2328 if (prevClaim != null
2329 && !isSubmit
2330 && !(prevClaim.getClaimStatus() == ClaimStatus.READY
2331 || prevClaim.getClaimStatus() == ClaimStatus.SEQH || prevClaim.getClaimStatus() == ClaimStatus.ERROR)) {
2332
2333 log.info("Patient in a/r id:" + billPatientDto.getId() + ", not updating claim:"
2334 + billPatientDto.getClaimKey());
2335 continue;
2336 }
2337 newClaimStatus = ClaimStatus.ERROR;
2338 reason = jaxbPatientBill.getError();
2339 billPatientDto.setReason(reason);
2340 isUpdateStatus = true;
2341 }
2342
2343 if (isUpdateStatus) {
2344 if (prevClaim != null) {
2345 prevClaim.setClaimStatus(newClaimStatus);
2346 prevClaim.setStatusReason(reason);
2347 }
2348 }
2349
2350 if (prevClaim != null) {
2351
2352 if (prevClaim.getReviewNeeded() != null && prevClaim.getPosted() != null) {
2353 /*
2354 * If the claim is being refreshed and we already have a
2355 * posted date, we shouldn't change the posted date, so
2356 * do nothing
2357 */
2358 } else {
2359 prevClaim.setPosted(financeService.getLastServiceOrBooksClosedDate(prevClaim));
2360 }
2361 }
2362
2363 billPatientDto.setClaimStatus(newClaimStatus);
2364
2365 // Add bill patient to list
2366 billPatientDTOList.add(billPatientDto);
2367
2368 if (newClaimStatus != null) {
2369 switch (newClaimStatus) {
2370 case ERROR:
2371 case SEQH:
2372 default:
2373 amountNotSubmitted = amountNotSubmitted.add(billPatientDto.getTotalReceivable());
2374 break;
2375 case SENT:
2376 amountSubmitted = amountSubmitted.add(billPatientDto.getTotalReceivable());
2377 break;
2378 }
2379 }
2380 }
2381 // Add patients to bill dto
2382 billDto.setPatients(billPatientDTOList);
2383 }
2384 billDto.setTotalBeingSubmittedAmount(amountSubmitted);
2385 billDto.setTotalNotBeingSubmittedAmount(amountNotSubmitted);
2386 return billDto;
2387 }
2388
2389 private double floatToDouble(float fl) {
2390 return Double.parseDouble(Float.toString(fl));
2391 }
2392
2393 /**
2394 * Creates or updates existing ClaimPatientVisits for the given Claim to
2395 * match the given visitLines.
2396 *
2397 * @param visitLines
2398 * @param claim
2399 */
2400 private void createOrUpdateClaimVisits(List<SvcLine> visitLines, Claim claim) {
2401 TypedQuery<ClaimPatientVisit> query = em.createNamedQuery("ClaimPatientVisit.findVisitsByClaimId",
2402 ClaimPatientVisit.class);
2403 query.setParameter("claimId", claim.getId());
2404
2405 List<ClaimPatientVisit> claimVisits = query.getResultList();
2406
2407 int i = 0;
2408 while (i < visitLines.size()) {
2409 SvcLine svcLine = visitLines.get(i);
2410
2411 ClaimPatientVisit claimVisit = null;
2412
2413 if (claimVisits.size() > i) {
2414 claimVisit = claimVisits.get(i);
2415 } else {
2416 claimVisit = new ClaimPatientVisit();
2417 claimVisit.setClaim(claim);
2418 }
2419
2420 try {
2421 claimVisit.setVisitDate(DateUtil.UTC_MMDDYY.parse(svcLine.getDate()));
2422 } catch (ParseException e) {
2423 e.printStackTrace();
2424 }
2425 claimVisit.setRevcode(svcLine.getRevCode().toString());
2426 if (svcLine.getDescription() != null)
2427 claimVisit.setDescription(svcLine.getDescription().trim());
2428 claimVisit.setHcpcs(svcLine.getHcpcs());
2429 claimVisit.setUnits(floatToDouble(svcLine.getUnits()));
2430 claimVisit.setCharges(svcLine.getAmount());
2431
2432 claimVisit = persistenceService.update(claimVisit);
2433 i++;
2434 }
2435
2436 while (i < claimVisits.size()) {
2437 ClaimPatientVisit claimVisit = claimVisits.get(i);
2438 persistenceService.remove(claimVisit, true);
2439 i++;
2440 }
2441
2442 }
2443
2444 /**
2445 * Creates or updates existing ClaimMedication for the given Claim to match
2446 * the given medLines.
2447 *
2448 * @param medLines
2449 * @param claim
2450 */
2451 private void createOrUpdateClaimMedication(List<MedLine> medLines, Claim claim) {
2452 TypedQuery<ClaimMedication> query = em.createNamedQuery("ClaimMedication.findMedsByClaimId",
2453 ClaimMedication.class);
2454 query.setParameter("claimId", claim.getId());
2455
2456 List<ClaimMedication> claimMeds = query.getResultList();
2457
2458 int i = 0;
2459 while (i < medLines.size()) {
2460 MedLine medLine = medLines.get(i);
2461
2462 ClaimMedication claimMed = null;
2463
2464 if (claimMeds.size() > i) {
2465 claimMed = claimMeds.get(i);
2466 } else {
2467 claimMed = new ClaimMedication();
2468 claimMed.setClaim(claim);
2469 }
2470
2471 try {
2472 claimMed.setFillDate(DateUtil.UTC_MMDDYY.parse(medLine.getDate()));
2473 } catch (ParseException e) {
2474 e.printStackTrace();
2475 }
2476
2477 claimMed.setRevCode(medLine.getRevCode().trim());
2478 claimMed.setUom(medLine.getUom().trim());
2479 claimMed.setDescription(medLine.getDescription().trim());
2480 claimMed.setHcpcs(medLine.getHcpcs());
2481 claimMed.setUnits(floatToDouble(medLine.getUnits()));
2482 claimMed.setReporting(medLine.getReporting().trim());
2483 claimMed.setCharges(medLine.getAmount());
2484 claimMed.setNdc(medLine.getNdc().trim());
2485
2486 claimMed = persistenceService.update(claimMed);
2487 i++;
2488 }
2489
2490 while (i < claimMeds.size()) {
2491 ClaimMedication claimMed = claimMeds.get(i);
2492 persistenceService.remove(claimMed, true);
2493 i++;
2494 }
2495
2496 }
2497
2498 private String encodeOccurrenceCodes(List<OccurrenceDTO> occurrenceCodes) {
2499 StringBuffer sb = new StringBuffer();
2500
2501 if (occurrenceCodes == null || occurrenceCodes.isEmpty()) {
2502 return null;
2503 }
2504
2505 for (int i = 0; i < occurrenceCodes.size(); i++) {
2506 OccurrenceDTO occurrenceCode = occurrenceCodes.get(i);
2507 if (i != 0) {
2508 sb.append("; ");
2509 }
2510 if (occurrenceCode.getType() != null)
2511 sb.append(occurrenceCode.getType());
2512 sb.append(", ");
2513 if (occurrenceCode.getCode() != null)
2514 sb.append(occurrenceCode.getCode());
2515 }
2516 return sb.toString();
2517 }
2518
2519 private String encodeListOfCodes(List<String> codes) {
2520 StringBuffer sb = new StringBuffer();
2521
2522 if (codes == null || codes.isEmpty()) {
2523 return null;
2524 }
2525
2526 for (int i = 0; i < codes.size(); i++) {
2527 String code = codes.get(i);
2528 if (i != 0) {
2529 sb.append("; ");
2530 }
2531 sb.append(code);
2532 }
2533 return sb.toString();
2534 }
2535
2536 private List<String> decodeListOfCodes(String codes) {
2537 List<String> result = new ArrayList<String>();
2538
2539 if (codes == null || codes.isEmpty()) {
2540 return result;
2541 }
2542
2543 String[] tokens = codes.split("; ");
2544 for (int i = 0; i < tokens.length; i++) {
2545 result.add(tokens[i]);
2546 }
2547
2548 return result;
2549 }
2550
2551 private List<OccurrenceDTO> decodeOccuranceCodes(String occurrenceCodes) {
2552 List<OccurrenceDTO> result = new ArrayList<OccurrenceDTO>();
2553
2554 if (occurrenceCodes == null || occurrenceCodes.isEmpty()) {
2555 return result;
2556 }
2557
2558 String[] tokens = occurrenceCodes.split("; ");
2559 for (int i = 0; i < tokens.length; i++) {
2560 String token = tokens[i];
2561 int index = token.indexOf(", ");
2562
2563 if (index >= 0) {
2564 String code = null;
2565 String date = null;
2566 if (index > 0) {
2567 code = token.substring(0, index);
2568 }
2569
2570 if ((index + 2) < token.length()) {
2571 date = token.substring(index + 2, token.length());
2572 }
2573 OccurrenceDTO occurrenceDTO = new OccurrenceDTO();
2574 occurrenceDTO.setType(code);
2575 occurrenceDTO.setCode(date);
2576 result.add(occurrenceDTO);
2577 }
2578 }
2579
2580 return result;
2581 }
2582
2583 /**
2584 * Gather All of the individual information necessary to create an R&B
2585 * claim. There is less information than a regular claim.
2586 *
2587 * @param claimEntity
2588 * @param insCoverage
2589 * @param rbFacilities
2590 * @param arc
2591 * @param siteTz
2592 * @param context
2593 * @param claimsGenerationCache
2594 * @return
2595 */
2596 public IndividualClaimDTO calculateRBClaimSync(Claim claimEntity, InsuranceCoverage insCoverage,
2597 List<PatientFacilityArcDTO> rbFacilities, AdmissionArcDTO arc, TimeZone siteTz, SecurityContext context,
2598 ClaimsGenerationCache claimsGenerationCache) {
2599 IndividualClaimDTO individualClaimDTO = new IndividualClaimDTO();
2600
2601 SecurityContextHolder.setContext(context);
2602
2603 if (claimEntity == null) {
2604 return null;
2605 }
2606
2607 Patient jpaPatient = claimEntity.getPatient();
2608 Date claimStartDateOnly = claimEntity.getServiceStart();
2609 Date claimEndDateOnly = claimEntity.getServiceEnd();
2610
2611 // When executing this method from the client, it's possible to run
2612 // synchronously, but lose the login context. we want to keep the
2613 // current tenant for the current claim
2614 if (auth.getCurrentUser() == null || multiTenantService.getCurrentTenantId() == null)
2615 multiTenantService.setFakeAuthentication(claimEntity.getPatient().getHospice().getBin(), true);
2616
2617 log.fine("started calculateClaim thread for patient with id : " + jpaPatient.getId());
2618
2619 com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory objFactory = new ObjectFactory();
2620
2621 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim jaxbPatientClaim = objFactory.createPatientClaim();
2622 com.hummingbird.ejb.resources.jaxb.crossbill.Patient jaxbPatient = objFactory.createPatient();
2623
2624 // Each claim is assigned a unique key
2625 final String claimKey = claimEntity.getClaimKey();
2626
2627 /**
2628 * Header details
2629 */
2630 jaxbPatientClaim.setClaimKey(claimKey);
2631 jaxbPatientClaim.setClaimStartDate(convertToOutgoingClaimDateOnlyFormat(claimStartDateOnly));
2632 jaxbPatientClaim.setClaimStopDate(convertToOutgoingClaimDateOnlyFormat(claimEndDateOnly));
2633 jaxbPatientClaim.setClaimHeld(claimEntity.getClaimStatus().isClaimToBeHeld()
2634 || (claimEntity.getIsOnManualHold() != null && claimEntity.getIsOnManualHold()));
2635
2636 /**
2637 * Basic details
2638 */
2639 jaxbPatient.setPatientID(jpaPatient.getId().toString().trim());
2640 String patNumber = null;
2641 if (jpaPatient.getPatientNumber() != null)
2642 patNumber = jpaPatient.getPatientNumber().replaceAll("-", "");
2643 jaxbPatient.setPatientNumber(patNumber);
2644 jaxbPatient.setFirstName(jpaPatient.getFirstName().trim());
2645 jaxbPatient.setLastName(jpaPatient.getLastName().trim());
2646
2647 // Gender
2648 if (jpaPatient.getSex() != null)
2649 jaxbPatient.setGender(jpaPatient.getSex().getGenderShort());
2650 com.hummingbird.jpa.Address jpaAddress = jpaPatient.getPrimaryAddress();
2651
2652 // Address
2653 if (jpaAddress != null) {
2654 com.hummingbird.ejb.resources.jaxb.crossbill.Address jaxbPatientAddress = objFactory.createAddress();
2655 jaxbPatientAddress.setStreet1(jpaAddress.getStreet1());
2656 jaxbPatientAddress.setCity(jpaAddress.getCity());
2657 jaxbPatientAddress.setState(jpaAddress.getState());
2658 if (jpaPatient.getPhones() != null && jpaPatient.getPhones().size() > 0) {
2659 Phone phone = jpaPatient.getPhones().get(0);
2660 jaxbPatientAddress.setPhone(phone.getNumber().replaceAll("[^\\d]", ""));
2661 }
2662 StringBuilder zipCode = new StringBuilder();
2663 zipCode.append(jpaAddress.getZip());
2664 zipCode.append(jpaAddress.getZip4() == null ? "9999" : jpaAddress.getZip4());
2665 jaxbPatientAddress.setZip(zipCode.toString());
2666 jaxbPatient.setAddress(jaxbPatientAddress);
2667 }
2668
2669 RateCodeGroup nationalRates = claimsGenerationCache.findEffectiveRateCodeGroup(claimStartDateOnly,
2670 rateCodeService);
2671
2672 // Insurance Coverages
2673 InsuranceCoverage jpaPrimeCoverage = insCoverage;
2674
2675 // Primary Insurance
2676 if (jpaPrimeCoverage != null) {
2677 List<PriorAuthorization> priorAuths = insService.fetchPriorAuthorizations(jpaPrimeCoverage.getId(),
2678 claimStartDateOnly, claimEndDateOnly, siteTz);
2679
2680 List<InsuranceCarrierRates> carrierSpecificRates = null;
2681 if (jpaPrimeCoverage.getInsurancecarrier() != null
2682 && jpaPrimeCoverage.getPayerType() == PayerType.COMMERCIAL) {
2683 carrierSpecificRates = claimsGenerationCache.findInsuranceCarrierRates(em, jpaPrimeCoverage
2684 .getInsurancecarrier().getId());
2685 }
2686 InsuranceCarrierSiteCredentials primeCarrierSiteCredentials = claimsGenerationCache
2687 .findCoverageSiteCredentials(em, jpaPrimeCoverage.getInsurancecarrier().getId());
2688 jaxbPatient.setPrimaryInsurance(createInsurancePolicyNode(jpaPrimeCoverage, priorAuths, nationalRates,
2689 carrierSpecificRates, null, primeCarrierSiteCredentials, objFactory));
2690 }
2691
2692 // **** Admission ****//
2693 Date dischargeDateOnly = null;
2694 Date utcDischargeDateTime = null;
2695 Calendar cal = Calendar.getInstance(DateUtil.UTC_ZONE);
2696 cal.setTime(claimEndDateOnly);
2697 // Searching for < next day, instead of <= 23:59 on same day.
2698 cal.add(Calendar.DATE, 1);
2699 Date nextDay = cal.getTime();
2700 boolean skipDischargeInfo = false;
2701
2702 if (arc != null) {
2703 if (arc.getDischargeDateOnly() != null) {
2704 dischargeDateOnly = arc.getDischargeDateOnly();
2705 utcDischargeDateTime = arc.getUtcDischargeDateTime();
2706 }
2707 } else { // for unit tests which do not make arcs in advance
2708 List<AdmissionArcDTO> statusdates = findAdmitsDischargesForPatientClaim(claimStartDateOnly,
2709 claimEndDateOnly, null, null, jpaPatient.getId(), claimEntity.getProgramSite().getId());
2710 if (statusdates.size() > 0) {
2711 arc = statusdates.get(0);
2712 if (statusdates.get(0).getDischargeDateOnly() != null) {
2713 dischargeDateOnly = statusdates.get(0).getDischargeDateOnly();
2714 utcDischargeDateTime = statusdates.get(0).getUtcDischargeDateTime();
2715 }
2716 }
2717 }
2718 if ((dischargeDateOnly != null && dischargeDateOnly.after(nextDay))
2719 && jpaPrimeCoverage.getEndDate().after(nextDay)) {
2720 skipDischargeInfo = true;
2721 arc.setDischargeDateOnly(null);
2722 arc.setDischargeInteraction_id(null);
2723 }
2724
2725 jaxbPatient.setPatientAdmissions(createPatientAdmitNode(jpaPatient, arc, insCoverage, null, skipDischargeInfo,
2726 0, objFactory));
2727
2728 jaxbPatient.setRoomBoardRecs(createRoomBoardRecsNode(jaxbPatient, insCoverage, rbFacilities, nationalRates,
2729 dischargeDateOnly, true, arc, claimEntity, claimsGenerationCache, false, objFactory, claimEndDateOnly));
2730
2731 // Date of birth
2732 if (jpaPatient.getDateOfBirth() != null)
2733 jaxbPatient.setBirthDate(convertToOutgoingClaimDateOnlyFormat(jpaPatient.getDateOfBirth()));
2734
2735 // Attending Physician
2736 Person jpaAttPhysician = getPatientPhysician(PatientPhysicianType.ATTENDING_PHYSICIAN, jpaPatient.getId(),
2737 claimEndDateOnly);
2738 if (jpaAttPhysician != null) {
2739 jaxbPatientClaim.setAttendingPhysician(createPhysicianNode(jpaAttPhysician, objFactory));
2740 }
2741
2742 // Referring Physician
2743 Person jpaRefPhysician = getPatientPhysician(PatientPhysicianType.REFERRING_PHYSICIAN, jpaPatient.getId(),
2744 claimEndDateOnly);
2745 if (jpaRefPhysician != null) {
2746 jaxbPatientClaim.setReferringPhysician(createPhysicianNode(jpaRefPhysician, objFactory));
2747 }
2748
2749 // **** Diagnoses ****//
2750 List<PatientDiagnoses> jpaDiags = getPatientDiagsForClaim(jpaPatient.getId(), claimEndDateOnly);
2751
2752 com.hummingbird.ejb.resources.jaxb.crossbill.Patient.OtherDiagnoses jaxbOtherDiagnoses = new com.hummingbird.ejb.resources.jaxb.crossbill.Patient.OtherDiagnoses();
2753 if (jpaDiags != null && !jpaDiags.isEmpty()) {
2754 boolean admit = false;
2755 for (PatientDiagnoses d : jpaDiags) {
2756 com.hummingbird.ejb.resources.jaxb.crossbill.Diagnosis diagnosis = createDiagnosisNode(
2757 d.getDiagnosis(), d.getIcd10diagnosis(), objFactory);
2758
2759 if (!admit) {
2760 jaxbPatient.setAdmittingDiagnosis(diagnosis);
2761 admit = true;
2762 } else {
2763 jaxbOtherDiagnoses.getDiagnosis().add(diagnosis);
2764 }
2765 }
2766 if (jaxbOtherDiagnoses.getDiagnosis().size() > 0) {
2767 jaxbPatient.setOtherDiagnoses(jaxbOtherDiagnoses);
2768 }
2769 }
2770
2771 // Add Patient to Claim
2772 jaxbPatientClaim.setPatientInfo(jaxbPatient);
2773
2774 // Setting the claim dto
2775 individualClaimDTO.setClaimKey(claimKey);
2776 individualClaimDTO.setStartDate(claimStartDateOnly);
2777 individualClaimDTO.setEndDate(claimEndDateOnly);
2778 individualClaimDTO.setPatientEntityId(jpaPatient.getId());
2779 individualClaimDTO.setJaxbPatientClaim(jaxbPatientClaim);
2780
2781 return individualClaimDTO;
2782 }
2783
2784 public IndividualClaimDTO calculatePalliativeClaim(Claim claimEntity, InsuranceCoverage insCoverage,
2785 AdmissionArcDTO arc, TimeZone siteTz, SecurityContext context, ClaimsGenerationCache claimsGenerationCache) {
2786 IndividualClaimDTO individualClaimDTO = new IndividualClaimDTO();
2787
2788 SecurityContextHolder.setContext(context);
2789
2790 if (claimEntity == null) {
2791 return null;
2792 }
2793
2794 Patient jpaPatient = claimEntity.getPatient();
2795 Date claimStartDateOnly = claimEntity.getServiceStart();
2796 Date claimEndDateOnly = claimEntity.getServiceEnd();
2797
2798 // When executing this method from the client, it's possible to run
2799 // synchronously, but lose the login context. we want to keep the
2800 // current tenant for the current claim
2801 if (auth.getCurrentUser() == null || multiTenantService.getCurrentTenantId() == null)
2802 multiTenantService.setFakeAuthentication(claimEntity.getPatient().getHospice().getBin(), true);
2803
2804 log.fine("started calculateClaim thread for patient with id : " + jpaPatient.getId());
2805
2806 com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory objFactory = new ObjectFactory();
2807
2808 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim jaxbPatientClaim = objFactory.createPatientClaim();
2809 com.hummingbird.ejb.resources.jaxb.crossbill.Patient jaxbPatient = objFactory.createPatient();
2810
2811 // Each claim is assigned a unique key
2812 final String claimKey = claimEntity.getClaimKey();
2813
2814 /**
2815 * Header details
2816 */
2817 jaxbPatientClaim.setClaimKey(claimKey);
2818 jaxbPatientClaim.setClaimStartDate(convertToOutgoingClaimDateOnlyFormat(claimStartDateOnly));
2819 jaxbPatientClaim.setClaimStopDate(convertToOutgoingClaimDateOnlyFormat(claimEndDateOnly));
2820 jaxbPatientClaim.setClaimHeld(claimEntity.getClaimStatus().isClaimToBeHeld()
2821 || (claimEntity.getIsOnManualHold() != null && claimEntity.getIsOnManualHold()));
2822
2823 /**
2824 * Basic details
2825 */
2826 jaxbPatient.setPatientID(jpaPatient.getId().toString().trim());
2827 String patNumber = null;
2828 if (jpaPatient.getPatientNumber() != null)
2829 patNumber = jpaPatient.getPatientNumber().replaceAll("-", "");
2830 jaxbPatient.setPatientNumber(patNumber);
2831 jaxbPatient.setFirstName(jpaPatient.getFirstName().trim());
2832 jaxbPatient.setLastName(jpaPatient.getLastName().trim());
2833
2834 // Gender
2835 if (jpaPatient.getSex() != null)
2836 jaxbPatient.setGender(jpaPatient.getSex().getGenderShort());
2837 com.hummingbird.jpa.Address jpaAddress = jpaPatient.getPrimaryAddress();
2838
2839 // Address
2840 if (jpaAddress != null) {
2841 com.hummingbird.ejb.resources.jaxb.crossbill.Address jaxbPatientAddress = objFactory.createAddress();
2842 jaxbPatientAddress.setStreet1(jpaAddress.getStreet1());
2843 jaxbPatientAddress.setCity(jpaAddress.getCity());
2844 jaxbPatientAddress.setState(jpaAddress.getState());
2845 if (jpaPatient.getPhones() != null && jpaPatient.getPhones().size() > 0) {
2846 Phone phone = jpaPatient.getPhones().get(0);
2847 jaxbPatientAddress.setPhone(phone.getNumber().replaceAll("[^\\d]", ""));
2848 }
2849 StringBuilder zipCode = new StringBuilder();
2850 zipCode.append(jpaAddress.getZip());
2851 zipCode.append(jpaAddress.getZip4() == null ? "9999" : jpaAddress.getZip4());
2852 jaxbPatientAddress.setZip(zipCode.toString());
2853 jaxbPatient.setAddress(jaxbPatientAddress);
2854 }
2855
2856 RateCodeGroup nationalRates = claimsGenerationCache.findEffectiveRateCodeGroup(claimStartDateOnly,
2857 rateCodeService);
2858
2859 // Insurance Coverages
2860 InsuranceCoverage jpaPrimeCoverage = insCoverage;
2861
2862 // Primary Insurance
2863 if (jpaPrimeCoverage != null) {
2864 List<PriorAuthorization> priorAuths = insService.fetchPriorAuthorizations(jpaPrimeCoverage.getId(),
2865 claimStartDateOnly, claimEndDateOnly, siteTz);
2866
2867 List<InsuranceCarrierRates> carrierSpecificRates = null;
2868 if (jpaPrimeCoverage.getInsurancecarrier() != null
2869 && jpaPrimeCoverage.getPayerType() == PayerType.COMMERCIAL) {
2870 carrierSpecificRates = claimsGenerationCache.findInsuranceCarrierRates(em, jpaPrimeCoverage
2871 .getInsurancecarrier().getId());
2872 }
2873 InsuranceCarrierSiteCredentials primeCarrierSiteCredentials = claimsGenerationCache
2874 .findCoverageSiteCredentials(em, jpaPrimeCoverage.getInsurancecarrier().getId());
2875 jaxbPatient.setPrimaryInsurance(createInsurancePolicyNode(jpaPrimeCoverage, priorAuths, nationalRates,
2876 carrierSpecificRates, null, primeCarrierSiteCredentials, objFactory));
2877 }
2878
2879 // **** Admission ****//
2880 Date dischargeDateOnly = null;
2881 Date utcDischargeDateTime = null;
2882 Calendar cal = Calendar.getInstance(DateUtil.UTC_ZONE);
2883 cal.setTime(claimEndDateOnly);
2884 // Searching for < next day, instead of <= 23:59 on same day.
2885 cal.add(Calendar.DATE, 1);
2886 Date nextDay = cal.getTime();
2887 boolean skipDischargeInfo = false;
2888
2889 if (arc != null) {
2890 if (arc.getDischargeDateOnly() != null) {
2891 dischargeDateOnly = arc.getDischargeDateOnly();
2892 utcDischargeDateTime = arc.getUtcDischargeDateTime();
2893 }
2894 } else { // for unit tests which do not make arcs in advance
2895 List<AdmissionArcDTO> statusdates = findAdmitsDischargesForPatientClaim(claimStartDateOnly,
2896 claimEndDateOnly, null, null, jpaPatient.getId(), claimEntity.getProgramSite().getId());
2897 if (statusdates.size() > 0) {
2898 arc = statusdates.get(0);
2899 if (statusdates.get(0).getDischargeDateOnly() != null) {
2900 dischargeDateOnly = statusdates.get(0).getDischargeDateOnly();
2901 utcDischargeDateTime = statusdates.get(0).getUtcDischargeDateTime();
2902 }
2903 }
2904 }
2905 if (dischargeDateOnly != null && dischargeDateOnly.after(nextDay)) {
2906 skipDischargeInfo = true;
2907 arc.setDischargeDateOnly(null);
2908 arc.setDischargeInteraction_id(null);
2909 }
2910
2911 jaxbPatient.setPatientAdmissions(createPatientAdmitNode(jpaPatient, arc, insCoverage, null, skipDischargeInfo,
2912 0, objFactory));
2913
2914 // Date of birth
2915 if (jpaPatient.getDateOfBirth() != null)
2916 jaxbPatient.setBirthDate(convertToOutgoingClaimDateOnlyFormat(jpaPatient.getDateOfBirth()));
2917
2918 // Attending Physician
2919 Person jpaAttPhysician = getPatientPhysician(PatientPhysicianType.ATTENDING_PHYSICIAN, jpaPatient.getId(),
2920 claimEndDateOnly);
2921 if (jpaAttPhysician != null) {
2922 jaxbPatientClaim.setAttendingPhysician(createPhysicianNode(jpaAttPhysician, objFactory));
2923 }
2924
2925 // Referring Physician
2926 Person jpaRefPhysician = getPatientPhysician(PatientPhysicianType.REFERRING_PHYSICIAN, jpaPatient.getId(),
2927 claimEndDateOnly);
2928 if (jpaRefPhysician != null) {
2929 jaxbPatientClaim.setReferringPhysician(createPhysicianNode(jpaRefPhysician, objFactory));
2930 }
2931
2932 jaxbPatientClaim.setPhysicianServices(createPhysicianServicesNode(jpaPatient.getId(), claimStartDateOnly,
2933 claimEndDateOnly, siteTz, true, objFactory));
2934
2935 // return null to revert the claim in case of no services.
2936 if (jaxbPatientClaim.getPhysicianServices() == null)
2937 return null;
2938
2939 // Add Patient to Claim
2940 jaxbPatientClaim.setPatientInfo(jaxbPatient);
2941
2942 // Setting the claim dto
2943 individualClaimDTO.setClaimKey(claimKey);
2944 individualClaimDTO.setStartDate(claimStartDateOnly);
2945 individualClaimDTO.setEndDate(claimEndDateOnly);
2946 individualClaimDTO.setPatientEntityId(jpaPatient.getId());
2947 individualClaimDTO.setJaxbPatientClaim(jaxbPatientClaim);
2948
2949 return individualClaimDTO;
2950 }
2951
2952 private Diagnosis createDiagnosisNode(String icd9Code, String icd10Code, ObjectFactory objFactory) {
2953 Diagnosis diagnosis = objFactory.createDiagnosis();
2954 diagnosis.setIcd9Code(icd9Code);
2955 diagnosis.setIcd10Code(icd10Code);
2956 return diagnosis;
2957 }
2958
2959 /**
2960 * Builds the RoomboardRecs node for creating a room and board claim.
2961 *
2962 * @param jaxbPatient
2963 * @param insCoverage
2964 * @param rbFacilities
2965 * @param nationalRates
2966 * @param dischargeDate
2967 * This date is expected to be in dateonly format. (i.e. time is
2968 * set to 00:00:00 UTC)
2969 * @param overridePrimary
2970 * @param admits
2971 * @param objFactory
2972 * @return
2973 */
2974 private RoomBoardRecs createRoomBoardRecsNode(com.hummingbird.ejb.resources.jaxb.crossbill.Patient jaxbPatient,
2975 InsuranceCoverage insCoverage, List<PatientFacilityArcDTO> rbFacilities, RateCodeGroup nationalRates,
2976 Date dischargeDate, boolean overridePrimary, AdmissionArcDTO admits, Claim claimEntity,
2977 ClaimsGenerationCache claimsCache, boolean isInclude, ObjectFactory objFactory, Date claimEndDateOnly) {
2978 RoomAndBoard roomAndBoard = null;
2979 LevelOfCareDTO locRecord = null;
2980 List<LevelOfCareDTO> locRecords = new ArrayList<LevelOfCareDTO>();
2981
2982 RoomBoardRecs records = objFactory.createPatientRoomBoardRecs();
2983 List<RoomAndBoard> rbRecords = records.getRoomBoardRecord();
2984 for (PatientFacilityArcDTO pfArc : rbFacilities) {
2985 PatientFacility facility = em.find(PatientFacility.class, pfArc.getPatientFacilityId().longValue());
2986
2987 InsuranceCoverage rbCoverage = facility.getInsuranceCoverage() == null ? insCoverage : facility
2988 .getInsuranceCoverage();
2989 if (overridePrimary) {
2990 InsuranceCarrierSiteCredentials rbCoverageCarrierSiteCredentials = claimsCache
2991 .findCoverageSiteCredentials(em, rbCoverage.getInsurancecarrier().getId());
2992 jaxbPatient.setPrimaryInsurance(createInsurancePolicyNode(rbCoverage, null, nationalRates, null, null,
2993 rbCoverageCarrierSiteCredentials, objFactory));
2994 }
2995 if (roomAndBoard != null) {
2996 // Set an end date on it.
2997 Date effDateOnly = facility.getInteraction().getEffectiveDate();
2998 Date dayBeforeEffDateOnly = DateUtil.getDaysFromDate(effDateOnly, -1);
2999 roomAndBoard.setStopDate(convertToOutgoingClaimDateOnlyFormat(dayBeforeEffDateOnly));
3000 }
3001 if (locRecord != null) {
3002 locRecord.setEndDate(DateUtil.getDaysFromDate(facility.getInteraction().getEffectiveDate(), -1));
3003 }
3004 roomAndBoard = objFactory.createRoomAndBoard();
3005 ServiceFacility rAndBFacility = objFactory.createServiceFacility();
3006 rAndBFacility.setFacilityName(CommonUtil.scrubNames(facility.getFacility().getName()));
3007 rAndBFacility.setFacilityNPI(facility.getFacility().getNpi());
3008 populateFacility(rAndBFacility, facility.getFacility().getPrimaryAddress());
3009 if (facility.getSelectedFacilityType() != null)
3010 rAndBFacility.setCmsPlaceCode(facility.getSelectedFacilityType().getCmsPlaceCode());
3011 WageIndex index = null;
3012 String cbsa = null;
3013 String zip = rAndBFacility.getFacilityAddress().getZip();
3014 if (zip != null) {
3015 zip = zip.substring(0, 5);
3016 index = claimsCache.getWageIndexMap().get(zip);
3017 if (index == null) {
3018 index = rateCodeService.searchWageIndexByZipcodeAndDate(zip, claimEndDateOnly);
3019 }
3020 claimsCache.getWageIndexMap().put(zip, index);
3021 }
3022 if (index != null) {
3023 cbsa = index.getCbsaCode();
3024 } else {
3025 log.warning("Unable to lookup Wage Index for zip on date: " + zip + ", "
3026 + (claimEndDateOnly == null ? null : claimEndDateOnly.toGMTString()));
3027 }
3028 rAndBFacility.getFacilityAddress().setCbsa(cbsa);
3029
3030 roomAndBoard.setFacility(rAndBFacility);
3031 if (checkMSAstate(facility.getFacility().getPrimaryAddress().getState())) {
3032 if (zip != null) {
3033 zip = zip.substring(0, 5);
3034 roomAndBoard.getFacility().getFacilityAddress().setCbsa(getMSACode(zip));
3035 }
3036
3037 }
3038 roomAndBoard.setDailyRate(facility.getRate() == null ? BigDecimal.ZERO : facility.getRate());
3039 roomAndBoard.setPatientPortion(facility.getPatientPortionAmount() == null ? BigDecimal.ZERO : facility
3040 .getPatientPortionAmount());
3041 if (facility.getDailyAmount() != null) {
3042 roomAndBoard.setPatientPortionInterval(facility.getDailyAmount() == true ? "D" : "M");
3043 // "D" signifies that patient portion is a daily rate
3044 // "M" signifies that patient portion is a monthly rate
3045 }
3046 Date effDateOnly = facility.getInteraction().getEffectiveDate();
3047 roomAndBoard.setStartDate(convertToOutgoingClaimDateOnlyFormat(effDateOnly));
3048 List<PatientStatus> admitstat = null;
3049 if (admits.getDischargeInteraction_id() != null) {
3050 admitstat = findStatusRecordsForPatientClaim(admits.getDischargeInteraction_id());
3051
3052 if (admitstat.size() > 0) {
3053 DischargeReason reason = admitstat.get(0).getDischargeReasonEntity();
3054 if (reason != null && reason.getDischargeCode() != null) {
3055 // Set the discharge day rules if applicable
3056 // should we default them true? I do not think the
3057 // engine picks them up here
3058 if (rbCoverage != null && rbCoverage.getInsurancecarrier() != null
3059 && reason.getDischargeCode().getCodeType() != null) {
3060 List<DischargeDayBillingRule> rules = rbCoverage.getInsurancecarrier()
3061 .getDischargeDayBillingRules();
3062 if (rules != null) {
3063 for (DischargeDayBillingRule rule : rules) {
3064 if (rule.getDischargeType() == reason.getDischargeCode().getCodeType()) {
3065 roomAndBoard.setBillDischargeDay(rule.getRoomAndBoard());
3066 break;
3067 }
3068 }
3069 }
3070 }
3071
3072 }
3073 }
3074 }
3075
3076 locRecord = new LevelOfCareDTO();
3077 locRecord.setStartDate(facility.getInteraction().getEffectiveDate());
3078 locRecord.setFacilityId(facility.getFacility().getId());
3079 locRecords.add(locRecord);
3080 rbRecords.add(roomAndBoard);
3081 }
3082 rbRecords.get(rbRecords.size() - 1).setStopDate(convertToOutgoingClaimDateOnlyFormat(dischargeDate));
3083
3084 List<LevelOfCareDTO> locRecordsPruned = pruneLevelOfCareDTOs(locRecords, claimEntity.getServiceStart(),
3085 claimEntity.getServiceEnd(), true);
3086 if (isInclude) {
3087 // locDtos for this claimEntity.getClaimKey() may have additional
3088 // locDtos
3089 List<LevelOfCareDTO> locDtos = claimsCache.getLOCRecords(claimEntity.getClaimKey());
3090
3091 if (locDtos != null) {
3092 locDtos.addAll(locRecordsPruned);
3093 } else {
3094 claimsCache.putLOCRecords(claimEntity.getClaimKey(), locRecordsPruned);
3095 }
3096 } else {
3097 claimsCache.putLOCRecords(claimEntity.getClaimKey(), locRecordsPruned);
3098 }
3099
3100 return records;
3101 }
3102
3103 /**
3104 * This method creates an IndividualClaimDTO from raw data.
3105 *
3106 * @param claimEntity
3107 * @param arc
3108 * @param userTz
3109 * @param patientClaimSchema
3110 * @param securityContext
3111 * @param noa
3112 * @param includeRB
3113 * @param claimsGenerationCache
3114 * @return
3115 */
3116 public IndividualClaimDTO calculateClaimSync(Claim claimEntity, AdmissionArcDTO arc, TimeZone userTz,
3117 Schema patientClaimSchema, SecurityContext securityContext, Noa noa, boolean includeRB,
3118 ClaimsGenerationCache claimsGenerationCache) {
3119 IndividualClaimDTO individualClaimDTO = new IndividualClaimDTO();
3120 SecurityContextHolder.setContext(securityContext);
3121
3122 // When executing this method from the client, it's possible to run
3123 // synchronously, but lose the login context. we want to keep the
3124 // current tenant for the current claim
3125 if (auth.getCurrentUser() == null || multiTenantService.getCurrentTenantId() == null)
3126 multiTenantService.setFakeAuthentication(claimEntity.getPatient().getHospice().getBin(), true);
3127
3128 Patient jpaPatient = claimEntity.getPatient();
3129 Date claimStartDateOnly = claimEntity.getServiceStart();
3130 Date claimEndDateOnly = claimEntity.getServiceEnd();
3131 InsuranceCoverage jpaPrimeCoverage = claimEntity.getInsuranceCoverage();
3132
3133 com.hummingbird.ejb.resources.jaxb.crossbill.ObjectFactory objFactory = new ObjectFactory();
3134 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim jaxbPatientClaim = objFactory.createPatientClaim();
3135 com.hummingbird.ejb.resources.jaxb.crossbill.Patient jaxbPatient = objFactory.createPatient();
3136
3137 // Each claim is assigned a unique key
3138 final String claimKey = claimEntity.getClaimKey();
3139
3140 /**
3141 * Header details
3142 */
3143 jaxbPatientClaim.setClaimKey(claimKey);
3144 jaxbPatientClaim.setClaimStartDate(convertToOutgoingClaimDateOnlyFormat(claimStartDateOnly));
3145 jaxbPatientClaim.setClaimStopDate(convertToOutgoingClaimDateOnlyFormat(claimEndDateOnly));
3146 jaxbPatientClaim.setClaimHeld(claimEntity.getClaimStatus().isClaimToBeHeld()
3147 || (claimEntity.getIsOnManualHold() != null && claimEntity.getIsOnManualHold()));
3148
3149 /**
3150 * Basic details
3151 */
3152 jaxbPatient.setPatientID(jpaPatient.getId().toString());
3153 String patNumber = null;
3154 if (jpaPatient.getPatientNumber() != null)
3155 patNumber = jpaPatient.getPatientNumber().replaceAll("-", "");
3156 jaxbPatient.setPatientNumber(patNumber);
3157 jaxbPatient.setFirstName(jpaPatient.getFirstName().trim());
3158 jaxbPatient.setLastName(jpaPatient.getLastName().trim());
3159
3160 // Date of birth
3161 if (jpaPatient.getDateOfBirth() != null)
3162 jaxbPatient.setBirthDate(convertToOutgoingClaimDateOnlyFormat(jpaPatient.getDateOfBirth()));
3163
3164 // Gender
3165 if (jpaPatient.getSex() != null)
3166 jaxbPatient.setGender(jpaPatient.getSex().getGenderShort());
3167
3168 com.hummingbird.jpa.Address jpaAddress = jpaPatient.getPrimaryAddress();
3169 // Address
3170 if (jpaAddress != null) {
3171 com.hummingbird.ejb.resources.jaxb.crossbill.Address jaxbPatientAddress = objFactory.createAddress();
3172 jaxbPatientAddress.setStreet1(jpaAddress.getStreet1());
3173 jaxbPatientAddress.setCity(jpaAddress.getCity());
3174 jaxbPatientAddress.setState(jpaAddress.getState());
3175 if (jpaPatient.getPhones() != null && jpaPatient.getPhones().size() > 0) {
3176 Phone phone = jpaPatient.getPhones().get(0);
3177 jaxbPatientAddress.setPhone(phone.getNumber().replaceAll("[^\\d]", ""));
3178 }
3179 StringBuilder zipCode = new StringBuilder();
3180 zipCode.append(jpaAddress.getZip());
3181 zipCode.append(jpaAddress.getZip4() == null ? "9999" : jpaAddress.getZip4());
3182 jaxbPatientAddress.setZip(zipCode.toString());
3183 jaxbPatient.setAddress(jaxbPatientAddress);
3184 }
3185
3186 RateCodeGroup nationalRates = claimsGenerationCache.findEffectiveRateCodeGroup(claimStartDateOnly,
3187 rateCodeService);
3188 Map<String, BillingRules> ruleMap = claimsGenerationCache.findApplicableRules(emRef, claimEndDateOnly);
3189
3190 // **** Admission ****//
3191 Date dischargeDateOnly = null;
3192 Date utcDischargeDateTime = null;
3193 Date admitDateOnly = null;
3194 Calendar cal = Calendar.getInstance(DateUtil.UTC_ZONE);
3195 cal.setTime(claimEndDateOnly);
3196 // Searching for < next day, instead of <= 23:59 on same day.
3197 cal.add(Calendar.DATE, 1);
3198 Date nextDayDateOnly = cal.getTime();
3199 BigInteger dischargeId = null;
3200 boolean skipDischargeInfo = false;
3201
3202 if (arc != null) {
3203 admitDateOnly = arc.getAdmitDateOnly();
3204 if (arc.getDischargeDateOnly() != null) {
3205 dischargeDateOnly = arc.getDischargeDateOnly();
3206 utcDischargeDateTime = arc.getUtcDischargeDateTime();
3207 dischargeId = arc.getDischargeInteraction_id();
3208 }
3209 } else { // for unit tests which do not make arcs in advance
3210 List<AdmissionArcDTO> statusdates = findAdmitsDischargesForPatientClaim(claimStartDateOnly,
3211 claimEndDateOnly, null, null, jpaPatient.getId(), claimEntity.getProgramSite().getId());
3212 if (statusdates.size() > 0) {
3213 arc = statusdates.get(0);
3214 admitDateOnly = statusdates.get(0).getAdmitDateOnly();
3215 if (statusdates.get(0).getDischargeDateOnly() != null) {
3216 dischargeDateOnly = statusdates.get(0).getDischargeDateOnly();
3217 utcDischargeDateTime = statusdates.get(0).getUtcDischargeDateTime();
3218 dischargeId = statusdates.get(0).getDischargeInteraction_id();
3219 }
3220 }
3221 }
3222 claimEntity.setPatientAdmitDate(arc.getUtcAdmitDateTime());
3223 if (dischargeDateOnly != null && dischargeDateOnly.compareTo(nextDayDateOnly) >= 0) {
3224 skipDischargeInfo = true;
3225 arc.setDischargeDateOnly(null);
3226 arc.setDischargeInteraction_id(null);
3227 } else if (dischargeDateOnly != null) {
3228 claimEntity.setPatientDischargeDate(arc.getUtcDischargeDateTime());
3229 }
3230
3231 // Insurance Coverages
3232 Map<String, String> carrierSpecificRevCodesMap = new HashMap<String, String>();
3233 // Primary Insurance
3234 if (jpaPrimeCoverage != null) {
3235 List<PriorAuthorization> priorAuths = insService.fetchPriorAuthorizations(jpaPrimeCoverage.getId(),
3236 claimStartDateOnly, claimEndDateOnly, userTz);
3237 List<InsuranceCarrierRates> carrierSpecificRates = null;
3238 if (jpaPrimeCoverage.getInsurancecarrier() != null
3239 && jpaPrimeCoverage.getPayerType() == PayerType.COMMERCIAL) {
3240 carrierSpecificRates = claimsGenerationCache.findInsuranceCarrierRates(em, jpaPrimeCoverage
3241 .getInsurancecarrier().getId());
3242 }
3243 InsuranceCarrierSiteCredentials primeCarrierSiteCredentials = claimsGenerationCache
3244 .findCoverageSiteCredentials(em, jpaPrimeCoverage.getInsurancecarrier().getId());
3245 jaxbPatient.setPrimaryInsurance(createInsurancePolicyNode(jpaPrimeCoverage, priorAuths, nationalRates,
3246 carrierSpecificRates, carrierSpecificRevCodesMap, primeCarrierSiteCredentials, objFactory));
3247
3248 // Secondary coverage
3249 // TODO refactor this during HB-13254 (remove the need for a
3250 // query)
3251 // Query the patient for secondary insurance
3252 Long patientId = claimEntity.getPatient().getId();
3253 // allow any payer type
3254 Set<PayerType> types = new HashSet<PayerType>(Arrays.asList(PayerType.values()));
3255 InsurancePriorityType priority = InsurancePriorityType.SECONDARY;
3256 List<InsuranceCoverage> secondaryCoverages = insService.findPatientInsuranceCoverageInPeriodByPriorityType(
3257 patientId, arc.getAdmitDateOnly(), claimEndDateOnly, types, priority);
3258 // Create secondary insurance node
3259 if (secondaryCoverages != null && secondaryCoverages.size() > 0) {
3260 InsuranceCoverage jpaSecondaryCoverage = secondaryCoverages.get(0);
3261 List<PriorAuthorization> priorAuthsSecondary = insService.fetchPriorAuthorizations(
3262 jpaSecondaryCoverage.getId(), claimStartDateOnly, claimEndDateOnly, userTz);
3263 List<InsuranceCarrierRates> carrierSpecificRatesSecondary = null;
3264 if (jpaSecondaryCoverage.getInsurancecarrier() != null
3265 && jpaSecondaryCoverage.getPayerType() == PayerType.COMMERCIAL) {
3266 carrierSpecificRates = claimsGenerationCache.findInsuranceCarrierRates(em, jpaSecondaryCoverage
3267 .getInsurancecarrier().getId());
3268 }
3269 InsuranceCarrierSiteCredentials secondaryCarrierSiteCredentials = claimsGenerationCache
3270 .findCoverageSiteCredentials(em, jpaSecondaryCoverage.getInsurancecarrier().getId());
3271 jaxbPatient.setSecondaryInsurance(createInsurancePolicyNode(jpaSecondaryCoverage, priorAuthsSecondary,
3272 nationalRates, carrierSpecificRatesSecondary, carrierSpecificRevCodesMap,
3273 secondaryCarrierSiteCredentials, objFactory));
3274 }
3275 }
3276
3277 // **** Eligibility History ****//
3278 com.hummingbird.ejb.resources.jaxb.crossbill.Patient.EligibilityHistory eligHistory = null;
3279 ElectionPeriodDTO mycert = electionService.fetchLastPatientCertificationForBilling(jpaPrimeCoverage.getId(),
3280 claimEndDateOnly);
3281
3282 if (mycert != null) {
3283 eligHistory = new com.hummingbird.ejb.resources.jaxb.crossbill.Patient.EligibilityHistory();
3284 com.hummingbird.ejb.resources.jaxb.crossbill.EligibilityPeriod eligPeriod = objFactory
3285 .createEligibilityPeriod();
3286 eligPeriod.setCertificationPeriod(mycert.getElectionPeriod());
3287 Date certStartDateOnly = mycert.getStartDate();
3288 Date writtenDateOnly = mycert.getCertificationDTO() == null ? null : mycert.getCertificationDTO()
3289 .getHospicePhysicianWrittenDate();
3290 // verbal date should supercede written date -if- there was a
3291 // signature later and the claims will pay. verbal dates alone do
3292 // not stand.
3293 if (writtenDateOnly == null) {
3294 certStartDateOnly = null;
3295 } else if (mycert.getCertificationDTO().getHospicePhysicianVerbalDate() != null) {
3296 writtenDateOnly = mycert.getCertificationDTO().getHospicePhysicianVerbalDate();
3297 }
3298 eligPeriod.setStartDate(convertToOutgoingClaimDateOnlyFormat(certStartDateOnly));
3299 eligPeriod.setCertifiedDate(convertToOutgoingClaimDateOnlyFormat(writtenDateOnly));
3300 eligHistory.getPatientEligibility().add(eligPeriod);
3301 }
3302 jaxbPatient.setEligibilityHistory(eligHistory);
3303
3304 // Patient Admission information
3305 Eligibility eligibility = accessAbilityService.findCurrentEligibilityForInsuranceCoverage(jpaPrimeCoverage
3306 .getId());
3307
3308 int highDaysLeft = eligibility == null || eligibility.getLeftOverDays() == null ? 60 : eligibility
3309 .getLeftOverDays();
3310
3311 jaxbPatient.setPatientAdmissions(createPatientAdmitNode(jpaPatient, arc, jpaPrimeCoverage, noa,
3312 skipDischargeInfo, highDaysLeft, objFactory));
3313
3314 // Certifying Physician
3315 if (mycert != null && mycert.getCertificationDTO() != null
3316 && mycert.getCertificationDTO().getHospicePhysicianId() != null) {
3317 // Set physician to claim
3318 Person certPhy = em.find(Person.class, mycert.getCertificationDTO().getHospicePhysicianId());
3319 jaxbPatientClaim.setCertifyingPhysician(createPhysicianNode(certPhy, objFactory));
3320 }
3321
3322 // Attending Physician
3323 Person jpaAttPhysician = getPatientPhysician(PatientPhysicianType.ATTENDING_PHYSICIAN, jpaPatient.getId(),
3324 claimEndDateOnly);
3325 if (jpaAttPhysician != null) {
3326 jaxbPatientClaim.setAttendingPhysician(createPhysicianNode(jpaAttPhysician, objFactory));
3327 }
3328
3329 // Referring Physician
3330 Person jpaRefPhysician = getPatientPhysician(PatientPhysicianType.REFERRING_PHYSICIAN, jpaPatient.getId(),
3331 claimEndDateOnly);
3332 if (jpaRefPhysician != null) {
3333 jaxbPatientClaim.setReferringPhysician(createPhysicianNode(jpaRefPhysician, objFactory));
3334 }
3335
3336 // **** Diagnoses ****//
3337 List<PatientDiagnoses> jpaDiags = getPatientDiagsForClaim(jpaPatient.getId(), claimEndDateOnly);
3338 // ccreateDia
3339 com.hummingbird.ejb.resources.jaxb.crossbill.Patient.OtherDiagnoses jaxbOtherDiagnoses = new com.hummingbird.ejb.resources.jaxb.crossbill.Patient.OtherDiagnoses();
3340 if (jpaDiags != null && !jpaDiags.isEmpty()) {
3341 boolean admit = false;
3342 for (PatientDiagnoses d : jpaDiags) {
3343 Diagnosis diagnosis = createDiagnosisNode(d.getDiagnosis(), d.getIcd10diagnosis(), objFactory);
3344 if (!admit) {
3345 jaxbPatient.setAdmittingDiagnosis(diagnosis);
3346 admit = true;
3347 if (ruleMap.containsKey("ICD10"))
3348 claimEntity.setDiagnosisCodes(diagnosis.getIcd10Code());
3349 else
3350 claimEntity.setDiagnosisCodes(diagnosis.getIcd9Code());
3351
3352 } else {
3353 jaxbOtherDiagnoses.getDiagnosis().add(diagnosis);
3354 }
3355 }
3356 if (jaxbOtherDiagnoses.getDiagnosis().size() > 0) {
3357 jaxbPatient.setOtherDiagnoses(jaxbOtherDiagnoses);
3358 }
3359 }
3360
3361 // **** LOC Records ****//
3362 Facility patFacility = processLOCinfoIntoNode(jpaPatient, jaxbPatient, arc.getRealAdmitDateOnly(),
3363 claimEndDateOnly, userTz, carrierSpecificRevCodesMap, claimEntity, claimsGenerationCache, objFactory);
3364
3365 if (includeRB) {
3366 List<PatientFacilityArcDTO> rbFacilities = findPatientFacilitiesForPatientClaim(arc.getRealAdmitDateOnly(),
3367 claimEndDateOnly, jpaPatient.getId());
3368 ;
3369 jaxbPatient.setRoomBoardRecs(createRoomBoardRecsNode(jaxbPatient, jpaPrimeCoverage, rbFacilities,
3370 nationalRates, dischargeDateOnly, false, arc, claimEntity, claimsGenerationCache, true, objFactory,
3371 claimEndDateOnly));
3372 }
3373
3374 // **** Service Facility ****//
3375 if (patFacility != null) {
3376 com.hummingbird.ejb.resources.jaxb.crossbill.ServiceFacility jaxbServiceFacility = objFactory
3377 .createServiceFacility();
3378 jaxbServiceFacility.setFacilityName(CommonUtil.scrubNames(patFacility.getName()));
3379 jaxbServiceFacility.setFacilityNPI(patFacility.getNpi());
3380 if (patFacility.getFacilityTypes() != null && !patFacility.getFacilityTypes().isEmpty())
3381 jaxbServiceFacility.setFacilityType(patFacility.getFacilityTypes().get(0).getName());
3382 if (patFacility.getPrimaryAddress() != null) {
3383 com.hummingbird.ejb.resources.jaxb.crossbill.Address jaxbAddress = objFactory.createAddress();
3384 com.hummingbird.jpa.Address facAddress = patFacility.getPrimaryAddress();
3385
3386 jaxbAddress.setStreet1(facAddress.getStreet1());
3387 jaxbAddress.setCity(facAddress.getCity());
3388 jaxbAddress.setState(facAddress.getState());
3389 if (facAddress.getZip() != null && facAddress.getZip4() != null) {
3390 jaxbAddress.setZip(facAddress.getZip() + facAddress.getZip4());
3391 }
3392 jaxbServiceFacility.setFacilityAddress(jaxbAddress);
3393 // Set facility to claim
3394 jaxbPatientClaim.setServiceFacility(jaxbServiceFacility);
3395 }
3396 }
3397
3398 // Add Patient to Claim
3399 jaxbPatientClaim.setPatientInfo(jaxbPatient);
3400
3401 // Visits
3402 if (jpaPrimeCoverage.getPayerType() == PayerType.MEDICARE) {
3403 Date utcPatientDeathDateTime = null;
3404
3405 if ("D".equals(jaxbPatient.getPatientAdmissions().getPatientAdmission().get(0).getDischargeReasonCode())) {
3406 utcPatientDeathDateTime = utcDischargeDateTime;
3407 }
3408 boolean splitHCPCS = ruleMap.containsKey("CR9369");
3409 jaxbPatientClaim.setVisits(createVisitsNode(jpaPatient.getId(), claimStartDateOnly, claimEndDateOnly,
3410 utcPatientDeathDateTime, splitHCPCS, userTz, objFactory));
3411
3412 // Medications
3413 jaxbPatientClaim.setMedications(createMedicationsNode(jpaPatient.getId(), claimStartDateOnly,
3414 claimEndDateOnly, objFactory));
3415 }
3416
3417 if (jpaPrimeCoverage.getInsurancecarrier().getIncludePhysicianServices() == true) {
3418 jaxbPatientClaim.setPhysicianServices(createPhysicianServicesNode(jpaPatient.getId(), claimStartDateOnly,
3419 claimEndDateOnly, userTz, false, objFactory));
3420 }
3421
3422 // Setting the claim dto
3423 individualClaimDTO.setClaimKey(claimKey);
3424 individualClaimDTO.setStartDate(claimStartDateOnly);
3425 individualClaimDTO.setEndDate(claimEndDateOnly);
3426 individualClaimDTO.setPatientEntityId(jpaPatient.getId());
3427 individualClaimDTO.setJaxbPatientClaim(jaxbPatientClaim);
3428
3429 /***
3430 * Commenting this code as we are now, also sending records with missing
3431 * required fields to PAS. Leaving it just in case we change our mind
3432 * later (like we've already done several times!)
3433 ***/
3434 // Check for missing required fields. If all required fields are
3435 // present, set the isValid flag to TRUE;
3436 // individualClaimDTO = validateRequestClaimXml(individualClaimDTO,
3437 // patientClaimSchema);
3438
3439 log.fine("finished calculateClaim thread for patient with id : " + jpaPatient.getId());
3440
3441 return individualClaimDTO;
3442 }
3443
3444 /**
3445 * Slightly different than the other node creation methods, this one returns
3446 * the relevant patient facility for further processing.
3447 *
3448 * TODO Possible performance bottleneck.
3449 *
3450 * @param jpaPatient
3451 * @param jaxbPatient
3452 * @param admitDateOnly
3453 * Date is expected to be in date only format. (i.e. time is set
3454 * to 00:00:00 UTC)
3455 * @param claimEndDateOnly
3456 * Date is expected to be in date only format. (i.e. time is set
3457 * to 00:00:00 UTC)
3458 * @param userTz
3459 * @param carrierSpecificRevCodesMap
3460 * @param objFactory
3461 * @return
3462 */
3463 private Facility processLOCinfoIntoNode(Patient jpaPatient,
3464 com.hummingbird.ejb.resources.jaxb.crossbill.Patient jaxbPatient, Date admitDateOnly,
3465 Date claimEndDateOnly, TimeZone userTz, Map<String, String> carrierSpecificRevCodesMap, Claim claimEntity,
3466 ClaimsGenerationCache claimsCache, ObjectFactory objFactory) {
3467
3468 List<LevelOfCareDTO> dtoLocRecords = locService.findBillingLevelsOfCare(jpaPatient.getId(), admitDateOnly,
3469 claimEndDateOnly);
3470 if (dtoLocRecords == null || dtoLocRecords.isEmpty()) {
3471 log.warning("No LoC records for patient " + jpaPatient.getId() + " " + jpaPatient.getFirstName() + " "
3472 + jpaPatient.getLastName());
3473 return null;
3474 }
3475
3476 com.hummingbird.ejb.resources.jaxb.crossbill.Patient.LocRecords jaxbLocRecords = new com.hummingbird.ejb.resources.jaxb.crossbill.Patient.LocRecords();
3477
3478 // We save the last facility on the list. If there aren't any, the
3479 // patient is at home.
3480 Facility patFacility = null;
3481 String cbsa = "0";
3482
3483 List<LevelOfCareDTO> locRecords = new ArrayList<LevelOfCareDTO>();
3484
3485 for (LevelOfCareDTO dtoLoc : dtoLocRecords) {
3486 // Skip records that start outside of this admission
3487 if (dtoLoc.getStartDate() != null
3488 && (dtoLoc.getStartDate().after(claimEndDateOnly) || dtoLoc.getStartDate().before(admitDateOnly)))
3489 continue;
3490
3491 com.hummingbird.ejb.resources.jaxb.crossbill.LevelOfCare jaxbLoc = objFactory.createLevelOfCare();
3492 if (dtoLoc.getStartDate() != null)
3493 jaxbLoc.setStartDate(convertToOutgoingClaimDateOnlyFormat(dtoLoc.getStartDate()));
3494 jaxbLoc.setStopDate(convertToOutgoingClaimDateOnlyFormat(dtoLoc.getEndDate()));
3495 if (dtoLoc.getTypeOfCare() != null) {
3496 jaxbLoc.setTypeOfCare(dtoLoc.getTypeOfCare().ordinal());
3497
3498 // populate carrier specific Revenue code to be sent to
3499 // pasbe. If carrierSpecificRevCodesMap is null or empty
3500 // then pasbe will assign the default revenue codes when
3501 // calculating rev-lines
3502 if (carrierSpecificRevCodesMap != null) {
3503 TypeOfCare typeOfCare = TypeOfCare.getTypeOfCareForLevelOfCare(dtoLoc.getTypeOfCare());
3504 if (typeOfCare != null) {
3505 String revCode = carrierSpecificRevCodesMap.get(typeOfCare.name());
3506 if (revCode != null)
3507 jaxbLoc.setRevenueCode(revCode);
3508 }
3509 }
3510 }
3511 jaxbLoc.setHours(dtoLoc.getCcHours() == null ? 0.0 : dtoLoc.getCcHours());
3512 Facility locLocation = facilityService.find(Facility.class, dtoLoc.getFacilityId());
3513 ServiceFacility locFacility = null;
3514 if (locLocation != null) {
3515 locFacility = objFactory.createServiceFacility();
3516 Facility home = systemDataService.getHomeFacility();
3517 if (dtoLoc.getFacilityId().equals(home.getId())) {
3518 populateFacility(locFacility, jpaPatient.getPrimaryAddress());
3519 locFacility.setFacilityType("Q5001");
3520 locFacility.setCmsPlaceCode("12");
3521 } else {
3522 populateFacility(locFacility, locLocation.getPrimaryAddress());
3523 locFacility.setFacilityType(dtoLoc.getQCode());
3524 locFacility.setCmsPlaceCode(dtoLoc.getCmsPlaceCode());
3525 }
3526 patFacility = locLocation;
3527 locFacility.setFacilityName(CommonUtil.scrubNames(locLocation.getName()));
3528 locFacility.setFacilityNPI(locLocation.getNpi());
3529 jaxbLoc.setFacility(locFacility);
3530 }
3531
3532 String wageIndexVal = null;
3533 WageIndex index = null;
3534 String zip = locFacility.getFacilityAddress().getZip();
3535
3536 if (zip != null) {
3537 zip = zip.substring(0, 5);
3538 index = claimsCache.getWageIndexMap().get(zip);
3539 if (index == null) {
3540 index = rateCodeService.searchWageIndexByZipcodeAndDate(zip, claimEndDateOnly);
3541 claimsCache.getWageIndexMap().put(zip, index);
3542 }
3543 }
3544 if (index != null) {
3545 wageIndexVal = index.getWageIndex().toString();
3546 cbsa = index.getCbsaCode();
3547 } else {
3548 wageIndexVal = null;
3549 log.warning("Unable to lookup Wage Index for zip on date: " + zip + ", "
3550 + (claimEndDateOnly == null ? null : claimEndDateOnly.toGMTString()));
3551 }
3552
3553 jaxbLoc.setWageindex(wageIndexVal);
3554 // Reset the CBSA to MSA if in a medicaid state that requires
3555 // it.
3556 if (jaxbPatient.getPrimaryInsurance().getPayerType().equals("MEDICAID")) {
3557 if (checkMSAstate(patFacility.getPrimaryAddress().getState())) {
3558 if (zip != null) {
3559 cbsa = getMSACode(zip);
3560 }
3561 }
3562 }
3563 jaxbLoc.getFacility().getFacilityAddress().setCbsa(cbsa);
3564
3565 jaxbLocRecords.getLocRecord().add(jaxbLoc);
3566 locRecords.add(dtoLoc);
3567 }
3568 jaxbPatient.setLocRecords(jaxbLocRecords);
3569 jaxbPatient.setCbsaCode(cbsa);
3570 claimsCache.putLOCRecords(claimEntity.getClaimKey(),
3571 pruneLevelOfCareDTOs(locRecords, claimEntity.getServiceStart(), claimEndDateOnly, false));
3572
3573 return patFacility;
3574 }
3575
3576 private com.hummingbird.ejb.resources.jaxb.crossbill.InsurancePolicy createInsurancePolicyNode(
3577 InsuranceCoverage jpaCoverage, List<PriorAuthorization> priorAuths, RateCodeGroup nationalRates,
3578 List<InsuranceCarrierRates> carrierSpecificRates, Map<String, String> carrierSpecificRevCodesMap,
3579 InsuranceCarrierSiteCredentials carrierSiteCredentials, ObjectFactory objFactory) {
3580 com.hummingbird.ejb.resources.jaxb.crossbill.InsurancePolicy jaxbInsurance = objFactory.createInsurancePolicy();
3581 CarrierRate rates = objFactory.createCarrierRate();
3582 Patient jpaPatient = jpaCoverage.getPatient();
3583 com.hummingbird.jpa.Address jpaAddress = null;
3584
3585 if (jpaCoverage.getInsurancecarrier().getPayerType() == PayerType.SELF_PAY
3586 || jpaCoverage.getInsurancecarrier().getPayerType() == PayerType.NON_FUNDED) {
3587 jaxbInsurance.setCarrierName(jpaCoverage.getPayerType().getName().trim());
3588 jaxbInsurance.setPayerType(jpaCoverage.getPayerType().getName().trim());
3589 if (jpaCoverage.getPolicynumber() != null) {
3590 jaxbInsurance.setPolicyNumber(jpaCoverage.getPolicynumber().trim());
3591 }
3592 jaxbInsurance.setFirstName(jpaPatient.getFirstName());
3593 jaxbInsurance.setLastName(jpaPatient.getLastName());
3594 jpaAddress = jpaPatient.getPrimaryAddress();
3595 } else if (jpaCoverage.getInsurancecarrier() != null) {
3596 // First and last name
3597 PatientPersonRelation ppr = jpaCoverage.getPolicyholderPatientRelation();
3598 InsuranceCarrier ic = jpaCoverage.getInsurancecarrier();
3599 String policyHolderRelation = null;
3600 jpaAddress = ic.getPrimaryAddress();
3601 if (ppr == null || ppr.getRelationship() == null || (ppr != null && ppr.getPerson() == null)) { // self
3602 jaxbInsurance.setFirstName(jpaPatient.getFirstName());
3603 jaxbInsurance.setLastName(jpaPatient.getLastName());
3604 jaxbInsurance.setCarrierName(ic.getCarrierName().trim());
3605 jaxbInsurance.setPayerType(jpaCoverage.getPayerType().getName().trim());
3606 } else {
3607 Person pprPer = ppr.getPerson();
3608 policyHolderRelation = ppr.getRelationship().name();
3609 if (pprPer == null)
3610 // we have a relationship, but we don't know who it is
3611 jaxbInsurance.setFirstName(ppr.getRelationship().getRelationship());
3612 else {
3613 jaxbInsurance.setFirstName(pprPer.getFirstName());
3614 jaxbInsurance.setLastName(pprPer.getLastName());
3615 jpaAddress = ppr.getPerson().getPrimaryAddress();
3616 }
3617 }
3618 jaxbInsurance.setRelationToPatient(policyHolderRelation);
3619
3620 // Carrier name
3621 jaxbInsurance.setCarrierName(ic.getCarrierName().trim());
3622 jaxbInsurance.setPayerType(jpaCoverage.getPayerType().getName().trim());
3623 // Require preAuth
3624 jaxbInsurance.setRequiresAuthorization(ic.getRequirePreAuthorization());
3625 // Billing Type
3626 if (ic.getBillingType() != null) {
3627 String billingCode = ic.getBillingType().getCode();
3628 jaxbInsurance.setBillingType(billingCode);
3629 }
3630
3631 // Policy number
3632 jaxbInsurance.setPolicyNumber((jpaCoverage.getPolicynumber() == null ? null : jpaCoverage.getPolicynumber()
3633 .trim()));
3634 if (jpaCoverage.getGroupnumber() != null) {
3635 jaxbInsurance.setGroupNumber(jpaCoverage.getGroupnumber().trim());
3636 }
3637
3638 // Use site-specific credentials if available else use default
3639 // credentials
3640 if (carrierSiteCredentials != null && carrierSiteCredentials.getHospiceContractId() != null
3641 && !carrierSiteCredentials.getHospiceContractId().isEmpty()) {
3642 jaxbInsurance.setHospiceContractId(carrierSiteCredentials.getHospiceContractId().trim());
3643 } else if (jpaCoverage.getInsurancecarrier().getHospiceContractId() != null) {
3644 jaxbInsurance.setHospiceContractId(jpaCoverage.getInsurancecarrier().getHospiceContractId().trim());
3645 }
3646 if (carrierSiteCredentials != null && carrierSiteCredentials.getHospiceTaxonomy() != null
3647 && !carrierSiteCredentials.getHospiceTaxonomy().isEmpty()) {
3648 jaxbInsurance.setHospiceTaxonomy(carrierSiteCredentials.getHospiceTaxonomy().trim());
3649 } else if (jpaCoverage.getInsurancecarrier().getHospiceTaxonomy() != null) {
3650 jaxbInsurance.setHospiceTaxonomy(jpaCoverage.getInsurancecarrier().getHospiceTaxonomy().trim());
3651 }
3652
3653 PayerType coveragePayer = ic.getPayerType();
3654 if (coveragePayer != PayerType.MEDICARE) {
3655 jaxbInsurance.setPayerId(ic.getPayerId());
3656 }
3657
3658 jaxbInsurance.setRequireBillingForm(ic.getRequireBillingForm());
3659 jaxbInsurance.setRequireCarrierBox38(ic.getRequireCarrierBox38());
3660 jaxbInsurance.setRequireFirstLast(ic.getRequireFirstLast());
3661 jaxbInsurance.setRequireProviderInfoBox57(ic.getRequireProviderInfo());
3662 // So pasbe knows how to create CC revlines for this carrier
3663 jaxbInsurance.setDo15CC(ic.getRequireContinuousCare());
3664
3665 }
3666 populateIndexRates(rates, nationalRates, jpaCoverage.getPayerType(), carrierSpecificRates,
3667 carrierSpecificRevCodesMap);
3668 jaxbInsurance.setRates(rates);
3669
3670 // Carrier Authorization Codes
3671 if (priorAuths != null && priorAuths.size() > 0) {
3672 List<String> authCodes = new ArrayList<String>();
3673 for (PriorAuthorization pa : priorAuths) {
3674 authCodes.add(pa.getAuthorizationCode());
3675 }
3676 if (authCodes.size() > 0) {
3677 jaxbInsurance.setAuthorizationCode(org.apache.commons.lang.StringUtils.join(authCodes, ","));
3678 }
3679 }
3680
3681 // Carrier Address
3682 if (jpaAddress != null) {
3683 com.hummingbird.ejb.resources.jaxb.crossbill.Address jaxbCarrierAddress = objFactory.createAddress();
3684 jaxbCarrierAddress.setStreet1(jpaAddress.getStreet1() == null ? "" : jpaAddress.getStreet1().trim());
3685 jaxbCarrierAddress.setCity(jpaAddress.getCity());
3686 jaxbCarrierAddress.setState(jpaAddress.getState());
3687 StringBuilder zipCode = new StringBuilder();
3688 zipCode.append(jpaAddress.getZip());
3689 if (jpaAddress.getZip() != null && jpaAddress.getZip().length() == 5)
3690 zipCode.append(jpaAddress.getZip4() == null ? "9999" : jpaAddress.getZip4());
3691 jaxbCarrierAddress.setZip(zipCode.toString());
3692 jaxbInsurance.setAddress(jaxbCarrierAddress);
3693 }
3694
3695 return jaxbInsurance;
3696 }
3697
3698 private com.hummingbird.ejb.resources.jaxb.crossbill.Physician createPhysicianNode(Person jpaPhysician,
3699 ObjectFactory objFactory) {
3700 com.hummingbird.ejb.resources.jaxb.crossbill.Physician jaxbPhysician = objFactory.createPhysician();
3701 if (jpaPhysician.getFirstName() != null && jpaPhysician.getFirstName().length() > 0)
3702 jaxbPhysician.setFirstName(jpaPhysician.getFirstName().trim());
3703 if (jpaPhysician.getLastName() != null && jpaPhysician.getLastName().length() > 0)
3704 jaxbPhysician.setLastName(jpaPhysician.getLastName().trim());
3705 jaxbPhysician.setNpi(jpaPhysician.getNpi());
3706 return jaxbPhysician;
3707 }
3708
3709 private com.hummingbird.ejb.resources.jaxb.crossbill.Patient.PatientAdmissions createPatientAdmitNode(
3710 Patient jpaPatient, AdmissionArcDTO admits, InsuranceCoverage jpaPrimeCoverage, Noa noa,
3711 boolean skipDischargeInfo, int highDaysLeft, ObjectFactory objFactory) {
3712 com.hummingbird.ejb.resources.jaxb.crossbill.Patient.PatientAdmissions jaxbPatAdmissions = new com.hummingbird.ejb.resources.jaxb.crossbill.Patient.PatientAdmissions();
3713 com.hummingbird.ejb.resources.jaxb.crossbill.PatientAdmission patAdmission = objFactory
3714 .createPatientAdmission();
3715
3716 patAdmission.setAdmitDate(convertToOutgoingClaimDateOnlyFormat(admits.getAdmitDateOnly()));
3717 patAdmission.setHighDaysLeft(highDaysLeft);
3718
3719 List<PatientStatus> admitstat = null;
3720 if (admits.getDischargeInteraction_id() == null) {
3721 admitstat = findStatusRecordsForPatientClaim(admits.getAdmissionInteraction_id());
3722 } else {
3723 admitstat = findStatusRecordsForPatientClaim(admits.getDischargeInteraction_id());
3724 }
3725 if (admitstat.size() > 0) {
3726 // TODO: Later when we will have program on all PatientStatus
3727 // Records, need to fix this
3728 if (admitstat.get(0).getProgramSite() != null && admitstat.get(0).getProgramSite().getProgram() != null) {
3729 patAdmission.setProgram(admitstat.get(0).getProgramSite().getProgram().getName());
3730 } else {
3731 patAdmission.setProgram(adminService.findHospiceProgram().getName());
3732 }
3733 }
3734 boolean billDischarge = true; // Default to true unless otherwise
3735 // stated!
3736 if (admits.getDischargeInteraction_id() != null && !skipDischargeInfo) {
3737 patAdmission.setDischargeDate(convertToOutgoingClaimDateOnlyFormat(admits.getDischargeDateOnly()));
3738 if (admitstat.size() > 0) {
3739 DischargeReason reason = admitstat.get(0).getDischargeReasonEntity();
3740 if (reason != null && reason.getDischargeCode() != null) {
3741 // Set the discharge day rules while we have them available
3742 DischargeDayBillingRule primaryInsuranceDischargeCodeRule = null;
3743 DischargeDayBillingRule primaryInsuranceDischargeReasonRule = null;
3744 if (jpaPrimeCoverage != null && jpaPrimeCoverage.getInsurancecarrier() != null
3745 && reason.getDischargeCode().getCodeType() != null) {
3746 List<DischargeDayBillingRule> rules = jpaPrimeCoverage.getInsurancecarrier()
3747 .getDischargeDayBillingRules();
3748 if (rules != null) {
3749 for (DischargeDayBillingRule rule : rules) {
3750 if (rule.getDischargeType() == reason.getDischargeCode().getCodeType()) {
3751 primaryInsuranceDischargeCodeRule = rule;
3752 billDischarge = rule.getHospiceService();
3753 }
3754 if (rule.getDischargeType().toString().equals(reason.getDescription())) {
3755 primaryInsuranceDischargeReasonRule = rule;
3756 }
3757 }
3758 }
3759 }
3760 // set discharge code from prime insurance carrier discharge
3761 // day billing rule
3762 if (primaryInsuranceDischargeCodeRule != null
3763 && primaryInsuranceDischargeCodeRule.getPatientStatusCode() != null
3764 && !primaryInsuranceDischargeCodeRule.getPatientStatusCode().isEmpty()) {
3765 patAdmission.setDischargeCode(primaryInsuranceDischargeCodeRule.getPatientStatusCode());
3766 } else {
3767 patAdmission.setDischargeCode(reason.getDischargeCode().getCode());
3768 }
3769 // set discharge reason code from prime insurance carrier
3770 // discharge day billing rule
3771 if (primaryInsuranceDischargeReasonRule != null
3772 && primaryInsuranceDischargeReasonRule.getPatientStatusCode() != null
3773 && !primaryInsuranceDischargeReasonRule.getPatientStatusCode().isEmpty()) {
3774 patAdmission.setDischargeReasonCode(primaryInsuranceDischargeReasonRule.getPatientStatusCode());
3775 } else
3776 // if discharge reason code not found, set from discharge
3777 // code
3778 if (primaryInsuranceDischargeCodeRule != null
3779 && primaryInsuranceDischargeCodeRule.getPatientStatusCode() != null
3780 && !primaryInsuranceDischargeCodeRule.getPatientStatusCode().isEmpty()) {
3781 patAdmission.setDischargeReasonCode(primaryInsuranceDischargeCodeRule.getPatientStatusCode());
3782 } else {
3783 patAdmission.setDischargeReasonCode(reason.getDischargeCode().getCode());
3784 }
3785 setXmlDischargeReasonCode(jpaPatient, reason, patAdmission);
3786
3787 } else
3788 log.warning("Discharge reason missing pat " + jpaPatient.getId());
3789 }
3790 } else {
3791 patAdmission.setDischargeDate(convertToOutgoingClaimDateOnlyFormat(null));
3792 patAdmission.setDischargeReasonCode("-");
3793 }
3794
3795 if (admits.getDischargeInteraction_id() == null && jpaPrimeCoverage.getEndDate() != null && !skipDischargeInfo) {
3796 patAdmission.setDischargeDate(convertToOutgoingClaimDateOnlyFormat(jpaPrimeCoverage.getEndDate()));
3797 patAdmission.setDischargeCode("01");
3798 patAdmission.setDischargeReasonCode("%");
3799 }
3800 // Get submission details from Noa
3801 patAdmission.setNoaSubmissionDate(convertToOutgoingClaimDateOnlyFormat(noa != null ? noa.getSubmissionDate()
3802 : null));
3803 patAdmission.setLateSubmissionReason(noa != null ? noa.getLateReason() : "");
3804
3805 patAdmission.setBillDischargeDay(billDischarge);
3806
3807 jaxbPatAdmissions.getPatientAdmission().add(patAdmission);
3808 return jaxbPatAdmissions;
3809 }
3810
3811 /**
3812 * Builds the Visits node for a PatientClaim
3813 *
3814 * @param patientId
3815 * @param claimStartDate
3816 * Date is expected to be in date only format (i.e time is set to
3817 * 00:00:00 UTC). Example 2016-01-01 00:00:00 UTC
3818 * @param claimEndDate
3819 * Date is expected to be in date only format (i.e time is set to
3820 * 00:00:00 UTC) Example 2016-01-31 00:00:00 UTC
3821 * @param utcDeathDateTime
3822 * Date is expected to be in datetime format, formatted in UTC
3823 * timezone.
3824 * @param splitHCPCS
3825 * @param userTz
3826 * @param objFactory
3827 * @return
3828 */
3829 private com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Visits createVisitsNode(Long patientId,
3830 Date claimStartDate, Date claimEndDate, Date utcDeathDateTime, boolean splitHCPCS, TimeZone userTz,
3831 ObjectFactory objFactory) {
3832 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Visits jaxbVisits = null;
3833
3834 // Note: patient_admissionperiods_v2_vw.admitdatetime_utc and
3835 // patient_admissionperiods_v2_vw.dischargedatetime_utc are in
3836 // datetime.
3837 // Thus, dateonly values must be converted into sitetz in order for
3838 // the comparison to be correct.
3839 // (00:00:00 UTC to become 00:00:00 <siteTz>)
3840 Date siteTzStart = DateUtil.convertDateTz(claimStartDate, DateUtil.UTC_ZONE, userTz, true);
3841 Date siteTzEnd = DateUtil.convertDateTz(claimEndDate, DateUtil.UTC_ZONE, userTz, true);
3842
3843 // Searching for < next day, instead of <= 23:59 on same day.
3844 Calendar cal = Calendar.getInstance(userTz);
3845 cal.setTime(siteTzEnd);
3846 cal.add(Calendar.DATE, 1);
3847 Date siteTzRealEnd = cal.getTime();
3848
3849 List<PatientVisit> patVisits = new ArrayList<PatientVisit>();
3850 String hcpcsquery = "";
3851 if (splitHCPCS) {
3852 hcpcsquery = " case when r.shortname = 'LPN' then 'G0300' " + "when r.shortname = 'RN' then 'G0299' "
3853 + "else rg.hcpcscode end as rolegroup_hcpcscode, ";
3854 } else {
3855 hcpcsquery = " rg.hcpcscode AS rolegroup_hcpcscode, ";
3856 }
3857 StringBuffer sql = new StringBuffer();
3858 sql.append("SELECT pi.id AS patientinteraction_id, pi.effectivedate AS patientinteraction_effectivedate, "
3859 + "pi.patient_id AS patientinteraction_patient_id, pi.person_id AS patientinteraction_person_id, "
3860 + "pv.id AS patvisit_id, pv.total AS patvisit_total, pv.rate AS patvisit_rate, pv.billable AS patvisit_billable, "
3861 + "pv.facetofacevisit AS patvisit_facetofacevisit, "
3862 + "rg.id AS rolegroup_id, "
3863 + hcpcsquery
3864 + "rg.directcarerevcode AS rolegroup_directcarerevcode, "
3865 + "rg.phonecallrevcode AS rolegroup_phonecallrevcode, "
3866 // converting duration in minutes to fractions of hours*100 for
3867 // PAS
3868 + " (100*pv.duration/60) as qty " + "FROM patientvisit pv "
3869 + "JOIN patientinteraction pi ON pi.id=pv.interaction_id " + "JOIN hbrole r ON r.id=pi.role_id "
3870 + "JOIN rolegroup rg ON rg.id=r.rolegroup_id "
3871 + "LEFT JOIN physicianservice phsvc ON pv.id=phsvc.patient_visit_id AND phsvc.deleted=false "
3872 + "WHERE pi.patient_id=:patientId AND pv.deleted=false AND phsvc.id IS NULL "
3873 + "AND pi.effectivedate >= :startDate AND pi.effectivedate < :endDate "
3874 + "AND pv.billable IN ('DirectBillable','PhoneBillable') AND pv.total > 0.0 "
3875 + "ORDER BY pi.effectiveDate");
3876 Query patVisitsQuery = em.createNativeQuery(sql.toString());
3877 patVisitsQuery.setParameter("patientId", patientId);
3878 patVisitsQuery.setParameter("startDate", siteTzStart);
3879 patVisitsQuery.setParameter("endDate", siteTzRealEnd);
3880
3881 List<Object[]> results = patVisitsQuery.getResultList();
3882
3883 for (Object[] row : results) {
3884 BigInteger pvId = (BigInteger) row[4];
3885
3886 PatientVisit pv = new PatientVisit();
3887
3888 String billable = (String) row[7];
3889
3890 pv.setTotal((BigDecimal) row[5]);
3891 pv.setRate((BigDecimal) row[6]);
3892 pv.setBillable(billable == null ? null : VisitBillingType.valueOf(billable));
3893 pv.setFaceToFaceVisit((Boolean) row[8]);
3894
3895 PatientInteraction pi = new PatientInteraction();
3896 Role role = new Role();
3897 RoleGroup roleGroup = new RoleGroup();
3898
3899 pi.setEffectiveDate((Date) row[1]);
3900 roleGroup.setHcpcsCode((String) row[10]);
3901 roleGroup.setDirectCareRevCode((String) row[11]);
3902 roleGroup.setPhoneCallRevCode((String) row[12]);
3903
3904 pi.setRole(role);
3905 role.setRoleGroup(roleGroup);
3906 pv.setInteraction(pi);
3907 pv.setDuration((Integer) row[13]);
3908
3909 patVisits.add(pv);
3910 }
3911
3912 if (patVisits != null && !patVisits.isEmpty()) {
3913 jaxbVisits = new com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Visits();
3914 for (PatientVisit patVisit : patVisits) {
3915 com.hummingbird.ejb.resources.jaxb.crossbill.ServiceLine jaxbServiceLine = objFactory
3916 .createServiceLine();
3917 if (patVisit.getTotal() != null)
3918 jaxbServiceLine.setLineAmount(patVisit.getTotal());
3919
3920 if (patVisit.getInteraction() != null) {
3921 if (patVisit.getInteraction().getEffectiveDate() != null) {
3922 // Please do not remove this extra conversion
3923 Date utcEffectiveDateTime = DateUtil.convertDateTz(
3924 patVisit.getInteraction().getEffectiveDate(), userTz, TimeZone.getTimeZone("UTC"));
3925 jaxbServiceLine.setServiceDate(convertToOutgoingClaimDateTimeFormat(utcEffectiveDateTime,
3926 userTz));
3927 }
3928 if (patVisit.getInteraction().getRole() != null
3929 && patVisit.getInteraction().getRole().getRoleGroup() != null) {
3930 RoleGroup roleGroup = patVisit.getInteraction().getRole().getRoleGroup();
3931 boolean postMortem = (utcDeathDateTime != null && patVisit.getInteraction().getEffectiveDate()
3932 .compareTo(utcDeathDateTime) >= 0);
3933 String hcpcs = postMortem ? roleGroup.getHcpcsCode() + ":PM" : roleGroup.getHcpcsCode();
3934
3935 jaxbServiceLine.setHcpcsCode(hcpcs);
3936 if (patVisit.getBillable() == VisitBillingType.DirectBillable)
3937 jaxbServiceLine.setRevenueCode(roleGroup.getDirectCareRevCode());
3938 else if (patVisit.getBillable() == VisitBillingType.PhoneBillable)
3939 jaxbServiceLine.setRevenueCode(roleGroup.getPhoneCallRevCode());
3940 }
3941 }
3942 if (patVisit.getFaceToFaceVisit() != null)
3943 jaxbServiceLine.setFaceToFace(patVisit.getFaceToFaceVisit());
3944 else
3945 jaxbServiceLine.setFaceToFace(false);
3946
3947 jaxbServiceLine.setUnits("UN");
3948 jaxbServiceLine.setQuantity(patVisit.getDuration());
3949
3950 jaxbVisits.getVisit().add(jaxbServiceLine);
3951 }
3952 }
3953 return jaxbVisits;
3954 }
3955
3956 /**
3957 * Builds the Visits node for a PatientClaim
3958 *
3959 * @param patientId
3960 * @param claimStartDateOnly
3961 * Date is expected to be in date only format (i.e. time is set
3962 * to 00:00:00 UTC)
3963 * @param claimEndDateOnly
3964 * Date is expected to be in date only format (i.e. time is set
3965 * to 00:00:00 UTC)
3966 * @param userTz
3967 * @param objFactory
3968 * @return
3969 */
3970 public com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.PhysicianServices createPhysicianServicesNode(
3971 Long patientId, Date claimStartDateOnly, Date claimEndDateOnly, TimeZone userTz, boolean palliative,
3972 ObjectFactory objFactory) {
3973 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.PhysicianServices jaxbPhysicianServices = null;
3974
3975 // A visit interaction has its effectivedate persisted with time.
3976 // Thus, dateonly values must be converted into sitetz in order for the
3977 // comparison with effectivedate to be correct.
3978 // (00:00:00 UTC to become 00:00:00 <siteTz>)
3979 Date siteTzStartDate = DateUtil.convertDateTz(claimStartDateOnly, DateUtil.UTC_ZONE, userTz, true);
3980 Date siteTzEndDate = DateUtil.convertDateTz(claimEndDateOnly, DateUtil.UTC_ZONE, userTz, true);
3981
3982 Calendar cal = Calendar.getInstance(userTz);
3983 cal.setTime(siteTzEndDate);
3984 // Searching for < next day, instead of <= 23:59 on same day.
3985 cal.add(Calendar.DATE, 1);
3986 Date siteTzRealEndDate = cal.getTime();
3987
3988 List<PatientVisit> patVisits = new ArrayList<PatientVisit>();
3989
3990 StringBuffer sql = new StringBuffer();
3991 sql.append("SELECT pi.id AS patientinteraction_id, pi.effectivedate AS patientinteraction_effectivedate, "
3992 + "pv.id AS patvisit_id, "
3993 + "phsvc.id AS physvc_id, phsvc.code AS physvc_code, phsvc.rate AS physvc_rate, phsvc.description AS physvc_description, "
3994 + "pi.person_id AS physician " + "FROM patientvisit pv "
3995 + "JOIN patientinteraction pi ON pi.id=pv.interaction_id "
3996 + "RIGHT JOIN physicianservice phsvc ON pv.id=phsvc.patient_visit_id "
3997 + "WHERE pi.patient_id=:patientId AND pv.deleted=false AND phsvc.deleted=false "
3998 + "AND pi.effectivedate >= :startDate AND pi.effectivedate < :endDate "
3999 + "AND pv.billable='DirectBillable' AND pv.facetofacevisit=false "
4000 + "AND pv.total > 0 ORDER BY pi.effectiveDate");
4001
4002 Query patVisitsQuery = em.createNativeQuery(sql.toString());
4003 patVisitsQuery.setParameter("patientId", patientId);
4004 patVisitsQuery.setParameter("startDate", siteTzStartDate);
4005 patVisitsQuery.setParameter("endDate", siteTzRealEndDate);
4006
4007 List<Object[]> results = patVisitsQuery.getResultList();
4008 if (results != null && results.size() > 0) {
4009
4010 jaxbPhysicianServices = new com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.PhysicianServices();
4011 for (Object[] row : results) {
4012 BigInteger svcId = (BigInteger) row[3];
4013 Date tmpDate = (Date) row[1];
4014 // Double conversion necessary here.
4015 Date serviceDate = DateUtil.convertDateTz(tmpDate, userTz, TimeZone.getTimeZone("UTC"));
4016 BigDecimal rate = (BigDecimal) row[5];
4017 String hcpcs = (String) row[4];
4018 com.hummingbird.ejb.resources.jaxb.crossbill.PhysicianService jaxbPhySvc = objFactory
4019 .createPhysicianService();
4020
4021 jaxbPhySvc.setProcedureCode(hcpcs);
4022
4023 jaxbPhySvc.setRate(rate);
4024 jaxbPhySvc.setLineAmount(rate);
4025 jaxbPhySvc.setServiceDate(convertToOutgoingClaimDateTimeFormat(serviceDate, userTz));
4026 jaxbPhySvc.setHcpcsCode(hcpcs);
4027
4028 jaxbPhySvc.setRevenueCode("0657");
4029
4030 jaxbPhySvc.setQuantity(1);
4031
4032 jaxbPhySvc.setUnits("UN");
4033
4034 if (palliative) {
4035 StringBuffer sqlD = new StringBuffer();
4036 sqlD.append("SELECT icd09diagnosis, icd10diagnosis"
4037 + " from physicianservicediagnoses where physicianservice_id = :serviceId"
4038 + " and deleted = false");
4039
4040 Query diagQuery = em.createNativeQuery(sqlD.toString());
4041 diagQuery.setParameter("serviceId", svcId);
4042
4043 @SuppressWarnings("unchecked")
4044 List<Object[]> resultsDiag = diagQuery.getResultList();
4045 if (resultsDiag != null && resultsDiag.size() > 0) {
4046 com.hummingbird.ejb.resources.jaxb.crossbill.PhysicianService.SvcDiagnoses jaxbPhySvcDiag = objFactory
4047 .createPhysicianServiceSvcDiagnoses();
4048 for (Object[] rowD : resultsDiag) {
4049 String icd9 = (String) rowD[0];
4050 String icd10 = (String) rowD[1];
4051 Diagnosis rowDiag = createDiagnosisNode(icd9, icd10, objFactory);
4052 jaxbPhySvcDiag.getDiagnosis().add(rowDiag);
4053 }
4054 jaxbPhySvc.setSvcDiagnoses(jaxbPhySvcDiag);
4055 }
4056 BigInteger physId = (BigInteger) row[7];
4057 Person svcPhys = em.find(Person.class, Long.parseLong(physId.toString()));
4058
4059 if (svcPhys != null) {
4060 jaxbPhySvc.setServicePhysician(createPhysicianNode(svcPhys, objFactory));
4061 }
4062 }
4063
4064 jaxbPhysicianServices.getPhysicianService().add(jaxbPhySvc);
4065 }
4066 }
4067 return jaxbPhysicianServices;
4068 }
4069
4070 /**
4071 * Builds the Medications node for a PatientClaim.
4072 *
4073 * @param patientId
4074 * @param claimStartDateOnly
4075 * Date is expected to be in date only format (i.e. time is set
4076 * to 00:00:00 UTC). Example: 2016-01-01 00:00:00 UTC
4077 * @param claimEndDateOnly
4078 * Date is expected to be in date only format (i.e. time is set
4079 * to 00:00:00 UTC). Example: 2016-01-31 00:00:00 UTC
4080 * @param objFactory
4081 * @return
4082 */
4083 private com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Medications createMedicationsNode(Long patientId,
4084 Date claimStartDateOnly, Date claimEndDateOnly, ObjectFactory objFactory) {
4085 TypedQuery<PatientMedication> queryM = em.createNamedQuery(
4086 "PatientMedication.findPatientMedicationsByPatientinPeriod", PatientMedication.class);
4087
4088 // Note: patientmedication.filldate is persisted in date only format
4089 // (Thus no time conversion needed)
4090
4091 queryM.setParameter("patientId", patientId);
4092 queryM.setParameter("startDate", claimStartDateOnly);
4093 queryM.setParameter("endDate", claimEndDateOnly);
4094 List<PatientMedication> patMedications = queryM.getResultList();
4095
4096 com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Medications jaxbMedications = null;
4097
4098 if (patMedications != null && !patMedications.isEmpty()) {
4099 jaxbMedications = new com.hummingbird.ejb.resources.jaxb.crossbill.PatientClaim.Medications();
4100 for (PatientMedication patMedication : patMedications) {
4101 com.hummingbird.ejb.resources.jaxb.crossbill.PatientMedication jaxbMedication = objFactory
4102 .createPatientMedication();
4103 jaxbMedication.setDescription(patMedication.getMedication());
4104 jaxbMedication.setHcpcsCode(patMedication.getHcpcs());
4105 jaxbMedication.setLineAmount(patMedication.getCost());
4106 jaxbMedication.setMedNDCvalue(patMedication.getNdc() != null ? (patMedication.getNdc() + "") : null);
4107 if (patMedication.getQuantity() != null)
4108 jaxbMedication.setQuantity(patMedication.getQuantity());
4109 jaxbMedication.setRevenueCode(patMedication.getRevCode() != null ? (patMedication.getRevCode() + "")
4110 : null);
4111 if (patMedication.getFillDate() != null)
4112 jaxbMedication.setServiceDate(convertToOutgoingClaimDateOnlyFormat(patMedication.getFillDate()));
4113 jaxbMedication.setUnits(patMedication.getUnitType() != null ? patMedication.getUnitType().toString()
4114 : null);
4115
4116 jaxbMedications.getMedication().add(jaxbMedication);
4117 }
4118 }
4119
4120 return jaxbMedications;
4121
4122 }
4123
4124 private void populateFacility(ServiceFacility locFacility, com.hummingbird.jpa.Address locLocation) {
4125 ObjectFactory fac = new ObjectFactory();
4126 com.hummingbird.ejb.resources.jaxb.crossbill.Address addr = fac.createAddress();
4127 if (locLocation != null) {
4128 addr.setStreet1(locLocation.getStreet1());
4129 addr.setCity(locLocation.getCity());
4130 if (locLocation.getCbsa() != null)
4131 addr.setCbsa(locLocation.getCbsa().trim());
4132 addr.setZip(mungZipcode(locLocation));
4133 addr.setState(locLocation.getState());
4134 }
4135 locFacility.setFacilityAddress(addr);
4136 fac = null;
4137 }
4138
4139 /**
4140 * Returns all patient diagnoses for a patient; before or during endDate.
4141 *
4142 * @param patientId
4143 * @param endDate
4144 * Date is expected to be in date only format (i.e time will be
4145 * set to 00:00:00 UTC)
4146 * @return
4147 */
4148 private List<PatientDiagnoses> getPatientDiagsForClaim(Long patientId, Date endDate) {
4149
4150 // Note: patientdiagnoses.effectivedate is persisted as date only format
4151 // (thus no time conversion is needed)
4152 Date endDateOnly = endDate;
4153
4154 String querystr = " select id, effectivedate, diagnosis, icd10diagnosis, rank, "
4155 + "issystemdefined,lastupdatetime,lastupdateuser,version, deleted,"
4156 + "interaction_id,description, icd10description,shortdescription, icd10ShortDescription "
4157 + " from "
4158 + "(select pd.* , rank() over (partition by rank order by pd.effectivedate desc) as daterank "
4159 + "from patientdiagnoses pd "
4160 + "left join patientinteraction pi on pi.id = pd.interaction_id "
4161 + "where pi.patient_id = :patientId and pd.effectivedate <= :endDate and pd.deleted = false ) as tmpranks where daterank = 1 "
4162 + "order by rank";
4163 Query query = em.createNativeQuery(querystr, PatientDiagnoses.class);
4164 query.setParameter("patientId", patientId);
4165 query.setParameter("endDate", endDateOnly);
4166 @SuppressWarnings("unchecked")
4167 List<PatientDiagnoses> diags = query.getResultList();
4168
4169 return diags;
4170 }
4171
4172 private List<PatientVisit> getPatientVisitsForClaim(Long patientId, Date startDate, Date endDate) {
4173 TypedQuery<PatientVisit> query = em.createNamedQuery("PatientVisit.findPatientBillingVisitsInPeriod",
4174 PatientVisit.class);
4175 query.setParameter("patientId", patientId);
4176 query.setParameter("startDate", startDate);
4177 query.setParameter("endDate", endDate);
4178 List<PatientVisit> visits = query.getResultList();
4179
4180 return visits;
4181 }
4182
4183 /**
4184 * Munges zip and zip4 into a single string field. This method is overly
4185 * paranoid because of odd data issues with migrated site data.
4186 *
4187 * @param address
4188 * @return
4189 */
4190 private String mungZipcode(com.hummingbird.jpa.Address address) {
4191 StringBuilder munged = new StringBuilder();
4192 String zipString = "NOZIP";
4193 String zip4String = "9999";
4194 if (address != null) {
4195 if (address.getZip() != null && !address.getZip().isEmpty()) {
4196 zipString = address.getZip().trim();
4197 if (address.getZip().contains("-"))
4198 zipString = address.getZip().trim().substring(0, address.getZip().indexOf('-'));
4199 }
4200
4201 if (address.getZip4() != null && !address.getZip4().isEmpty()) {
4202 zip4String = address.getZip4();
4203 }
4204 }
4205 munged.append(zipString, 0, 5);
4206 munged.append(zip4String);
4207 return munged.toString().trim();
4208 }
4209
4210 private void populateIndexRates(CarrierRate jaxbRates, RateCodeGroup nationalRates, PayerType payerType,
4211 List<InsuranceCarrierRates> insCarrierRates, Map<String, String> carrierSpecificRevCodesMap) {
4212 if (payerType == null)
4213 return;
4214
4215 ObjectFactory objFactory = new ObjectFactory();
4216 Map<String, RateCode> nationalRateMap = new HashMap<String, RateCode>();
4217 Map<String, InsuranceCarrierRate> insCarrierRateMap = new HashMap<String, InsuranceCarrierRate>();
4218
4219 boolean useNationalRates = true;
4220 boolean useMedicareRates = PayerType.specialPayerTypes.contains(payerType);
4221
4222 if (nationalRates != null) {
4223 for (RateCode r : nationalRates.getRatesCodes()) {
4224 nationalRateMap.put(r.getTypeOfCare(), r);
4225 }
4226 }
4227
4228 if (insCarrierRates != null) {
4229 for (InsuranceCarrierRates rates : insCarrierRates) {
4230 for (InsuranceCarrierRate r : rates.getRates()) {
4231 insCarrierRateMap.put(r.getTypeOfCare(), r);
4232 }
4233 }
4234 }
4235
4236 if (payerType == PayerType.COMMERCIAL && !insCarrierRateMap.isEmpty()) {
4237 useNationalRates = false;
4238 } else
4239 useNationalRates = true;
4240
4241 if (useNationalRates && nationalRateMap.isEmpty())
4242 return;
4243
4244 if (!useNationalRates && insCarrierRateMap.isEmpty())
4245 return;
4246
4247 for (TypeOfCare loc : TypeOfCare.values()) {
4248 BigDecimal rate = CurrencyUtil.ZERO;
4249 BigDecimal wage = CurrencyUtil.ZERO;
4250
4251 if (useNationalRates) {
4252 RateCode currentRate = nationalRateMap.get(loc.getName());
4253 if (currentRate == null)
4254 continue; // This LOC has no associate rate in our
4255 // RateCodeGroup
4256
4257 try {
4258 wage = CurrencyUtil.create(currentRate.getWageAmount());
4259 if (useMedicareRates) {
4260 rate = CurrencyUtil.create(currentRate.getMedicareNationalRate());
4261 } else {
4262 rate = CurrencyUtil.create(currentRate.getMedicaidNationalRate());
4263 wage = CurrencyUtil.add(wage, CurrencyUtil.create(currentRate.getMedicaidOffset()));
4264 }
4265 } catch (ParseException | NullPointerException | NumberFormatException e) {
4266 // Generally this means that either a non-number or no value
4267 // at all has been entered in the field.
4268 log.fine("Index Rate not set up for: " + loc.getName());
4269 continue;
4270 }
4271
4272 } else {
4273 InsuranceCarrierRate currentRate = insCarrierRateMap.get(loc.getName());
4274 if (currentRate == null)
4275 continue; // This LOC has no associated rate
4276 try {
4277 wage = CurrencyUtil.ZERO;
4278 rate = CurrencyUtil.create(currentRate.getPerDiemRate().replaceAll("[\\$,\\s]", ""));
4279 } catch (ParseException | NullPointerException | NumberFormatException e) {
4280 continue;
4281 }
4282 // carrierSpecificRevCodesMap will be later used to put
4283 // appropriate revenue codes for Level of care records
4284 if (carrierSpecificRevCodesMap != null)
4285 carrierSpecificRevCodesMap.put(loc.name(), currentRate.getRevenueCode());
4286 }
4287 IndexRate index = objFactory.createIndexRate();
4288 index.setRate(rate);
4289 index.setWage(wage);
4290
4291 switch (loc) {
4292 case CC:
4293 jaxbRates.setCcare(index);
4294 break;
4295 case GIP:
4296 jaxbRates.setGip(index);
4297 break;
4298 case RESPITE:
4299 jaxbRates.setRespite(index);
4300 break;
4301 case ROUTINE:
4302 jaxbRates.setRoutine(index);
4303 break;
4304 // Defaults for now TODO: might need to populate these one day, but
4305 // the schema doesn't support them.
4306 case PHYSERVICE:
4307 case PP:
4308 case RB:
4309 break;
4310 }
4311 }
4312 objFactory = null;
4313 }
4314
4315 /**
4316 * Private utility method performing a mapping of discharge reason code type
4317 * to PAS code.
4318 *
4319 * @param jpaPatient
4320 * @param patAdmission
4321 */
4322 private void setXmlDischargeReasonCode(Patient jpaPatient, DischargeReason reason,
4323 com.hummingbird.ejb.resources.jaxb.crossbill.PatientAdmission patAdmission) {
4324 switch (reason.getDischargeCode().getCodeType()) {
4325 case DEATH:
4326 patAdmission.setDischargeReasonCode("D");
4327 break;
4328 case FOR_CAUSE:
4329 patAdmission.setDischargeReasonCode("C");
4330 break;
4331 case NON_CERTIFIED:
4332 case REVOKED:
4333 patAdmission.setDischargeReasonCode("R");
4334 break;
4335 case TRANSFERRED:
4336 case LEFT_GEOGRAPHIC_AREA:
4337 case LEFT_TO_NON_CONTRACT_FACILITY:
4338 patAdmission.setDischargeReasonCode("L");
4339 break;
4340 }
4341 }
4342
4343 /**
4344 * Calculates totals for the Prepare claims panel.
4345 *
4346 * TODO: Need to add additional parameters so we can filter by office etc.
4347 *
4348 * @param billDTO
4349 * @return
4350 */
4351 public BillDTO calcualteTotalsForClaimsPanel(BillDTO billDTO) {
4352
4353 BillDTO updatedBillDTO = null;
4354 int totalBeingSubmitted = 0;
4355 int totalNotBeingSubmitted = 0;
4356
4357 BigDecimal totalBeingSubmittedAmount = BigDecimal.ZERO;
4358 BigDecimal totalNotBeingSubmittedAmount = BigDecimal.ZERO;
4359
4360 if (billDTO != null && billDTO.getPatients() != null && !billDTO.getPatients().isEmpty()) {
4361
4362 updatedBillDTO = billDTO;
4363
4364 for (BillPatientDTO billPatientDTO : updatedBillDTO.getPatients()) {
4365
4366 if (billPatientDTO.getClaimStatus() != null) {
4367 if (billPatientDTO.getClaimStatus().isGoodToSubmit()) {
4368 totalBeingSubmitted++;
4369 totalBeingSubmittedAmount = totalBeingSubmittedAmount.add(billPatientDTO.getTotalReceivable());
4370 } else {
4371 totalNotBeingSubmitted++;
4372 totalNotBeingSubmittedAmount = totalNotBeingSubmittedAmount.add(billPatientDTO
4373 .getTotalReceivable());
4374 }
4375 } else {
4376 log.severe("Couldn't find claim status for claimKey: " + billPatientDTO.getClaimKey());
4377 }
4378 }
4379 } else {
4380 updatedBillDTO = new BillDTO();
4381
4382 }
4383
4384 updatedBillDTO.setTotalNotBeingSubmitted(totalNotBeingSubmitted);
4385 updatedBillDTO.setTotalBeingSubmitted(totalBeingSubmitted);
4386 updatedBillDTO.setTotalNotBeingSubmittedAmount(totalNotBeingSubmittedAmount);
4387 updatedBillDTO.setTotalBeingSubmittedAmount(totalBeingSubmittedAmount);
4388 return updatedBillDTO;
4389 }
4390
4391 /**
4392 * We are no longer using this as we are getting the totals from PAS. But we
4393 * dont want to delete it as we might use the method in future when PAS goes
4394 * away
4395 *
4396 * @param monthDate
4397 * @param officeIdList
4398 * @param programIdList
4399 * @param payerTypeList
4400 * @param patientId
4401 * @param billDTO
4402 */
4403 @Deprecated
4404 public void totalForClaimPanel(Date monthDate, Long officeIdList, Long programIdList, PayerType payerTypeList,
4405 Long patientId, BillDTO billDTO) {
4406 Connection conn = null;
4407 CallableStatement statement = null;
4408
4409 CallableStatement stmtPayer = null;
4410 ResultSet rs = null;
4411 ResultSet rsPayer = null;
4412
4413 Date dates[] = DateUtil.calculatePreviousMonthDateRangeForBilling(DateUtil.UTC_ZONE);
4414 Date startDate = dates[0];
4415 Date endDate = dates[1];
4416
4417 int totalBeingSubmitted = 0;
4418 int totalBeingNotSubmitted = 0;
4419
4420 BigDecimal totalBeingSubmittedAmount = BigDecimal.ZERO;
4421 BigDecimal totalBeingNotSubmittedAmount = BigDecimal.ZERO;
4422
4423 String tenantId = multiTenantService.getCurrentTenantId();
4424 DataSource ds = HBMultiTenantConnectionProvider.getTenantDataSource(tenantId);
4425
4426 try {
4427 conn = ds.getConnection();
4428
4429 List<Long> ids = new ArrayList<Long>();
4430
4431 stmtPayer = conn.prepareCall("{ ? = call patientsbypayertype(?) }");
4432 stmtPayer.registerOutParameter(1, Types.OTHER);
4433 stmtPayer.setString(2, (payerTypeList != null) ? payerTypeList.toString() : null);
4434 stmtPayer.execute();
4435 rsPayer = (ResultSet) stmtPayer.getObject(1);
4436 while (rsPayer.next()) {
4437 ids.add(rsPayer.getLong("id"));
4438 }
4439 statement = conn.prepareCall("{ ? = call calculateclaims(?,?,?,?,?,?) }");
4440 statement.registerOutParameter(1, Types.OTHER);
4441 statement.setDate(2, new java.sql.Date(startDate.getTime()));
4442 statement.setDate(3, new java.sql.Date(endDate.getTime()));
4443 statement.setLong(4, officeIdList);
4444 statement.setLong(5, programIdList);
4445 statement.setString(6, (payerTypeList != null) ? payerTypeList.toString() : null);
4446 statement.setLong(7, patientId);
4447 statement.execute();
4448 rs = (ResultSet) statement.getObject(1);
4449 while (rs.next()) {
4450 if (payerTypeList != null && !ids.isEmpty()) {
4451 if (ids.contains(rs.getLong("id"))) {
4452 String status = rs.getString("claimstatus");
4453 BigDecimal claimtotl = rs.getBigDecimal("claimstotal");
4454 if (status != null && ClaimStatus.valueOf(status).displayInPrepareClaims()) {
4455 totalBeingNotSubmittedAmount = totalBeingNotSubmittedAmount.add(claimtotl);
4456 totalBeingNotSubmitted++;
4457 } else if (status != null) {
4458 totalBeingSubmitted++;
4459 totalBeingSubmittedAmount = totalBeingSubmittedAmount.add(claimtotl);
4460 }
4461 if (status == null) {
4462 totalBeingNotSubmittedAmount = totalBeingNotSubmittedAmount.add(claimtotl);
4463 totalBeingNotSubmitted++;
4464 }
4465 }
4466 } else if (payerTypeList == null) {
4467 String status = rs.getString("claimstatus");
4468 BigDecimal claimtotl = rs.getBigDecimal("claimstotal");
4469 if (status != null && ClaimStatus.valueOf(status).displayInPrepareClaims()) {
4470 totalBeingNotSubmittedAmount = totalBeingNotSubmittedAmount.add(claimtotl);
4471 totalBeingNotSubmitted++;
4472 } else if (status != null) {
4473 totalBeingSubmitted++;
4474 totalBeingSubmittedAmount = totalBeingSubmittedAmount.add(claimtotl);
4475 }
4476 if (status == null) {
4477 totalBeingNotSubmittedAmount = totalBeingNotSubmittedAmount.add(claimtotl);
4478 totalBeingNotSubmitted++;
4479 }
4480
4481 }
4482
4483 }
4484
4485 billDTO.setTotalBeingSubmitted(totalBeingSubmitted);
4486 billDTO.setTotalBeingSubmittedAmount(totalBeingSubmittedAmount);
4487 billDTO.setTotalNotBeingSubmitted(totalBeingNotSubmitted);
4488 billDTO.setTotalNotBeingSubmittedAmount(totalBeingNotSubmittedAmount);
4489
4490 } catch (Exception ex) {
4491 ex.printStackTrace();
4492 } finally {
4493 try {
4494 if (rs != null)
4495 rs.close();
4496
4497 if (statement != null)
4498 statement.close();
4499
4500 if (rsPayer != null)
4501 rsPayer.close();
4502
4503 if (stmtPayer != null)
4504 stmtPayer.close();
4505
4506 if (conn != null)
4507 conn.close();
4508 } catch (SQLException ex) {
4509 }
4510 }
4511
4512 }
4513
4514 /**
4515 *
4516 * <pre>
4517 * This method will populate the calculated total for the claims which are not being passed PAS billing engine
4518 * These calculations are done from our side.
4519 * Here we call the CalculateClaims function , which does the calculations from our side
4520 *
4521 * </pre>
4522 *
4523 * @param billDto
4524 * @param monthDate
4525 */
4526 public void populateCalcualedAmt(BillDTO billDto) {
4527 if (billDto == null || billDto.getPatients() == null)
4528 return;
4529
4530 Connection conn = null;
4531 CallableStatement statement = null;
4532 ResultSet rs = null;
4533
4534 String tenantId = multiTenantService.getCurrentTenantId();
4535 DataSource ds = HBMultiTenantConnectionProvider.getTenantDataSource(tenantId);
4536
4537 try {
4538 conn = ds.getConnection();
4539 conn = ds.getConnection();
4540 Date startDate = billDto.getStartDate();
4541 Date endDate = billDto.getEndDate();
4542 Map<Long, BillPatientDTO> billIdPatientDtoMap = createBillIdPatientDtoMap(billDto.getPatients());
4543 statement = conn.prepareCall("{ ? = call calculateclaims(?,?,?,?,?,?) }");
4544 statement.registerOutParameter(1, Types.OTHER);
4545
4546 statement.setDate(2, new java.sql.Date(startDate.getTime()));
4547 statement.setDate(3, new java.sql.Date(endDate.getTime()));
4548 statement.setLong(4, 0l);// Office ID to be PAssed else 0 to
4549 // ignore it
4550 statement.setLong(5, 0l);// program id to be passes else 0 to
4551 // ignore it
4552 statement.setString(6, null);// Payer Type to be passed by if it
4553 // is for specific payer type
4554 statement.setLong(7, 0l);// Patient ID to be passed if it is for
4555 // specific Patient
4556 statement.execute();
4557 rs = (ResultSet) statement.getObject(1);
4558 while (rs.next()) {
4559 if (billIdPatientDtoMap.get(rs.getLong("id")) != null) {
4560 BillPatientDTO patient = billIdPatientDtoMap.get(rs.getLong("id"));
4561 if (patient.getClaimStatus() != null && patient.getClaimStatus().displayInPrepareClaims()) {
4562 patient.setTotalReceivable(rs.getBigDecimal("claimstotal"));
4563 // String dateRange = "";
4564 // dateRange = DateUtil.UTC_MMDDYY.format(startDate)
4565 // + " - "
4566 // + DateUtil.UTC_MMDDYY.format(endDate);
4567 // patient.setDays(DateUtil.daysBetween(startDate,
4568 // endDate));
4569 // patient.setDateRange(dateRange);
4570 }
4571 }
4572 }
4573
4574 } catch (Exception e) {
4575 throw new RuntimeException(e);
4576 } finally {
4577 try {
4578 if (rs != null && !rs.isClosed())
4579 rs.close();
4580 if (statement != null && !statement.isClosed())
4581 statement.close();
4582 if (conn != null && !conn.isClosed())
4583 conn.close();
4584
4585 } catch (SQLException ex) {
4586 }
4587 }
4588 }
4589
4590 private Map<Long, BillPatientDTO> createBillIdPatientDtoMap(List<BillPatientDTO> patients) {
4591 Map<Long, BillPatientDTO> billPatientDtoMap = new HashMap<Long, BillPatientDTO>();
4592 if (patients != null) {
4593 for (BillPatientDTO patientDto : patients) {
4594 billPatientDtoMap.put(patientDto.getId(), patientDto);
4595 }
4596 }
4597 return billPatientDtoMap;
4598 }
4599
4600 /**
4601 * When the listeners are fired, this method finds all the claims that might
4602 * be affected by the patient update.
4603 *
4604 * @param claimInfo
4605 * @return
4606 */
4607 public List<Claim> findClaimsAffectedByPatientUpdate(PatientClaimInfo claimInfo) {
4608
4609 if (claimInfo == null || claimInfo.getPatientId() == null)
4610 return null;
4611
4612 List<String> excludeList = new ArrayList<String>();
4613
4614 for (ClaimStatus cs : ClaimStatus.excludedFromClaimSearch)
4615 excludeList.add(cs.toString());
4616
4617 String queryStr = "select c.* from Claim c JOIN patient p on p.id = c.patient_id"
4618 + " WHERE p.id = :patientId AND c.claimStatus NOT IN (:excludedClaimStatuses)"
4619 + " AND (c.typeOfBill IS NULL OR c.typeOfBill NOT IN (:noaTypeOfBill))"
4620 + " AND ( NOT EXISTS (select 1 from claim cl where cl.parentclaim_id = c.id AND cl.claimStatus != 'DRAFT'))";
4621
4622 if (claimInfo.getEffectiveDate() != null) {
4623 queryStr = queryStr
4624 .concat(" AND (c.servicestart <= :effectiveDateUtc AND c.serviceend >= :effectiveDateUtc");
4625
4626 if (claimInfo.getEndDate() != null) {
4627 queryStr = queryStr.concat(" OR (c.servicestart <= :endDateUtc AND c.serviceend >= :endDateUtc)");
4628 queryStr = queryStr.concat(" OR (c.servicestart >= :endDateUtc AND c.serviceend <= :endDateUtc)");
4629 }
4630
4631 queryStr = queryStr.concat(")");
4632 }
4633
4634 queryStr = queryStr.concat(" ORDER BY c.servicestart");
4635
4636 Query query = em.createNativeQuery(queryStr, Claim.class);
4637 query.setParameter("patientId", claimInfo.getPatientId());
4638 query.setParameter("excludedClaimStatuses", excludeList);
4639 query.setParameter("noaTypeOfBill", NoaTypeOfBill.getAllTypeOfBills());
4640
4641 if (claimInfo.getEffectiveDate() != null) {
4642 query.setParameter("effectiveDateUtc", claimInfo.getEffectiveDate());
4643
4644 if (claimInfo.getEndDate() != null)
4645 query.setParameter("endDateUtc", claimInfo.getEndDate());
4646 }
4647
4648 @SuppressWarnings("unchecked")
4649 List<Claim> claims = query.getResultList();
4650
4651 log.info("--- A total of " + claims.size() + " claim(s) affected by change made (effective on "
4652 + claimInfo.getEffectiveDate().toGMTString() + ") to patient " + claimInfo.getPatientId() + " ---");
4653
4654 return claims;
4655
4656 }
4657
4658 /**
4659 * Update the claim by calling PASBE and getting the latest claim
4660 * information. From here, we will update the claim accordingly, so that the
4661 * books match what the patient's claim would look like today versus when it
4662 * was last processed.
4663 *
4664 * @param claimInfo
4665 */
4666 public void updateClaimsFromPASBE(PatientClaimInfo claimInfo) {
4667 markClaimsForReview(findClaimsAffectedByPatientUpdate(claimInfo), ReviewClaimReason.OTHER);
4668 }
4669
4670 public void markClaims(Patient patient, Date effectiveDateUtc, ReviewClaimReason reason) {
4671 markClaims(patient, effectiveDateUtc, null, reason);
4672 }
4673
4674 public void markClaims(Patient patient, Date effectiveDateUtc, Date endDateUtc, ReviewClaimReason reason) {
4675
4676 if (patient == null)
4677 return;
4678
4679 PatientClaimInfo claimInfo = new PatientClaimInfo();
4680
4681 claimInfo.setPatientId(patient.getId());
4682 claimInfo.setHospiceBin(patient.getHospice().getBin());
4683 claimInfo.setEffectiveDate(effectiveDateUtc);
4684 claimInfo.setEndDate(endDateUtc);
4685
4686 markClaimsForReview(findClaimsAffectedByPatientUpdate(claimInfo), reason);
4687 }
4688
4689 /**
4690 * This method marks all claims that need to be re-run through the claims
4691 * processor so they are no longer outdated.
4692 *
4693 * @param claims
4694 */
4695 private void markClaimsForReview(List<Claim> claims, ReviewClaimReason reason) {
4696 if (claims == null || claims.isEmpty())
4697 return;
4698
4699 String enableMarkingOfClaims = configService.lookupEnvProperty("enableMarkingOfClaims");
4700 if (!enableMarkingOfClaims.toLowerCase().equals("true"))
4701 return;
4702
4703 for (Claim claim : claims) {
4704 log.info("--- Claim key " + claim.getClaimKey() + " for patient id " + claim.getPatient().getId()
4705 + " has been marked ---");
4706 claim.setReviewNeeded(HBUtil.generateStringWithoutDuplicates(claim.getReviewNeeded(), reason));
4707 em.merge(claim);
4708 }
4709
4710 em.flush();
4711 }
4712
4713 @Override
4714 public int syncClaimsMarkedForReview(boolean syncClaims) {
4715
4716 long siteId = auth.getCurrentUserSiteId();
4717
4718 List<String> excludeList = new ArrayList<String>();
4719
4720 for (ClaimStatus cs : ClaimStatus.excludedFromClaimSearch)
4721 excludeList.add(cs.toString());
4722
4723 String query = "SELECT cl.* FROM Claim cl JOIN programsite ps ON ps.id = cl.programsite_id"
4724 + " WHERE cl.reviewNeeded IS NOT NULL AND cl.claimStatus NOT IN (:excludedClaimStatuses)"
4725 + " AND ps.site_id = :siteId";
4726
4727 Query claimsQuery = em.createNativeQuery(query, Claim.class);
4728 claimsQuery.setParameter("excludedClaimStatuses", excludeList);
4729 claimsQuery.setParameter("siteId", siteId);
4730 @SuppressWarnings("unchecked")
4731 List<Claim> claims = claimsQuery.getResultList();
4732
4733 if (syncClaims && !CollectionUtils.isEmpty(claims)) {
4734 log.info(" **** SYNCING CLAIMS THAT NEED REVIEW ****");
4735 for (Claim claim : claims)
4736 reProcessClaim(claim);
4737 }
4738
4739 return claims != null ? claims.size() : 0;
4740 }
4741
4742 public TimerServiceResponse syncReviewClaimsForAllHospice() {
4743 TimerServiceResponse result = new TimerServiceResponse();
4744
4745 Authentication oldAuthentication = SecurityContextHolder.getContext().getAuthentication();
4746
4747 List<com.hummingbird.shared.dto.Hospice> hospices = multiTenantService.fetchMummsAdminHospices();
4748 for (com.hummingbird.shared.dto.Hospice hospice : hospices) {
4749 if (hospice.getBin().equals("mummsupport")) {
4750 log.severe("Skipping Hospice mummsupport");
4751 continue;
4752 }
4753
4754 log.fine("Invoking generateClaims for hospice: "
4755 + (hospice == null ? null : hospice.getBin() + " " + hospice.getId()));
4756 multiTenantService.setFakeAuthentication(hospice.getBin(), true);
4757 result.getTenantRecordsAffected().put(hospice.getBin(), syncClaimsMarkedForReview(true));
4758 }
4759
4760 if (oldAuthentication != null) {
4761 SecurityContextHolder.getContext().setAuthentication(oldAuthentication);
4762 }
4763
4764 return result;
4765
4766 }
4767
4768 /**
4769 * 1) If claim is sent, amend the claim. The user needs to manually approve
4770 * the amendment.
4771 *
4772 * 2) If claim is not sent & books are closed replace the claim. The claim
4773 * is auto replaced
4774 *
4775 * 3) If claim is not sent & books are open update the amount on the claim
4776 *
4777 * @param claims
4778 * @param effectiveDateUTC
4779 */
4780 private void reProcessClaim(Claim claim) {
4781
4782 if (claim == null || claim.getPatient() == null) {
4783 log.warning("Couldnt reprocess claim");
4784 return;
4785 }
4786
4787 BillingOptions opts = new BillingOptions();
4788 DateRange billingDates = new DateRange(claim.getServiceStart(), claim.getServiceEnd());
4789 opts.setBillingDates(billingDates);
4790 opts.setSinglePatient(claim.getPatient().getId());
4791 opts.setPayerType(claim.getPayerType());
4792 opts.setProgramSiteId(claim.getProgramSite().getId());
4793
4794 generateClaimsForCurrentSite(opts, false);
4795 }
4796
4797 /**
4798 * Update the claim with new revlines and new total
4799 *
4800 * @param claim
4801 */
4802 private void updateClaimTotalAndRevLines(Claim claim, BillPatientDTO billPatientDto) {
4803
4804 if (claim == null || billPatientDto == null)
4805 return;
4806
4807 /*
4808 * Update the rev lines
4809 */
4810
4811 // get old rev lines
4812 List<ClaimRevenueDetail> oldRevLines = financeService.findRevenueDetailsByClaimId(claim.getId());
4813
4814 // get rid of the old ones
4815 for (ClaimRevenueDetail rev : HBUtil.emptyIfNull(oldRevLines)) {
4816 boolean deleted = persistenceService.tryDelete(rev);
4817 if (!deleted)
4818 // oh no! couldn't delete. probably due to a
4819 // ClaimTransaction, which is bad
4820 log.severe("Couldn't delete revenue line id:" + rev.getId() + ", revcode:" + rev.getRevCode()
4821 + ", claim_id:" + claim.getId() + ", date:" + rev.getDate());
4822 }
4823
4824 // create new rev lines
4825 createClaimRevenueDetails(billPatientDto, claim, true);
4826
4827 /*
4828 * Update claim total
4829 */
4830 claim.setClaimTotal(billPatientDto.getTotalReceivable());
4831 persistenceService.update(claim);
4832
4833 }
4834
4835 /**
4836 * Update claims for a given hospice from a given effective date.
4837 *
4838 * @param hospiceId
4839 * @param effectiveDateUTC
4840 */
4841 public void updateHospiceClaims(HospiceClaimInfo hospiceClaimInfo) {
4842
4843 // TODO: Add logic when Reposting is working correctly
4844 }
4845
4846 /**
4847 * We are no longer using this as we are getting the totals from PAS. But we
4848 * dont want to delete it as we might use the method in future when PAS goes
4849 * away
4850 */
4851 @Deprecated
4852 public void populateDischarePatientReceivables(BillDTO billDto, List<Patient> dischargedPatients) {
4853 if (dischargedPatients == null)
4854 return;
4855
4856 Connection conn = null;
4857 CallableStatement statement = null;
4858 ResultSet rs = null;
4859 Date today = DateUtil.getTodayDateOnly(getUserTz());
4860 String tenantId = multiTenantService.getCurrentTenantId();
4861 DataSource ds = HBMultiTenantConnectionProvider.getTenantDataSource(tenantId);
4862
4863 try {
4864 conn = ds.getConnection();
4865 Map<Long, BillPatientDTO> billIdPatientDtoMap = createBillIdPatientDtoMap(billDto.getPatients());
4866 for (Patient patient : dischargedPatients) {
4867 Date startDate = billDto.getStartDate();
4868 Date endDate = today;
4869 statement = conn.prepareCall("{ ? = call calculateclaims(?,?,?,?,?,?) }");
4870 statement.registerOutParameter(1, Types.OTHER);
4871 statement.setDate(2, new java.sql.Date(startDate.getTime()));
4872 statement.setDate(3, new java.sql.Date(endDate.getTime()));
4873 statement.setLong(4, 0l);
4874 statement.setLong(5, 0l);
4875 statement.setString(6, null);
4876 statement.setLong(7, patient.getId());
4877 statement.execute();
4878 rs = (ResultSet) statement.getObject(1);
4879 while (rs.next()) {
4880 if (billIdPatientDtoMap.containsKey(patient.getId())) {
4881 BillPatientDTO billPatientDto = billIdPatientDtoMap.get(patient.getId());
4882 if (billPatientDto.getClaimStatus() != null
4883 && billPatientDto.getClaimStatus().displayInPrepareClaims()) {
4884 billPatientDto.setTotalReceivable(rs.getBigDecimal("claimstotal"));
4885
4886 }
4887 }
4888 }
4889 rs.close();
4890 statement.close();
4891 }
4892 } catch (Exception e) {
4893 throw new RuntimeException(e);
4894 } finally {
4895 try {
4896 if (rs != null && !rs.isClosed())
4897 rs.close();
4898 if (statement != null && !statement.isClosed())
4899 statement.close();
4900 if (conn != null)
4901 conn.close();
4902 } catch (SQLException ex) {
4903 }
4904 }
4905 }
4906
4907 public String generateClaimKey(String patientNumber, Date claimStartDate) {
4908 return generateClaimKey(null, null, null, patientNumber, claimStartDate, false);
4909 }
4910
4911 /**
4912 * This unique key is used to identify claims and are saved to the
4913 * BillingStatus.
4914 *
4915 * @return
4916 */
4917 public String generateClaimKey(PayerType payer, ProgramEnum program, String parentClaimKey, String patientNumber,
4918 Date claimStartDate, boolean isNoeClaim) {
4919 StringBuilder keyBuilder = new StringBuilder();
4920 keyBuilder.append(generateClaimKeyPrefix(payer, program, parentClaimKey, isNoeClaim));
4921
4922 if (patientNumber != null)
4923 keyBuilder.append(patientNumber.replaceAll("-", ""));
4924
4925 if (claimStartDate != null)
4926 keyBuilder.append(DateEncoder.encodeB36short(claimStartDate));
4927
4928 if (keyBuilder.toString().length() > 20)
4929 log.severe("!ATTENTION!: Claim key [" + keyBuilder.toString() + "] generated for patient number "
4930 + patientNumber + " is too long");
4931
4932 return keyBuilder.toString();
4933 }
4934
4935 public String generateClaimKeyPrefix(PayerType payer, ProgramEnum program, String parentClaimKey, boolean isNoeClaim) {
4936 StringBuilder prefixBuilder = new StringBuilder();
4937
4938 if (isNoeClaim)
4939 return "NOECLM";
4940
4941 // Add payer
4942 prefixBuilder.append(payer == null ? "XD" : payer.getCode());
4943
4944 // Add program
4945 prefixBuilder.append(program == null ? ProgramEnum.HOSPICE.getCode() : program.getCode());
4946
4947 // Add child code (for amends/replacement: ZZ (parent), A1, A2, ....,
4948 // A9, B1,....,B9, C1....so on)
4949 if (parentClaimKey == null)
4950 prefixBuilder.append("ZZ");
4951 else {
4952
4953 String str = parentClaimKey.substring(4, 6);
4954
4955 if (str.equals("ZZ"))
4956 prefixBuilder.append("A1");
4957 else {
4958 int n = Integer.parseInt(str.substring(1, 2));
4959 if (n == 9) {
4960 // if we are already at 9, increment the alphabet and reset
4961 // count
4962 char c[] = parentClaimKey.substring(4, 6).toCharArray();
4963 c[0] = ((char) ((int) c[0] + 1));
4964 c[1] = '1';
4965 prefixBuilder.append(new String(c));
4966 } else {
4967 String str_0 = str.substring(0, 1);
4968 str_0 = str_0.concat(String.valueOf(++n));
4969 prefixBuilder.append(str_0);
4970 }
4971 }
4972 }
4973
4974 return prefixBuilder.toString();
4975 }
4976
4977 public ProgramEnum fetchProgramFromClaimKey(String claimKey) {
4978
4979 if (claimKey == null || claimKey.isEmpty() || claimKey.length() < 4)
4980 return ProgramEnum.HOSPICE;
4981
4982 return ProgramEnum.fromString(claimKey.substring(2, 4));
4983 }
4984
4985 /**
4986 * Attempts to fetch the claim for the given patient/period/coverage or
4987 * creates a claim for this if none exist.
4988 *
4989 * @param patientId
4990 * @param programSiteId
4991 * Claims should be associated with the correct program site for
4992 * the period being billed.
4993 * @param billingStartDate
4994 * Date is expected to be in date only format (i.e. time is set
4995 * to 00:00:00 UTC). Example 2016-01-01 00:00:00 UTC
4996 * @param billingEndDate
4997 * Date is expected to be in date only format (i.e. time is set
4998 * to 00:00:00 UTC). Example 2016-01-31 00:00:00 UTC
4999 * @return
5000 */
5001 public Claim fetchNewOrExistingClaim(Long patientId, Long programSiteId, Date billingStartDate,
5002 Date billingEndDate, Long coverageId, Long secondaryCoverageId, ProgramEnum program, boolean createNewClaim) {
5003 TypedQuery<Claim> query = em.createNamedQuery("Claim.findClaimByDateRangePatient", Claim.class);
5004 query.setParameter("patientid", patientId);
5005 query.setParameter("startDate", billingStartDate);
5006 query.setParameter("endDate", billingEndDate);
5007 query.setParameter("coverageId", coverageId);
5008 query.setParameter("excludedStatuses", ClaimStatus.excludedFromClaimSearch);
5009 query.setParameter("noaTypeOfBill", NoaTypeOfBill.getAllTypeOfBills());
5010
5011 List<Claim> claimList = query.getResultList();
5012
5013 if (claimList != null && claimList.size() == 0) {
5014 // Looking for claims with start date the same and end date
5015 // different to catch discharges
5016 query = em.createNamedQuery("Claim.findClaimsByStartDatePatient", Claim.class);
5017 query.setParameter("patientid", patientId);
5018 query.setParameter("startDate", billingStartDate);
5019 query.setParameter("endDate", billingEndDate);
5020 query.setParameter("coverageId", coverageId);
5021 query.setParameter("excludedStatuses", ClaimStatus.excludedFromClaimSearch);
5022 query.setParameter("noaTypeOfBill", NoaTypeOfBill.getAllTypeOfBills());
5023
5024 claimList = query.getResultList();
5025 }
5026 Claim object = null;
5027
5028 if (claimList.size() == 0 || createNewClaim) {
5029 InsuranceCoverage coverage = null;
5030 if (coverageId != null)
5031 coverage = em.find(InsuranceCoverage.class, coverageId);
5032
5033 InsuranceCoverage secondaryCoverage = null;
5034 if (secondaryCoverageId != null) {
5035 secondaryCoverage = em.find(InsuranceCoverage.class, secondaryCoverageId);
5036 }
5037
5038 object = financeService.createClaim(patientId, programSiteId, billingStartDate, billingEndDate, null,
5039 coverage, secondaryCoverage, program);
5040
5041 object = persistenceService.update(object);
5042 } else if (claimList.size() > 1) {
5043 // Need to allow room and board and hospice to coexist
5044 for (Claim cl : claimList) {
5045 if (program == ProgramEnum.ROOM_BOARD && isRoomAndBoardClaim(cl))
5046 object = cl;
5047 else if (!(program == ProgramEnum.ROOM_BOARD) && !isRoomAndBoardClaim(cl))
5048 object = cl;
5049 }
5050
5051 } else { // 1 claim found, need to make a new one if it was room and
5052 // board first
5053 if (!(program == ProgramEnum.ROOM_BOARD) && isRoomAndBoardClaim(claimList.get(0))) {
5054 InsuranceCoverage coverage = null;
5055 if (coverageId != null)
5056 coverage = em.find(InsuranceCoverage.class, coverageId);
5057
5058 object = financeService.createClaim(patientId, programSiteId, billingStartDate, billingEndDate, null,
5059 coverage, null, program);
5060
5061 object = persistenceService.update(object);
5062 } else
5063 object = claimList.get(0);
5064 }
5065 if (object != null
5066 && !(object.getServiceEnd().equals(billingEndDate))
5067 && (object.getClaimStatus() == ClaimStatus.READY || object.getClaimStatus() == ClaimStatus.SEQH || object
5068 .getClaimStatus() == ClaimStatus.ERROR)) {
5069 object.setServiceEnd(billingEndDate);
5070 object = persistenceService.update(object);
5071 }
5072
5073 return object;
5074 }
5075
5076 public boolean isRoomAndBoardClaim(Claim claim) {
5077 if (claim == null || claim.getClaimKey() == null)
5078 return false;
5079
5080 return claim.getClaimKey().startsWith("MDRB");
5081 }
5082
5083 /**
5084 * Look at existing claims to determine if there should be a sequential
5085 * hold.
5086 *
5087 * @param patientId
5088 * @param claimServiceStart
5089 * Only claims before this date will be referenced when
5090 * considering sequential hold
5091 * @param payerType
5092 * to consider when checking for unpaid claims
5093 * @return
5094 */
5095 public ClaimStatus fetchReadyOrSeqh(Claim claim) {
5096
5097 if (claim == null || claim.getPatient() == null || claim.getPayerType() == null || claim.getClaimKey() == null)
5098 return ClaimStatus.ERROR;
5099
5100 if (claim.getPayerType() == PayerType.MEDICARE
5101 && isPatientPreviousClaimUnprocessed(claim.getPatient().getId(), claim.getServiceStart(),
5102 claim.getPayerType(), claim.getClaimKey())) {
5103 /*
5104 * If any of the patients previous claims are unpaid, then the new
5105 * claim is automatically set to status 'SEQH' (sequential hold)
5106 */
5107 return ClaimStatus.SEQH;
5108
5109 } else
5110 return ClaimStatus.READY;
5111 }
5112
5113 /**
5114 * Checks for any previous 'unprocessed' claims for the patient
5115 *
5116 * @param patientId
5117 * @param claimServiceStart
5118 * Only claims before this date will be checked
5119 * @return
5120 */
5121 public boolean isPatientPreviousClaimUnprocessed(Long patientId, Date claimServiceStart, PayerType payerType,
5122 String claimKey) {
5123
5124 List<String> excludeList = new ArrayList<String>();
5125
5126 for (ClaimStatus cs : ClaimStatus.excludedFromClaimSearch)
5127 excludeList.add(cs.toString());
5128
5129 for (ClaimStatus cs : ClaimStatus.medicareProcessedValues)
5130 excludeList.add(cs.toString());
5131
5132 String queryStr = "select c.* from Claim c JOIN patient p on p.id = c.patient_id"
5133 + " WHERE p.id = :patientId AND c.claimStatus NOT IN (:excludedClaimStatuses) AND c.claimKey!= :claimKey"
5134 + " AND c.payertype = :payerType AND (c.serviceend < :claimServiceStart OR c.servicestart = :claimServiceStart)"
5135 + " ORDER BY c.serviceend DESC";
5136
5137 Query query = em.createNativeQuery(queryStr, Claim.class);
5138 query.setParameter("patientId", patientId);
5139 query.setParameter("excludedClaimStatuses", excludeList);
5140 query.setParameter("payerType", payerType.toString());
5141 query.setParameter("claimKey", claimKey);
5142 query.setParameter("claimServiceStart", claimServiceStart);
5143
5144 @SuppressWarnings("unchecked")
5145 List<Claim> claims = query.getResultList();
5146 return claims.size() > 0;
5147 }
5148
5149 /**
5150 * This method tells us if a claim exists for the given claimKey
5151 *
5152 * @param claimKey
5153 * @return
5154 */
5155 public boolean isClaimExistInAR(String claimKey) {
5156 Claim claim = findClaim(claimKey);
5157
5158 if (claim != null)
5159 return true;
5160 else
5161 return false;
5162 }
5163
5164 /**
5165 * This method returns a claim for the given claimKey
5166 *
5167 * @param claimKey
5168 * @return
5169 */
5170 public Claim findClaim(String claimKey) {
5171
5172 TypedQuery<Claim> query = em.createNamedQuery("Claim.findClaimByClaimKey", Claim.class);
5173 query.setParameter("claimKey", claimKey);
5174 List<Claim> claimList = query.getResultList();
5175
5176 if (claimList != null && !claimList.isEmpty())
5177 return claimList.get(0);
5178 else
5179 return null;
5180 }
5181
5182 /**
5183 * Converts patient bill info from PAS into BillPatientDTO
5184 *
5185 * @param jaxbPatientBill
5186 * @return
5187 * @throws ParseException
5188 */
5189 public BillPatientDTO convertPatientBillToBillPatientDTO(PatientBill jaxbPatientBill,
5190 ClaimsGenerationCache claimsCache) throws ParseException {
5191
5192 BillPatientDTO billPatientDto = new BillPatientDTO();
5193
5194 // Claim Key
5195 billPatientDto.setClaimKey(jaxbPatientBill.getClaimKey());
5196
5197 // Bill type
5198 if (jaxbPatientBill.getBillInfo() != null) {
5199 if (jaxbPatientBill.getBillInfo().getTypeBill() != null)
5200 billPatientDto.setTypeOfBill(jaxbPatientBill.getBillInfo().getTypeBill());
5201 // Status code
5202 if (jaxbPatientBill.getBillInfo().getStatus() != null)
5203 billPatientDto.setCurrentStatusCode(jaxbPatientBill.getBillInfo().getStatus());
5204 }
5205 if (jaxbPatientBill.getID() != null) {
5206 billPatientDto.setId(Long.valueOf(jaxbPatientBill.getID()));
5207 }
5208 if (jaxbPatientBill.getPatient() != null) {
5209 com.hummingbird.ejb.resources.jaxb.pas.prebill.PreBillReport.PatientBill.Patient jaxbPatient = jaxbPatientBill
5210 .getPatient();
5211 // Patient's basic info
5212 billPatientDto.setFirstName(jaxbPatient.getFirstName());
5213 billPatientDto.setLastName(jaxbPatient.getLastName());
5214 if (jaxbPatient.getPatientNumber() != null)
5215 billPatientDto.setPatientNumber(jaxbPatient.getPatientNumber());
5216 // Patient's address
5217 if (jaxbPatient.getAddress() != null) {
5218 billPatientDto.getAddress().setStreet1(jaxbPatient.getAddress().getStreet1());
5219 billPatientDto.getAddress().setCity(jaxbPatient.getAddress().getCity());
5220 billPatientDto.getAddress().setState(jaxbPatient.getAddress().getState());
5221 billPatientDto.getAddress().setZip(
5222 jaxbPatient.getAddress().getZipcode() == null ? "" : jaxbPatient.getAddress().getZipcode()
5223 .toString());
5224 }
5225 // Diagnosis code
5226 billPatientDto.setDiagnosisCodes(jaxbPatient.getDiagnosis());
5227 }
5228
5229 if (jaxbPatientBill.getConditions() != null) {
5230 billPatientDto.setConditionCodes(Arrays.asList(jaxbPatientBill.getConditions().split(":")));
5231 }
5232
5233 /*
5234 * Date range: The getPeriodCovered dates don't have time associated
5235 * with them and are the exact dates of the claim. Meaning, if a patient
5236 * was admitted in the middle of the month, then the claim start date
5237 * would be the admit date.
5238 */
5239
5240 if (jaxbPatientBill.getPeriodCovered() != null) {
5241 StringBuffer dateRangeBuffer = new StringBuffer("");
5242 if (jaxbPatientBill.getPeriodCovered().getBeginDate() != null) {
5243 String beginDate = jaxbPatientBill.getPeriodCovered().getBeginDate();
5244 billPatientDto.setStartDateOfClaim(DateUtil.UTC_MMDDYY.parse(beginDate));
5245
5246 dateRangeBuffer.append(beginDate);
5247 dateRangeBuffer.append(" - ");
5248 }
5249
5250 if (jaxbPatientBill.getPeriodCovered().getEndDate() != null) {
5251 String endDate = jaxbPatientBill.getPeriodCovered().getEndDate();
5252 billPatientDto.setEndDateOfClaim(DateUtil.UTC_MMDDYY.parse(endDate));
5253 dateRangeBuffer.append(endDate);
5254 }
5255 billPatientDto.setDateRange(dateRangeBuffer.toString());
5256 }
5257
5258 // Footer: Insurance carriers and physicians
5259 if (jaxbPatientBill.getFooter() != null) {
5260 Footer jaxbPatientBillFooter = jaxbPatientBill.getFooter();
5261 // Primary Insurance Carrier
5262 if (jaxbPatientBillFooter.getPrimeCarrier() != null) {
5263 PrimeCarrier jaxbPrimeCarrier = jaxbPatientBillFooter.getPrimeCarrier();
5264 billPatientDto.setPrimaryInsuranceName(jaxbPrimeCarrier.getName());
5265 billPatientDto.setPrimaryInsurancePolicyNumber(jaxbPrimeCarrier.getPolicyNumber());
5266 billPatientDto.setPrimaryInsurancePolicyHolder(jaxbPrimeCarrier.getInsured());
5267 if (jaxbPatientBillFooter.getPrimeCarrier().getAuthorizationCode() != null)
5268 billPatientDto.setPriorAuthorizationCode(jaxbPatientBillFooter.getPrimeCarrier()
5269 .getAuthorizationCode());
5270 }
5271 // Secondary Insurance Carrier
5272 if (jaxbPatientBillFooter.getSecondaryCarrier() != null) {
5273 SecondaryCarrier jaxbSecCarrier = jaxbPatientBillFooter.getSecondaryCarrier();
5274 billPatientDto.setSecondaryInsuranceName(jaxbSecCarrier.getName());
5275 billPatientDto.setSecondaryInsurancePolicyNumber(jaxbSecCarrier.getPolicyNumber());
5276 billPatientDto.setSecondaryInsurancePolicyHolder(jaxbSecCarrier.getInsured());
5277 }
5278 // Attending Physician
5279 if (jaxbPatientBillFooter.getPhysician() != null) {
5280 billPatientDto.setAttendingPhysicianName(jaxbPatientBillFooter.getPhysician().getName());
5281 billPatientDto.setAttendingPhysicianNpi(jaxbPatientBillFooter.getPhysician().getNpi() == null ? ""
5282 : jaxbPatientBillFooter.getPhysician().getNpi().toString());
5283 }
5284 // Certifying Physician
5285 if (jaxbPatientBillFooter.getCertPhy() != null) {
5286 billPatientDto.setCertifyingPhysicianName(jaxbPatientBillFooter.getCertPhy().getName());
5287 billPatientDto.setCertifyingPhysicianNpi(jaxbPatientBillFooter.getCertPhy().getNpi() == null ? ""
5288 : jaxbPatientBillFooter.getCertPhy().getNpi().toString());
5289 }
5290 }
5291
5292 // Occurrence codes
5293 if (jaxbPatientBill.getOccurrences() != null && jaxbPatientBill.getOccurrences().getOccurrence() != null) {
5294 List<OccurrenceDTO> occurrenceDTOList = new ArrayList<OccurrenceDTO>();
5295 List<String> valueCodeList = new ArrayList<String>();
5296 for (Occurrence occurrence : jaxbPatientBill.getOccurrences().getOccurrence()) {
5297 if (occurrence.getType().equals("occ61"))
5298 billPatientDto.setCbsaCode(occurrence.getCode());
5299
5300 if (occurrence.getType().equals("occG8"))
5301 valueCodeList.add(occurrence.getType().substring(3) + ":" + occurrence.getCode());
5302
5303 OccurrenceDTO occurrenceDTO = new OccurrenceDTO();
5304 occurrenceDTO.setType(occurrence.getType());
5305 occurrenceDTO.setCode(occurrence.getCode());
5306 occurrenceDTOList.add(occurrenceDTO);
5307 }
5308
5309 billPatientDto.setOccurrenceCodes(occurrenceDTOList);
5310 billPatientDto.setValueCodes(valueCodeList);
5311 }
5312
5313 // Total receivable
5314 if (jaxbPatientBill.getTotalBill() != null)
5315 billPatientDto.setTotalReceivable(jaxbPatientBill.getTotalBill());
5316
5317 // Total with visits
5318 if (jaxbPatientBill.getTotalCharges() != null)
5319 billPatientDto.setTotalWithBillables(jaxbPatientBill.getTotalCharges());
5320
5321 // Billing Days
5322 boolean populateFacNPI = false;
5323 if (jaxbPatientBill.getCoveredDays() != null) {
5324 CoveredDays coveredDays = jaxbPatientBill.getCoveredDays();
5325 Map<LevelOfCareType, Integer> billingDaysMap = new HashMap<LevelOfCareType, Integer>();
5326
5327 billingDaysMap.put(LevelOfCareType.CONTINUOUS_CARE, coveredDays.getCc() != null ? coveredDays.getCc() : 0);
5328 billingDaysMap.put(LevelOfCareType.GENERAL_IN_PATIENT, coveredDays.getGip() != null ? coveredDays.getGip()
5329 : 0);
5330 billingDaysMap.put(LevelOfCareType.RESPITE, coveredDays.getRespit() != null ? coveredDays.getRespit() : 0);
5331 billingDaysMap
5332 .put(LevelOfCareType.ROUTINE, coveredDays.getRoutine() != null ? coveredDays.getRoutine() : 0);
5333
5334 populateFacNPI = billingDaysMap.get(LevelOfCareType.RESPITE) > 0
5335 || billingDaysMap.get(LevelOfCareType.GENERAL_IN_PATIENT) > 0;
5336 billPatientDto.setDays(coveredDays.getTotalDays());
5337 billPatientDto.setBillingDays(billingDaysMap);
5338 }
5339
5340 if (populateFacNPI) {
5341 String NHNpi = "";
5342
5343 for (LevelOfCareDTO dto : claimsCache.getLOCRecords(jaxbPatientBill.getClaimKey())) {
5344 if (dto.getTypeOfCare() == LevelOfCareType.RESPITE
5345 || dto.getTypeOfCare() == LevelOfCareType.GENERAL_IN_PATIENT) {
5346 if (dto.getFacilityId() != -1) {
5347 Facility nhFac = persistenceService.find(Facility.class, dto.getFacilityId());
5348 NHNpi = nhFac.getNpi();
5349 }
5350 }
5351 }
5352 billPatientDto.setFacilityNpi(NHNpi);
5353
5354 }
5355
5356 // Revenue Lines
5357 if (jaxbPatientBill.getRevLine() != null) {
5358 List<BillRevenueLineDTO> revenueLines = new ArrayList<BillRevenueLineDTO>();
5359 for (RevLine jaxbRevLine : jaxbPatientBill.getRevLine()) {
5360 BillRevenueLineDTO revLineDto = new BillRevenueLineDTO();
5361
5362 revLineDto.setStartDate(DateUtil.UTC_MMDDYY.parse(jaxbRevLine.getDate()));
5363 revLineDto.setRevenueCode(jaxbRevLine.getRevCode() == null ? "" : jaxbRevLine.getRevCode().toString());
5364 revLineDto.setHcpcsCode(jaxbRevLine.getHcpcs());
5365 revLineDto.setUnits(jaxbRevLine.getUnits());
5366 revLineDto.setCharges(jaxbRevLine.getAmount());
5367 revLineDto.setWeek(jaxbRevLine.getWeek() == null ? 0 : jaxbRevLine.getWeek());
5368
5369 LevelOfCareType levelOfCareType = null;
5370 if (jaxbRevLine.getDescription() != null) {
5371 levelOfCareType = LevelOfCareType.getTypeByPASBEValue(jaxbRevLine.getDescription().trim());
5372 revLineDto.setTypeOfCare(levelOfCareType);
5373 }
5374
5375 // Calculate the end date
5376 int units = (int) revLineDto.getUnits();
5377 if (levelOfCareType == LevelOfCareType.CONTINUOUS_CARE) {
5378 // Convert 15 minutes units into dates
5379 int days = (units / 96) + 1;
5380 revLineDto.setEndDate(DateUtil.getDaysFromDate(revLineDto.getStartDate(), days));
5381 } else {
5382 revLineDto.setEndDate(DateUtil.getDaysFromDate(revLineDto.getStartDate(), units));
5383 }
5384
5385 revenueLines.add(revLineDto);
5386 }
5387
5388 // Service Visit Lines
5389 List<SvcLine> jaxbVisitLines = jaxbPatientBill.getSvcLine();
5390 Map<Integer, List<VisitLineDTO>> visitsByWeekMap = new HashMap<Integer, List<VisitLineDTO>>();
5391
5392 for (SvcLine jaxbVisitLine : jaxbVisitLines) {
5393 int week = jaxbVisitLine.getWeek() == null ? 0 : jaxbVisitLine.getWeek();
5394
5395 VisitLineDTO visitLineDto = new VisitLineDTO();
5396 visitLineDto.setCharges(jaxbVisitLine.getAmount());
5397 visitLineDto.setDescription(jaxbVisitLine.getDescription());
5398 visitLineDto.setHcpcsCode(jaxbVisitLine.getHcpcs());
5399 if (jaxbVisitLine.getRevCode() != null)
5400 visitLineDto.setRevenueCode(jaxbVisitLine.getRevCode().toString());
5401 visitLineDto.setUnits(floatToDouble(jaxbVisitLine.getUnits()));
5402 visitLineDto.setStartDate(DateUtil.UTC_MMDDYY.parse(jaxbVisitLine.getDate()));
5403
5404 if (visitsByWeekMap.get(week) == null) {
5405 visitsByWeekMap.put(week, new ArrayList<VisitLineDTO>());
5406 }
5407 // Add the visit to map
5408 visitsByWeekMap.get(week).add(visitLineDto);
5409 }
5410
5411 // Add visit lines to revenue lines
5412 for (BillRevenueLineDTO revLineDto : revenueLines) {
5413 List<VisitLineDTO> dtoVisitLines = visitsByWeekMap.get(revLineDto.getWeek());
5414 List<RevenueDetailDTO> dtoRevDetails = new ArrayList<RevenueDetailDTO>();
5415 if (dtoVisitLines != null)
5416 for (VisitLineDTO dtoVisitLine : dtoVisitLines) {
5417 dtoRevDetails.add(dtoVisitLine);
5418 }
5419 revLineDto.setDetailLines(dtoRevDetails);
5420 }
5421 // Visits come on weeks even though rev lines do not.
5422 if (revenueLines.size() > 0) {
5423 for (Integer week : visitsByWeekMap.keySet()) {
5424 BillRevenueLineDTO revLineDto = revenueLines.get(0);
5425 List<VisitLineDTO> dtoVisitLines = visitsByWeekMap.get(week);
5426 List<RevenueDetailDTO> dtoRevDetails = new ArrayList<RevenueDetailDTO>();
5427 if (dtoVisitLines != null) {
5428 for (VisitLineDTO dtoVisitLine : dtoVisitLines) {
5429 dtoRevDetails.add(dtoVisitLine);
5430 }
5431 revLineDto.setDetailLines(dtoRevDetails);
5432 }
5433 }
5434 }
5435
5436 // Add revenue lines to patient
5437 billPatientDto.setRevenueLines(revenueLines);
5438 }
5439
5440 // Medications
5441 if (jaxbPatientBill.getMedLine() != null) {
5442 List<BillMedicationDTO> dtoMedications = new ArrayList<BillMedicationDTO>();
5443
5444 for (MedLine jaxbMedLine : jaxbPatientBill.getMedLine()) {
5445 BillMedicationDTO medicationDto = new BillMedicationDTO();
5446
5447 if (jaxbMedLine.getAmount() != null)
5448 medicationDto.setCharge(jaxbMedLine.getAmount());
5449 medicationDto.setDate(DateUtil.UTC_MMDDYY.parse(jaxbMedLine.getDate()));
5450 medicationDto.setDescription(jaxbMedLine.getDescription());
5451 medicationDto.setHcpcs(jaxbMedLine.getHcpcs());
5452 if (jaxbMedLine.getRevCode() != null)
5453 medicationDto.setRevCode(jaxbMedLine.getRevCode().trim());
5454 if (jaxbMedLine.getUnits() != null)
5455 medicationDto.setUnit(jaxbMedLine.getUnits().toString());
5456 medicationDto.setUom(jaxbMedLine.getUom().trim());
5457 medicationDto.setNdc(jaxbMedLine.getNdc());
5458
5459 dtoMedications.add(medicationDto);
5460 }
5461 billPatientDto.setBillMedications(dtoMedications);
5462 }
5463
5464 return billPatientDto;
5465 }
5466
5467 /**
5468 * This method is used to create new claims if we dont already have one, or
5469 * update an existing claim
5470 *
5471 * @param hospice
5472 *
5473 * @param defaultTz
5474 *
5475 * @param billPatientDTOList
5476 * @return
5477 */
5478 public Map<String, Claim> createOrUpdateARClaims(BillDTO billDto, com.hummingbird.jpa.claims.ClaimBatch batch,
5479 Hospice hospice, boolean isDropBatch, ClaimsGenerationCache claimsCache) {
5480
5481 Map<String, Claim> claimMap = new HashMap<String, Claim>();
5482
5483 if (billDto == null)
5484 return claimMap;
5485
5486 List<BillPatientDTO> billPatientDTOList = billDto.getPatients();
5487 // This holds the PatientNmber vs Claim objects
5488
5489 List<Claim> claims = new ArrayList<Claim>();
5490
5491 for (BillPatientDTO billPatientDto : HBUtil.emptyIfNull(billPatientDTOList)) {
5492
5493 Patient pat = null;
5494 if (billPatientDto.getId() != null) {
5495 pat = em.find(Patient.class, billPatientDto.getId());
5496 } else if (billPatientDto.getPatientNumber() != null) {
5497 pat = patientService.findPatientByPatientNumber(billPatientDto.getPatientNumber());
5498 }
5499 if (pat == null) {
5500 log.warning("Could not find patient number:" + billPatientDto.getPatientNumber() + ", id:"
5501 + billPatientDto.getId() + ", not creating claim:" + billPatientDto.getClaimKey());
5502 continue;
5503 }
5504
5505 Claim claim = findClaim(billPatientDto.getClaimKey());
5506
5507 if (claim == null) {
5508 log.warning("Could find find claim: " + billPatientDto.getClaimKey() + " for patient: "
5509 + billPatientDto.getId());
5510 continue;
5511 }
5512 claim.setClaimBatch(batch);
5513
5514 // Persist claim
5515 claim = persistenceService.update(claim);
5516 claimMap.put(billPatientDto.getClaimKey(), claim);
5517
5518 // Update revenue details (remember here we are un-sent claims
5519 // only)
5520
5521 List<LevelOfCareDTO> locRecords = claimsCache.getLOCRecords(claim.getClaimKey());
5522 if (locRecords != null && billPatientDto.getRevenueLines() != null) {
5523 for (LevelOfCareDTO loc : locRecords) {
5524
5525 for (BillRevenueLineDTO revLine : billPatientDto.getRevenueLines()) {
5526
5527 if (loc.getBillRB()) {
5528 // R & B revenue lines don't have Type Of Care
5529 if (loc.getStartDate() != null && revLine.getStartDate() != null) {
5530 if (loc.getStartDate().compareTo(revLine.getStartDate()) == 0) {
5531 revLine.setFacilityId(loc.getFacilityId());
5532 }
5533 }
5534
5535 } else {
5536 if (loc.getStartDate() != null && revLine.getStartDate() != null
5537 && loc.getTypeOfCare() != null && revLine.getTypeOfCare() != null) {
5538 if (loc.getStartDate().compareTo(revLine.getStartDate()) == 0
5539 && loc.getTypeOfCare().equals(revLine.getTypeOfCare())) {
5540 revLine.setFacilityId(loc.getFacilityId());
5541 }
5542 }
5543 }
5544 }
5545 }
5546 }
5547
5548 updateOrRepostClaim(claim, billPatientDto);
5549 claims.add(claim);
5550 }
5551
5552 if (batch != null) {
5553 batch.setPosted(new Date());
5554 batch.setStatus(isDropBatch ? BatchStatus.SENT : BatchStatus.READY);
5555 batch.setSent(new Date());
5556 batch.setServiceStart(billDto.getStartDate());
5557 batch.setServiceEnd(billDto.getEndDate());
5558 batch.setClaims(claims);
5559 batch = persistenceService.update(batch);
5560 }
5561
5562 return claimMap;
5563 }
5564
5565 /**
5566 * This method decides whether a claim should be updated, replaced or
5567 * amended
5568 *
5569 * @param claim
5570 * @param billPatientDto
5571 */
5572 private void updateOrRepostClaim(Claim claim, BillPatientDTO billPatientDto) {
5573
5574 log.info("Ready to update or repost claim");
5575
5576 if (claim == null || claim.getPatient() == null || billPatientDto == null)
5577 return;
5578
5579 // check if existing claim total matches the new total
5580 boolean doClaimTotalsMatch = claim.getClaimTotal() == null
5581 || claim.getClaimTotal().compareTo(billPatientDto.getTotalReceivable()) == 0;
5582
5583 // check if books are open
5584 boolean areBooksOpen = ClaimUtil.isClaimOpen(claim.getPosted(), claim.getSent(),
5585 claim.getProgramSite() != null ? claim.getProgramSite().getSite().getBooksClosedDate() : null);
5586
5587 // check if claim is not yet sent
5588 boolean isNotSent = claim.getSent() == null;
5589
5590 /*
5591 * If old and new claim totals match OR (claim is not sent AND books are
5592 * open), just update the claim
5593 */
5594 if (doClaimTotalsMatch || (isNotSent && areBooksOpen)) {
5595
5596 log.info("-~- Updating total and rev lines for claim [claim id: " + claim.getId() + "] ");
5597 updateClaimTotalAndRevLines(claim, billPatientDto);
5598 } else if (!isNotSent) {
5599 // Claim sent, Amend claim
5600
5601 log.info("-~- Preparing claim for amendment [claim id: " + claim.getId() + "] ");
5602
5603 // Check if we already have a draft claim, if yes, throw it away and
5604 // create new.
5605 Claim childClaim = financeService.findClaimByParentClaim(claim.getId(), ClaimStatus.DRAFT);
5606
5607 if (childClaim != null)
5608 financeService.removeClaim(childClaim.getId());
5609
5610 childClaim = financeService.createChildClaim(claim, claim.getClaimStatus(), ClaimStatus.DRAFT,
5611 billPatientDto.getTotalReceivable(), fetchProgramFromClaimKey(claim.getClaimKey()));
5612
5613 claim.setStatusReason(claim.getReviewNeeded());
5614 childClaim.setReviewNeeded(null);
5615 em.merge(childClaim);
5616
5617 updateClaimTotalAndRevLines(childClaim, billPatientDto);
5618
5619 } else if (!areBooksOpen) {
5620 // Replace claim
5621
5622 log.info("-~- Replacing claim [claim id: " + claim.getId() + "] ");
5623
5624 Claim childClaim = financeService.createChildClaim(claim, ClaimStatus.REPLACED, claim.getClaimStatus(),
5625 billPatientDto.getTotalReceivable(), fetchProgramFromClaimKey(claim.getClaimKey()));
5626
5627 financeService.adjustClaimAmount(claim, childClaim, RepostType.REPLACED);
5628
5629 claim.setStatusReason(claim.getReviewNeeded());
5630 childClaim.setReviewNeeded(null);
5631 em.merge(childClaim);
5632
5633 updateClaimTotalAndRevLines(childClaim, billPatientDto);
5634
5635 // TODO: save the reviewNeeded string in another field so user can
5636 // see it in future.
5637
5638 } else
5639 log.severe("Something is very wrong with claim id: " + claim.getId());
5640
5641 log.info("Un-marking claim Id:" + claim.getId());
5642 claim.setReviewNeeded(null);
5643 em.merge(claim);
5644 em.flush();
5645
5646 }
5647
5648 /**
5649 * We can either finalize the amend or discard the draft claim
5650 *
5651 * @param parentClaimId
5652 * @param acceptAmend
5653 * @return
5654 */
5655 public boolean finalizeAmend(Long parentClaimId, boolean acceptAmend) {
5656
5657 Claim parentClaim = em.find(Claim.class, parentClaimId);
5658
5659 if (parentClaim == null)
5660 return false;
5661
5662 Claim childClaim = financeService.findClaimByParentClaim(parentClaim.getId(), null);
5663
5664 if (childClaim == null || childClaim.getClaimStatus() != ClaimStatus.DRAFT)
5665 return false;
5666
5667 if (acceptAmend) {
5668 financeService.adjustClaimAmount(parentClaim, childClaim, RepostType.AMENDED);
5669 childClaim
5670 .setClaimStatus(ClaimStatus.medicareProcessedValues.contains(parentClaim.getClaimStatus()) ? ClaimStatus.READY
5671 : ClaimStatus.SEQH);
5672 em.merge(childClaim);
5673
5674 } else
5675 financeService.removeClaim(childClaim.getId());
5676
5677 return true;
5678
5679 }
5680
5681 private List<LevelOfCareDTO> pruneLevelOfCareDTOs(List<LevelOfCareDTO> locDtos, Date serviceStart, Date serviceEnd,
5682 boolean isForRoomAndBoard) {
5683 List<LevelOfCareDTO> result = new ArrayList<LevelOfCareDTO>();
5684
5685 for (LevelOfCareDTO loc : locDtos) {
5686 if (loc.getEndDate() == null)
5687 loc.setEndDate(serviceEnd);
5688
5689 if (loc.getStartDate() == null || loc.getEndDate().before(serviceStart)) {
5690 continue;
5691 }
5692
5693 int compStartStart = loc.getStartDate().compareTo(serviceStart);
5694 int compStartEnd = loc.getStartDate().compareTo(serviceEnd);
5695 int compEndStart = loc.getEndDate().compareTo(serviceStart);
5696 int compEndEnd = loc.getEndDate().compareTo(serviceEnd);
5697 boolean add = false;
5698
5699 if (compStartStart <= 0 && compEndEnd <= 0 && compEndStart >= 0) {
5700 loc.setStartDate(serviceStart);
5701 add = true;
5702 } else if (compStartStart <= 0 && compEndEnd >= 0) {
5703 loc.setStartDate(serviceStart);
5704 loc.setEndDate(serviceEnd);
5705 add = true;
5706 } else if (compStartStart >= 0 && compEndEnd <= 0) {
5707 add = true;
5708 } else if (compStartStart >= 0 && compStartEnd <= 0 && compEndEnd >= 0) {
5709 loc.setEndDate(serviceEnd);
5710 add = true;
5711 }
5712
5713 if (add) {
5714 // this done because we send start date to pasbe using
5715 // convertToOutgoingClaimDateFormat() and we parse it back to
5716 // date using DateUtil.UTC_MMDDYY date format
5717
5718 Calendar calUtc = Calendar.getInstance(DateUtil.UTC_ZONE);
5719 calUtc.setTime(loc.getStartDate());
5720 calUtc.set(Calendar.HOUR_OF_DAY, 0);
5721 calUtc.set(Calendar.MINUTE, 0);
5722 calUtc.set(Calendar.SECOND, 0);
5723 calUtc.set(Calendar.MILLISECOND, 0);
5724 loc.setStartDate(calUtc.getTime());
5725
5726 loc.setBillRB(isForRoomAndBoard);
5727 result.add(loc);
5728 }
5729 }
5730
5731 return result;
5732 }
5733
5734 public BillLog createBillLog(String batchId, String startDate, String stopDate, String fc) {
5735 TimeZone userTz = auth.getUserTimeZone();
5736 Hospice hospice = em.find(Hospice.class, auth.getCurrentUserHospiceId());
5737
5738 BillLog billLog = new BillLog();
5739 billLog.setBatchKey(batchId);
5740 billLog.setPostedDate(DateUtil.getTodayDateOnly(userTz));
5741 billLog.setPostedBy(auth.getCurrentUser().getUsername());
5742 billLog.setFc(fc);
5743 billLog.setHospice(hospice);
5744
5745 DateFormat dateFormat = DateUtil.getDateOnlyFormat(userTz);
5746 try {
5747 billLog.setFromDate(startDate == null ? null : dateFormat.parse(startDate));
5748 billLog.setToDate(stopDate == null ? null : dateFormat.parse(stopDate));
5749 } catch (ParseException e) {
5750 e.printStackTrace();
5751 }
5752 billLog = persistenceService.update(billLog);
5753
5754 return billLog;
5755 }
5756
5757 /**
5758 * Create the claim revenue lines for a provided claim and the bill patient
5759 * from PASBE
5760 *
5761 * @param billPatientDto
5762 * - patient claim from PASBE
5763 * @param claim
5764 * - claim we'll store in the DB
5765 * @param persist
5766 * - should we persist now
5767 * @return
5768 */
5769 private List<ClaimRevenueDetail> createClaimRevenueDetails(BillPatientDTO billPatientDto, Claim claim,
5770 boolean persist) {
5771 List<BillRevenueLineDTO> dtoRevenueLines = billPatientDto.getRevenueLines();
5772 List<ClaimRevenueDetail> claimRevenueDetails = new ArrayList<ClaimRevenueDetail>();
5773
5774 if (dtoRevenueLines != null && !dtoRevenueLines.isEmpty()) {
5775 for (BillRevenueLineDTO dtoRevLine : dtoRevenueLines) {
5776 // Create ClaimRevenueDetail
5777 ClaimRevenueDetail claimRevDetail = new ClaimRevenueDetail();
5778 if (persist)
5779 claimRevDetail.setClaim(claim);
5780 claimRevDetail.setDate(dtoRevLine.getStartDate());
5781 claimRevDetail.setDays((int) dtoRevLine.getUnits());
5782 claimRevDetail.setRevCode(dtoRevLine.getRevenueCode());
5783 claimRevDetail.setHcpcs(dtoRevLine.getHcpcsCode());
5784 claimRevDetail.setLevelOfCare(dtoRevLine.getTypeOfCare() == null ? null : dtoRevLine.getTypeOfCare()
5785 .name());
5786 if (dtoRevLine.getFacilityId() != null) {
5787 Facility facility = em.find(Facility.class, dtoRevLine.getFacilityId());
5788 if (facility != null) {
5789 claimRevDetail.setFacility(facility);
5790 }
5791 }
5792 claimRevDetail.setAmountBilled(dtoRevLine.getCharges());
5793 log.fine("Creating claim rev detail for claim:" + claim.getId() + ", persist?:" + persist + ", date:"
5794 + claimRevDetail.getDate());
5795 if (persist) {
5796 claimRevDetail = persistenceService.update(claimRevDetail);
5797 }
5798 claimRevenueDetails.add(claimRevDetail);
5799 }
5800 }
5801
5802 return claimRevenueDetails;
5803 }
5804
5805 private void revertNewlyCreatedClaim(String claimKey) {
5806 TypedQuery<Claim> query = em.createNamedQuery("Claim.findClaimByClaimKey", Claim.class);
5807 query.setParameter("claimKey", claimKey);
5808 List<Claim> claimList = query.getResultList();
5809
5810 if (claimList != null && !claimList.isEmpty()) {
5811 for (Claim claim : claimList) {
5812 if (claim.getClaimBatch() == null) {
5813 List<ClaimTransaction> claimTransactions = claim.getClaimTransactions();
5814 if (claimTransactions != null) {
5815 for (ClaimTransaction ct : claimTransactions)
5816 persistenceService.remove(ct, true);
5817 }
5818 List<ClaimRevenueDetail> revenueDetails = claim.getRevenueDetails();
5819 if (revenueDetails != null) {
5820 for (ClaimRevenueDetail rv : revenueDetails)
5821 persistenceService.remove(rv, true);
5822 }
5823 persistenceService.remove(claim, true);
5824 }
5825 }
5826 }
5827
5828 }
5829
5830 /**
5831 * Generates or updates claims for the hospice.
5832 *
5833 * for each hospice, we need to iterate over each site and since each site
5834 * may have its own ProgramSite submitter ID, we make a separate call to
5835 * PASBE.
5836 *
5837 * Note how the TransactionAttribute annotation is used. This is in the
5838 * cases where we expressly want to start a new transaction on an EJB method
5839 * invocation. We do this here so that the multi-tenant resolver will
5840 * re-resolve the current tenant connection because switching hospices means
5841 * potentially switching databases.
5842 *
5843 * In the case where persistenceService cannot find a hospice with the
5844 * passed ID the method is a noop
5845 *
5846 * @param hospiceId
5847 * @param hospiceTenant
5848 */
5849 public List<Claim> generateClaimsForHospice(String tenantId, boolean runAsync) {
5850 int status = tsr.getTransactionStatus();
5851 Object key = tsr.getTransactionKey();
5852 log.severe("genClaimHosp trans: " + status + " key: " + key);
5853
5854 com.hummingbird.shared.dto.Hospice hosp = multiTenantService.lookupHospice(tenantId, true);
5855
5856 multiTenantService.setFakeAuthentication(tenantId, true);
5857 List<Claim> result = new ArrayList<Claim>();
5858 for (com.hummingbird.shared.dto.Site site : hosp.getSites()) {
5859 result.addAll(self.generateClaimsForSite(site.getId(), runAsync));
5860 }
5861 return result;
5862 }
5863
5864 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
5865 public List<Claim> generateClaimsForSite(long siteId, boolean runAsync) {
5866 Site site = em.find(Site.class, siteId);
5867 if (site.getTimezone() == null) {
5868 log.warning("Timezone not configured for site " + site.getName() + " . Defaulting to UTC.");
5869 log.warning("Claims will not be generate for site " + site.getName());
5870 return new ArrayList<Claim>();
5871 }
5872
5873 TimeZone siteTz = DateUtil.getTimeZone(site.getTimezone());
5874 List<Claim> result = new ArrayList<Claim>();
5875 for (ProgramSite programSite : site.getProgramSites()) {
5876 result.addAll(generateClaimsForSite(programSite, siteTz, runAsync,
5877 new ClaimsGenerationCache(programSite.getSite()), null));
5878 }
5879 return result;
5880 }
5881
5882 /**
5883 * PayerTypes are specified in an enum, but there are several pieces of Site
5884 * configuration that have to occur for each billing scenario. This method
5885 * exists as a sanity check, if a site doesn't have all of the information
5886 * for a billable payer type configured we don't bill for that payer type.
5887 */
5888 private Set<PayerType> getBillablePayerTypes(Hospice hospice, ProgramSite billingProgram) {
5889 Set<PayerType> billablePayerTypes = new HashSet<PayerType>();
5890 if (billingProgram == null)
5891 return billablePayerTypes;
5892 Site site = billingProgram.getSite();
5893
5894 for (PayerType payerType : PayerType.billablePayerTypes) {
5895 switch (payerType) {
5896 case COMMERCIAL:
5897 case MEDICAID:
5898 if (billingProgram.getNpi().isEmpty()) {
5899 log.warning("Site incorrectly configured for billing: #" + site.getId() + " in " + hospice.getBin());
5900 continue;
5901 }
5902 billablePayerTypes.add(payerType);
5903 break;
5904 case MEDICARE:
5905 if (site.getMedicareIntermediaryInfo() == null || billingProgram.getNpi().isEmpty()) {
5906 log.warning("Site incorrectly configured for billing: #" + site.getId() + " in " + hospice.getBin());
5907 continue;
5908 }
5909 billablePayerTypes.add(PayerType.MEDICARE);
5910 break;
5911 case NON_FUNDED:
5912 billablePayerTypes.add(PayerType.NON_FUNDED);
5913 break;
5914 case SELF_PAY:
5915 billablePayerTypes.add(PayerType.SELF_PAY);
5916 break;
5917 default:
5918 break;
5919
5920 }
5921 }
5922 return billablePayerTypes;
5923 }
5924
5925 /**
5926 * Generates all claims for "last month" in the given programsite, for the
5927 * given payer. Asynchronicity is on. A new transaction is started at the
5928 * beginning of this method.
5929 */
5930 @Override
5931 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
5932 public List<Claim> generateMonthlyClaims(Long progSiteId, PayerType payerType) {
5933
5934 ProgramSite progSite = em.find(ProgramSite.class, progSiteId);
5935 if (progSite == null || progSite.getSite() == null) {
5936 log.warning("No Valid site Found for Claim Generation");
5937 return new ArrayList<Claim>();
5938 }
5939 Site site = progSite.getSite();
5940 Hospice hospice = site.getHospice();
5941 if (PayerType.npiRequiredPayerTypes.contains(payerType)
5942 && (progSite.getNpi() == null || progSite.getNpi().isEmpty())) {
5943 log.warning("Site incorrectly configured for billing " + payerType + ": #" + site.getId() + " in "
5944 + hospice.getBin());
5945 return new ArrayList<Claim>();
5946 }
5947
5948 if (payerType == PayerType.MEDICARE
5949 && (site.getMedicareIntermediaryInfo() == null || progSite.getNpi() == null || progSite.getNpi()
5950 .isEmpty())) {
5951 log.warning("Site incorrectly configured for billing " + payerType + ": #" + site.getId() + " in "
5952 + hospice.getBin());
5953 return new ArrayList<Claim>();
5954 }
5955
5956 if (site.getTimezone() == null) {
5957 log.warning("Timezone not configured for site " + site.getName() + " . Defaulting to UTC.");
5958 log.warning("Claims will not be generate for site " + site.getName());
5959 return new ArrayList<Claim>();
5960 }
5961 TimeZone siteTz = DateUtil.getTimeZone(site.getTimezone());
5962 ClaimsGenerationCache claimCache = new ClaimsGenerationCache(site);
5963 StateBillingRules siteStateRules = systemDataService.getStateBillingRules(site.getBillingAddress().getState()
5964 .trim());
5965
5966 List<Claim> claimList = new ArrayList<Claim>();
5967
5968 try {
5969 boolean separateRB = payerType == PayerType.MEDICAID && siteStateRules != null
5970 && siteStateRules.getDoRBandHSPseparateBatches() == true;
5971 if (separateRB) {
5972 // We're in a state that MUST process R&B in a separate
5973 // file. IE: dorbandhsp not (null or true) and on Medicaid
5974 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimCache);
5975
5976 BillDTO billDto = generateAndPostXML(progSite, hospice, payerType, separateRB, true, siteTz, true,
5977 claimCache, null, null, batchEntity);
5978 if (billDto != null)
5979 claimList.addAll(createOrUpdateARClaims(billDto, batchEntity, hospice, false, claimCache).values());
5980 }
5981
5982 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimCache);
5983 BillDTO billDto = generateAndPostXML(progSite, hospice, payerType, false, separateRB, siteTz, true,
5984 claimCache, null, null, batchEntity);
5985 if (billDto != null)
5986 claimList.addAll(createOrUpdateARClaims(billDto, batchEntity, hospice, false, claimCache).values());
5987 } catch (SAXException | JAXBException | ParseException x) {
5988 log.log(Level.SEVERE, "Error while generating Claims for Site#" + site.getId() + " for PayerType: "
5989 + payerType, x);
5990 }
5991
5992 return claimList;
5993 }
5994
5995 /**
5996 * Generates or updates claims for the specified site for all billable payer
5997 * types
5998 */
5999 public List<Claim> generateClaimsForSite(ProgramSite programSite, TimeZone siteTz, boolean runAsync,
6000 ClaimsGenerationCache claimsGenerationCache, BillingOptions bilopts) {
6001 return new ArrayList<Claim>(generateClaims(programSite, siteTz, runAsync, claimsGenerationCache, bilopts)
6002 .values());
6003 }
6004
6005 private Map<String, Claim> generateClaims(ProgramSite programSite, TimeZone siteTz, boolean runAsync,
6006 ClaimsGenerationCache claimsGenerationCache, BillingOptions bilopts) {
6007 Map<String, Claim> claimMap = new HashMap<String, Claim>();
6008 Site site = programSite.getSite();
6009 claimsGenerationCache.setClaimSite(site);
6010 Hospice hospice = site.getHospice();
6011
6012 log.info("*************** Initiating claim generation for Site: "
6013 + (site == null ? null : site.getName() + " (" + site.getId() + ")") + " ***************");
6014 BillDTO billDto = null;
6015
6016 // TODO We should iterate over
6017 ProgramSite billingProgram = programSite;
6018 StateBillingRules siteStateRules = systemDataService.getStateBillingRules(site.getBillingAddress().getState()
6019 .trim());
6020
6021 Set<PayerType> billablePayerTypes = getBillablePayerTypes(hospice, billingProgram);
6022 Long patientId = null;
6023 Date[] dateRange = null;
6024 boolean onlyRB = false;
6025
6026 if (bilopts != null) {
6027 if (bilopts.getPayerType() != null)
6028 billablePayerTypes = EnumSet.of(bilopts.getPayerType());
6029
6030 patientId = bilopts.getSinglePatient();
6031
6032 if (bilopts.getBillingDates() != null) {
6033 dateRange = bilopts.getBillingDates().getRange();
6034 if (DateUtil.isDateTime(dateRange[0])) {
6035 dateRange[0] = DateUtil.convertDateTz(dateRange[0], siteTz, DateUtil.UTC_ZONE);
6036 dateRange[1] = DateUtil.convertDateTz(dateRange[1], siteTz, DateUtil.UTC_ZONE);
6037 }
6038 }
6039 onlyRB = bilopts.isOnlyRoomAndBoard();
6040 }
6041
6042 // Generating ClaimBatch from raw data
6043 for (PayerType payerType : billablePayerTypes) {
6044 try {
6045 boolean separateRB = payerType == PayerType.MEDICAID && siteStateRules != null
6046 && siteStateRules.getDoRBandHSPseparateBatches() == true;
6047 if (separateRB) {
6048 // We're in a state that MUST process R&B in a separate
6049 // file. IE: dorbandhsp not (null or true) and on Medicaid
6050 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimsGenerationCache);
6051
6052 billDto = generateAndPostXML(programSite, hospice, payerType, separateRB, true, siteTz, runAsync,
6053 claimsGenerationCache, patientId, dateRange, batchEntity);
6054 if (billDto != null) {
6055 claimMap.putAll(createOrUpdateARClaims(billDto, batchEntity, hospice, false,
6056 claimsGenerationCache));
6057 }
6058 }
6059
6060 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimsGenerationCache);
6061 billDto = generateAndPostXML(programSite, hospice, payerType, onlyRB, separateRB, siteTz, runAsync,
6062 claimsGenerationCache, patientId, dateRange, batchEntity);
6063 if (billDto != null) {
6064 claimMap.putAll(createOrUpdateARClaims(billDto, batchEntity, hospice, false, claimsGenerationCache));
6065 }
6066 } catch (SAXException | JAXBException | ParseException x) {
6067 log.log(Level.SEVERE, "Error while generating Claims for Site#" + site.getId() + " for PayerType: "
6068 + payerType, x);
6069 }
6070 }
6071
6072 log.info("*************** " + claimMap.size() + " Claims generated for Site: "
6073 + (site == null ? null : site.getName() + " (" + site.getId() + ")") + " ***************");
6074 return claimMap;
6075 }
6076
6077 /**
6078 *
6079 * @param site
6080 * @param hospice
6081 * @param payerType
6082 * @param processOnlyRB
6083 * @param separateState
6084 * @param siteTz
6085 * @param runAsync
6086 * @param claimsGenerationCache
6087 * @param patientId
6088 * @param dateRange
6089 * dateRange is expected to be in date only format (i.e. time is
6090 * set 00:00:00 UTC)
6091 * @return
6092 * @throws SAXException
6093 * @throws JAXBException
6094 * @throws ParseException
6095 */
6096 public BillDTO generateAndPostXML(ProgramSite site, Hospice hospice, PayerType payerType, boolean processOnlyRB,
6097 boolean separateState, TimeZone siteTz, boolean runAsync, ClaimsGenerationCache claimsGenerationCache,
6098 Long patientId, Date[] dateRange, com.hummingbird.jpa.claims.ClaimBatch batchEntity) throws SAXException,
6099 JAXBException, ParseException {
6100 BillDTO billDto = null;
6101 ClaimBatchDTO claimBatchDTO = null;
6102 claimBatchDTO = generateBillingXMLForPAS(hospice.getId(), siteTz, site, payerType, processOnlyRB,
6103 separateState, runAsync, claimsGenerationCache, patientId, dateRange, batchEntity);
6104
6105 if (claimBatchDTO == null)
6106 return billDto;
6107
6108 claimBatchDTO.setRoomAndBoardOnly(processOnlyRB);
6109
6110 // Sending claims to PASBe Web service
6111 log.info("** Sending claims to PASBe Web service For PayerType: " + payerType.name() + " ...");
6112
6113 billDto = sendClaimsToPASWebservice(claimBatchDTO, payerType, false, true, null, claimsGenerationCache);
6114
6115 return billDto;
6116 }
6117
6118 /**
6119 * Generates or updates claims for the current user site.
6120 */
6121 public void generateClaimsForCurrentSite(BillingOptions bilopts, boolean runAsync) {
6122 Site site = em.find(Site.class, auth.getCurrentUserSiteId());
6123 ProgramSite programSite = null;
6124
6125 if (bilopts != null) {
6126 if (bilopts.getProgramSiteId() != null) {
6127 programSite = em.find(ProgramSite.class, bilopts.getProgramSiteId());
6128 }
6129 }
6130 if (programSite == null) {
6131 programSite = patientService.getHospiceProgramSite(site);
6132 }
6133
6134 TimeZone siteTz = TimeZone.getTimeZone(site.getTimezone());
6135 try {
6136 ClaimsGenerationCache cache = new ClaimsGenerationCache(programSite.getSite());
6137 generateClaimsForSite(programSite, siteTz, runAsync, cache, bilopts);
6138 } catch (Exception ex) {
6139 String msg = "Error generating claims xml for site " + site.getName();
6140 log.log(Level.SEVERE, msg, ex);
6141 }
6142 }
6143
6144 /**
6145 * Generates claims for all payer types for the current site. All the
6146 * generated XMLs are sent as a single attachment in an email. (The claims
6147 * are not sent to PAS). If desired, mark all the claims created as SENT
6148 */
6149 public void generateAndEmailClaims(BillingOptions bilopts, boolean runAsync, boolean markClaimsSent) {
6150
6151 Site site = hospiceService.findSite(auth.getCurrentUserSiteId());
6152 List<String> emailAddress = adminService.getSiteNotificationEmailList("REMITS", site.getPasBin());
6153 FileArchiveDTO archieveDto = generateClaims(bilopts, runAsync, markClaimsSent, site.getId());
6154
6155 try {
6156 File archive = new File(archieveDto.getArchivePath());
6157 ClaimUtil.emailClaims(emailAddress, archive, archieveDto.getErrorMessages(), site.getHospice().getName(),
6158 site.getNickName());
6159 if (archive != null && archive.exists())
6160 archive.delete();
6161 } catch (MessagingException e) {
6162 log.log(Level.SEVERE, "Error emailing claims xml.", e);
6163 }
6164 }
6165
6166 /**
6167 * Generates claims for all payer types for the current site. All the
6168 * generated XMLs are archive as a single attachment.
6169 */
6170 public FileArchiveDTO generateClaims(BillingOptions bilopts, boolean runAsync, boolean markClaimsSent, Long siteId) {
6171 FileArchiveDTO archieveDto = new FileArchiveDTO();
6172 Site site = em.find(Site.class, siteId);
6173 ProgramSite billingProgramSite = null;
6174
6175 if (bilopts != null) {
6176 if (bilopts.getProgramSiteId() != null) {
6177 billingProgramSite = em.find(ProgramSite.class, bilopts.getProgramSiteId());
6178 }
6179 }
6180 if (billingProgramSite == null) {
6181 billingProgramSite = patientService.getHospiceProgramSite(site);
6182 }
6183
6184 TimeZone siteTz = TimeZone.getTimeZone(site.getTimezone());
6185 Hospice hospice = site.getHospice();
6186
6187 List<PayerType> payerTypes = new ArrayList<PayerType>(PayerType.billablePayerTypes);
6188 Date[] billingRange = null;
6189 Long singlePatientId = null;
6190 BillingExportOptions billType = null;
6191 boolean onlyRB = false;
6192
6193 if (bilopts != null) {
6194 singlePatientId = bilopts.getSinglePatient();
6195 billType = bilopts.getExportOptions();
6196 if (bilopts.getBillingDates() != null) {
6197 billingRange = bilopts.getBillingDates().getRange();
6198 // We are not trusting what the UI gives us, and getting the
6199 // range by a day IN the month.
6200 LocalDate inMonth = new LocalDate(billingRange[0]).plusDays(1);
6201 billingRange = DateUtil.calculateDateOnlyArbitraryMonthRange(inMonth);
6202 }
6203 if (bilopts.getPayerType() != null) {
6204 payerTypes.clear();
6205 payerTypes.add(bilopts.getPayerType());
6206 }
6207 onlyRB = bilopts.isOnlyRoomAndBoard();
6208 }
6209
6210 List<File> files = new ArrayList<File>();
6211 List<String> errorMessages = new ArrayList<String>();
6212 StateBillingRules siteStateRules = systemDataService.getStateBillingRules(site.getBillingAddress().getState()
6213 .trim());
6214
6215 for (PayerType payerType : payerTypes) {
6216 File file = null;
6217 ClaimBatchDTO claimBatchDTO = null;
6218 try {
6219 // Generate claim
6220 boolean separateRB = payerType == PayerType.MEDICAID && siteStateRules != null
6221 && siteStateRules.getDoRBandHSPseparateBatches() == true;
6222 if (separateRB) {
6223 // We're in a state that MUST process R&B in a separate
6224 // file. IE: dorbandhsp not (null or true) and on Medicaid
6225 ClaimsGenerationCache claimsCache = new ClaimsGenerationCache(billingProgramSite.getSite());
6226 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimsCache);
6227
6228 claimBatchDTO = generateBillingXMLForPAS(hospice.getId(), siteTz, billingProgramSite, payerType,
6229 separateRB, true, runAsync, claimsCache, singlePatientId, billingRange, batchEntity);
6230 if (claimBatchDTO != null) {
6231 claimBatchDTO.setBatchKey(batchEntity.getBatchKey());
6232 BillDTO billDTO = sendClaimsToPASWebservice(claimBatchDTO, payerType, false, true, billType,
6233 claimsCache);
6234 if (billDTO != null) {
6235 createOrUpdateARClaims(billDTO, batchEntity, hospice, false, claimsCache);
6236 }
6237 String rawReport = getReportFromPASBE(claimBatchDTO, payerType, false, true, billType);
6238 if (rawReport != null) {
6239 // Write to File
6240 file = initClaimFile(billingProgramSite, payerType, separateRB, billType, null);
6241 FileUtils.writeStringToFile(file, rawReport, "US-ASCII");
6242 files.add(file);
6243 }
6244 }
6245 }
6246
6247 ClaimsGenerationCache claimsCache = new ClaimsGenerationCache(billingProgramSite.getSite());
6248 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimsCache);
6249
6250 claimBatchDTO = generateBillingXMLForPAS(hospice.getId(), siteTz, billingProgramSite, payerType,
6251 onlyRB, separateRB, runAsync, claimsCache, singlePatientId, billingRange, batchEntity);
6252
6253 if (claimBatchDTO == null)
6254 continue;
6255
6256 BillDTO billDTO = sendClaimsToPASWebservice(claimBatchDTO, payerType, false, true, null, claimsCache);
6257 if (billDTO != null) {
6258 Map<String, Claim> claims = createOrUpdateARClaims(billDTO, batchEntity, hospice, false,
6259 claimsCache);
6260 // if we need to mark the claims as sent, do so
6261 if (markClaimsSent) {
6262 markClaimsSent(new ArrayList<Claim>(claims.values()));
6263 }
6264 }
6265 if (payerType == PayerType.COMMERCIAL && billType == BillingExportOptions.PRINT_CLAIMS
6266 && (claimBatchDTO.getJaxbClaimBatch().getPatientClaim().size() > 1)) {
6267 // Only actually need to split a batch larger than one.
6268 List<PatientClaim> ub04 = new ArrayList<PatientClaim>();
6269 List<PatientClaim> hcfa1500 = new ArrayList<PatientClaim>();
6270 for (PatientClaim cl : claimBatchDTO.getJaxbClaimBatch().getPatientClaim()) {
6271 if (cl.getPatientInfo().getPrimaryInsurance().getRequireBillingForm().equals("1500")) {
6272 hcfa1500.add(cl);
6273 } else {
6274 ub04.add(cl);
6275 }
6276 }
6277 if (ub04.size() > 0) {
6278 if (ub04.size() != claimBatchDTO.getJaxbClaimBatch().getPatientClaim().size()) {
6279 claimBatchDTO.getJaxbClaimBatch().getPatientClaim().clear();
6280 claimBatchDTO.getJaxbClaimBatch().getPatientClaim().addAll(ub04);
6281 }
6282 String rawReport = getReportFromPASBE(claimBatchDTO, payerType, false, true, billType);
6283
6284 // Write to File
6285 file = initClaimFile(billingProgramSite, payerType, false, billType, "UB04.doc");
6286 FileUtils.writeStringToFile(file, rawReport, "US-ASCII");
6287 files.add(file);
6288 }
6289 if (hcfa1500.size() > 0) {
6290 claimBatchDTO.getJaxbClaimBatch().getPatientClaim().clear();
6291 claimBatchDTO.getJaxbClaimBatch().getPatientClaim().addAll(hcfa1500);
6292 String rawReport = getReportFromPASBE(claimBatchDTO, payerType, false, true, billType);
6293
6294 // Write to File
6295 file = initClaimFile(billingProgramSite, payerType, false, billType, "1500.doc");
6296 FileUtils.writeStringToFile(file, rawReport, "US-ASCII");
6297 files.add(file);
6298 }
6299 } else {
6300 String rawReport = getReportFromPASBE(claimBatchDTO, payerType, false, true, billType);
6301
6302 // Write to File
6303 file = initClaimFile(billingProgramSite, payerType, false, billType, null);
6304 FileUtils.writeStringToFile(file, rawReport, "US-ASCII");
6305 files.add(file);
6306 }
6307 } catch (Exception e) {
6308 String msg = "Error generating claims xml for site " + site.getName() + " for PayerType: "
6309 + payerType.name();
6310 log.log(Level.SEVERE, msg, e);
6311 errorMessages.add(msg + "<br>" + e.getMessage());
6312 }
6313 }
6314
6315 File archive = null;
6316 try {
6317 archive = ClaimUtil.archiveClaims(files);
6318 archieveDto.setArchivePath(archive.getAbsolutePath());
6319 archieveDto.setFileSize(archive.length());
6320 } catch (IllegalArgumentException | IOException e) {
6321 String msg = "No attachment. Error archiving claims xml for site " + site.getName();
6322 log.log(Level.SEVERE, msg);
6323 errorMessages.add(msg);
6324 }
6325 for (File file : files) {
6326 file.delete();
6327 }
6328 archieveDto.setErrorMessages(errorMessages);
6329 return archieveDto;
6330 }
6331
6332 /**
6333 * Mark all provided claims that are READY and not Medicare/Live Billing as
6334 * SENT
6335 *
6336 * @param claims
6337 */
6338 public void markClaimsSent(List<Claim> claims) {
6339 /*
6340 * Mark all the new claims as sent, so long as they aren't medicare &
6341 * site's isLiveBilling
6342 */
6343
6344 log.fine("Marking claims as SENT");
6345 for (Claim c : claims) {
6346 // is it ready?
6347 if (!c.getClaimStatus().equals(ClaimStatus.READY))
6348 continue;
6349
6350 // see the payer type
6351 PayerType payerType = c.getPayerType();
6352
6353 // if payer type is Medicare, check live billing
6354 if (payerType != null && payerType.equals(PayerType.MEDICARE)
6355 && c.getProgramSite().getSite().getIsLiveBilling())
6356 continue;
6357
6358 // otherwise, mark as SENT & update
6359 log.fine("Marking status for claim key " + c.getClaimKey() + " as SENT");
6360 c.setClaimStatus(ClaimStatus.SENT);
6361 c = em.merge(c);
6362 }
6363 }
6364
6365 private com.hummingbird.jpa.claims.ClaimBatch createBatchEntity(ClaimsGenerationCache claimsCache) {
6366 com.hummingbird.jpa.claims.ClaimBatch batch = persistenceService
6367 .update(new com.hummingbird.jpa.claims.ClaimBatch());
6368
6369 String divKey = "ZZZ";
6370 if (claimsCache.getClaimSite() != null && claimsCache.getClaimSite().getDivKey() != null
6371 && claimsCache.getClaimSite().getDivKey().length() == 3) {
6372 divKey = claimsCache.getClaimSite().getDivKey().toUpperCase().trim();
6373 } else {
6374 log.severe("[ClaimGeneration] Missing or invalid divkey on site table");
6375 }
6376
6377 String batchNum = StringUtils.leftPad(DateEncoder.encodeIDtoB36(batch.getId()), 5, "0");
6378 batch.setBatchKey(divKey + "_" + batchNum);
6379
6380 return persistenceService.update(batch);
6381 }
6382
6383 private File initClaimFile(ProgramSite site, PayerType payerType, boolean onlyRB, BillingExportOptions billType,
6384 String suffix) throws IllegalArgumentException {
6385
6386 Hospice hospice = site.getSite().getHospice();
6387 MedicareIntermediaryInfo intermediary = site.getSite().getMedicareIntermediaryInfo();
6388 ProgramSite billingProgram = site;
6389 String hospiceBin = hospice.getBin();
6390
6391 String fileSuffix = "";
6392
6393 // payerType
6394 if (payerType != null) {
6395 fileSuffix += "_" + (onlyRB ? payerType + "_RB" : payerType);
6396 } else {
6397 String msg = "Missing payerType. Cannot generate claims xml file.";
6398 log.severe(msg);
6399 throw new IllegalArgumentException(msg);
6400 }
6401
6402 String dirPath = "pasbe";
6403 // intermediary
6404 if (payerType == PayerType.MEDICARE) {
6405 if (intermediary != null && intermediary.getMedicareIntermediary() != null
6406 && intermediary.getMedicareIntermediary().getName() != null
6407 && intermediary.getMedicareIntermediary().getName().length() > 0) {
6408 dirPath += "/" + intermediary.getMedicareIntermediary().getName().trim();
6409 } else {
6410 log.severe("Medicare Intermediary missing for selected site! Generating claims in generic file.");
6411 dirPath += "/nointermed";
6412 }
6413 } else {
6414 // For now, all non-medicare claims go through Zirmed
6415 dirPath += "/" + "ZIRMED";
6416 }
6417 // submitterId
6418 if (payerType == PayerType.MEDICARE && billingProgram != null) {
6419 String submitterId = billingProgram.getSubmitterID();
6420 if (submitterId != null && submitterId.length() > 0) {
6421 dirPath += "/" + submitterId.trim();
6422 } else {
6423 log.severe("Hospice Program SubmitterID missing for selected site! Generating claims in generic file.");
6424 dirPath += "/nosubid";
6425 }
6426 } else {
6427 String zirmedLogin = site.getSite().getZirmedlogin();
6428 if (zirmedLogin != null && !zirmedLogin.isEmpty()) {
6429 dirPath += "/" + zirmedLogin.trim();
6430 } else {
6431 log.severe("Zirmed Credentials missing for selected site! Generating claims in generic file.");
6432 dirPath += "/nozirmedcred";
6433 }
6434 }
6435
6436 File dir = new File(configService.getDataRoot(), dirPath);
6437
6438 // hospiceBin
6439 if (hospiceBin != null && hospiceBin.length() > 0) {
6440 fileSuffix += "_" + hospiceBin.trim();
6441 } else {
6442 String msg = "Hopsice bin missing from site. Cannot generate claims xml file.";
6443 log.severe(msg);
6444 throw new IllegalArgumentException(msg);
6445 }
6446 if (suffix != null && suffix.length() > 0) {
6447 fileSuffix += suffix;
6448 } else {
6449 if (billType != null) {
6450 switch (billType) {
6451 case EMC:
6452 fileSuffix += "EMC.out";
6453 break;
6454 case PRINT_CLAIMS:
6455 fileSuffix += ".doc";
6456 break;
6457 }
6458 } else {
6459 fileSuffix += "RawClaimData.xml";
6460 }
6461 }
6462 String fileName = new String(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + fileSuffix);
6463
6464 File file = new File(dir + "/" + fileName);
6465
6466 return file;
6467 }
6468
6469 /**
6470 * Chops off the time, or if date = null, then PAS needs date in the format
6471 * a particular format.
6472 *
6473 * @param date
6474 * Date is expected to be in date only format (i.e. site is set
6475 * to 00:00:00 UTC)
6476 */
6477 public String convertToOutgoingClaimDateOnlyFormat(Date date) {
6478 if (date == null)
6479 return "**/**/****";
6480
6481 // Sanity check
6482 if (DateUtil.isDateTime(date)) {
6483 log.fine("Malformed date. Date " + date.toGMTString()
6484 + " is not in dateonly format. Date may be encoded incorrectly on claim.");
6485 return null;
6486 }
6487
6488 // Date only values need no conversion
6489 return DateUtil.getDateOnlyFormat(DateUtil.UTC_ZONE).format(date);
6490 }
6491
6492 /**
6493 * Chops off the time, or if date = null, then PAS needs date in the format
6494 * a particular format.
6495 *
6496 * @param date
6497 * Date is expected to be in date time format in UTC timezone.
6498 * @param siteTz
6499 */
6500 public String convertToOutgoingClaimDateTimeFormat(Date date, TimeZone siteTz) {
6501 String result;
6502 if (date != null) {
6503 // Datetimes values must be converted to siteTz before its time
6504 // is chopped off
6505 Date siteTzDateTime = DateUtil.convertDateTz(date, DateUtil.UTC_ZONE, siteTz, true);
6506 result = DateUtil.getDateOnlyFormat(siteTz).format(siteTzDateTime);
6507 } else {
6508 result = "**/**/****";
6509 }
6510 return result;
6511 }
6512
6513 /**
6514 * TransactionStatusPair - keep the pair of transaction lines & status lines
6515 * from a 277
6516 *
6517 * @author bob
6518 *
6519 */
6520 class TransactionStatusPair {
6521 Segment trn;
6522 Segment stc;
6523
6524 public TransactionStatusPair(Segment t, Segment s) {
6525 this.trn = t;
6526 this.stc = s;
6527 }
6528 }
6529
6530 @Deprecated
6531 public String process277File(File file) {
6532 // first, read the value
6533 EdiBatchDTO value = read277File(file);
6534
6535 // see if it was processed
6536 if (value == null) {
6537 return "Could not process 277 file";
6538 }
6539
6540 boolean updated = updateBatchAndClaims(value);
6541
6542 return updated ? "Success" : "Could not find batch for this 277";
6543 }
6544
6545 public com.hummingbird.jpa.claims.ClaimBatch findClaimBatchByBatchKey(String key) {
6546 TypedQuery<com.hummingbird.jpa.claims.ClaimBatch> query = em.createNamedQuery(
6547 "ClaimBatch.findClaimBatchByBatchKey", com.hummingbird.jpa.claims.ClaimBatch.class);
6548 query.setParameter("batchKey", key);
6549 try {
6550 return query.getSingleResult();
6551 } catch (NoResultException e) {
6552 return null;
6553 }
6554 }
6555
6556 @Deprecated
6557 public boolean updateBatchAndClaims(EdiBatchDTO batchInfo) {
6558 // match up the EdiBatch with our billing data
6559 com.hummingbird.jpa.claims.ClaimBatch batch = findClaimBatchByBatchKey(batchInfo.getBatchId());
6560
6561 if (batch == null)
6562 return false;
6563
6564 // get the bill log too
6565 BillLog billLog = findBillLogByBatchKey(batchInfo.getBatchId());
6566 if (billLog == null)
6567 return false;
6568
6569 List<Claim> claims = new ArrayList<Claim>(batch.getClaims());
6570
6571 // update the Batch & BillLog status with the batch status
6572 if (EdiBatchDTO.batchAccepted.equals(batchInfo.getBatchStatus())) {
6573 batch.setStatus(BatchStatus.RECEIVED);
6574 billLog.setStatus(BatchStatus.RECEIVED.toString());
6575 batch.getClaims().size(); // lazy load
6576 Iterator<Claim> iter = claims.iterator();
6577 while (iter.hasNext()) {
6578 Claim c = iter.next();
6579 // find the corresponding claim info
6580 EdiClaimDTO claimInfo = null;
6581 for (EdiClaimDTO ci : batchInfo.getClaims()) {
6582 if (ci.getClaimId().equals(c.getClaimKey())) {
6583 // got it - update the status
6584 claimInfo = ci;
6585 break;
6586 }
6587 }
6588 if (claimInfo != null) {
6589 if (EdiClaimDTO.claimAccepted.equals(claimInfo.getClaimStatus())) {
6590 // claim accepted
6591 c.setStatusReason(null);
6592 } else {
6593 c.setClaimStatus(ClaimStatus.RJECTED);
6594 c.setStatusReason(claimInfo.getError());
6595 }
6596
6597 }
6598 persistenceService.update(c);
6599 }
6600 } else {
6601 batch.setStatus(BatchStatus.ERROR);
6602 billLog.setStatus(BatchStatus.ERROR.toString());
6603 // update each claim too
6604 batch.getClaims().size(); // lazy load
6605 Iterator<Claim> iter = claims.iterator();
6606 while (iter.hasNext()) {
6607 Claim c = iter.next();
6608 c.setClaimStatus(ClaimStatus.ERROR);
6609 c.setStatusReason(batchInfo.getError());
6610 persistenceService.update(c);
6611 }
6612 }
6613 persistenceService.update(batch);
6614 persistenceService.update(billLog);
6615 return true;
6616 }
6617
6618 /**
6619 * Return the Edi info with a given 277 file
6620 *
6621 * @param file
6622 * @return
6623 */
6624 @Deprecated
6625 public EdiBatchDTO read277File(File file) {
6626 EdiBatchDTO result = null;
6627
6628 X12Builder builder = new X12Builder();
6629 try {
6630 Document doc = builder.buildDocument(file);
6631
6632 List<TransactionStatusPair> rows = new ArrayList<TransactionStatusPair>();
6633
6634 List<Segment> segments = doc.getSegments("TRN");
6635 for (Segment s : segments) {
6636 if (s.nextSegment().getSegmentTag().equals("STC")) {
6637 rows.add(new TransactionStatusPair(s, s.nextSegment()));
6638 }
6639 }
6640
6641 if (rows.size() == 0) {
6642 log.warning("Could not identify batch from EDI document:" + file.getAbsolutePath());
6643 return null;
6644 }
6645
6646 result = new EdiBatchDTO();
6647 // get the batch's info
6648 TransactionStatusPair pair = rows.get(0);
6649 Segment trn = pair.trn;
6650 Segment stc = pair.stc;
6651 result.setBatchId(trn.getField(2).getValue());
6652 String status = stc.getField(1).getValue();
6653 result.setBatchStatus(status);
6654 if (!EdiBatchDTO.batchAccepted.equals(status)) {
6655 result.setError(EdiUtil.getReadableStatus(status));
6656 }
6657 List<EdiClaimDTO> claims = new ArrayList<EdiClaimDTO>();
6658 // there's a dupe pair for the batch info with the wrong ID - start
6659 // at 2
6660 for (int i = 2; i < rows.size(); ++i) {
6661 pair = rows.get(i);
6662 trn = pair.trn;
6663 stc = pair.stc;
6664
6665 // it's a claim
6666 EdiClaimDTO claim = new EdiClaimDTO();
6667 claim.setClaimId(trn.getField(2).getValue());
6668 status = stc.getField(1).getValue();
6669 claim.setClaimStatus(status);
6670 if (!EdiClaimDTO.claimAccepted.equals(status)) {
6671 claim.setError(EdiUtil.getReadableStatus(status));
6672 }
6673 claims.add(claim);
6674 }
6675 result.setClaims(claims);
6676
6677 } catch (IOException | WrongDocuemntFormatException e) {
6678 log.warning("Could not build EDI document with given file:" + file.getAbsolutePath() + ", "
6679 + e.getLocalizedMessage());
6680 }
6681
6682 return result;
6683 }
6684
6685 public BillLog findBillLogByBatchKey(String batchKey) {
6686 TypedQuery<BillLog> query = em.createNamedQuery("BillLog.findBillLogByBatchKey", BillLog.class);
6687 query.setParameter("batchKey", batchKey);
6688 try {
6689 return query.getSingleResult();
6690 } catch (NoResultException e) {
6691 return null;
6692 }
6693 }
6694
6695 @Override
6696 public List<BillLog> findBillLogs(int offset, int maxResults) {
6697 TypedQuery<BillLog> query = em.createNamedQuery("BillLog.findBillLogsByHospice", BillLog.class);
6698 query.setParameter("hospiceId", auth.getCurrentUserHospiceId());
6699
6700 query.setFirstResult(offset);
6701 query.setMaxResults(maxResults);
6702
6703 return query.getResultList();
6704 }
6705
6706 public Date[] getFirstAndLastUTCDatesForCurrentMonth() {
6707 return DateUtil.calculateCurrentMonthDateRange();
6708 }
6709
6710 @Override
6711 public void submitReadyClaims(BillingOptions bilopts, boolean runAsync) {
6712
6713 BillingService billingService = (runAsync) ? sessionContext.getBusinessObject(BillingService.class) : this;
6714
6715 Date dateRange[];
6716 boolean onlyRB = false;
6717 // For the Current site.
6718 final Long siteId = auth.getCurrentUserSiteId();
6719 final Site currentSite = em.find(Site.class, siteId);
6720 ClaimsGenerationCache claimsGenerationCache = new ClaimsGenerationCache(currentSite);
6721 claimsGenerationCache.setClaimSite(currentSite);
6722 StateBillingRules siteStateRules = systemDataService.getStateBillingRules(currentSite.getBillingAddress()
6723 .getState().trim());
6724 if (siteStateRules == null)
6725 log.severe("SiteRules null");
6726 final TimeZone claimTimeZone = DateUtil.getTimeZone(currentSite.getTimezone());
6727 Claim searchObj = new Claim();
6728
6729 List<PayerType> payerTypes = new ArrayList<PayerType>(PayerType.billablePayerTypes);
6730 if (bilopts != null) {
6731 if (bilopts.getSinglePatient() != null) {
6732 searchObj.setPatient(em.find(Patient.class, bilopts.getSinglePatient()));
6733 }
6734 if (bilopts.getBillingDates() != null) {
6735 dateRange = bilopts.getBillingDates().getRange();
6736 if (DateUtil.isDateTime(dateRange[0])) {
6737 dateRange[0] = DateUtil.convertDateTz(dateRange[0], claimTimeZone, DateUtil.UTC_ZONE);
6738 dateRange[1] = DateUtil.convertDateTz(dateRange[1], claimTimeZone, DateUtil.UTC_ZONE);
6739 }
6740 searchObj.setServiceStart(dateRange[0]);
6741 searchObj.setServiceEnd(dateRange[1]);
6742 }
6743 onlyRB = bilopts.isOnlyRoomAndBoard();
6744
6745 if (bilopts.getPayerType() != null) {
6746 payerTypes.clear();
6747 payerTypes.add(bilopts.getPayerType());
6748 }
6749 }
6750
6751 // For each programSite
6752 Collection<ProgramSite> programs = new ConcurrentLinkedQueue<ProgramSite>(currentSite.getProgramSites());
6753 for (ProgramSite programSite : programs) {
6754 // For each PayerType
6755 searchObj.setProgramSite(programSite);
6756 for (PayerType fc : payerTypes) {
6757 searchObj.setPayerType(fc);
6758
6759 if (onlyRB && (fc == PayerType.MEDICAID || fc == PayerType.COMMERCIAL))
6760 searchObj.setClaimKey(fc.getCode() + "RB");
6761
6762 // Select Ready claims by payer type
6763 List<Claim> readyClaims = financeService.findSiteClaims(searchObj, 0, 0,
6764 ImmutableSet.of(ClaimStatus.READY), false);
6765 // TODO Commercial claims are going to be a pain with this
6766 // setup.
6767 Long batchCarrierId = null;
6768 for (Claim currentClaim : readyClaims) {
6769 batchCarrierId = currentClaim.getInsuranceCoverage().getInsurancecarrier().getId();
6770 }
6771 // separate claims into two lists, and if requires separate
6772 // batches, make them.
6773 List<Claim> rbList = new ArrayList<Claim>();
6774 List<Claim> hspList = new ArrayList<Claim>();
6775 List<Claim> hcfa1500List = new ArrayList<Claim>();
6776 if (fc.equals(PayerType.MEDICAID) && siteStateRules.getDoRBandHSPseparateBatches() == true) {
6777 for (Claim cl : readyClaims) {
6778 if (cl.getClaimKey().startsWith("MDRB")) {
6779 rbList.add(cl);
6780 } else {
6781 hspList.add(cl);
6782 }
6783 }
6784 // Commercial is the only time we have to split batches
6785 } else if (fc.equals(PayerType.COMMERCIAL)) {
6786 for (Claim cl : readyClaims) {
6787 if (cl.getInsuranceCoverage().getInsurancecarrier().getRequireBillingForm().equals("1500")) {
6788 hcfa1500List.add(cl);
6789 } else {
6790 hspList.add(cl);
6791 }
6792 }
6793 } else {
6794 hspList = readyClaims;
6795 }
6796 List<List<Claim>> submitList = new ArrayList<List<Claim>>(3);
6797 submitList.add(hspList);
6798 submitList.add(rbList);
6799 submitList.add(hcfa1500List);
6800 for (List<Claim> submit : submitList) {
6801 if (submit.size() <= 0)
6802 continue;
6803
6804 ClaimBatchDTO batchDTO = null;
6805 BillLog billLog = null;
6806 com.hummingbird.jpa.claims.ClaimBatch batchEntity = createBatchEntity(claimsGenerationCache);
6807
6808 try {
6809 Date[] range = { searchObj.getServiceStart(), searchObj.getServiceEnd() };
6810 // DTOS EVERYWHERE!!
6811 List<IndividualClaimDTO> patientClaimDTOList = createClaimDTOsFromClaims(submit, claimTimeZone,
6812 runAsync, claimsGenerationCache);
6813 batchDTO = createClaimBatchDTO(patientClaimDTOList, programSite, batchCarrierId, range,
6814 batchEntity, fc, claimsGenerationCache);
6815 } catch (SAXException | JAXBException | ParseException e) {
6816 log.log(Level.SEVERE, "Error Generating Claims", e);
6817 continue;
6818 }
6819
6820 try {
6821 // Update claim statuses to ClaimStatus.SENT
6822 // Only if the batch actually succeeded
6823 for (Claim currentClaim : submit) {
6824 currentClaim.setClaimStatus(ClaimStatus.SENT);
6825 currentClaim.setSent(new Date());
6826 persistenceService.update(currentClaim);
6827 }
6828 BillingExportOptions billType = bilopts.getExportOptions();
6829 ClaimBatch jaxbClaimBatch = batchDTO.getJaxbClaimBatch();
6830
6831 // Call PASBE
6832 BillDTO billDto = sendClaimsToPASWebservice(batchDTO, fc, true, currentSite.getIsLiveBilling(),
6833 billType, claimsGenerationCache);
6834 // Pass result to createOrUpdateARClaims
6835 billingService.createOrUpdateARClaims(billDto, batchEntity, currentSite.getHospice(), true,
6836 claimsGenerationCache);
6837
6838 if (billDto.getTotalBeingSubmittedAmount().compareTo(BigDecimal.ZERO) > 0) {
6839 billLog = createBillLog(jaxbClaimBatch.getBatchID(), jaxbClaimBatch.getBatchStartDate(),
6840 jaxbClaimBatch.getBatchStopDate(), fc.name());
6841
6842 billLog.setSubmittedAmount(billDto.getTotalBeingSubmittedAmount());
6843 billLog.setHeldAmount(billDto.getTotalNotBeingSubmittedAmount());
6844 billLog.setStatus(BatchStatus.SENT.name());
6845 billLog = persistenceService.update(billLog);
6846 }
6847 } catch (Exception e) {
6848 log.log(Level.SEVERE, "Error sending claims", e);
6849 if (billLog != null)
6850 persistenceService.remove(billLog, true);
6851 }
6852 }
6853 }
6854 }
6855 }
6856
6857 /**
6858 * Retrieves the oldest claim's servicestart date for the current site.
6859 *
6860 * @return Returns a date if at least 1 claim exists. Otherwise null is
6861 * returned.
6862 */
6863 private Date getOldestClaimDate() {
6864 Date result = null;
6865 Long siteId = auth.getCurrentUserSiteId();
6866
6867 TypedQuery<Date> query = em.createNamedQuery("Claim.findOldestClaimDate", Date.class);
6868 query.setParameter("siteId", siteId);
6869 List<Date> claims = query.getResultList();
6870
6871 if (claims.size() > 0) {
6872 result = claims.get(0);
6873 }
6874
6875 return result;
6876 }
6877
6878 /**
6879 * Utility method, given the patient, insurance coverages, and relevant
6880 * patientfacility entities, it checks if we should separate room and board
6881 * claims.
6882 */
6883 private boolean roomAndBoardSameClaim(Patient patient, List<InsuranceCoverage> coverages,
6884 List<PatientFacilityArcDTO> rbFacilities) {
6885 boolean result = false;
6886
6887 if (patient != null
6888 && ((coverages != null && coverages.size() > 0) || (rbFacilities != null && rbFacilities.size() > 0))) {
6889 // We may have a medicaid or R&B claim.
6890 for (PatientFacilityArcDTO patFacArc : rbFacilities) {
6891 PatientFacility patFac = em.find(PatientFacility.class, patFacArc.getPatientFacilityId().longValue());
6892 if (patFac == null)
6893 continue;
6894 String state = patFac.getFacility().getPrimaryAddress().getState().toUpperCase().trim();
6895 if (state.isEmpty()) {
6896 log.warning("No state provided for RB fac: " + patFac.getId());
6897 continue;
6898 }
6899 StateBillingRules rules = systemDataService.getStateBillingRules(state);
6900 if (rules == null) {
6901 log.warning("No state billing rules defined for state:" + state);
6902 continue;
6903 }
6904 Boolean doRBandHSPsameClaim = rules.getDoRBandHSPsameClaim();
6905 if (doRBandHSPsameClaim != null && doRBandHSPsameClaim == true) {
6906 result = true;
6907 break;
6908 }
6909 }
6910 }
6911 return result;
6912 }
6913
6914 /**
6915 *
6916 * Another utility, this one to discern if we are in a state that needs MSA
6917 * not CBSA for medicaid.
6918 *
6919 * @param location
6920 * @return
6921 */
6922 private boolean checkMSAstate(String state) {
6923 boolean MSAstate = false;
6924 MSAstate = systemDataService.getStateBillingRules(state).isSendMSA();
6925 return MSAstate;
6926 }
6927
6928 /**
6929 * Retrieves MSA off the zip code database where needed (rarely)
6930 *
6931 * @param location
6932 * @return
6933 */
6934 private String getMSACode(String zip) {
6935 String MSA = null;
6936 TypedQuery<Zipcode> query = emRef.createNamedQuery("Zipcode.findMSACode", Zipcode.class);
6937 query.setParameter("zipcode", zip);
6938 List<Zipcode> MSAs = query.getResultList();
6939 if (MSAs != null && MSAs.size() > 0)
6940 MSA = MSAs.get(0).getMSACode();
6941 return MSA;
6942 }
6943
6944 public BillPatientDTO generateBillPatientDTOFromClaim(Claim claim) {
6945 BillPatientDTO billPatientDTO = new BillPatientDTO();
6946
6947 Patient patient = claim.getPatient();
6948
6949 billPatientDTO.setFirstName(patient.getFirstName());
6950 billPatientDTO.setLastName(patient.getLastName());
6951 billPatientDTO.setPatientNumber(patient.getPatientNumber());
6952 billPatientDTO.setDateOfBirth(patient.getDateOfBirth());
6953 billPatientDTO.setClaimKey(claim.getClaimKey());
6954 billPatientDTO.setCbsaCode(claim.getCbsa());
6955 billPatientDTO.setFacilityNpi(claim.getServiceFacilityNpi());
6956
6957 if (claim.getPatientAddress() != null) {
6958 Address addressDto = new Address();
6959
6960 addressDto.setStreet1(claim.getPatientAddress().getStreet1());
6961 addressDto.setStreet2(claim.getPatientAddress().getStreet2());
6962 addressDto.setCity(claim.getPatientAddress().getCity());
6963 addressDto.setState(claim.getPatientAddress().getState());
6964 addressDto.setZip(claim.getPatientAddress().getZip());
6965 addressDto.setCbsa(claim.getPatientAddress().getCbsa());
6966
6967 billPatientDTO.setAddress(addressDto);
6968
6969 }
6970 billPatientDTO.setAdmissionDate(claim.getPatientAdmitDate());
6971 billPatientDTO.setTypeOfBill(claim.getTypeOfBill());
6972 billPatientDTO.setCurrentStatusCode(claim.getStatusCode());
6973
6974 billPatientDTO.setStartDateOfClaim(claim.getServiceStart());
6975 billPatientDTO.setEndDateOfClaim(claim.getServiceEnd());
6976 billPatientDTO.setClaimStatus(claim.getClaimStatus());
6977
6978 // primary insurance
6979 InsuranceCoverage insCoverage = claim.getInsuranceCoverage();
6980
6981 if (insCoverage != null) {
6982 if (insCoverage.getInsurancecarrier() != null) {
6983 billPatientDTO.setPrimaryInsuranceName(insCoverage.getInsurancecarrier().getCarrierName());
6984 }
6985 billPatientDTO.setPrimaryInsurancePolicyNumber(insCoverage.getPolicynumber());
6986
6987 PatientPersonRelation ppr = insCoverage.getPolicyholderPatientRelation();
6988 if (ppr == null || ppr.getRelationship() == null || ppr.getPerson() == null) { // self
6989 billPatientDTO.setPrimaryInsurancePolicyHolder(PersonUtil.getFullName(patient.getFirstName(),
6990 patient.getMiddleInitial(), patient.getLastName()));
6991 } else {
6992 if (ppr.getPerson() != null) {
6993 billPatientDTO.setPrimaryInsurancePolicyHolder(PersonUtil.getFullName(ppr.getPerson()
6994 .getFirstName(), null, ppr.getPerson().getLastName()));
6995 }
6996 }
6997 }
6998
6999 // secondary insurance
7000 InsuranceCoverage secInsCoverage = claim.getSecondaryInsuranceCoverage();
7001
7002 if (secInsCoverage != null) {
7003 if (secInsCoverage.getInsurancecarrier() != null) {
7004 billPatientDTO.setSecondaryInsuranceName(secInsCoverage.getInsurancecarrier().getCarrierName());
7005 }
7006 billPatientDTO.setSecondaryInsurancePolicyNumber(secInsCoverage.getPolicynumber());
7007
7008 PatientPersonRelation ppr = insCoverage.getPolicyholderPatientRelation();
7009 if (ppr == null || ppr.getRelationship() == null || ppr.getPerson() == null) { // self
7010 billPatientDTO.setSecondaryInsurancePolicyHolder(PersonUtil.getFullName(patient.getFirstName(),
7011 patient.getMiddleInitial(), patient.getLastName()));
7012 } else {
7013 if (ppr.getPerson() != null) {
7014 billPatientDTO.setSecondaryInsurancePolicyHolder(PersonUtil.getFullName(ppr.getPerson()
7015 .getFirstName(), null, ppr.getPerson().getLastName()));
7016 }
7017 }
7018 }
7019
7020 // attending and certifying physicians
7021 billPatientDTO.setAttendingPhysicianName(claim.getAttendingPhysicianName());
7022 billPatientDTO.setAttendingPhysicianNpi(claim.getAttendingPhysicianNpi());
7023 billPatientDTO.setCertifyingPhysicianName(claim.getCertifyingPhysicianName());
7024 billPatientDTO.setCertifyingPhysicianNpi(claim.getCertifyingPhysicianNpi());
7025
7026 billPatientDTO.setDiagnosisCodes(claim.getDiagnosisCodes());
7027
7028 billPatientDTO.setConditionCodes(decodeListOfCodes(claim.getConditionCodes()));
7029
7030 billPatientDTO.setOccurrenceCodes(decodeOccuranceCodes(claim.getOccurrenceCodes()));
7031
7032 billPatientDTO.setValueCodes(decodeListOfCodes(claim.getValueCodes()));
7033
7034 // billing days:
7035 Map<LevelOfCareType, Integer> billingDaysMap = new HashMap<LevelOfCareType, Integer>();
7036
7037 billingDaysMap.put(LevelOfCareType.CONTINUOUS_CARE,
7038 Integer.valueOf(claim.getDaysCc() != null ? claim.getDaysCc() : 0));
7039 billingDaysMap.put(LevelOfCareType.GENERAL_IN_PATIENT,
7040 Integer.valueOf(claim.getDaysGip() != null ? claim.getDaysGip() : 0));
7041 billingDaysMap.put(LevelOfCareType.RESPITE,
7042 Integer.valueOf(claim.getDaysResp() != null ? claim.getDaysResp() : 0));
7043 billingDaysMap.put(LevelOfCareType.ROUTINE,
7044 Integer.valueOf(claim.getDaysRtn() != null ? claim.getDaysRtn() : 0));
7045
7046 billPatientDTO.setBillingDays(billingDaysMap);
7047 billPatientDTO.setDays(claim.getCoveredDays() == null ? 0 : claim.getCoveredDays());
7048 billPatientDTO.setTotalWithBillables(claim.getTotalWithVisits() == null ? CurrencyUtil.ZERO : claim
7049 .getTotalWithVisits());
7050
7051 // Revenue Lines
7052 TypedQuery<ClaimRevenueDetail> queryRevenueDetails = em.createNamedQuery(
7053 "ClaimRevenueDetail.findRevenueDetailsByClaimId", ClaimRevenueDetail.class);
7054 queryRevenueDetails.setParameter("claimId", claim.getId());
7055 List<ClaimRevenueDetail> claimRevenueDetails = queryRevenueDetails.getResultList();
7056
7057 for (ClaimRevenueDetail claimRevLine : claimRevenueDetails) {
7058 BillRevenueLineDTO revLineDto = new BillRevenueLineDTO();
7059
7060 revLineDto.setStartDate(claimRevLine.getDate());
7061 revLineDto.setRevenueCode(claimRevLine.getRevCode());
7062 revLineDto.setHcpcsCode(claimRevLine.getHcpcs());
7063 revLineDto.setUnits(claimRevLine.getDays() == null ? 0 : claimRevLine.getDays());
7064 revLineDto.setCharges(claimRevLine.getAmountBilled() == null ? CurrencyUtil.ZERO : claimRevLine
7065 .getAmountBilled());
7066
7067 LevelOfCareType levelOfCareType = claimRevLine.getLevelOfCare() == null ? null : LevelOfCareType
7068 .valueOf(claimRevLine.getLevelOfCare());
7069 revLineDto.setTypeOfCare(levelOfCareType);
7070
7071 // Calculate the end date
7072 int units = claimRevLine.getDays();
7073 if (levelOfCareType != null && levelOfCareType == LevelOfCareType.CONTINUOUS_CARE) {
7074 // Convert 15 minutes units into dates
7075 int days = (units / 96) + 1;
7076 revLineDto.setEndDate(DateUtil.getDaysFromDate(revLineDto.getStartDate(), days));
7077 } else {
7078 revLineDto.setEndDate(DateUtil.getDaysFromDate(revLineDto.getStartDate(), units));
7079 }
7080
7081 if (claimRevLine.getFacility() != null) {
7082 revLineDto.setFacilityName(CommonUtil.scrubNames(claimRevLine.getFacility().getName()));
7083 }
7084 billPatientDTO.getRevenueLines().add(revLineDto);
7085 }
7086
7087 // Visit Lines
7088 TypedQuery<ClaimPatientVisit> queryClaimVisits = em.createNamedQuery("ClaimPatientVisit.findVisitsByClaimId",
7089 ClaimPatientVisit.class);
7090 queryClaimVisits.setParameter("claimId", claim.getId());
7091 List<ClaimPatientVisit> claimVisits = queryClaimVisits.getResultList();
7092
7093 billPatientDTO.setTotalVisits(claimVisits.size());
7094
7095 List<VisitLineDTO> visitLineDtos = new ArrayList<VisitLineDTO>();
7096 for (ClaimPatientVisit claimVisit : claimVisits) {
7097
7098 VisitLineDTO visitLineDto = new VisitLineDTO();
7099 visitLineDto.setCharges(claimVisit.getCharges() == null ? CurrencyUtil.ZERO : claimVisit.getCharges());
7100 visitLineDto.setDescription(claimVisit.getDescription());
7101 visitLineDto.setHcpcsCode(claimVisit.getHcpcs());
7102 visitLineDto.setRevenueCode(claimVisit.getRevcode());
7103
7104 visitLineDto.setUnits(claimVisit.getUnits() == null ? 0 : claimVisit.getUnits());
7105 visitLineDto.setStartDate(claimVisit.getVisitDate());
7106
7107 visitLineDtos.add(visitLineDto);
7108 }
7109
7110 for (BillRevenueLineDTO revLineDto : billPatientDTO.getRevenueLines()) {
7111 Date startDate = revLineDto.getStartDate();
7112 Date endDate = revLineDto.getEndDate();
7113
7114 Iterator<VisitLineDTO> iter = visitLineDtos.iterator();
7115 while (iter.hasNext()) {
7116 VisitLineDTO visitLineDTO = iter.next();
7117
7118 if (startDate != null && endDate != null && visitLineDTO.getStartDate() != null) {
7119 int compStart = visitLineDTO.getStartDate().compareTo(startDate);
7120 int compEnd = visitLineDTO.getStartDate().compareTo(endDate);
7121
7122 if (compStart >= 0 && compEnd <= 0) {
7123 revLineDto.getDetailLines().add(visitLineDTO);
7124 iter.remove();
7125 }
7126 }
7127 }
7128 }
7129
7130 if (visitLineDtos.size() > 0 && billPatientDTO.getRevenueLines().size() > 0) {
7131 billPatientDTO.getRevenueLines().get(billPatientDTO.getRevenueLines().size() - 1).getDetailLines()
7132 .addAll(visitLineDtos);
7133 }
7134
7135 // Medications
7136 TypedQuery<ClaimMedication> queryClaimMeds = em.createNamedQuery("ClaimMedication.findMedsByClaimId",
7137 ClaimMedication.class);
7138 queryClaimMeds.setParameter("claimId", claim.getId());
7139 List<ClaimMedication> claimMeds = queryClaimMeds.getResultList();
7140
7141 List<BillMedicationDTO> medDtos = new ArrayList<BillMedicationDTO>();
7142 for (ClaimMedication claimMed : claimMeds) {
7143
7144 BillMedicationDTO medDto = new BillMedicationDTO();
7145 medDto.setDate(claimMed.getFillDate());
7146 medDto.setRevCode(claimMed.getRevCode());
7147 medDto.setNdc(claimMed.getNdc());
7148 medDto.setDescription(claimMed.getDescription());
7149 medDto.setHcpcs(claimMed.getHcpcs());
7150 medDto.setUnit(claimMed.getUnits() == null ? "0" : claimMed.getUnits().toString());
7151 medDto.setUom(claimMed.getUom());
7152 medDto.setCharge(claimMed.getAmount() == null ? CurrencyUtil.ZERO : claimMed.getAmount());
7153
7154 medDtos.add(medDto);
7155 }
7156
7157 billPatientDTO.setBillMedications(medDtos);
7158
7159 // Total Revenue
7160 billPatientDTO.setTotalReceivable(claim.getClaimTotal() == null ? CurrencyUtil.ZERO : claim.getClaimTotal());
7161
7162 return billPatientDTO;
7163 }
7164
7165 public BillDTO getPreBillFromClaim(Long claimId) {
7166 Claim claim = em.find(Claim.class, claimId);
7167 BillPatientDTO billPatientDTO = generateBillPatientDTOFromClaim(claim);
7168 BillDTO billDTO = new BillDTO();
7169 billDTO.getPatients().add(billPatientDTO);
7170
7171 return billDTO;
7172 }
7173
7174 /**
7175 * Dates are assumed to be in UTC timezone
7176 *
7177 * @param startDate
7178 * @param endDate
7179 * @return
7180 */
7181 public BillDTO getPreBillFromClaims(Date startDate, Date endDate) {
7182 BillDTO billDTO = new BillDTO();
7183
7184 Site currentSite = em.find(Site.class, auth.getCurrentUserSiteId());
7185 TimeZone localTz = TimeZone.getTimeZone(currentSite.getTimezone());
7186 Date startDateUTC = DateUtil.convertDateTz(startDate, localTz, DateUtil.UTC_ZONE, true);
7187 Date endDateUTC = DateUtil.convertDateTz(endDate, localTz, DateUtil.UTC_ZONE, true);
7188
7189 TypedQuery<Claim> claimsQuery = em.createNamedQuery("Claim.findClaimByDateRangeSite", Claim.class);
7190
7191 claimsQuery.setParameter("siteId", auth.getCurrentUserSiteId());
7192 claimsQuery.setParameter("startDate", startDateUTC);
7193 claimsQuery.setParameter("endDate", endDateUTC);
7194
7195 List<Claim> claims = claimsQuery.getResultList();
7196
7197 for (Claim claim : claims) {
7198 BillPatientDTO billPatientDTO = generateBillPatientDTOFromClaim(claim);
7199 billDTO.getPatients().add(billPatientDTO);
7200 }
7201
7202 return billDTO;
7203 }
7204
7205 /**
7206 * A list of monthly date ranges. Because gwt restricts transport types (no
7207 * arrays or nested collections), each date range is stored as alternating
7208 * startDate/endDate pairs. Example:
7209 *
7210 * List=(Apr 1st, Apr 30th, March 1st, March 31th, ...)
7211 *
7212 * @return List of date ranges. List will be empty, if site contains no
7213 * claims.
7214 */
7215 public List<Date> getBillingRanges() {
7216 Date oldestClaimDate = getOldestClaimDate();
7217 List<Date> monthRanges = new ArrayList<Date>();
7218
7219 if (oldestClaimDate != null) {
7220 List<Date[]> dateRanges = generateBillingMonthsList(oldestClaimDate);
7221
7222 // Convert to a transportable type
7223 for (Date[] dateRange : dateRanges) {
7224 monthRanges.add(dateRange[0]);
7225 monthRanges.add(dateRange[1]);
7226 }
7227 }
7228 return monthRanges;
7229 }
7230
7231 /**
7232 * Create a list of monthly date ranges, descending. The last date range
7233 * will encompass oldestDate.
7234 *
7235 * OldestDate must be before the currentDate.
7236 *
7237 * @return List of date ranges; Each date range is an array of size 2
7238 * {startDate, endDate}. startDate is the first millisecond of the
7239 * month, and endDate is last millisecond of the month. Example:
7240 *
7241 * List.get(0)={1 Apr 2015 00:00:00 GMT, 30 Apr 2015 23:59:59 GMT}
7242 *
7243 * List.get(1)={1 Mar 2015 00:00:00 GMT, 31 Mar 2015 23:59:59 GMT}
7244 *
7245 * Date ranges are in UTC/GMT timezone.
7246 */
7247 private List<Date[]> generateBillingMonthsList(Date oldestDate) {
7248 List<Date[]> localTzRanges = new ArrayList<Date[]>();
7249 Site currentSite = em.find(Site.class, auth.getCurrentUserSiteId());
7250
7251 // Sanity check
7252 Date currentDate = new Date();
7253 if (!oldestDate.before(currentDate)) {
7254 String msg = "oldestDate is after the current date. No month list will be generated.";
7255 throw new IllegalArgumentException(msg);
7256 }
7257
7258 // Going backwards in time, all months, up to and including the month
7259 // that contains the oldest date
7260 Date startOfTheMonth = DateUtils.truncate(new Date(), Calendar.MONTH);
7261 Date endOfTheMonth = DateUtils.addMonths(startOfTheMonth, 1);
7262
7263 while (endOfTheMonth.after(oldestDate) || localTzRanges.isEmpty()) {
7264 startOfTheMonth = DateUtils.addMonths(endOfTheMonth, -1);
7265
7266 // The dateRange we receive is in the format:
7267 // 1 Mar 2015 00:00:00 - 1 Apr 2015 00:00:00
7268 // So we need to subtract a milli sec
7269 // 1 Mar 2015 00:00:00 - 31 Mar 2015 23:59:59
7270 Date lastMillisOfMonth = new Date(endOfTheMonth.getTime() - 1);
7271
7272 Date[] dateRange = new Date[] { startOfTheMonth, lastMillisOfMonth };
7273 localTzRanges.add(dateRange);
7274
7275 endOfTheMonth = startOfTheMonth;
7276 }
7277
7278 return localTzRanges;
7279 }
7280
7281 /**
7282 * This method sends claims to PAS webservice, and receives a BillDTO in
7283 * response.
7284 *
7285 * @param claimBatch
7286 * @param monthDate
7287 * @return
7288 */
7289 private BillDTO sendClaimsToPASWebservice(ClaimBatchDTO claimBatchDTO, PayerType payer, boolean isDropClaimBatch,
7290 boolean isLiveBill, BillingExportOptions billType, ClaimsGenerationCache claimsCache) {
7291
7292 if (claimBatchDTO.getJaxbClaimBatch().getPatientClaim().isEmpty())
7293 return null;
7294
7295 log.info("*** sendClaimsToPASWebservice: SENDING " + claimBatchDTO.getJaxbClaimBatch().getPatientClaim().size()
7296 + " Claims to PASBe");
7297
7298 BillDTO billDto = new BillDTO();
7299
7300 // If we're dropping... call it with a bill type.
7301 if (isDropClaimBatch) {
7302 getReportFromPASBE(claimBatchDTO, payer, isDropClaimBatch, isLiveBill, billType);
7303 }
7304
7305 // We still need to invoke separately for prebill XML
7306 String rawReport = getReportFromPASBE(claimBatchDTO, payer, false, isLiveBill, null);
7307 rawReport = rawReport.replaceAll("\\p{C}", "").trim();
7308
7309 PreBillReport report = JAXB.unmarshal(new StreamSource(new StringReader(rawReport)), PreBillReport.class);
7310
7311 try {
7312 billDto = convertPasbeToDTO(report, isDropClaimBatch, isLiveBill, claimsCache);
7313 } catch (ParseException e) {
7314 throw new PASBillingReportException(e);
7315 }
7316
7317 if (billDto != null) {
7318 log.info("*** sendClaimsToPASWebservice: RECIEVED " + billDto.getPatients().size() + " Claims from PASBe");
7319 }
7320 return billDto;
7321 }
7322
7323 private String getReportFromPASBE(ClaimBatchDTO claimBatchDTO, PayerType payer, boolean isDropClaimBatch,
7324 boolean isLiveBill, BillingExportOptions billType) {
7325 ClaimBatch claimBatch = claimBatchDTO.getJaxbClaimBatch();
7326 String response = null;
7327 try {
7328 PasbeServiceClient client = new PasbeServiceClient(configService);
7329 if (claimBatch != null && CollectionUtils.isNotEmpty(claimBatch.getPatientClaim())) {
7330
7331 if (isDropClaimBatch && isLiveBill) {
7332 client.setDropFlag(true);
7333 }
7334 Site billSite = em.find(Site.class, claimBatchDTO.getSiteId());
7335 String pasBin = "NONE";
7336 if (billSite != null && billSite.getPasBin() != null && billSite.getPasBin().length() > 0) {
7337 pasBin = billSite.getPasBin();
7338 pasBin = pasBin.replaceAll("^bin", "");
7339 }
7340 // Sending claimBatch to PAS
7341 response = client.submitPASClaims(claimBatch, pasBin, payer, claimBatchDTO.isRoomAndBoardOnly(),
7342 billType);
7343
7344 if (response == null) {
7345 // There may have been a problem while connecting to PASBe
7346 if (claimBatch != null && claimBatch.getPatientClaim() != null
7347 && !claimBatch.getPatientClaim().isEmpty())
7348 log.severe("** There may have been a problem while connecting to PASBe!");
7349 }
7350
7351 }
7352 } catch (PASBillingReportException e) {
7353 log.severe("Billing engine failure " + e.toString());
7354 throw new PASBillingReportException(e);
7355 } catch (Exception e) {
7356 throw new PASBillingReportException(e);
7357 }
7358 return response;
7359 }
7360
7361 /**
7362 *
7363 * This method will fetch filescheduler entry from the database for a given
7364 * site,processname and current user. If any entry exist, It would proceed
7365 * with claim generation by changing the process flag to true(In Process).
7366 * If not, It would create new entry and proceed with claim generation.
7367 *
7368 * Generates claims for given payer types for the current site. All the
7369 * generated XMLs are archived in a tar file.
7370 */
7371
7372 @Override
7373 public void generateAndArchiveClaims(BillingOptions bilopts, boolean runAsync, boolean markClaimsSent) {
7374 FileScheduler fileScheduler = fileSchedulerService.getFileScheduler(auth.getCurrentUserSiteId(),
7375 FileProcessType.DOWNLOAD_CLAIMS);
7376 if (fileScheduler != null && fileScheduler.getFilePath() != null) {
7377 HBFileUtils.deleteFile(fileScheduler.getFilePath());
7378 }
7379 fileSchedulerService.updateScheduleStatus(true, auth.getCurrentUserSiteId(), FileProcessType.DOWNLOAD_CLAIMS,
7380 null);
7381 Site site = hospiceService.findSite(auth.getCurrentUserSiteId());
7382 BillingService billingService = (runAsync) ? sessionContext.getBusinessObject(BillingService.class) : this;
7383 billingService.archiveClaims(bilopts, runAsync, markClaimsSent, SecurityContextHolder.getContext(), site);
7384 }
7385
7386 public void archiveClaims(BillingOptions bilopts, boolean runAsync, boolean markClaimsSent,
7387 SecurityContext context, Site site) {
7388 SecurityContextHolder.setContext(context);
7389 File errorArchiveFile = null;
7390 FileArchiveDTO archieveDto = null;
7391 try {
7392 archieveDto = self.generateClaims(bilopts, runAsync, markClaimsSent, site.getId());
7393 if (archieveDto.getArchivePath() == null) {
7394 errorArchiveFile = ClaimUtil.archiveErrorClaims(archieveDto.getErrorMessage(),
7395 configService.getDataRoot(), site.getName());
7396 archieveDto.setFileName(errorArchiveFile.getName());
7397 archieveDto.setArchivePath(errorArchiveFile.getAbsolutePath());
7398 archieveDto.setFileSize(errorArchiveFile.length());
7399 }
7400 } catch (IOException e) {
7401 log.severe("Refused to create file on root path" + e.getMessage());
7402 }
7403 fileSchedulerService.updateScheduleStatus(false, site.getId(), FileProcessType.DOWNLOAD_CLAIMS, archieveDto);
7404 }
7405}