/** * Copyright © ${project.inceptionYear} Instituto Superior Técnico * * This file is part of Fenix IST. * * Fenix IST is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fenix IST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Fenix IST. If not, see <http://www.gnu.org/licenses/>. */ package pt.ist.fenix.util.sibs; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.fenixedu.academic.domain.accounting.PaymentCode; import org.fenixedu.academic.util.Money; import org.joda.time.DateTime; import org.joda.time.YearMonthDay; import pt.ist.fenix.domain.accounting.events.export.PrintedPaymentCodes; /** * * @author naat * */ public class SibsOutgoingPaymentFile { private static final String DATE_FORMAT = "yyyyMMdd"; private static final String NUMBER_FILLER = "0"; private static final String LINE_TERMINATOR = "\r\n"; private static class Header { private static final String HEADER_REGISTER_TYPE = "0"; private static final String FILE_TYPE = "AEPS"; private static final String OMISSION_SEQUENCE_NUMBER = "1"; private static final String CURRENCY_CODE = "978"; private static final int WHITE_SPACES_IN_HEADER = 3; private String sourceInstitutionId; private String destinationInstitutionId; private String entityCode; private DateTime lastSentPaymentFile; public Header(String sourceInstitutionId, String destinationInstitutionId, String entityCode) { this.sourceInstitutionId = sourceInstitutionId; this.destinationInstitutionId = destinationInstitutionId; this.entityCode = entityCode; } public Header(String sourceInstitutionId, String destinationInstitutionId, String entityCode, DateTime lastSuccessfulSentDate) { this.sourceInstitutionId = sourceInstitutionId; this.destinationInstitutionId = destinationInstitutionId; this.entityCode = entityCode; this.lastSentPaymentFile = lastSuccessfulSentDate; } public String render() { final StringBuilder header = new StringBuilder(); header.append(HEADER_REGISTER_TYPE); header.append(FILE_TYPE); header.append(this.sourceInstitutionId); header.append(this.destinationInstitutionId); header.append(new YearMonthDay().toString(DATE_FORMAT)); header.append(OMISSION_SEQUENCE_NUMBER); // last file's data if it was already sent header.append(lastSentPaymentFile != null ? lastSentPaymentFile.toString(DATE_FORMAT) : "00000000"); header.append(OMISSION_SEQUENCE_NUMBER); header.append(this.entityCode); header.append(CURRENCY_CODE); header.append(StringUtils.leftPad("", WHITE_SPACES_IN_HEADER)); header.append(LINE_TERMINATOR); return header.toString(); } } private static class Footer { private static final String FOOTER_REGISTER_TYPE = "9"; private static final int NUMBER_OF_LINES_DESCRIPTOR_LENGTH = 8; public static final int WHITE_SPACES_IN_FOOTER = 41; public Footer() { } public String render(int totalLines) { final StringBuilder footer = new StringBuilder(); footer.append(FOOTER_REGISTER_TYPE); footer.append(StringUtils.leftPad(String.valueOf(totalLines), NUMBER_OF_LINES_DESCRIPTOR_LENGTH, NUMBER_FILLER)); footer.append(StringUtils.leftPad("", WHITE_SPACES_IN_FOOTER)); footer.append(LINE_TERMINATOR); return footer.toString(); } } private static class Line { private static final String LINE_REGISTER_TYPE = "1"; // Line Processing code (usually 80 but it can be 82) private static final String LINE_PROCESSING_CODE = "80"; private static int DECIMAL_PLACES_FACTOR = 100; private static final int WHITE_SPACES_IN_LINE = 2; private static final int AMOUNT_LENGTH = 10; private String code; private Money minAmount; private Money maxAmount; private YearMonthDay startDate; private YearMonthDay endDate; public Line(String code, Money minAmount, Money maxAmount, YearMonthDay startDate, YearMonthDay endDate) { checkAmounts(code, minAmount, maxAmount); this.code = code; this.minAmount = minAmount; this.maxAmount = maxAmount; this.startDate = startDate; this.endDate = endDate; } private void checkAmounts(String code, Money minAmount, Money maxAmount) { if (minAmount.lessThan(Money.ZERO)) { throw new RuntimeException(MessageFormat.format("Min amount for code {0} must be greater than zero", code)); } if (maxAmount.lessOrEqualThan(Money.ZERO)) { throw new RuntimeException(MessageFormat.format("Max amount for code {0} must be greater than zero", code)); } } public String render() { final StringBuilder result = new StringBuilder(); result.append(LINE_REGISTER_TYPE); result.append(LINE_PROCESSING_CODE); result.append(this.code); result.append(this.endDate.toString(DATE_FORMAT)); result.append(leftPadAmount(this.maxAmount)); result.append(this.startDate.toString(DATE_FORMAT)); result.append(leftPadAmount(this.minAmount)); result.append(StringUtils.leftPad("", WHITE_SPACES_IN_LINE)); result.append(LINE_TERMINATOR); return result.toString(); } private String leftPadAmount(final Money amount) { return StringUtils.leftPad(String.valueOf(amount.multiply(BigDecimal.valueOf(DECIMAL_PLACES_FACTOR)).longValue()), AMOUNT_LENGTH, NUMBER_FILLER); } } private Header header; private List<Line> lines; private Footer footer; private Set<String> existingCodes; PrintedPaymentCodes associatedPaymentCodes; public SibsOutgoingPaymentFile(String sourceInstitutionId, String destinationInstitutionId, String entity) { this.header = new Header(sourceInstitutionId, destinationInstitutionId, entity); this.lines = new ArrayList<Line>(); this.footer = new Footer(); this.existingCodes = new HashSet<String>(); this.associatedPaymentCodes = new PrintedPaymentCodes(); } public SibsOutgoingPaymentFile(String sourceInstitutionId, String destinationInstitutionId, String entity, DateTime lastSuccessfulSentDate) { this.header = new Header(sourceInstitutionId, destinationInstitutionId, entity, lastSuccessfulSentDate); this.lines = new ArrayList<Line>(); this.footer = new Footer(); this.existingCodes = new HashSet<String>(); this.associatedPaymentCodes = new PrintedPaymentCodes(); } public void addAssociatedPaymentCode(final PaymentCode paymentCode) { this.associatedPaymentCodes.addPaymentCode(paymentCode); } public PrintedPaymentCodes getAssociatedPaymentCodes() { return associatedPaymentCodes; } public void addLine(String code, Money minAmount, Money maxAmount, YearMonthDay startDate, YearMonthDay endDate) { if (existingCodes.contains(code)) { throw new RuntimeException(MessageFormat.format("Code {0} is duplicated", code)); } existingCodes.add(code); this.lines.add(new Line(code, minAmount, maxAmount, startDate, endDate)); } public String render() { final StringBuilder result = new StringBuilder(); result.append(this.header.render()); for (final Line line : this.lines) { result.append(line.render()); } result.append(this.footer.render(this.lines.size())); return result.toString(); } @Override public String toString() { return render(); } public void save(final File destinationFile) { BufferedOutputStream outputStream = null; try { outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile)); outputStream.write(render().getBytes()); outputStream.flush(); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } }