org.openvpms.esci.adapter.map.order.OrderMapperImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.openvpms.esci.adapter.map.order.OrderMapperImpl.java

Source

/*
 * 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 2015 (C) OpenVPMS Ltd. All Rights Reserved.
 */

package org.openvpms.esci.adapter.map.order;

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.party.ContactArchetypes;
import org.openvpms.archetype.rules.party.PartyRules;
import org.openvpms.archetype.rules.practice.LocationRules;
import org.openvpms.archetype.rules.practice.PracticeRules;
import org.openvpms.archetype.rules.supplier.SupplierRules;
import org.openvpms.component.business.domain.im.act.Act;
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.EntityRelationship;
import org.openvpms.component.business.domain.im.lookup.Lookup;
import org.openvpms.component.business.domain.im.party.Contact;
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.IMObjectBean;
import org.openvpms.component.business.service.archetype.helper.IMObjectBeanFactory;
import org.openvpms.component.business.service.lookup.ILookupService;
import org.openvpms.esci.adapter.i18n.ESCIAdapterMessages;
import org.openvpms.esci.adapter.map.UBLHelper;
import org.openvpms.esci.adapter.util.ESCIAdapterException;
import org.openvpms.esci.ubl.common.aggregate.AddressLineType;
import org.openvpms.esci.ubl.common.aggregate.AddressType;
import org.openvpms.esci.ubl.common.aggregate.ContactType;
import org.openvpms.esci.ubl.common.aggregate.CustomerPartyType;
import org.openvpms.esci.ubl.common.aggregate.ItemIdentificationType;
import org.openvpms.esci.ubl.common.aggregate.ItemType;
import org.openvpms.esci.ubl.common.aggregate.LineItemType;
import org.openvpms.esci.ubl.common.aggregate.MonetaryTotalType;
import org.openvpms.esci.ubl.common.aggregate.OrderLineType;
import org.openvpms.esci.ubl.common.aggregate.PartyNameType;
import org.openvpms.esci.ubl.common.aggregate.PartyType;
import org.openvpms.esci.ubl.common.aggregate.PriceType;
import org.openvpms.esci.ubl.common.aggregate.SupplierPartyType;
import org.openvpms.esci.ubl.common.aggregate.TaxTotalType;
import org.openvpms.esci.ubl.common.basic.BaseQuantityType;
import org.openvpms.esci.ubl.common.basic.CityNameType;
import org.openvpms.esci.ubl.common.basic.CopyIndicatorType;
import org.openvpms.esci.ubl.common.basic.CountrySubentityType;
import org.openvpms.esci.ubl.common.basic.CustomerAssignedAccountIDType;
import org.openvpms.esci.ubl.common.basic.DescriptionType;
import org.openvpms.esci.ubl.common.basic.ElectronicMailType;
import org.openvpms.esci.ubl.common.basic.IDType;
import org.openvpms.esci.ubl.common.basic.IssueDateType;
import org.openvpms.esci.ubl.common.basic.IssueTimeType;
import org.openvpms.esci.ubl.common.basic.LineExtensionAmountType;
import org.openvpms.esci.ubl.common.basic.LineType;
import org.openvpms.esci.ubl.common.basic.NameType;
import org.openvpms.esci.ubl.common.basic.PackQuantityType;
import org.openvpms.esci.ubl.common.basic.PackSizeNumericType;
import org.openvpms.esci.ubl.common.basic.PayableAmountType;
import org.openvpms.esci.ubl.common.basic.PostalZoneType;
import org.openvpms.esci.ubl.common.basic.PriceAmountType;
import org.openvpms.esci.ubl.common.basic.QuantityType;
import org.openvpms.esci.ubl.common.basic.SupplierAssignedAccountIDType;
import org.openvpms.esci.ubl.common.basic.TaxAmountType;
import org.openvpms.esci.ubl.common.basic.TelefaxType;
import org.openvpms.esci.ubl.common.basic.TelephoneType;
import org.openvpms.esci.ubl.common.basic.TotalTaxAmountType;
import org.openvpms.esci.ubl.common.basic.UBLVersionIDType;
import org.openvpms.esci.ubl.order.Order;

import javax.annotation.Resource;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import java.math.BigDecimal;
import java.util.GregorianCalendar;

/**
 * Maps <em>act.supplierOrder</em> acts to UBL Orders.
 *
 * @author Tim Anderson
 * @author bcharlton(benjicharlton@gmail.com)
 */
public class OrderMapperImpl implements OrderMapper {

    /**
     * The lookup service.
     */
    private ILookupService lookupService;

    /**
     * The practice rules.
     */
    private PracticeRules practiceRules;

    /**
     * Location rules.
     */
    private LocationRules locationRules;

    /**
     * Party rules.
     */
    private PartyRules partyRules;

    /**
     * The supplier rules.
     */
    private SupplierRules supplierRules;

    /**
     * The currencies.
     */
    private Currencies currencies;

    /**
     * The bean factory.
     */
    private IMObjectBeanFactory factory;

    /**
     * XML data type factory.
     */
    private DatatypeFactory datatypeFactory;

    /**
     * The logger.
     */
    private static final Log log = LogFactory.getLog(OrderMapperImpl.class);

    /**
     * Default package unit code, if none is specified. This corresponds to "each" in UNE/ECE rec 20
     * (http://docs.oasis-open.org/ubl/cs-UBL-2.0/cl/gc/cefact/UnitOfMeasureCode-2.0.gc)
     */
    private static final String DEFAULT_PACKAGE_UNITS = "EA";

    /**
     * Default constructor.
     */
    public OrderMapperImpl() {
        try {
            datatypeFactory = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException exception) {
            throw new IllegalStateException(exception);
        }
    }

    /**
     * Registers the practice rules.
     *
     * @param rules the practice rules
     */
    @Resource
    public void setPracticeRules(PracticeRules rules) {
        practiceRules = rules;
    }

    /**
     * Registers the location rules.
     *
     * @param rules the location rules
     */
    @Resource
    public void setLocationRules(LocationRules rules) {
        locationRules = rules;
    }

    /**
     * Registers the party rules.
     *
     * @param rules the party rules
     */
    @Resource
    public void setPartyRules(PartyRules rules) {
        partyRules = rules;
    }

    /**
     * Registers the supplier rules.
     *
     * @param rules the supplier rules
     */
    @Resource
    public void setSupplierRules(SupplierRules rules) {
        supplierRules = rules;
    }

    /**
     * Registers the lookup service.
     *
     * @param service the lookup service
     */
    @Resource
    public void setLookupService(ILookupService service) {
        lookupService = service;
    }

    /**
     * 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 <em>act.supplierOrder</em> to an UBL order.
     *
     * @param order the <em>act.supplierOrder</em> to map
     * @return the corresponding UBL order
     * @throws ESCIAdapterException      for mapping errors
     * @throws ArchetypeServiceException for any archetype service error
     */
    public Order map(FinancialAct order) {
        Order result = new Order();
        Currency currency = getCurrency();

        UBLVersionIDType version = UBLHelper.initID(new UBLVersionIDType(), "2.0");
        IDType id = UBLHelper.createID(order.getId());
        CopyIndicatorType copyIndicator = getCopyIndicatorType(false);

        GregorianCalendar startTime = new GregorianCalendar();
        startTime.setTime(order.getActivityStartTime());
        IssueDateType issueDate = UBLHelper.createIssueDate(startTime, datatypeFactory);
        IssueTimeType issueTime = UBLHelper.createIssueTime(startTime, datatypeFactory);

        ActBean bean = factory.createActBean(order);
        Entity author = bean.getNodeParticipant("author");
        Party stockLocation = (Party) bean.getNodeParticipant("stockLocation");
        Party location = getLocation(stockLocation);

        Party supplier = (Party) bean.getNodeParticipant("supplier");
        EntityRelationship supplierStockLocation = supplierRules.getSupplierStockLocation(supplier, stockLocation);
        if (supplierStockLocation == null) {
            throw new ESCIAdapterException(ESCIAdapterMessages.ESCINotConfigured(supplier, stockLocation));
        }
        String contactName = (author != null) ? author.getName() : null;
        CustomerPartyType customerParty = getCustomer(contactName, location, stockLocation, supplierStockLocation);
        SupplierPartyType supplierParty = getSupplier(supplier);

        TaxTotalType taxTotal = getTaxTotal(order, currency);
        MonetaryTotalType total = getMonetaryTotal(order, currency);

        result.setUBLVersionID(version);
        result.setID(id);
        result.setCopyIndicator(copyIndicator);
        result.setIssueDate(issueDate);
        result.setIssueTime(issueTime);
        result.setBuyerCustomerParty(customerParty);
        result.setSellerSupplierParty(supplierParty);
        result.getTaxTotal().add(taxTotal);
        result.setAnticipatedMonetaryTotal(total);

        for (Act item : bean.getNodeActs("items")) {
            OrderLineType line = getOrderLine(item, supplier, currency);
            result.getOrderLine().add(line);
        }
        return result;
    }

    /**
     * Returns an <tt>OrderLineType</tt> for an <em>act.supplierOrderItem</em>.
     *
     * @param act      the order item to map
     * @param supplier the supplier
     * @param currency the currency that amounts are expressed in
     * @return a new <tt>OrderLineType</tt> corresponding to the act
     */
    private OrderLineType getOrderLine(Act act, Party supplier, Currency currency) {
        ActBean bean = factory.createActBean(act);
        Product product = (Product) bean.getNodeParticipant("product");

        OrderLineType orderLine = new OrderLineType();
        LineItemType lineItem = new LineItemType();

        String packageUnits = bean.getString("packageUnits");
        String unitCode = getUnitCode(packageUnits);

        ItemType item = getItem(bean, supplier, product, unitCode);
        lineItem.setItem(item);
        orderLine.setLineItem(lineItem);

        IDType id = UBLHelper.createID(act.getId());
        QuantityType quantity = UBLHelper.initQuantity(new QuantityType(), bean.getBigDecimal("quantity"),
                unitCode);
        LineExtensionAmountType lineAmount = UBLHelper.initAmount(new LineExtensionAmountType(),
                bean.getBigDecimal("total"), currency);
        TotalTaxAmountType taxAmount = UBLHelper.initAmount(new TotalTaxAmountType(), bean.getBigDecimal("tax"),
                currency);
        PriceType price = getPrice(bean.getBigDecimal("unitPrice"), unitCode, currency);

        lineItem.setID(id);
        lineItem.setQuantity(quantity);
        lineItem.setLineExtensionAmount(lineAmount);
        lineItem.setTotalTaxAmount(taxAmount);
        lineItem.setPrice(price);

        return orderLine;
    }

    /**
     * Returns a <tt>ItemType</tt> for a supplier, order item, and product.
     *
     * @param bean         the order item
     * @param supplier     the supplier
     * @param product      the product
     * @param packageUnits the package size unit code. May be <tt>null</tt>
     * @return an <tt>ItemType</tt> corresponding to the supplier and product
     */
    private ItemType getItem(ActBean bean, Party supplier, Product product, String packageUnits) {
        ItemType result = new ItemType();
        ItemIdentificationType buyersId = getItemIdentification(product.getId());
        String reorderCode = bean.getString("reorderCode");
        String reorderDescription = bean.getString("reorderDescription");
        if (!StringUtils.isEmpty(reorderCode)) {
            ItemIdentificationType sellersId = getItemIdentification(reorderCode);
            result.setSellersItemIdentification(sellersId);
        } else {
            throw new ESCIAdapterException(ESCIAdapterMessages.noSupplierOrderCode(supplier, product));
        }
        if (!StringUtils.isEmpty(reorderDescription)) {
            DescriptionType description = UBLHelper.initText(new DescriptionType(), reorderDescription);
            result.getDescription().add(description);
        }
        NameType name = UBLHelper.initName(new NameType(), product.getName());

        result.setBuyersItemIdentification(buyersId);
        result.setName(name);

        BigDecimal packageSize = bean.getBigDecimal("packageSize");
        if (packageSize != null && packageUnits != null) {
            PackQuantityType quantity = UBLHelper.initQuantity(new PackQuantityType(), BigDecimal.ONE,
                    packageUnits);
            PackSizeNumericType size = UBLHelper.createPackSizeNumeric(packageSize);
            result.setPackQuantity(quantity);
            result.setPackSizeNumeric(size);
        }

        return result;
    }

    /**
     * Returns a <tt>PriceType</tt> for the specified price and unit code.
     *
     * @param price    the price
     * @param unitCode the quantity unit code (UN/CEFACT). May be <tt>null</tt>
     * @param currency the currency
     * @return the corresponding <tt>PriceType</tt> for price and unitCode
     */
    private PriceType getPrice(BigDecimal price, String unitCode, Currency currency) {
        PriceType result = new PriceType();
        PriceAmountType priceAmount = UBLHelper.initAmount(new PriceAmountType(), price, currency);
        result.setPriceAmount(priceAmount);
        result.setBaseQuantity(UBLHelper.initQuantity(new BaseQuantityType(), BigDecimal.ONE, unitCode));
        return result;
    }

    /**
     * Returns a <tt>TaxTotalType</tt> for an order.
     *
     * @param order    the order
     * @param currency the currency
     * @return the corresponding <tt>TaxTotalType</tt>
     */
    private TaxTotalType getTaxTotal(FinancialAct order, Currency currency) {
        TaxTotalType result = new TaxTotalType();
        result.setTaxAmount(UBLHelper.initAmount(new TaxAmountType(), order.getTaxAmount(), currency));
        return result;
    }

    /**
     * Returns a <tt>MonetaryTotalType</tt> for an order.
     *
     * @param order    the order
     * @param currency the currency
     * @return the corresponding <tt>MonetaryTotalType</tt>
     */
    private MonetaryTotalType getMonetaryTotal(FinancialAct order, Currency currency) {
        BigDecimal payableAmount = order.getTotal();
        BigDecimal lineExtensionAmount = payableAmount.subtract(order.getTaxAmount());

        MonetaryTotalType result = new MonetaryTotalType();
        result.setLineExtensionAmount(
                UBLHelper.initAmount(new LineExtensionAmountType(), lineExtensionAmount, currency));
        result.setPayableAmount(UBLHelper.initAmount(new PayableAmountType(), payableAmount, currency));
        return result;
    }

    /**
     * Returns the UN/CEFACT unit code for the given package units code from an <em>lookup.uom</em>.
     * <p/>
     * If no package is specified, defaults to {@link #DEFAULT_PACKAGE_UNITS}.
     *
     * @param packageUnits the package units code
     * @return the corresponding unit code
     */
    private String getUnitCode(String packageUnits) {
        String result = null;
        if (!StringUtils.isEmpty("packageUnits")) {
            Lookup lookup = lookupService.getLookup("lookup.uom", packageUnits);
            if (lookup != null) {
                IMObjectBean lookupBean = factory.createBean(lookup);
                String unitCode = lookupBean.getString("unitCode");
                if (!StringUtils.isEmpty(unitCode)) {
                    result = unitCode;
                }
            }
            if (result == null) {
                log.warn("No unit code for package units=" + packageUnits + ". Defaulting to "
                        + DEFAULT_PACKAGE_UNITS);
            }
        }
        if (result == null) {
            result = DEFAULT_PACKAGE_UNITS;
        }
        return result;
    }

    /**
     * Helper to return the location associated with a stock location.
     *
     * @param stockLocation the stock location
     * @return the corresponding location
     * @throws ESCIAdapterException if the stock location isn't associated with a practice location
     */
    private Party getLocation(Party stockLocation) {
        EntityBean bean = factory.createEntityBean(stockLocation);
        // TODO - there could be more than one location which refers to different party.organisationLocation 
        Party result = (Party) bean.getNodeSourceEntity("locations");
        if (result == null) {
            throw new ESCIAdapterException(ESCIAdapterMessages.noPracticeLocationForStockLocation(stockLocation));
        }
        return result;
    }

    /**
     * Returns a <tt>CustomerPartyType</tt> corresponding to the passed <em>party.organisationStockLocation</em>.
     * <p/>
     * The contact details will be either those of the <em>party.organisationLocation</em> or the parent
     * </em>party.organisationPractice</em>. If the location has a <em>contact.location</em>, then the location's
     * details will be used, otherwise the practice's details will be used.
     * <p/>
     * The customer identifier will be that of the stock location.
     * <p/>
     * NOTE: the supplied <em>entityRelationship.supplierStockLocation*</em> relationship may have an optional
     * <em>accountId</em> node, used to populate the <tt>SupplierAssignedAccountIDType</tt>
     *
     * @param contactName           a contact name to supply with telephone, email and fax details, May be <tt>null</tt>
     * @param location              the practice location
     * @param stockLocation         the stock location
     * @param supplierStockLocation an <em>entityRelationship.supplierStockLocation*</em> relationship
     * @return the corresponding <tt>CustomerPartyType</tt>
     */
    private CustomerPartyType getCustomer(String contactName, Party location, Party stockLocation,
            EntityRelationship supplierStockLocation) {
        CustomerPartyType result = new CustomerPartyType();
        Party customer;

        Party practice = locationRules.getPractice(location);
        if (practice == null) {
            throw new IllegalStateException("No practice for location: " + location.getId());
        }

        Contact locationContact = partyRules.getContact(location, ContactArchetypes.LOCATION, "BILLING");
        if (locationContact == null) {
            locationContact = partyRules.getContact(practice, ContactArchetypes.LOCATION, "BILLING");
            if (locationContact == null) {
                throw new IllegalStateException("No contact.location for location: " + location.getId());
            }
            customer = practice;
        } else {
            customer = location;
        }
        Contact phoneContact = partyRules.getContact(customer, ContactArchetypes.PHONE, false, "FAX", "BILLING");
        Contact faxContact = partyRules.getContact(customer, ContactArchetypes.PHONE, true, null, "FAX", "BILLING");
        if (faxContact == null) {
            faxContact = partyRules.getContact(customer, ContactArchetypes.PHONE, true, null, "FAX");
        }
        Contact emailContact = partyRules.getContact(customer, ContactArchetypes.EMAIL, "BILLING");

        CustomerAssignedAccountIDType customerId = UBLHelper.initID(new CustomerAssignedAccountIDType(),
                stockLocation.getId());

        PartyType party = getParty(customer, locationContact);
        party.setContact(getContact(contactName, phoneContact, faxContact, emailContact));

        result.setCustomerAssignedAccountID(customerId);

        IMObjectBean bean = factory.createBean(supplierStockLocation);
        String accountId = bean.getString("accountId");
        if (!StringUtils.isEmpty(accountId)) {
            SupplierAssignedAccountIDType supplierId = UBLHelper.initID(new SupplierAssignedAccountIDType(),
                    accountId);
            result.setSupplierAssignedAccountID(supplierId);
        }

        result.setParty(party);
        return result;
    }

    /**
     * Returns a <tt>SupplierPartyType</tt> corresponding to the passed supplier.
     *
     * @param supplier the supplier
     * @return the corresponding <tt>SupplierPartyType</tt>
     */
    private SupplierPartyType getSupplier(Party supplier) {
        SupplierPartyType result = new SupplierPartyType();

        CustomerAssignedAccountIDType accountId = UBLHelper.initID(new CustomerAssignedAccountIDType(),
                supplier.getId());
        Contact contact = partyRules.getContact(supplier, ContactArchetypes.LOCATION, null);

        result.setCustomerAssignedAccountID(accountId);
        result.setParty(getParty(supplier, contact));
        return result;
    }

    /**
     * Returns a <tt>PartyType</tt> for the supplied party and contact.
     *
     * @param party    the party
     * @param location the location contact. May be <tt>null</tt>
     * @return the corresponding <tt>PartyType</tt>
     */
    private PartyType getParty(Party party, Contact location) {
        PartyType result = new PartyType();

        PartyNameType partyName = new PartyNameType();
        partyName.setName(UBLHelper.createName(party.getName()));
        result.getPartyName().add(partyName);
        if (location != null) {
            result.setPostalAddress(getAddress(location));
        }
        return result;
    }

    /**
     * Returns a <tt>ContactType</tt> for the supplied contacts.
     *
     * @param name  a contact name
     * @param phone the phone contact. May be <tt>null</tt>
     * @param fax   the fax contact. May be <tt>null</tt>
     * @param email the email contact. May be <tt>null</tt>
     * @return the corresponding <tt>ContactType</tt>
     */
    private ContactType getContact(String name, Contact phone, Contact fax, Contact email) {
        ContactType contact = new ContactType();
        if (!StringUtils.isEmpty(name)) {
            contact.setName(UBLHelper.initName(new NameType(), name));
        }
        contact.setTelephone(getPhone(phone));
        contact.setTelefax(getFax(fax));
        contact.setElectronicMail(getEmail(email));
        return contact;
    }

    /**
     * Returns an <tt>TelephoneType</tt> for a <em>contact.phoneNumber</em>.
     *
     * @param contact the phone contact. May be <tt>null</tt>
     * @return a new <tt>TelephoneType</tt> or <tt>null</tt> if <tt>contact</tt> is null or unpopulated
     */
    private TelephoneType getPhone(Contact contact) {
        String phone = formatPhone(contact, "areaCode", "telephoneNumber");
        return (phone != null) ? UBLHelper.initText(new TelephoneType(), phone) : null;
    }

    /**
     * Returns an <tt>TelefaxType</tt> for a fax contact.
     *
     * @param contact the fax contact. May be <tt>null</tt>
     * @return a new <tt>TelefaxType</tt> or <tt>null</tt> if <tt>contact</tt> is null or unpopulated
     */
    private TelefaxType getFax(Contact contact) {
        String fax = formatPhone(contact, "areaCode", "telephoneNumber");
        return (fax != null) ? UBLHelper.initText(new TelefaxType(), fax) : null;
    }

    /**
     * Helper to format a phone/fax number.
     *
     * @param contact      the phone/fax contact. May be <tt>null</tt>
     * @param areaCodeNode the area code node.
     * @param numberNode   the phone number node
     * @return a formatted number, or <tt>null</tt> if <tt>number</tt> is null
     */
    private String formatPhone(Contact contact, String areaCodeNode, String numberNode) {
        String result = null;
        if (contact != null) {
            IMObjectBean bean = factory.createBean(contact);
            String number = bean.getString(numberNode);
            if (!StringUtils.isEmpty(number)) {
                String areaCode = bean.getString(areaCodeNode);
                if (!StringUtils.isEmpty(areaCode)) {
                    result = "(" + areaCode + ") " + number; // todo - localise
                } else {
                    result = number;
                }
            }
        }
        return result;
    }

    /**
     * Returns an <tt>ElectronicMailType</tt> for a <em>contact.email</em>.
     *
     * @param contact the email contact. May be <tt>null</tt>
     * @return a new <tt>ElectronicMailType</tt> or <tt>null</tt> if <tt>contact</tt> is null or unpopulated
     */
    private ElectronicMailType getEmail(Contact contact) {
        String email = null;
        if (contact != null) {
            IMObjectBean bean = factory.createBean(contact);
            email = StringUtils.trimToNull(bean.getString("emailAddress"));
        }
        return (email != null) ? UBLHelper.initText(new ElectronicMailType(), email) : null;
    }

    /**
     * Returns an <tt>AddressType</tt> for the supplied <em>contact.location</em>.
     *
     * @param contact the location contact
     * @return the corresponding <tt>AddressType</tt>
     */
    private AddressType getAddress(Contact contact) {
        IMObjectBean bean = factory.createBean(contact);

        AddressType result = new AddressType();
        AddressLineType addressLineType = new AddressLineType();
        LineType line = UBLHelper.initText(new LineType(), bean.getString("address"));
        addressLineType.setLine(line);

        String city = lookupService.getName(contact, "suburb");
        CityNameType cityName = UBLHelper.initName(new CityNameType(), city);

        String state = lookupService.getName(contact, "state");
        CountrySubentityType stateName = UBLHelper.initText(new CountrySubentityType(), state);

        PostalZoneType postCode = UBLHelper.initText(new PostalZoneType(), bean.getString("postcode"));

        result.getAddressLine().add(addressLineType);
        result.setCityName(cityName);
        result.setCountrySubentity(stateName);
        result.setPostalZone(postCode);
        return result;
    }

    /**
     * Returns an <tt>ItemIdentificationType</tt> for the given identifier.
     *
     * @param id the identifier
     * @return a new <tt>ItemIdentificationType</tt>
     */
    private ItemIdentificationType getItemIdentification(long id) {
        ItemIdentificationType result = new ItemIdentificationType();
        result.setID(UBLHelper.createID(id));
        return result;
    }

    /**
     * Returns an <tt>ItemIdentificationType</tt> for the given identifier.
     *
     * @param id the identifier
     * @return a new <tt>ItemIdentificationType</tt>
     */
    private ItemIdentificationType getItemIdentification(String id) {
        ItemIdentificationType result = new ItemIdentificationType();
        result.setID(UBLHelper.createID(id));
        return result;
    }

    /**
     * Returns a <tt>CopyIndicatorType</tt> with the specified value.
     *
     * @param value the indicator value
     * @return a new <tt>CopyIndicatorType</tt>
     */
    private CopyIndicatorType getCopyIndicatorType(boolean value) {
        CopyIndicatorType result = new CopyIndicatorType();
        result.setValue(value);
        return result;
    }

    /**
     * Returns the currency associated with the practice.
     *
     * @return the currency code
     */
    private Currency getCurrency() {
        return UBLHelper.getCurrency(practiceRules, currencies, factory);
    }

}