001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * Created on Jul 16, 2004
018: *
019: */
020: package org.kuali.module.pdp.xml.impl;
021:
022: import java.math.BigDecimal;
023: import java.sql.Timestamp;
024: import java.text.SimpleDateFormat;
025: import java.util.ArrayList;
026: import java.util.Calendar;
027: import java.util.Date;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031:
032: import org.apache.log4j.Logger;
033: import org.kuali.kfs.context.SpringContext;
034: import org.kuali.module.chart.bo.Account;
035: import org.kuali.module.chart.bo.ObjectCode;
036: import org.kuali.module.chart.bo.ProjectCode;
037: import org.kuali.module.chart.bo.SubAccount;
038: import org.kuali.module.chart.bo.SubObjCd;
039: import org.kuali.module.chart.service.AccountService;
040: import org.kuali.module.chart.service.ObjectCodeService;
041: import org.kuali.module.chart.service.ProjectCodeService;
042: import org.kuali.module.chart.service.SubAccountService;
043: import org.kuali.module.chart.service.SubObjectCodeService;
044: import org.kuali.module.pdp.bo.AccountingChange;
045: import org.kuali.module.pdp.bo.Batch;
046: import org.kuali.module.pdp.bo.Code;
047: import org.kuali.module.pdp.bo.CustomerProfile;
048: import org.kuali.module.pdp.bo.PaymentAccountDetail;
049: import org.kuali.module.pdp.bo.PaymentAccountHistory;
050: import org.kuali.module.pdp.bo.PaymentDetail;
051: import org.kuali.module.pdp.bo.PaymentGroup;
052: import org.kuali.module.pdp.bo.PaymentNoteText;
053: import org.kuali.module.pdp.bo.PaymentStatus;
054: import org.kuali.module.pdp.bo.PdpUser;
055: import org.kuali.module.pdp.dao.CustomerProfileDao;
056: import org.kuali.module.pdp.dao.PaymentFileLoadDao;
057: import org.kuali.module.pdp.dao.ReferenceDao;
058: import org.kuali.module.pdp.xml.PdpFileHandler;
059: import org.kuali.module.pdp.xml.XmlAccounting;
060: import org.kuali.module.pdp.xml.XmlDetail;
061: import org.kuali.module.pdp.xml.XmlGroup;
062: import org.kuali.module.pdp.xml.XmlHeader;
063: import org.kuali.module.pdp.xml.XmlTrailer;
064:
065: /**
066: * @author jsissom
067: */
068: public class DataLoadHandler implements PdpFileHandler {
069: private static Logger LOG = Logger.getLogger(DataLoadHandler.class);
070:
071: private static String HELD_TAX_EMPLOYEE_CD = "HTXE";
072: private static String HELD_TAX_NRA_CD = "HTXN";
073: private static String HELD_TAX_NRA_EMPL_CD = "HTXB";
074: private static String OPEN_CD = "OPEN";
075:
076: // Injected services
077: private AccountService accountService;
078: private SubAccountService subAccountService;
079: private ObjectCodeService objectCodeService;
080: private SubObjectCodeService subObjectCodeService;
081: private ProjectCodeService projectCodeService;
082: private PaymentFileLoadDao loadDao;
083: private CustomerProfileDao customerDao;
084: private ReferenceDao referenceDao;
085:
086: private Map acctgChngCds;
087: private PaymentStatus openStatus;
088: private PaymentStatus heldForNRA;
089: private PaymentStatus heldForEmployee;
090: private PaymentStatus heldForNRAEmployee;
091:
092: private CustomerProfile customer = null;
093: private Batch batch;
094:
095: // Data from the XML
096: private XmlHeader header;
097: private XmlTrailer trailer;
098:
099: private List errors = new ArrayList();
100: private boolean abort = false;
101: private String filename;
102: private PdpUser user;
103: private int count = 0;
104:
105: private boolean taxEmailRequired = false;
106:
107: private Timestamp now;
108: private Calendar nowPlus30;
109: private Calendar nowMinus30;
110:
111: // ADDED for Threshold E-Mail
112: private Boolean detailThreshold;
113: private List detailThresholdMessages;
114: private Boolean fileThreshold;
115: private String fileThresholdMessage;
116:
117: //
118:
119: public DataLoadHandler(XmlTrailer t) {
120: customerDao = SpringContext.getBean(CustomerProfileDao.class);
121: loadDao = SpringContext.getBean(PaymentFileLoadDao.class);
122: referenceDao = SpringContext.getBean(ReferenceDao.class);
123: accountService = SpringContext.getBean(AccountService.class);
124: subAccountService = SpringContext
125: .getBean(SubAccountService.class);
126: objectCodeService = SpringContext
127: .getBean(ObjectCodeService.class);
128: subObjectCodeService = SpringContext
129: .getBean(SubObjectCodeService.class);
130: projectCodeService = SpringContext
131: .getBean(ProjectCodeService.class);
132: fileThreshold = new Boolean(false);
133: detailThreshold = new Boolean(false);
134: detailThresholdMessages = new ArrayList();
135:
136: trailer = t;
137: }
138:
139: public void clear() {
140: header = null;
141: trailer = null;
142: errors.clear();
143: abort = false;
144: batch = null;
145: filename = null;
146: user = null;
147: count = 0;
148: }
149:
150: public Boolean getDetailThreshold() {
151: return detailThreshold;
152: }
153:
154: public Boolean getFileThreshold() {
155: return fileThreshold;
156: }
157:
158: public void setDetailThreshold(Boolean detailThreshold) {
159: this .detailThreshold = detailThreshold;
160: }
161:
162: public void setFileThreshold(Boolean fileThreshold) {
163: this .fileThreshold = fileThreshold;
164: }
165:
166: public List getDetailThresholdMessages() {
167: return detailThresholdMessages;
168: }
169:
170: public String getFileThresholdMessage() {
171: return fileThresholdMessage;
172: }
173:
174: public void setDetailThresholdMessages(List detailThresholdMessages) {
175: this .detailThresholdMessages = detailThresholdMessages;
176: }
177:
178: public void setFileThresholdMessage(String fileThresholdMessage) {
179: this .fileThresholdMessage = fileThresholdMessage;
180: }
181:
182: public void setMaxNoteLines(int lines) {
183: // Not needed
184: }
185:
186: // Manually set
187: public void setFilename(String f) {
188: filename = f;
189: }
190:
191: // Manually set
192: public void setUser(PdpUser u) {
193: user = u;
194: }
195:
196: public XmlHeader getHeader() {
197: return header;
198: }
199:
200: public XmlTrailer getTrailer() {
201: return trailer;
202: }
203:
204: public void setHeader(XmlHeader header) {
205: this .header = header;
206:
207: // Get the customer
208: customer = customerDao.get(header.getChart(), header.getOrg(),
209: header.getSubUnit());
210: if (customer == null) {
211: LOG
212: .error("setCreationDate() File uploaded with invalid customer: "
213: + header.getChart()
214: + "/"
215: + header.getOrg()
216: + "/" + header.getSubUnit());
217: setErrorMessage("Invalid Customer: " + header.getChart()
218: + "/" + header.getOrg() + "/" + header.getSubUnit());
219: abort = true;
220: return;
221: }
222:
223: // Create a batch
224: now = new Timestamp(new Date().getTime());
225:
226: nowPlus30 = Calendar.getInstance();
227: nowPlus30.setTime(now);
228: nowPlus30.add(Calendar.DATE, 30);
229:
230: nowMinus30 = Calendar.getInstance();
231: nowMinus30.setTime(now);
232: nowMinus30.add(Calendar.DATE, -30);
233:
234: Integer iCount = new Integer(trailer.getPaymentCount());
235:
236: batch = new Batch();
237: batch.setCustomerProfile(customer);
238: batch.setCustomerFileCreateTimestamp(new Timestamp(header
239: .getCreationDate().getTime()));
240: batch.setFileProcessTimestamp(now);
241: batch.setPaymentCount(iCount);
242: if (filename.length() > 30) {
243: batch.setPaymentFileName(filename.substring(0, 30));
244: } else {
245: batch.setPaymentFileName(filename);
246: }
247: batch.setPaymentTotalAmount(trailer.getPaymentTotalAmount());
248: batch.setSubmiterUser(user);
249: loadDao.createBatch(batch);
250:
251: // Load all the accounting change codes
252: acctgChngCds = referenceDao.getAllMap("AccountingChange");
253: openStatus = (PaymentStatus) referenceDao.getCode(
254: "PaymentStatus", OPEN_CD);
255: heldForEmployee = (PaymentStatus) referenceDao.getCode(
256: "PaymentStatus", HELD_TAX_EMPLOYEE_CD);
257: heldForNRA = (PaymentStatus) referenceDao.getCode(
258: "PaymentStatus", HELD_TAX_NRA_CD);
259: heldForNRAEmployee = (PaymentStatus) referenceDao.getCode(
260: "PaymentStatus", HELD_TAX_NRA_EMPL_CD);
261:
262: }
263:
264: public void setTrailer(XmlTrailer trailer) {
265: this .trailer = trailer;
266: if (trailer.getPaymentTotalAmount().compareTo(
267: customer.getFileThresholdAmount()) > 0) {
268: setErrorMessage("The total amount of this file ("
269: + trailer.getPaymentTotalAmount()
270: + ") is greater than the threshold amount ("
271: + customer.getFileThresholdAmount() + ")");
272: fileThreshold = new Boolean(true);
273: setFileThresholdMessage("The total amount of this file ("
274: + trailer.getPaymentTotalAmount()
275: + ") is greater than the threshold amount ("
276: + customer.getFileThresholdAmount() + ")");
277: }
278: }
279:
280: public void setGroup(XmlGroup group) {
281: if (abort) {
282: return; // Don't process detail if the header wasn't right
283: }
284:
285: PaymentGroup pg = group.getPaymentGroup();
286:
287: pg.setBatch(batch);
288: pg.setPaymentStatus(openStatus);
289: pg.setPayeeName(pg.getPayeeName().toUpperCase());
290:
291: // Set defaults for missing information
292: if (pg.getCombineGroups() == null) {
293: pg.setCombineGroups(Boolean.TRUE);
294: }
295: if (pg.getCampusAddress() == null) {
296: pg.setCampusAddress(Boolean.FALSE);
297: }
298: if (pg.getPymtAttachment() == null) {
299: pg.setPymtAttachment(Boolean.FALSE);
300: }
301: if (pg.getPymtSpecialHandling() == null) {
302: pg.setPymtSpecialHandling(Boolean.FALSE);
303: }
304: if (pg.getProcessImmediate() == null) {
305: pg.setProcessImmediate(Boolean.FALSE);
306: }
307: if (pg.getEmployeeIndicator() == null) {
308: pg.setEmployeeIndicator(Boolean.FALSE);
309: }
310: if (pg.getNraPayment() == null) {
311: pg.setNraPayment(Boolean.FALSE);
312: }
313: if (pg.getTaxablePayment() == null) {
314: pg.setTaxablePayment(Boolean.FALSE);
315: }
316:
317: // Tax Group Requirements for automatic Holding
318: if (customer.getNraReview().booleanValue()
319: && customer.getEmployeeCheck().booleanValue()
320: && pg.getEmployeeIndicator().booleanValue()
321: && pg.getNraPayment().booleanValue()) {
322: if (heldForNRAEmployee != null) {
323: pg.setPaymentStatus(heldForNRAEmployee);
324: this .setTaxEmailRequired(true);
325: }
326: } else if (customer.getEmployeeCheck().booleanValue()
327: && pg.getEmployeeIndicator().booleanValue()) {
328: if (heldForEmployee != null) {
329: pg.setPaymentStatus(heldForEmployee);
330: this .setTaxEmailRequired(true);
331: }
332: } else if (customer.getNraReview().booleanValue()
333: && pg.getNraPayment().booleanValue()) {
334: if (heldForNRA != null) {
335: pg.setPaymentStatus(heldForNRA);
336: this .setTaxEmailRequired(true);
337: }
338: }
339:
340: processGroupSoftEdits(pg);
341:
342: for (Iterator iter = group.getDetail().iterator(); iter
343: .hasNext();) {
344: XmlDetail item = (XmlDetail) iter.next();
345:
346: count++;
347: LOG.debug("setGroup() Detail Count " + count);
348:
349: // Fill in missing amounts if necessary
350: item.updateAmounts();
351:
352: PaymentDetail detail = item.getPaymentDetail();
353: detail.setPaymentGroup(pg);
354: pg.addPaymentDetails(detail);
355:
356: processDetailSoftEdits(detail);
357:
358: for (Iterator i = item.getAccounting().iterator(); i
359: .hasNext();) {
360: XmlAccounting acct = (XmlAccounting) i.next();
361: PaymentAccountDetail pad = acct
362: .getPaymentAccountDetail();
363: pad.setPaymentDetail(detail);
364:
365: processAccountSoftEdits(pad, now);
366: detail.addAccountDetail(pad);
367: }
368:
369: int noteCount = 1;
370: for (Iterator i = item.getPayment_text().iterator(); i
371: .hasNext();) {
372: String note = (String) i.next();
373:
374: PaymentNoteText pnt = new PaymentNoteText();
375: pnt.setPaymentDetail(detail);
376: pnt.setCustomerNoteLineNbr(new Integer(noteCount));
377: pnt.setCustomerNoteText(note);
378: detail.addNote(pnt);
379:
380: noteCount++;
381: }
382: }
383:
384: loadDao.createGroup(pg);
385: }
386:
387: private void processGroupSoftEdits(PaymentGroup group) {
388: // Check payment date
389: if (group.getPaymentDate() != null) {
390: Calendar payDate = Calendar.getInstance();
391: payDate.setTime(group.getPaymentDate());
392:
393: if (payDate.before(nowMinus30)) {
394: SimpleDateFormat sdf = new SimpleDateFormat(
395: "MM/dd/yyyy");
396: errors.add("Payment date of "
397: + sdf.format(group.getPaymentDate())
398: + " is more than 30 days ago");
399: }
400: if (payDate.after(nowPlus30)) {
401: SimpleDateFormat sdf = new SimpleDateFormat(
402: "MM/dd/yyyy");
403: errors.add("Payment date of "
404: + sdf.format(group.getPaymentDate())
405: + " is more than 30 days in the future");
406: }
407: } else {
408: group.setPaymentDate(now);
409: }
410: }
411:
412: private void processDetailSoftEdits(PaymentDetail detail) {
413: // BELOW LINE REPLACED DUE TO JIRA ISSUE KULPDP-33
414: // BigDecimal testAmount = detail.getCalculatedPaymentAmount();
415: // Check net payment amount
416: BigDecimal testAmount = detail.getNetPaymentAmount();
417: if (testAmount.compareTo(customer.getPaymentThresholdAmount()) > 0) {
418: errors.add("Detail amount " + testAmount
419: + " is greater than threshold");
420: detailThreshold = new Boolean(true);
421: detailThresholdMessages.add("Payment Detail to "
422: + detail.getPaymentGroup().getPayeeName()
423: + " with amount " + testAmount + "\n");
424: }
425:
426: // Set invoice date if it doesn't exist
427: if (detail.getInvoiceDate() == null) {
428: detail.setInvoiceDate(now);
429: }
430: }
431:
432: private PaymentAccountHistory newAccountHistory(String attName,
433: String newValue, String oldValue, Code changeCode) {
434: PaymentAccountHistory pah = new PaymentAccountHistory();
435: pah.setAcctAttributeName(attName);
436: pah.setAcctAttributeNewValue(newValue);
437: pah.setAcctAttributeOrigValue(oldValue);
438: pah.setAcctChangeDate(now);
439: pah.setAccountingChange((AccountingChange) changeCode);
440: return pah;
441: }
442:
443: private void replaceAccountingString(Code objChangeCd,
444: List changeRecords, PaymentAccountDetail pad) {
445: changeRecords.add(newAccountHistory("fin_coa_cd", customer
446: .getDefaultChartCode(), pad.getFinChartCode(),
447: objChangeCd));
448: changeRecords.add(newAccountHistory("account_nbr", customer
449: .getDefaultAccountNumber(), pad.getAccountNbr(),
450: objChangeCd));
451: changeRecords.add(newAccountHistory("sub_acct_nbr", customer
452: .getDefaultSubAccountNumber(), pad.getSubAccountNbr(),
453: objChangeCd));
454: changeRecords.add(newAccountHistory("fin_object_cd", customer
455: .getDefaultObjectCode(), pad.getFinObjectCode(),
456: objChangeCd));
457: changeRecords.add(newAccountHistory("fin_sub_obj_cd", customer
458: .getDefaultSubObjectCode(), pad.getFinSubObjectCode(),
459: objChangeCd));
460:
461: pad.setFinChartCode(customer.getDefaultChartCode());
462: pad.setAccountNbr(customer.getDefaultAccountNumber());
463: pad.setSubAccountNbr(customer.getDefaultSubAccountNumber());
464: pad.setFinObjectCode(customer.getDefaultObjectCode());
465: pad.setFinSubObjectCode(customer.getDefaultSubObjectCode());
466: }
467:
468: private void processAccountSoftEdits(PaymentAccountDetail pad,
469: Timestamp now) {
470: List changeRecords = new ArrayList();
471: boolean replacement = false;
472:
473: // Lookup accounting info in the FIS
474: if (customer.getAccountingEditRequired().booleanValue()) {
475: // Check account number
476: Account acct = accountService.getByPrimaryId(pad
477: .getFinChartCode(), pad.getAccountNbr());
478: if (acct == null) {
479: // Put in account number from customer profile
480: errors
481: .add("Account number "
482: + pad.getFinChartCode()
483: + "-"
484: + pad.getAccountNbr()
485: + " is not valid. Replaced with default accounting string");
486:
487: Code objChangeCd = (Code) acctgChngCds.get("ACCT");
488: replaceAccountingString(objChangeCd, changeRecords, pad);
489: replacement = true;
490: }
491:
492: if (!replacement) {
493: // Check sub account code
494: if (pad.getSubAccountNbr() != null) {
495: SubAccount sa = subAccountService.getByPrimaryId(
496: pad.getFinChartCode(), pad.getAccountNbr(),
497: pad.getSubAccountNbr());
498: if (sa == null) {
499: // Get rid of sub account
500: errors.add("Sub Account code "
501: + pad.getFinChartCode() + "-"
502: + pad.getAccountNbr() + "-"
503: + pad.getSubAccountNbr()
504: + " is invalid. Removed");
505:
506: changeRecords.add(newAccountHistory(
507: "sub_acct_nbr", "-----", pad
508: .getSubAccountNbr(),
509: (Code) acctgChngCds.get("SA")));
510: pad.setSubAccountNbr("-----");
511: }
512: }
513: }
514:
515: if (!replacement) {
516: // Check object code
517: ObjectCode oc = objectCodeService
518: .getByPrimaryIdForCurrentYear(pad
519: .getFinChartCode(), pad
520: .getFinObjectCode());
521: if (oc == null) {
522: // Put in object from customer profile
523: errors
524: .add("Object code "
525: + pad.getFinChartCode()
526: + "-"
527: + pad.getFinObjectCode()
528: + " is invalid. Replaced with default accounting string");
529:
530: Code objChangeCd = (Code) acctgChngCds.get("OBJ");
531: replaceAccountingString(objChangeCd, changeRecords,
532: pad);
533: replacement = true;
534: }
535: }
536:
537: if (!replacement) {
538: // Check sub object code
539: if (pad.getFinSubObjectCode() != null) {
540: SubObjCd soc = subObjectCodeService
541: .getByPrimaryIdForCurrentYear(pad
542: .getFinChartCode(), pad
543: .getAccountNbr(), pad
544: .getFinObjectCode(), pad
545: .getFinSubObjectCode());
546: if (soc == null) {
547: // Get rid of sub object
548: errors.add("Sub Object code "
549: + pad.getFinChartCode() + "-"
550: + pad.getAccountNbr() + "-"
551: + pad.getFinObjectCode() + "-"
552: + pad.getFinSubObjectCode()
553: + " is invalid. Removed");
554:
555: changeRecords.add(newAccountHistory(
556: "fin_subobj_cd", "---", pad
557: .getFinSubObjectCode(),
558: (Code) acctgChngCds.get("SO")));
559: pad.setFinSubObjectCode("---");
560: }
561: }
562: }
563:
564: // Check project code
565: if (pad.getProjectCode() != null) {
566: ProjectCode pc = projectCodeService.getByName(pad
567: .getProjectCode());
568: if (pc == null) {
569: // Get rid of project
570: errors.add("Project code " + pad.getProjectCode()
571: + " is invalid. Removed");
572:
573: changeRecords.add(newAccountHistory("project_cd",
574: "----------", pad.getProjectCode(),
575: (Code) acctgChngCds.get("PROJ")));
576:
577: pad.setProjectCode("----------");
578: }
579: }
580: }
581:
582: // Change nulls into ---'s for the fields that need it
583: if (pad.getFinSubObjectCode() == null) {
584: pad.setFinSubObjectCode("---");
585: }
586: if (pad.getSubAccountNbr() == null) {
587: pad.setSubAccountNbr("-----");
588: }
589: if (pad.getProjectCode() == null) {
590: pad.setProjectCode("----------");
591: }
592:
593: // Add them all to the payment detail
594: for (Iterator i = changeRecords.iterator(); i.hasNext();) {
595: PaymentAccountHistory pah = (PaymentAccountHistory) i
596: .next();
597:
598: pah.setPaymentAccountDetail(pad);
599: pad.addAccountHistory(pah);
600: }
601: }
602:
603: public void setErrorMessage(String message) {
604: errors.add(message);
605: }
606:
607: public List getErrorMessages() {
608: return errors;
609: }
610:
611: public Batch getBatch() {
612: return batch;
613: }
614:
615: /**
616: * @return Returns the taxEmailRequired.
617: */
618: public boolean isTaxEmailRequired() {
619: return taxEmailRequired;
620: }
621:
622: /**
623: * @param taxEmailRequired The taxEmailRequired to set.
624: */
625: public void setTaxEmailRequired(boolean taxEmailRequired) {
626: this.taxEmailRequired = taxEmailRequired;
627: }
628: }
|