org.estatio.dom.invoice.Invoice.java Source code

Java tutorial

Introduction

Here is the source code for org.estatio.dom.invoice.Invoice.java

Source

/*
 *
 *  Copyright 2012-2014 Eurocommercial Properties NV
 *
 *
 *  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.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.estatio.dom.invoice;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Index;
import javax.jdo.annotations.Indices;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.VersionStrategy;
import org.apache.commons.lang3.ObjectUtils;
import org.joda.time.LocalDate;
import org.apache.isis.applib.annotation.Action;
import org.apache.isis.applib.annotation.BookmarkPolicy;
import org.apache.isis.applib.annotation.CollectionLayout;
import org.apache.isis.applib.annotation.DomainObject;
import org.apache.isis.applib.annotation.DomainObjectLayout;
import org.apache.isis.applib.annotation.Editing;
import org.apache.isis.applib.annotation.Hidden;
import org.apache.isis.applib.annotation.InvokeOn;
import org.apache.isis.applib.annotation.Optionality;
import org.apache.isis.applib.annotation.Parameter;
import org.apache.isis.applib.annotation.ParameterLayout;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.annotation.Property;
import org.apache.isis.applib.annotation.PropertyLayout;
import org.apache.isis.applib.annotation.RenderType;
import org.apache.isis.applib.annotation.SemanticsOf;
import org.apache.isis.applib.annotation.Where;
import org.isisaddons.module.security.dom.tenancy.ApplicationTenancy;
import org.estatio.dom.EstatioDomainObject;
import org.estatio.dom.JdoColumnLength;
import org.estatio.dom.apptenancy.WithApplicationTenancyPathPersisted;
import org.estatio.dom.apptenancy.WithApplicationTenancyPropertyLocal;
import org.estatio.dom.asset.FixedAsset;
import org.estatio.dom.bankmandate.BankMandate;
import org.estatio.dom.charge.Charge;
import org.estatio.dom.currency.Currency;
import org.estatio.dom.financial.bankaccount.BankAccount;
import org.estatio.dom.lease.Lease;
import org.estatio.dom.lease.invoicing.InvoiceItemForLease;
import org.estatio.dom.numerator.Numerator;
import org.estatio.dom.party.Party;

@javax.jdo.annotations.PersistenceCapable(identityType = IdentityType.DATASTORE)
@javax.jdo.annotations.DatastoreIdentity(strategy = IdGeneratorStrategy.NATIVE, column = "id")
@javax.jdo.annotations.Version(strategy = VersionStrategy.VERSION_NUMBER, column = "version")
@javax.jdo.annotations.Queries({
        @javax.jdo.annotations.Query(name = "findMatchingInvoices", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE " + "lease == :lease && "
                + "seller == :seller && " + "buyer == :buyer && " + "paymentMethod == :paymentMethod && "
                + "status == :status && " + "dueDate == :dueDate"),
        @javax.jdo.annotations.Query(name = "findByFixedAssetAndStatus", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE " + "fixedAsset == :fixedAsset && "
                + "status == :status " + "ORDER BY invoiceNumber"),
        @javax.jdo.annotations.Query(name = "findByFixedAssetAndDueDateAndStatus", language = "JDOQL", value = "SELECT FROM org.estatio.dom.invoice.Invoice "
                + "WHERE " + "fixedAsset == :fixedAsset && " + "status == :status && " + "dueDate == :dueDate "
                + "ORDER BY invoiceNumber"),
        @javax.jdo.annotations.Query(name = "findByFixedAssetAndDueDate", language = "JDOQL", value = "SELECT FROM org.estatio.dom.invoice.Invoice "
                + "WHERE " + "fixedAsset == :fixedAsset && " + "dueDate == :dueDate " + "ORDER BY invoiceNumber"),
        @javax.jdo.annotations.Query(name = "findByStatus", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE status == :status " + "ORDER BY invoiceNumber"),
        @javax.jdo.annotations.Query(name = "findByBuyer", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE buyer == :buyer " + "ORDER BY invoiceNumber"),
        @javax.jdo.annotations.Query(name = "findByLease", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE lease == :lease "),
        @javax.jdo.annotations.Query(name = "findByRunId", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE runId == :runId "),
        @javax.jdo.annotations.Query(name = "findByInvoiceNumber", language = "JDOQL", value = "SELECT "
                + "FROM org.estatio.dom.invoice.Invoice " + "WHERE invoiceNumber.matches(:invoiceNumber) "
                + "ORDER BY invoiceDate DESC") })
@Indices({ @Index(name = "Invoice_runId_IDX", members = { "runId" }),
        @Index(name = "Invoice_fixedAsset_status_IDX", members = { "fixedAsset", "status" }),
        @Index(name = "Invoice_fixedAsset_dueDate_IDX", members = { "fixedAsset", "dueDate" }),
        @Index(name = "Invoice_fixedAsset_dueDate_status_IDX", members = { "fixedAsset", "dueDate", "status" }),
        @Index(name = "Invoice_Lease_Seller_Buyer_PaymentMethod_DueDate_Status_IDX", members = { "lease", "seller",
                "buyer", "paymentMethod", "dueDate", "status" }),
        @Index(name = "Invoice_invoiceNumber_IDX", members = { "invoiceNumber" }) })
@DomainObject(editing = Editing.DISABLED)
@DomainObjectLayout(bookmarking = BookmarkPolicy.AS_ROOT)
public class Invoice extends EstatioDomainObject<Invoice>
        implements WithApplicationTenancyPropertyLocal, WithApplicationTenancyPathPersisted {

    public Invoice() {
        super("invoiceNumber, collectionNumber, buyer, dueDate, lease, uuid");
    }

    private String uuid;

    @Property(hidden = Where.EVERYWHERE, optionality = Optionality.OPTIONAL)
    public String getUuid() {
        return uuid;
    }

    public void setUuid(final String uuid) {
        this.uuid = uuid;
    }

    // //////////////////////////////////////

    private String applicationTenancyPath;

    @javax.jdo.annotations.Column(length = ApplicationTenancy.MAX_LENGTH_PATH, allowsNull = "false", name = "atPath")
    @Hidden
    public String getApplicationTenancyPath() {
        return applicationTenancyPath;
    }

    public void setApplicationTenancyPath(final String applicationTenancyPath) {
        this.applicationTenancyPath = applicationTenancyPath;
    }

    @PropertyLayout(named = "Application Level", describedAs = "Determines those users for whom this object is available to view and/or modify.")
    public ApplicationTenancy getApplicationTenancy() {
        return applicationTenancies.findTenancyByPath(getApplicationTenancyPath());
    }

    // //////////////////////////////////////

    public String title() {
        if (getInvoiceNumber() != null) {
            return String.format("Invoice %s", getInvoiceNumber());
        }
        if (getCollectionNumber() != null) {
            return String.format("Collection %s", getCollectionNumber());
        }
        return String.format("Temp *%08d", Integer.parseInt(getId()));
    }

    // //////////////////////////////////////

    @Property(hidden = Where.OBJECT_FORMS)
    public String getNumber() {
        return ObjectUtils.firstNonNull(getInvoiceNumber(), getCollectionNumber(), title());
    }

    // //////////////////////////////////////

    private Party buyer;

    @javax.jdo.annotations.Column(name = "buyerPartyId", allowsNull = "false")
    @Property(editing = Editing.DISABLED)
    public Party getBuyer() {
        return buyer;

    }

    public void setBuyer(final Party buyer) {
        this.buyer = buyer;
    }

    // //////////////////////////////////////

    private Party seller;

    @javax.jdo.annotations.Column(name = "sellerPartyId", allowsNull = "false")
    @Property(hidden = Where.ALL_TABLES, editing = Editing.DISABLED)
    public Party getSeller() {
        return seller;
    }

    public void setSeller(final Party seller) {
        this.seller = seller;
    }

    // //////////////////////////////////////

    private String collectionNumber;

    @javax.jdo.annotations.Column(allowsNull = "true", length = JdoColumnLength.Invoice.NUMBER)
    @Property(hidden = Where.ALL_TABLES, editing = Editing.DISABLED)
    public String getCollectionNumber() {
        return collectionNumber;
    }

    public void setCollectionNumber(final String collectionNumber) {
        this.collectionNumber = collectionNumber;
    }

    // //////////////////////////////////////

    private String invoiceNumber;

    @javax.jdo.annotations.Column(allowsNull = "true", length = JdoColumnLength.Invoice.NUMBER)
    @Property(hidden = Where.ALL_TABLES, editing = Editing.DISABLED)
    public String getInvoiceNumber() {
        return invoiceNumber;
    }

    public void setInvoiceNumber(final String invoiceNumber) {
        this.invoiceNumber = invoiceNumber;
    }

    // //////////////////////////////////////

    private String runId;

    @Property(hidden = Where.ALL_TABLES, editing = Editing.DISABLED, optionality = Optionality.OPTIONAL)
    public String getRunId() {
        return runId;
    }

    public void setRunId(final String runId) {
        this.runId = runId;
    }

    // //////////////////////////////////////

    private Lease lease;

    @javax.jdo.annotations.Column(name = "leaseId", allowsNull = "true")
    @Property(editing = Editing.DISABLED, optionality = Optionality.OPTIONAL)
    public Lease getLease() {
        return lease;
    }

    public void setLease(final Lease lease) {
        this.lease = lease;
    }

    // //////////////////////////////////////

    @javax.jdo.annotations.Persistent
    private LocalDate invoiceDate;

    @javax.jdo.annotations.Column(allowsNull = "true")
    @Property(editing = Editing.DISABLED)
    public LocalDate getInvoiceDate() {
        return invoiceDate;
    }

    public void setInvoiceDate(final LocalDate invoiceDate) {
        this.invoiceDate = invoiceDate;
    }

    // //////////////////////////////////////

    @javax.jdo.annotations.Persistent
    private LocalDate dueDate;

    @javax.jdo.annotations.Column(allowsNull = "false")
    @Property(editing = Editing.DISABLED)
    public LocalDate getDueDate() {
        return dueDate;
    }

    public void setDueDate(final LocalDate dueDate) {
        this.dueDate = dueDate;
    }

    public void changeDueDate(final @ParameterLayout(named = "Due date") LocalDate dueDate) {
        setDueDate(dueDate);
    }

    public LocalDate default0ChangeDueDate(final LocalDate dueDate) {
        return getDueDate();
    }

    public String disableChangeDueDate(final LocalDate dueDate) {
        if (!getStatus().invoiceIsChangable()) {
            return "Due date can't be changed";
        }
        return null;
    }

    // //////////////////////////////////////

    private InvoiceStatus status;

    @javax.jdo.annotations.Column(allowsNull = "false", length = JdoColumnLength.STATUS_ENUM)
    @Property(editing = Editing.DISABLED)
    public InvoiceStatus getStatus() {
        return status;
    }

    public void setStatus(final InvoiceStatus status) {
        this.status = status;
    }

    // //////////////////////////////////////

    private Currency currency;

    // REVIEW: invoice generation is not populating this field.
    @javax.jdo.annotations.Column(name = "currencyId", allowsNull = "true")
    @Property(editing = Editing.DISABLED, hidden = Where.ALL_TABLES)
    public Currency getCurrency() {
        return currency;
    }

    public void setCurrency(final Currency currency) {
        this.currency = currency;
    }

    // //////////////////////////////////////

    private PaymentMethod paymentMethod;

    @javax.jdo.annotations.Column(allowsNull = "false", length = JdoColumnLength.PAYMENT_METHOD_ENUM)
    public PaymentMethod getPaymentMethod() {
        return paymentMethod;
    }

    public void setPaymentMethod(final PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    public Invoice changePaymentMethod(final PaymentMethod paymentMethod,
            final @ParameterLayout(named = "Reason") String reason) {
        setPaymentMethod(paymentMethod);
        return this;
    }

    public PaymentMethod default0ChangePaymentMethod() {
        return getPaymentMethod();
    }

    public String disableChangePaymentMethod(final PaymentMethod paymentMethod, final String reason) {
        return getStatus().invoiceIsChangable() ? null : "Invoice cannot be changed";
    }

    // //////////////////////////////////////

    @javax.jdo.annotations.Persistent(mappedBy = "invoice")
    private SortedSet<InvoiceItem> items = new TreeSet<InvoiceItem>();

    @Property(editing = Editing.DISABLED)
    @CollectionLayout(render = RenderType.EAGERLY)
    public SortedSet<InvoiceItem> getItems() {
        return items;
    }

    public void setItems(final SortedSet<InvoiceItem> items) {
        this.items = items;
    }

    // //////////////////////////////////////

    @Persistent
    private BigInteger lastItemSequence;

    @javax.jdo.annotations.Column(allowsNull = "true")
    @Property(hidden = Where.EVERYWHERE)
    public BigInteger getLastItemSequence() {
        return lastItemSequence;
    }

    public void setLastItemSequence(final BigInteger lastItemSequence) {
        this.lastItemSequence = lastItemSequence;
    }

    @Programmatic
    public BigInteger nextItemSequence() {
        BigInteger nextItemSequence = getLastItemSequence() == null ? BigInteger.ONE
                : getLastItemSequence().add(BigInteger.ONE);
        setLastItemSequence(nextItemSequence);
        return nextItemSequence;
    }

    // //////////////////////////////////////

    @Property(notPersisted = true)
    public BigDecimal getNetAmount() {
        BigDecimal total = BigDecimal.ZERO;
        for (InvoiceItem item : getItems()) {
            total = total.add(item.getNetAmount());
        }
        return total;
    }

    @Property(notPersisted = true, hidden = Where.ALL_TABLES)
    public BigDecimal getVatAmount() {
        BigDecimal total = BigDecimal.ZERO;
        for (InvoiceItem item : getItems()) {
            total = total.add(item.getVatAmount());
        }
        return total;
    }

    @Property(notPersisted = true)
    public BigDecimal getGrossAmount() {
        BigDecimal total = BigDecimal.ZERO;
        for (InvoiceItem item : getItems()) {
            total = total.add(item.getGrossAmount());
        }
        return total;
    }

    // //////////////////////////////////////

    @Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION)
    public Invoice approve() {
        doApprove();
        return this;
    }

    public boolean hideApprove() {
        return false;
    }

    public String disableApprove() {
        return getStatus() != InvoiceStatus.NEW ? "Can only approve 'new' invoices" : null;
    }

    @Programmatic
    public void doApprove() {
        // Bulk guard
        if (!hideApprove() && disableApprove() == null) {
            setStatus(InvoiceStatus.APPROVED);
            setRunId(null);
        }
    }

    // //////////////////////////////////////

    @Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION)
    public Invoice collect(final @ParameterLayout(named = "Are you sure?") Boolean confirm) {
        return doCollect();
    }

    public boolean hideCollect() {
        // only applies to direct debits
        return !getPaymentMethod().isDirectDebit();
    }

    public String disableCollect(Boolean confirm) {
        if (getCollectionNumber() != null) {
            return "Collection number already assigned";
        }
        final Numerator numerator = collectionNumerators.findCollectionNumberNumerator();
        if (numerator == null) {
            return "No 'collection number' numerator found for invoice's property";
        }
        if (getStatus() != InvoiceStatus.APPROVED) {
            return "Must be in status of 'approved'";
        }
        if (getLease() == null) {
            return "No lease related to invoice";
        }
        if (getLease().getPaidBy() == null) {
            return String.format("No mandate assigned to invoice's lease");
        }
        final BankAccount bankAccount = (BankAccount) getLease().getPaidBy().getBankAccount();
        if (!bankAccount.isValidIban()) {
            return "The Iban code is invalid";
        }
        return null;
    }

    // perhaps we should also store the specific bank mandate on the invoice
    // that we want to deduct the money from
    // is this a concept of account then?

    @Programmatic
    public Invoice doCollect() {
        if (hideCollect()) {
            return this;
        }
        if (disableCollect(true) != null) {
            return this;
        }
        final Numerator numerator = collectionNumerators.findCollectionNumberNumerator();
        setCollectionNumber(numerator.nextIncrementStr());
        return this;
    }

    @Programmatic
    public void createPaymentTerms() {

    }

    // //////////////////////////////////////

    public Invoice invoice(final @ParameterLayout(named = "Invoice date") LocalDate invoiceDate,
            final @ParameterLayout(named = "Are you sure?") Boolean confirm) {
        return doInvoice(invoiceDate);
    }

    @Programmatic
    public Invoice doInvoice(final @ParameterLayout(named = "Invoice date") LocalDate invoiceDate) {
        // bulk action, so need these guards
        if (disableInvoice(invoiceDate, true) != null) {
            return this;
        }
        if (!validInvoiceDate(invoiceDate)) {
            warnUser(String.format(
                    "Invoice date %d is invalid for %s becuase it's before the invoice date of the last invoice",
                    invoiceDate.toString(), getContainer().titleOf(this)));
            return this;
        }
        final Numerator numerator = collectionNumerators.findInvoiceNumberNumerator(getFixedAsset());
        setInvoiceNumber(numerator.nextIncrementStr());
        setInvoiceDate(invoiceDate);
        this.setStatus(InvoiceStatus.INVOICED);
        informUser("Assigned " + this.getInvoiceNumber() + " to invoice " + getContainer().titleOf(this));
        return this;
    }

    public String disableInvoice(final LocalDate invoiceDate, Boolean confirm) {
        if (getInvoiceNumber() != null) {
            return "Invoice number already assigned";
        }
        final Numerator numerator = collectionNumerators.findInvoiceNumberNumerator(getFixedAsset());
        if (numerator == null) {
            return "No 'invoice number' numerator found for invoice's property";
        }
        if (getStatus() != InvoiceStatus.APPROVED) {
            return "Must be in status of 'Invoiced'";
        }
        return null;
    }

    // //////////////////////////////////////

    @Programmatic
    boolean validInvoiceDate(LocalDate invoiceDate) {
        if (getDueDate() != null && getDueDate().compareTo(invoiceDate) < 0) {
            return false;
        }
        final Numerator numerator = collectionNumerators.findInvoiceNumberNumerator(getFixedAsset());
        if (numerator != null) {
            final String invoiceNumber = numerator.lastIncrementStr();
            if (invoiceNumber != null) {
                List<Invoice> result = invoices.findInvoicesByInvoiceNumber(invoiceNumber);
                if (result.size() > 0) {
                    return result.get(0).getInvoiceDate().compareTo(invoiceDate) <= 0;
                }
            }
        }
        return true;
    }

    // //////////////////////////////////////

    public InvoiceItem newItem(final Charge charge, final @ParameterLayout(named = "Quantity") BigDecimal quantity,
            final @ParameterLayout(named = "Net amount") BigDecimal netAmount,
            final @ParameterLayout(named = "Start date") @Parameter(optionality = Optionality.OPTIONAL) LocalDate startDate,
            final @ParameterLayout(named = "End date") @Parameter(optionality = Optionality.OPTIONAL) LocalDate endDate) {
        InvoiceItem invoiceItem = invoiceItems.newInvoiceItem(this, getDueDate());
        invoiceItem.setQuantity(quantity);
        invoiceItem.setCharge(charge);
        invoiceItem.setDescription(charge.getDescription());
        invoiceItem.setTax(charge.getTax());
        invoiceItem.setNetAmount(netAmount);
        invoiceItem.setStartDate(startDate);
        invoiceItem.setEndDate(endDate);
        invoiceItem.verify();
        // TODO: we need to create a new subclass InvoiceForLease but that
        // requires a database change so this is quick fix
        InvoiceItemForLease invoiceItemForLease = (InvoiceItemForLease) invoiceItem;
        invoiceItemForLease.setLease(getLease());
        if (getLease() != null && getLease().getOccupancies() != null
                && getLease().getOccupancies().first() != null) {
            invoiceItemForLease.setFixedAsset(getLease().getOccupancies().first().getUnit());
        }
        return invoiceItemForLease;
    }

    public BigDecimal default1NewItem() {
        return BigDecimal.ONE;
    }

    public String validateNewItem(final Charge charge, final BigDecimal quantity, final BigDecimal netAmount,
            final LocalDate startDate, final LocalDate endDate) {
        if (startDate != null && endDate == null) {
            return "Also enter an end date when using a start date";
        }
        if (ObjectUtils.compare(startDate, endDate) > 0) {
            return "Start date must be before end date";
        }
        return null;
    }

    // //////////////////////////////////////

    private FixedAsset fixedAsset;

    /**
     * Derived from the {@link #getLease() lease}, but safe to persist since
     * business rule states that we never generate invoices for invoice items
     * that relate to different properties.
     * 
     * <p>
     * Another reason for persisting this is that it allows eager validation
     * when attaching additional {@link InvoiceItem}s to an invoice, to check
     * that they relate to the same fixed asset.
     */
    @javax.jdo.annotations.Column(name = "fixedAssetId", allowsNull = "false")
    // for the moment, might be generalized (to the user) in the future
    @Property(editing = Editing.DISABLED, hidden = Where.PARENTED_TABLES)
    @PropertyLayout(named = "Property")
    public FixedAsset getFixedAsset() {
        return fixedAsset;
    }

    public void setFixedAsset(final FixedAsset fixedAsset) {
        this.fixedAsset = fixedAsset;
    }

    // //////////////////////////////////////

    @javax.jdo.annotations.Column(name = "paidByBankMandateId")
    private BankMandate paidBy;

    /**
     * Derived from the {@link #getLease() lease}, but safe to persist since
     * business rule states that all invoice items that are paid by
     * {@link BankMandate} (as opposed to simply by bank transfer) will be for
     * the same bank mandate.
     * 
     * <p>
     * Another reason for persisting this is that it allows eager validation
     * when attaching additional {@link InvoiceItem}s to an invoice, to check
     * that they relate to the same bank mandate (if they are to be paid by bank
     * mandate).
     */
    @Property(optionality = Optionality.OPTIONAL, editing = Editing.DISABLED, hidden = Where.ALL_TABLES)
    public BankMandate getPaidBy() {
        return paidBy;
    }

    public void setPaidBy(final BankMandate paidBy) {
        this.paidBy = paidBy;
    }

    // //////////////////////////////////////

    @Action(semantics = SemanticsOf.NON_IDEMPOTENT, invokeOn = InvokeOn.OBJECT_AND_COLLECTION)
    public Invoice submitToCoda() {
        doCollect();
        return this;
    }

    public String disableSubmitToCoda() {
        return getStatus() == InvoiceStatus.INVOICED || getStatus() == InvoiceStatus.APPROVED ? null
                : "Must be approved or invoiced";
    }

    // //////////////////////////////////////

    @Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION)
    public void remove() {
        // Can be called as bulk so have a safeguard
        if (disableRemove() == null) {
            doRemove();
        }
    }

    public String disableRemove() {
        return getStatus().invoiceIsChangable() ? null : "Only invoices with status New can be removed.";
    }

    @Programmatic
    public void doRemove() {
        for (InvoiceItem item : getItems()) {
            item.remove();
        }
        getContainer().remove(this);
    }

    // //////////////////////////////////////

    @javax.inject.Inject
    CollectionNumerators collectionNumerators;

    @javax.inject.Inject
    Invoices invoices;

    @javax.inject.Inject
    InvoiceItems invoiceItems;

}