All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.dvbern.oss.lib.iso20022.camt.CamtServiceBean Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 DV Bern AG
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * limitations under the License.
 */

package ch.dvbern.oss.lib.iso20022.camt;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

import javax.xml.transform.stream.StreamSource;

import ch.dvbern.oss.lib.iso20022.Iso20022Util;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.CamtDocument;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.Notification;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.header.GroupHeader;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.header.MessagePagination;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.StatementOrNotification;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.Entry;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.TransactionDetails;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.parties.PartyOrDeptor;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.parties.PostalAddress;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.parties.RelatedParties;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.remittenceinformation.CreditorReferenceInformation;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.remittenceinformation.RemittanceInformation;
import ch.dvbern.oss.lib.iso20022.camt.xsdinterfaces.notification.statement.entry.details.remittenceinformation.StructuredRemittanceInformation;
import ch.dvbern.oss.lib.iso20022.dtos.camt.Account;
import ch.dvbern.oss.lib.iso20022.dtos.camt.Booking;
import ch.dvbern.oss.lib.iso20022.dtos.camt.DocumentDTO;
import ch.dvbern.oss.lib.iso20022.dtos.camt.IsrTransaction;
import ch.dvbern.oss.lib.iso20022.dtos.camt.MessageIdentifier;
import ch.dvbern.oss.lib.iso20022.dtos.shared.TransactionInformationDTO;
import ch.dvbern.oss.lib.iso20022.exceptions.Iso20022RuntimeException;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Objects.requireNonNull;

/**
 * This Service reads an xml document complying to camt054 version 00104 standard and returns parts or the whole
 * document
 */
@ApplicationScoped
public class CamtServiceBean implements CamtService {

	private static final Logger LOG = LoggerFactory.getLogger(CamtServiceBean.class);

	/**
	 * Returns the Notification part of the xml input as Java object.
	 *
	 * @param xmlAsBytes xml content
	 * @return object of type BankToCustomerDebitCreditNotificationV04
	 */
	@Nonnull
	@Override
	public CamtDocument getNotificationFromXml(@Nonnull byte[] xmlAsBytes) {
		CamtTypeVersion camtTypeVersion = CamtUtil.detectCamtTypeVersion(xmlAsBytes);

		return unmarshallNotificationFromXml(xmlAsBytes, camtTypeVersion);
	}

	/**
	 * Reads an xml file and returns data about booked credit notes with reference number (no reversals).
	 *
	 * @param xmlAsBytes xml content
	 * @return object of type DocumentDTO containing data about booked ESR notifications
	 */
	@Nonnull
	@Override
	public DocumentDTO getCreditingRecords(@Nonnull byte[] xmlAsBytes) throws Iso20022RuntimeException {
		CamtTypeVersion camtTypeVersion = CamtUtil.detectCamtTypeVersion(xmlAsBytes);

		CamtDocument document = unmarshallNotificationFromXml(xmlAsBytes, camtTypeVersion);
		Notification notification = document.getNotification();

		return toDocument(requireNonNull(notification), camtTypeVersion);
	}

	@Nonnull
	private CamtDocument unmarshallNotificationFromXml(byte[] xmlAsBytes, @Nonnull CamtTypeVersion camtTypeVersion) {

		try (ByteArrayInputStream xmlAsStream = new ByteArrayInputStream(xmlAsBytes)) {
			JAXBContext jaxbContext = JAXBContext.newInstance(camtTypeVersion.getDocumentClass());
			Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();

			StreamSource streamSource = new StreamSource(xmlAsStream);
			JAXBElement doc =
				jaxbUnmarshaller.unmarshal(streamSource, camtTypeVersion.getDocumentClass());

			return doc.getValue();
		} catch (JAXBException e) {
			throw new Iso20022RuntimeException("Document can not be parsed by JAXB", e);
		} catch (IOException e) {
			throw new Iso20022RuntimeException("IO Exception while unmarshalling notification from xml", e);
		}
	}

	@Nonnull
	private DocumentDTO toDocument(@Nonnull Notification notification, @Nonnull CamtTypeVersion camtTypeVersion) {
		MessageIdentifier messageIdentifier = toMessageIdentifier(notification.getGrpHdr());
		DocumentDTO documentDTO = new DocumentDTO(camtTypeVersion, messageIdentifier);

		List accountNotification = notification.getAccountNotification();

		if (accountNotification.size() > 1) {
			// According to the swiss implementation guideline there should be only one notification per document
			LOG.warn("More than one Account Statement present in {}", documentDTO);
		}

		accountNotification.stream()
			.filter(n -> n.getAcct().getId().getIBAN() != null)
			.map(n -> toAccount(n, documentDTO))
			.forEach(dto -> documentDTO.getAccounts().add(dto));

		return documentDTO;
	}

	@Nonnull
	private MessageIdentifier toMessageIdentifier(@Nonnull GroupHeader grpHdr) {
		MessagePagination pagination = grpHdr.getMsgPgntn();

		if (pagination == null) {
			return new MessageIdentifier(grpHdr.getMsgId(), Iso20022Util.from(grpHdr.getCreDtTm()));
		}

		return new MessageIdentifier(
			grpHdr.getMsgId(),
			Iso20022Util.from(grpHdr.getCreDtTm()),
			pagination.getPgNb(),
			pagination.isLastPgInd()
		);
	}

	@Nonnull
	private Account toAccount(@Nonnull StatementOrNotification notification, @Nonnull DocumentDTO document) {
		Account account = new Account(
			document,
			notification.getId(),
			notification.getElctrncSeqNb(),
			requireNonNull(notification.getAcct().getId().getIBAN()),
			Iso20022Util.from(notification.getCreDtTm())
		);

		// 'Statement Entry' (C-Level) (0..n)
		notification.getNtry().stream()
			.filter(e -> {
				if (e.isCreditingEntry() && e.isBookedEntry() && !e.isReversal()) {
					return true;
				}
				LOG.debug("Skipped Statement Entry with Credit Debit Indicator {}, Status {}, Reversal Indicator {}",
					e.getCdtDbtInd(), e.getSts(), e.isRvslInd());
				return false;
			})
			.map(reportEntry4 -> toBooking(reportEntry4, account))
			.forEach(c -> account.getBookings().add(c));

		return account;
	}

	@Nonnull
	private Booking toBooking(@Nonnull Entry reportEntry4, @Nonnull Account account) {
		Booking booking = new Booking(
			account,
			requireNonNull(Iso20022Util.from(reportEntry4.getBookgDt())),
			requireNonNull(Iso20022Util.from(reportEntry4.getValDt())),
			reportEntry4.getNtryRef()
		);

		reportEntry4.getNtryDtls().stream()
			.flatMap(entryDetails3 -> entryDetails3.getTxDtls().stream())
			.filter(this::isIsrTransaction)
			.map(entryTransaction4 -> toTransaction(entryTransaction4, booking))
			.forEach(transaction -> booking.getTransactions().add(transaction));

		return booking;
	}

	@Nonnull
	private IsrTransaction toTransaction(@Nonnull TransactionDetails entryTransaction4, @Nonnull Booking booking) {
		String referenceNumber = findIsrRemittanceInfo(requireNonNull(entryTransaction4.getRmtInf()))
			.map(StructuredRemittanceInformation::getCdtrRefInf)
			.map(CreditorReferenceInformation::getRef)
			.orElseThrow(() -> new IllegalStateException("This should have been checked earlier"));

		return new IsrTransaction(
			booking,
			entryTransaction4.getAmt().getCcy(),
			entryTransaction4.getAmt().getValue(),
			referenceNumber,
			toTransactionDetails(entryTransaction4.getRltdPties())
		);
	}

	@Nullable
	private TransactionInformationDTO toTransactionDetails(@Nullable RelatedParties transactionParties) {
		if (transactionParties == null) {
			return null;
		}

		TransactionInformationDTO transactionInformationDTO = new TransactionInformationDTO();

		Optional.ofNullable(transactionParties.getDbtr())
			.map(PartyOrDeptor::getParty)
			.ifPresent(party -> {
				String name = party.getName();
				if (name != null && !name.equalsIgnoreCase(INVALID_NAME)) {
					transactionInformationDTO.setDebitorName(name);
				}
				toDbtrPostalDetails(party.getPstlAdr(), transactionInformationDTO);
			});

		if (transactionParties.getDbtrAcct() != null) {
			transactionInformationDTO.setDebitorIBAN(transactionParties.getDbtrAcct().getId().getIBAN());
		}

		return transactionInformationDTO;
	}

	private void toDbtrPostalDetails(
		@Nullable PostalAddress postalAddress,
		@Nonnull TransactionInformationDTO transactionInformationDTO) {
		if (postalAddress == null) {
			return;
		}

		transactionInformationDTO.setDebitorBuildingNumber(postalAddress.getBldgNb());
		transactionInformationDTO.setDebitorStreetName(postalAddress.getStrtNm());
		transactionInformationDTO.setDebitorPostCode(postalAddress.getPstCd());
		transactionInformationDTO.setDebitorTownName(postalAddress.getTwnNm());
		transactionInformationDTO.setDebitorCountry(postalAddress.getCtry());
	}

	private boolean isIsrTransaction(@Nonnull TransactionDetails transaction) {
		if (transaction.getRmtInf() == null) {
			return false;
		}

		return findIsrRemittanceInfo(transaction.getRmtInf()).isPresent();
	}

	@Nonnull
	private Optional findIsrRemittanceInfo(@Nonnull RemittanceInformation info) {
		// bookings can only be assigned automatically to bills if the referenceId is present thus we return only these
		return info.getStrd().stream()
			.filter(this::hasReferenceNumber)
			.findAny();
	}

	private boolean hasReferenceNumber(@Nullable StructuredRemittanceInformation structuredRemittanceInformation9) {
		return Optional.ofNullable(structuredRemittanceInformation9)
			.map(StructuredRemittanceInformation::getCdtrRefInf)
			.map(CreditorReferenceInformation::getRef)
			.isPresent();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy