Java tutorial
/* * Version: 1.0 * * The contents of this file are subject to the OpenVPMS License Version * 1.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.openvpms.org/license/ * * Software distributed under the License is distributed on an 'AS IS' basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Copyright 2010 (C) OpenVPMS Ltd. All Rights Reserved. * * $Id$ */ package org.openvpms.esci.adapter.map.invoice; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openvpms.archetype.rules.math.Currencies; import org.openvpms.archetype.rules.math.Currency; import org.openvpms.archetype.rules.math.MathRules; import org.openvpms.archetype.rules.practice.PracticeRules; import org.openvpms.archetype.rules.product.ProductRules; import org.openvpms.archetype.rules.product.ProductSupplier; import org.openvpms.archetype.rules.supplier.SupplierArchetypes; import org.openvpms.archetype.rules.supplier.SupplierRules; import org.openvpms.archetype.rules.util.DateRules; import org.openvpms.archetype.rules.util.DateUnits; import org.openvpms.component.business.domain.im.act.FinancialAct; import org.openvpms.component.business.domain.im.common.Entity; import org.openvpms.component.business.domain.im.common.IMObjectReference; import org.openvpms.component.business.domain.im.party.Party; import org.openvpms.component.business.domain.im.product.Product; import org.openvpms.component.business.service.archetype.ArchetypeServiceException; import org.openvpms.component.business.service.archetype.helper.ActBean; import org.openvpms.component.business.service.archetype.helper.EntityBean; import org.openvpms.component.business.service.archetype.helper.IMObjectBeanFactory; import org.openvpms.component.business.service.lookup.ILookupService; import org.openvpms.component.system.common.exception.OpenVPMSException; import org.openvpms.component.system.common.query.ArchetypeQuery; import org.openvpms.component.system.common.query.Constraints; import org.openvpms.component.system.common.query.IMObjectQueryIterator; import org.openvpms.esci.adapter.i18n.ESCIAdapterMessages; import org.openvpms.esci.adapter.map.AbstractUBLMapper; import org.openvpms.esci.adapter.map.ErrorContext; import org.openvpms.esci.adapter.map.UBLHelper; import org.openvpms.esci.adapter.map.UBLType; import org.openvpms.esci.adapter.util.ESCIAdapterException; import org.openvpms.esci.ubl.invoice.InvoiceType; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.Date; import java.util.List; /** * Maps UBL invoices to <em>act.supplierDelivery</em> acts. * * @author <a href="mailto:support@openvpms.org">OpenVPMS Team</a> * @version $LastChangedDate: 2006-05-02 05:16:31Z $ */ public class InvoiceMapperImpl extends AbstractUBLMapper implements InvoiceMapper { /** * The practice rules. */ private PracticeRules practiceRules; /** * The lookup service. */ private ILookupService lookupService; /** * The supplier rules. */ private SupplierRules supplierRules; /** * The product rules. */ private ProductRules productRules; /** * The currencies. */ private Currencies currencies; /** * The bean factory. */ private IMObjectBeanFactory factory; /** * The package helper. */ private PackageHelper packageHelper; /** * The logger. */ private static final Log log = LogFactory.getLog(InvoiceMapperImpl.class); /** * Default constructor. */ public InvoiceMapperImpl() { } /** * Registers the practice rules. * * @param rules the practice rules */ @Resource public void setPracticeRules(PracticeRules rules) { practiceRules = rules; } /** * Registers the lookup service. * * @param service the lookup service */ @Resource public void setLookupService(ILookupService service) { lookupService = service; } /** * Registers the supplier rules. * * @param rules the supplier rules */ @Resource public void setSupplierRules(SupplierRules rules) { supplierRules = rules; } /** * Registers the product rules. * * @param rules the product rules */ @Resource public void setProductRules(ProductRules rules) { productRules = rules; } /** * Registers the currencies. * * @param currencies the currencies */ @Resource public void setCurrencies(Currencies currencies) { this.currencies = currencies; } /** * Registers the bean factory. * * @param factory the bean factory */ @Resource public void setBeanFactory(IMObjectBeanFactory factory) { this.factory = factory; } /** * Maps an UBL invoice to an <em>act.supplierDelivery</em>. * * @param invoice the invoice to map * @param supplier the supplier that submitted the invoice * @param stockLocation the stock location * @param accountId the supplier assigned account identifier. May be <tt>null</tt> * @return the acts produced in the mapping. The first element is always the <em>act.supplierDelivery</em> * @throws ESCIAdapterException if the invoice cannot be mapped * @throws OpenVPMSException for any OpenVPMS error */ public Delivery map(InvoiceType invoice, Party supplier, Party stockLocation, String accountId) { Delivery result = new Delivery(); Currency practiceCurrency = UBLHelper.getCurrency(practiceRules, currencies, factory); packageHelper = new PackageHelper(productRules, lookupService, factory); TaxRates rates = new TaxRates(lookupService, factory); UBLInvoice wrapper = new UBLInvoice(invoice, practiceCurrency.getCode(), getArchetypeService(), supplierRules); String invoiceId = wrapper.getID(); checkUBLVersion(wrapper); Date issueDatetime = wrapper.getIssueDatetime(); String notes = wrapper.getNotes(); wrapper.checkSupplier(supplier, accountId); wrapper.checkStockLocation(stockLocation, accountId); checkDuplicateInvoice(supplier, invoiceId, issueDatetime); InvoiceOrderMatcher matcher = new InvoiceOrderMatcher(getArchetypeService(), factory); MappingContext context = matcher.match(wrapper, supplier, stockLocation); result.setOrder(context.getDocumentOrder()); BigDecimal payableAmount = wrapper.getPayableAmount(); BigDecimal invoiceLineExtensionAmount = wrapper.getLineExtensionAmount(); BigDecimal chargeTotal = wrapper.getChargeTotal(); BigDecimal taxExclusiveAmount = wrapper.getTaxExclusiveAmount(); BigDecimal taxTotal = wrapper.getTaxAmount(); ActBean delivery = factory.createActBean(SupplierArchetypes.DELIVERY); delivery.setValue("startTime", issueDatetime); delivery.setValue("supplierNotes", notes); delivery.setValue("amount", payableAmount); delivery.setValue("tax", taxTotal); delivery.setValue("supplierInvoiceId", invoiceId); delivery.addNodeParticipation("supplier", supplier); delivery.addNodeParticipation("stockLocation", stockLocation); result.setDelivery((FinancialAct) delivery.getAct()); List<InvoiceLineState> lines = context.getInvoiceLines(); if (lines.isEmpty()) { throw new ESCIAdapterException( ESCIAdapterMessages.ublInvalidCardinality("InvoiceLine", "Invoice", invoiceId, "1..*", 0)); } BigDecimal itemTaxIncTotal; BigDecimal itemTaxExTotal = BigDecimal.ZERO; BigDecimal itemTax = BigDecimal.ZERO; BigDecimal itemLineExtensionAmount = BigDecimal.ZERO; BigDecimal itemCharge = BigDecimal.ZERO; for (InvoiceLineState state : lines) { UBLInvoiceLine line = state.getLine(); BigDecimal amount = line.getLineExtensionAmount(); FinancialAct item = mapInvoiceLine(state, issueDatetime, rates, context); getArchetypeService().deriveValues(item); delivery.addNodeRelationship("items", item); result.addDeliveryItem(item); itemLineExtensionAmount = itemLineExtensionAmount.add(amount); itemTax = itemTax.add(item.getTaxAmount()); itemTaxExTotal = itemTaxExTotal.add(item.getTotal().subtract(item.getTaxAmount())); } for (FinancialAct relatedOrder : context.getOrders()) { delivery.addNodeRelationship("orders", relatedOrder); } Entity author = getAuthor(context); if (author != null) { delivery.addNodeParticipation("author", author); } // map any charges for (UBLAllowanceCharge allowanceCharge : wrapper.getAllowanceCharges()) { // only support charges at present FinancialAct item = mapCharge(allowanceCharge, issueDatetime, invoiceId, rates); getArchetypeService().deriveValues(item); delivery.addNodeRelationship("items", item); result.addDeliveryItem(item); itemTax = itemTax.add(item.getTaxAmount()); itemCharge = itemCharge.add(item.getTotal()).subtract(item.getTaxAmount()); } if (chargeTotal.compareTo(itemCharge) != 0) { throw new ESCIAdapterException( ESCIAdapterMessages.invoiceInvalidChargeTotal(invoiceId, chargeTotal, itemCharge)); } itemTaxExTotal = itemTaxExTotal.add(itemCharge); itemTaxIncTotal = itemTaxExTotal.add(itemTax); if (taxTotal.compareTo(itemTax) != 0) { throw new ESCIAdapterException(ESCIAdapterMessages.invoiceInvalidTax(invoiceId, taxTotal, itemTax)); } if (itemLineExtensionAmount.compareTo(invoiceLineExtensionAmount) != 0) { throw new ESCIAdapterException(ESCIAdapterMessages.invoiceInvalidLineExtensionAmount(invoiceId, invoiceLineExtensionAmount, itemLineExtensionAmount)); } if (itemTaxExTotal.compareTo(taxExclusiveAmount) != 0) { throw new ESCIAdapterException(ESCIAdapterMessages.invoiceInvalidTaxExclusiveAmount(invoiceId, taxExclusiveAmount, itemTaxExTotal)); } if (payableAmount.compareTo(itemTaxIncTotal) != 0) { throw new ESCIAdapterException( ESCIAdapterMessages.invoiceInvalidPayableAmount(invoiceId, payableAmount, itemTaxIncTotal)); } return result; } /** * Determines if the invoice is a duplicate. This is limited to checking against deliveries for the supplier around * the invoice time, as there are no key fields to query. * <p/> * This will detect simple duplication (e.g failure to acknowlege an invoice), but will fail to detect invoices * that have been re-issued on a different day. * <p/> * The latter can be detected by {@link #checkOrder} if: * <ul> * <li>the invoice refers to orders; and * <li>the invoice doesn't refer to different orders to those previously * </ul> * Ideally, we'd keep track of the identifiers of all received documents, and reject any duplicates that way, * but there is currently no facility to do this. TODO * * @param supplier the supplier * @param invoiceId the invoice identifier * @param issueDatetime the invoice issue timestamp * @throws ESCIAdapterException if the invoice is a duplicate */ protected void checkDuplicateInvoice(Party supplier, String invoiceId, Date issueDatetime) { ArchetypeQuery query = new ArchetypeQuery(SupplierArchetypes.DELIVERY); query.add(Constraints.join("supplier").add(Constraints.eq("entity", supplier.getObjectReference()))); Date from = DateRules.getDate(issueDatetime); Date to = DateRules.getDate(DateRules.getDate(from, 1, DateUnits.DAYS)); query.add(Constraints.between("startTime", from, to)); IMObjectQueryIterator<FinancialAct> iter = new IMObjectQueryIterator<FinancialAct>(getArchetypeService(), query); while (iter.hasNext()) { FinancialAct delivery = iter.next(); ActBean bean = factory.createActBean(delivery); String supplierInvoiceId = bean.getString("supplierInvoiceId"); if (ObjectUtils.equals(supplierInvoiceId, invoiceId)) { throw new ESCIAdapterException(ESCIAdapterMessages.duplicateInvoice(invoiceId, delivery.getId())); } } } /** * Verifies that the TaxTotal/TaxAmount corresponds to that of the TaxTotal/TaxSubtotal. * <p/> * This implementation only supports a single TaxTotal/Subtotal and expects the TaxTotal/Subtotal/TaxableAmount * to match that supplied. * * @param line the invoice line * @param rates the tax rates * @throws ESCIAdapterException if the tax is incorrectly specified */ protected void checkTax(UBLInvoiceLine line, TaxRates rates) { BigDecimal expectedTaxAmount = line.getTaxAmount(); UBLTaxSubtotal subtotal = line.getTaxSubtotal(); checkTax(subtotal, expectedTaxAmount, line.getLineExtensionAmount(), rates, line); } /** * Verfies that a tax subtotal matches that expected, and has a valid rate. * * * @param subtotal the subtotal * @param expectedTaxAmount the expected tax amount * @param amount the line extension amount * @param rates the tax rates * @param parent the parent element * @throws ESCIAdapterException if the subtotal is invalid */ protected void checkTax(UBLTaxSubtotal subtotal, BigDecimal expectedTaxAmount, BigDecimal amount, TaxRates rates, UBLType parent) { if (subtotal != null) { BigDecimal taxAmount = subtotal.getTaxAmount(); if (expectedTaxAmount.compareTo(taxAmount) != 0) { ErrorContext context = new ErrorContext(subtotal, "TaxAmount"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), expectedTaxAmount.toString(), taxAmount.toString())); } UBLTaxCategory category = subtotal.getTaxCategory(); BigDecimal rate = checkTaxCategory(category, rates); BigDecimal divisor = BigDecimal.valueOf(100); if (taxAmount.compareTo(BigDecimal.ZERO) != 0) { BigDecimal calc = MathRules.divide(amount.multiply(rate), divisor, 2); if (calc.compareTo(expectedTaxAmount) != 0) { ErrorContext context = new ErrorContext(subtotal, "TaxTotal/TaxAmount"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), calc.toString(), expectedTaxAmount.toString())); } } } else if (expectedTaxAmount.compareTo(BigDecimal.ZERO) != 0) { ErrorContext context = new ErrorContext(parent, "TaxTotal"); throw new ESCIAdapterException( ESCIAdapterMessages.ublElementRequired(context.getPath(), context.getType(), context.getID())); } } /** * Verifies that a tax category matches that expected. * * @param category the tax category * @param rates the tax rates * @return the tax rate used * @throws ESCIAdapterException if the category is invalid */ protected BigDecimal checkTaxCategory(UBLTaxCategory category, TaxRates rates) { String taxCategory = category.getID(); BigDecimal rate = category.getTaxRate(); String taxScheme = category.getTaxTypeCode(); BigDecimal expectedRate = rates.getTaxRate(taxScheme, taxCategory); if (expectedRate == null) { ErrorContext context = new ErrorContext(category); throw new ESCIAdapterException(ESCIAdapterMessages.invalidTaxSchemeAndCategory(context.getPath(), context.getType(), context.getID(), taxScheme, taxCategory)); } if (expectedRate.compareTo(rate) != 0) { ErrorContext context = new ErrorContext(category, "Percent"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), expectedRate.toString(), rate.toString())); } return rate; } /** * Returns the associated order item for an invoice line, if an order line reference is specified. * <p/> * If there is: * <ul> * <li>a document-level order reference, then all order lines must reference this order * <li>no document level order reference, order references must be fully qualified i.e must specify both * the order line and order * <li>a document-level order reference, but no order line reference, the first order item matching the invoice item * will be returned * <ul> * * @param line the invoice line * @return the corresponding order item, or <tt>null</tt> if none is present */ protected FinancialAct mapOrderItem(InvoiceLineState line) { FinancialAct item = line.getOrderItem(); Product invoiced = line.getProduct(); if (item != null && invoiced != null) { // verify that the invoice item has the same product as ordered. If not, don't want to refer // to the order item in the delivery item. ActBean itemBean = factory.createActBean(item); IMObjectReference orderedProduct = itemBean.getNodeParticipantRef("product"); if (!ObjectUtils.equals(orderedProduct, invoiced.getObjectReference())) { log.warn("Invoiced product doesn't refer to that ordered"); // TODO - log context item = null; } } return item; } /** * Maps an <tt>InvoiceLineType</tt> to an <em>act.supplierDeliveryItem</em>. * * @param lineState the invoice line state * @param startTime the invoice start time * @param rates the tax rates * @param context the mapping context * @return a new <em>act.supplierDeliveryItem</em> corresponding to the invoice line * @throws ESCIAdapterException if the order wasn't submitted by the supplier * @throws ArchetypeServiceException for any archetype service error */ protected FinancialAct mapInvoiceLine(InvoiceLineState lineState, Date startTime, TaxRates rates, MappingContext context) { ActBean deliveryItem = factory.createActBean(SupplierArchetypes.DELIVERY_ITEM); UBLInvoiceLine line = lineState.getLine(); BigDecimal quantity = line.getInvoicedQuantity(); String invoicedUnitCode = line.getInvoicedQuantityUnitCode(); checkPackQuantity(line, invoicedUnitCode); checkBaseQuantity(line, invoicedUnitCode); Party supplier = context.getSupplier(); Product product = lineState.getProduct(); BigDecimal lineExtensionAmount = line.getLineExtensionAmount(); String reorderCode = line.getSellersItemID(); String reorderDescription = line.getItemName(); BigDecimal tax = line.getTaxAmount(); FinancialAct orderItem = mapOrderItem(lineState); Package pkg = packageHelper.getPackage(orderItem, product, supplier); ProductSupplier ps = (pkg != null) ? pkg.getProductSupplier() : null; BigDecimal unitPrice = line.getPriceAmount(); String packageUnits = getPackageUnits(invoicedUnitCode, pkg); int packageSize = getPackageSize(line, pkg); BigDecimal listPrice = getListPrice(line, product, ps, reorderCode, packageSize, packageUnits, context); BigDecimal calcLineExtensionAmount = unitPrice.multiply(quantity); if (calcLineExtensionAmount.compareTo(lineExtensionAmount) != 0) { throw new ESCIAdapterException(ESCIAdapterMessages.invoiceLineInvalidLineExtensionAmount(line.getID(), lineExtensionAmount, calcLineExtensionAmount)); } checkTax(line, rates); deliveryItem.setValue("supplierInvoiceLineId", line.getID()); deliveryItem.setValue("startTime", startTime); deliveryItem.setValue("quantity", quantity); deliveryItem.setValue("unitPrice", unitPrice); deliveryItem.setValue("listPrice", listPrice); deliveryItem.setValue("tax", tax); deliveryItem.setValue("packageUnits", packageUnits); deliveryItem.setValue("packageSize", packageSize); if (product != null) { deliveryItem.addNodeParticipation("product", product); } deliveryItem.setValue("reorderCode", reorderCode); deliveryItem.setValue("reorderDescription", reorderDescription); if (orderItem != null) { deliveryItem.addNodeRelationship("order", orderItem); } getArchetypeService().deriveValues(deliveryItem.getObject()); return (FinancialAct) deliveryItem.getAct(); } /** * Returns the list price. * <p/> * This is available as the wholesale price in the invoice line. * <p/> * If it is not present: * <ol> * <li>it will be defaulted to that from <tt>ps</tt>, if available * <li>if ps is not present or the value is 0, it will be defaulted to the unit price from the invoice line. * </ol> * * @param line the invoice line * @param product the product. May be <tt>null</tt> * @param ps the product/supplier relationship. May be <tt>null</tt> * @param reorderCode the reorder code. May be <tt>null</tt> * @param packageSize the package size * @param packageUnits the package units * @param context the mapping context * @return the list price */ private BigDecimal getListPrice(UBLInvoiceLine line, Product product, ProductSupplier ps, String reorderCode, int packageSize, String packageUnits, MappingContext context) { BigDecimal listPrice = line.getWholesalePrice(); if (listPrice != null && listPrice.compareTo(BigDecimal.ZERO) == 0) { log.warn("Received 0.0 list price from supplier."); listPrice = null; } if (listPrice == null) { if (ps == null && product != null) { ps = productRules.getProductSupplier(product, context.getSupplier(), reorderCode, packageSize, packageUnits); } if (ps != null) { listPrice = ps.getListPrice(); } if (listPrice == null || listPrice.compareTo(BigDecimal.ZERO) == 0) { log.warn("Product/supplier list price is 0.0"); } } if (listPrice == null || listPrice.compareTo(BigDecimal.ZERO) == 0) { // no list price in the invoice line, nor from a product/supplier. Default to the unit price listPrice = line.getPriceAmount(); } return listPrice; } /** * Returns the package units. * * @param invoicedUnitCode the invoiced quantity unit code * @param pkg the package information. May be <tt>null</tt> * @return the package units, or <tt>null</tt> if they are not known */ private String getPackageUnits(String invoicedUnitCode, Package pkg) { String result = null; String expected = (pkg != null) ? pkg.getPackageUnits() : null; List<String> matches = packageHelper.getPackageUnits(invoicedUnitCode); if (expected != null) { if (!matches.contains(expected)) { log.warn("Invoice package units (" + StringUtils.join(matches.iterator(), ", ") // TODO - log context + ") don't match that expected: " + expected); } result = expected; } else if (matches.size() == 1) { result = matches.get(0); } else if (matches.size() > 1) { log.warn("Cannot determine package units. " + matches.size() // TODO - log context + " package units match unit code: " + invoicedUnitCode); } return result; } /** * Returns the package size. * * @param line the invoice line * @param pkg the expected package, or <tt>null</tt> if it is not known * @return the package size, or <tt>0</tt> if it is not known * @throws ESCIAdapterException if the package size is incorrectly specified */ private int getPackageSize(UBLInvoiceLine line, Package pkg) { int result; BigDecimal packageSize = line.getPackSizeNumeric(); int expectedSize = (pkg != null) ? pkg.getPackageSize() : 0; int invoiceSize; try { invoiceSize = packageSize.intValueExact(); } catch (ArithmeticException exception) { ErrorContext context = new ErrorContext(line, "PackSizeNumeric"); String intValue = Integer.toString(packageSize.intValue()); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), intValue, packageSize.toString())); } if (expectedSize != 0) { if (invoiceSize != 0 && invoiceSize != expectedSize) { log.warn("Different package size received for invoice. Expected package size=" + expectedSize + ", invoiced package size=" + invoiceSize); // TODO - log context } result = expectedSize; } else { result = invoiceSize; } return result; } /** * Verifies that the invoice line item's <em>PackQuantity</em> is specified correctly, if present. * * @param line the invoice line * @param unitCode the expected unit code */ private void checkPackQuantity(UBLInvoiceLine line, String unitCode) { BigDecimal quantity = line.getPackQuantity(); if (quantity != null) { if (quantity.compareTo(BigDecimal.ONE) != 0) { ErrorContext context = new ErrorContext(line, "PackQuantity"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), "1", quantity.toString())); } String packageUnits = line.getPackQuantityUnitCode(); if (packageUnits != null && !ObjectUtils.equals(unitCode, packageUnits)) { ErrorContext context = new ErrorContext(line, "PackQuantity@unitCode"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), unitCode, packageUnits)); } } } /** * Verifies that the invoice line's <em>BaseQuantity</em> is specified correctly, if present. * * @param line the invoice line * @param unitCode the expected unit code */ private void checkBaseQuantity(UBLInvoiceLine line, String unitCode) { BigDecimal quantity = line.getBaseQuantity(); if (quantity != null) { if (quantity.compareTo(BigDecimal.ONE) != 0) { ErrorContext context = new ErrorContext(line, "BaseQuantity"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), "1", quantity.toString())); } String baseQuantityUnitCode = line.getBaseQuantityUnitCode(); if (!StringUtils.equals(unitCode, baseQuantityUnitCode)) { ErrorContext context = new ErrorContext(line, "BaseQuantity@unitCode"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), unitCode, baseQuantityUnitCode)); } } } /** * Maps a charge to a delivery item. * * @param charge the allowance/charge * @param startTime the invoice start time * @param invoiceId the invoice identifier * @param rates the tax rates * @return a new delivery item * @throws ESCIAdapterException if the allowance/charge cannot be mapped */ protected FinancialAct mapCharge(UBLAllowanceCharge charge, Date startTime, String invoiceId, TaxRates rates) { if (!charge.isCharge()) { throw new ESCIAdapterException(ESCIAdapterMessages.invoiceAllowanceNotSupported(invoiceId)); } BigDecimal unitPrice = charge.getAmount(); ActBean deliveryItem = factory.createActBean(SupplierArchetypes.DELIVERY_ITEM); BigDecimal tax = charge.getTaxAmount(); BigDecimal rate = checkTaxCategory(charge.getTaxCategory(), rates); BigDecimal divisor = BigDecimal.valueOf(100); if (tax.compareTo(BigDecimal.ZERO) != 0) { BigDecimal expectedTax = MathRules.divide(unitPrice.multiply(rate), divisor, 2); if (expectedTax.compareTo(tax) != 0) { ErrorContext context = new ErrorContext(charge, "TaxTotal/TaxAmount"); throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue(context.getPath(), context.getType(), context.getID(), expectedTax.toString(), tax.toString())); } } deliveryItem.setValue("startTime", startTime); deliveryItem.setValue("quantity", BigDecimal.ONE); deliveryItem.setValue("packageUnits", null); // override default deliveryItem.setValue("unitPrice", unitPrice); deliveryItem.setValue("tax", tax); deliveryItem.setValue("reorderDescription", charge.getAllowanceChargeReason()); // TODO - not ideal getArchetypeService().deriveValues(deliveryItem.getObject()); return (FinancialAct) deliveryItem.getAct(); } /** * Verifies that the UBL version matches that expected. * * @param invoice the invoice */ protected void checkUBLVersion(UBLInvoice invoice) { if (!UBL_VERSION.equals(invoice.getUBLVersionID())) { throw new ESCIAdapterException(ESCIAdapterMessages.ublInvalidValue("UBLVersionID", "Invoice", invoice.getID(), UBL_VERSION, invoice.getUBLVersionID())); } } /** * Returns an author to associate with the delivery. * <p/> * This returns the author of the original order, if present. If not, it returns that from the stock location. * * @param context the mapping context * @return the author reference, or <tt>null</tt> if none is available */ private Entity getAuthor(MappingContext context) { Entity result = null; FinancialAct order = context.getDocumentOrder(); if (order == null) { // no primary order. If there is ony one secondary order, use that (which is essentially the primary order) List<FinancialAct> orders = context.getOrders(); if (orders.size() == 1) { order = orders.get(0); } } if (order != null) { ActBean bean = factory.createActBean(order); result = bean.getNodeParticipant("author"); } if (result == null) { EntityBean bean = factory.createEntityBean(context.getStockLocation()); result = bean.getNodeTargetEntity("defaultAuthor"); } return result; } }