001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.invoice.server.logic;
018
019import com.echothree.model.control.invoice.common.InvoiceLineUseTypes;
020import com.echothree.model.control.invoice.common.InvoiceRoleTypes;
021import com.echothree.model.control.invoice.common.InvoiceTimeTypes;
022import com.echothree.model.control.invoice.common.InvoiceTypes;
023import com.echothree.model.control.invoice.common.exception.UnknownInvoiceRoleTypeNameException;
024import com.echothree.model.control.invoice.common.exception.UnknownInvoiceSequenceException;
025import com.echothree.model.control.invoice.common.exception.UnknownInvoiceSequenceTypeException;
026import com.echothree.model.control.invoice.common.exception.UnknownInvoiceTypeNameException;
027import com.echothree.model.control.invoice.server.control.InvoiceControl;
028import com.echothree.model.control.party.server.control.PartyControl;
029import com.echothree.model.control.payment.common.BillingAccountRoleTypes;
030import com.echothree.model.control.payment.server.control.BillingControl;
031import com.echothree.model.control.payment.server.logic.BillingAccountLogic;
032import com.echothree.model.control.sequence.server.control.SequenceControl;
033import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic;
034import com.echothree.model.control.shipment.server.control.PartyFreeOnBoardControl;
035import com.echothree.model.control.term.common.TermTypes;
036import com.echothree.model.control.term.server.control.TermControl;
037import com.echothree.model.data.accounting.server.entity.Currency;
038import com.echothree.model.data.accounting.server.entity.GlAccount;
039import com.echothree.model.data.contact.server.entity.PartyContactMechanism;
040import com.echothree.model.data.invoice.server.entity.Invoice;
041import com.echothree.model.data.invoice.server.entity.InvoiceLine;
042import com.echothree.model.data.invoice.server.entity.InvoiceLineType;
043import com.echothree.model.data.invoice.server.entity.InvoiceLineUseType;
044import com.echothree.model.data.invoice.server.entity.InvoiceRoleType;
045import com.echothree.model.data.invoice.server.entity.InvoiceType;
046import com.echothree.model.data.invoice.server.entity.InvoiceTypeDetail;
047import com.echothree.model.data.party.server.entity.Party;
048import com.echothree.model.data.sequence.server.entity.Sequence;
049import com.echothree.model.data.sequence.server.entity.SequenceType;
050import com.echothree.model.data.shipment.server.entity.FreeOnBoard;
051import com.echothree.model.data.term.server.entity.Term;
052import com.echothree.model.data.term.server.entity.TermDetail;
053import com.echothree.util.common.message.ExecutionErrors;
054import com.echothree.util.common.persistence.BasePK;
055import com.echothree.util.server.control.BaseLogic;
056import com.echothree.util.server.message.ExecutionErrorAccumulator;
057import com.echothree.util.server.persistence.Session;
058
059public class InvoiceLogic
060        extends BaseLogic {
061
062    private InvoiceLogic() {
063        super();
064    }
065
066    private static class InvoiceLogicHolder {
067        static InvoiceLogic instance = new InvoiceLogic();
068    }
069
070    public static InvoiceLogic getInstance() {
071        return InvoiceLogicHolder.instance;
072    }
073    
074    public Currency getInvoiceCurrency(final Invoice invoice) {
075        return invoice.getLastDetail().getBillingAccount().getLastDetail().getCurrency();
076    }
077    
078    public InvoiceType getInvoiceTypeByName(final ExecutionErrorAccumulator eea, final String invoiceTypeName) {
079        var invoiceControl = Session.getModelController(InvoiceControl.class);
080        InvoiceType invoiceType = invoiceControl.getInvoiceTypeByName(invoiceTypeName);
081
082        if(invoiceType == null) {
083            handleExecutionError(UnknownInvoiceTypeNameException.class, eea, ExecutionErrors.UnknownInvoiceTypeName.name(), invoiceTypeName);
084        }
085
086        return invoiceType;
087    }
088
089    public InvoiceRoleType getInvoiceRoleTypeByName(final ExecutionErrorAccumulator eea, final String invoiceRoleTypeName) {
090        var invoiceControl = Session.getModelController(InvoiceControl.class);
091        InvoiceRoleType invoiceRoleType = invoiceControl.getInvoiceRoleTypeByName(invoiceRoleTypeName);
092
093        if(invoiceRoleType == null) {
094            handleExecutionError(UnknownInvoiceRoleTypeNameException.class, eea, ExecutionErrors.UnknownInvoiceRoleTypeName.name(), invoiceRoleTypeName);
095        }
096
097        return invoiceRoleType;
098    }
099
100    public SequenceType getInvoiceSequenceType(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
101        SequenceType sequenceType = null;
102        InvoiceType parentInvoiceType = invoiceType;
103        
104        do {
105            InvoiceTypeDetail invoiceTypeDetail = parentInvoiceType.getLastDetail();
106            
107            sequenceType = invoiceTypeDetail.getInvoiceSequenceType();
108            
109            if(sequenceType == null) {
110                parentInvoiceType = invoiceTypeDetail.getParentInvoiceType();
111            } else {
112                break;
113            }
114        } while(parentInvoiceType != null);
115        
116        if(sequenceType == null) {
117            var sequenceControl = Session.getModelController(SequenceControl.class);
118            
119            sequenceType = sequenceControl.getDefaultSequenceType();
120        }
121        
122        if(sequenceType == null) {
123            handleExecutionError(UnknownInvoiceSequenceTypeException.class, eea, ExecutionErrors.UnknownInvoiceSequenceType.name(), invoiceType.getLastDetail().getInvoiceTypeName());
124        }
125        
126        return sequenceType;
127    }
128    
129    public Sequence getInvoiceSequence(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
130        Sequence sequence = null;
131        SequenceType sequenceType = getInvoiceSequenceType(eea, invoiceType);
132        
133        if(eea == null || !eea.hasExecutionErrors()) {
134            var sequenceControl = Session.getModelController(SequenceControl.class);
135            
136            sequence = sequenceControl.getDefaultSequence(sequenceType);
137        }
138        
139        if(sequence == null) {
140            handleExecutionError(UnknownInvoiceSequenceException.class, eea, ExecutionErrors.UnknownInvoiceSequence.name(), invoiceType.getLastDetail().getInvoiceTypeName());
141        }
142        
143        return sequence;
144    }
145    
146    public String getInvoiceName(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
147        String invoiceName = null;
148        Sequence sequence = getInvoiceSequence(eea, invoiceType);
149        
150        if(eea == null || !eea.hasExecutionErrors()) {
151            invoiceName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(sequence);
152        }
153        
154        return invoiceName;
155    }
156
157    public Term getInvoiceTerm(final ExecutionErrorAccumulator eea, final Party billFrom, Term term) {
158        if(term == null) {
159            var termControl = Session.getModelController(TermControl.class);
160
161            term = termControl.getPartyTerm(billFrom).getTerm();
162        }
163
164        if(term == null) {
165            eea.addExecutionError(ExecutionErrors.UnknownPartyTerm.name(), billFrom.getLastDetail().getPartyName());
166        }
167
168        return term;
169    }
170
171    public FreeOnBoard getInvoiceFreeOnBoard(final ExecutionErrorAccumulator eea, final Party billFrom, FreeOnBoard freeOnBoard) {
172        if(freeOnBoard == null) {
173            var partyFreeOnBoardControl = Session.getModelController(PartyFreeOnBoardControl.class);
174
175            freeOnBoard = partyFreeOnBoardControl.getPartyFreeOnBoard(billFrom).getFreeOnBoard();
176        }
177
178        if(freeOnBoard == null) {
179            eea.addExecutionError(ExecutionErrors.UnknownPartyFreeOnBoard.name(), billFrom.getLastDetail().getPartyName());
180        }
181
182        return freeOnBoard;
183    }
184
185    public String getTermTypeName(final Term term) {
186        TermDetail termDetail = term.getLastDetail();
187        
188        return termDetail.getTermType().getTermTypeName();
189    }
190    
191    public Long getDueTime(final Session session, final Term term, final String termTypeName, Long invoicedTime) {
192        var termControl = Session.getModelController(TermControl.class);
193        Long dueTime;
194        
195        if(termTypeName.equals(TermTypes.STANDARD.name())) {
196            dueTime = invoicedTime + termControl.getStandardTerm(term).getNetDueDays() * (1000 * 60 * 60 * 24);
197        } else if(termTypeName.equals(TermTypes.PREPAID.name())) {
198            dueTime = null;
199        } else { // TODO: TermTypes.DATE_DRIVEN.name()
200            dueTime = session.START_TIME_LONG;
201        }
202        
203        return dueTime;
204    }
205    
206    public Long getPaidTime(final Session session, final String termTypeName) {
207        Long paidTime;
208        
209        if(termTypeName.equals(TermTypes.PREPAID.name())) {
210            paidTime = session.START_TIME_LONG;
211        } else {
212            paidTime = null;
213        }
214        
215        return paidTime;
216    }
217    
218    public Invoice createInvoice(final Session session, final ExecutionErrorAccumulator eea, String invoiceTypeName, final Party billFrom,
219            final PartyContactMechanism billFromPartyContactMechanism, final Party billTo, final PartyContactMechanism billToPartyContactMechanism, Currency currency, final GlAccount glAccount,
220            Term term, FreeOnBoard freeOnBoard, final String reference, final String description, Long invoicedTime, Long dueTime, Long paidTime, final BasePK createdBy) {
221        var partyControl = Session.getModelController(PartyControl.class);
222        Invoice invoice = null;
223        
224        currency = currency == null ? partyControl.getPreferredCurrency(billFrom) : currency;
225        var billingAccount = BillingAccountLogic.getInstance().getBillingAccount(eea, billFrom, billFromPartyContactMechanism, billTo, billToPartyContactMechanism, currency, null,
226                null, createdBy);
227        
228        if(eea == null || !eea.hasExecutionErrors()) {
229            var invoiceControl = Session.getModelController(InvoiceControl.class);
230            var invoiceType = invoiceControl.getInvoiceTypeByName(invoiceTypeName);
231            
232            if(invoiceType != null) {
233                var invoiceName = getInvoiceName(eea, invoiceType);
234
235                invoiceTypeName = invoiceType.getLastDetail().getInvoiceTypeName(); // Clean-up capitalization.
236
237                if(eea == null || !eea.hasExecutionErrors()) {
238                    term = getInvoiceTerm(eea, billFrom, term);
239
240                    // FreeOnBoard is only allowed for SALES_INVOICEs and PURCHASE_INVOICEs.
241                    if(invoiceTypeName.equals(InvoiceTypes.SALES_INVOICE.name()) || invoiceTypeName.equals(InvoiceTypes.PURCHASE_INVOICE.name())) {
242                        freeOnBoard = getInvoiceFreeOnBoard(eea, billFrom, freeOnBoard);
243                    } else if(freeOnBoard != null) {
244                        eea.addExecutionError(ExecutionErrors.FreeOnBoardNotAllowed.name(), freeOnBoard.getLastDetail().getFreeOnBoardName());
245                    }
246
247                    if(eea == null || !eea.hasExecutionErrors()) {
248                        var billingControl = Session.getModelController(BillingControl.class);
249                        var invoicedTimeLogic = InvoiceTimeLogic.getInstance();
250                        var billFromContactMechanism = billingControl.getBillingAccountRoleUsingNames(billingAccount, BillingAccountRoleTypes.BILL_FROM.name()).getPartyContactMechanism();
251                        var billToContactMechanism = billingControl.getBillingAccountRoleUsingNames(billingAccount, BillingAccountRoleTypes.BILL_TO.name()).getPartyContactMechanism();
252                        var termTypeName = getTermTypeName(term);
253
254                        invoicedTime = invoicedTime == null ? session.START_TIME_LONG : invoicedTime;
255                        dueTime = dueTime == null ? getDueTime(session, term, termTypeName, invoicedTime) : dueTime;
256                        paidTime = paidTime == null ? getPaidTime(session, termTypeName) : paidTime;
257
258                        invoice = invoiceControl.createInvoice(invoiceType, invoiceName, billingAccount, glAccount, term, freeOnBoard, reference, description, createdBy);
259                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.INVOICED.name(), invoicedTime, createdBy);
260                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.DUE.name(), dueTime, createdBy);
261                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.PAID.name(), paidTime, createdBy);
262
263                        invoiceControl.createInvoiceRoleUsingNames(invoice, billFrom, billFromContactMechanism, InvoiceRoleTypes.INVOICE_FROM.name(), createdBy);
264                        invoiceControl.createInvoiceRoleUsingNames(invoice, billTo, billToContactMechanism, InvoiceRoleTypes.INVOICE_TO.name(), createdBy);
265                    }
266                }
267            } else {
268                eea.addExecutionError(ExecutionErrors.UnknownInvoiceTypeName.name(), invoiceTypeName);
269            }
270        }
271        
272        return invoice;
273    }
274    
275    public InvoiceLine createInvoiceLine(final ExecutionErrorAccumulator eea, final Invoice invoice, final Integer invoiceLineSequence, final InvoiceLine parentInvoiceLine,
276            final Long amount, final InvoiceLineType invoiceLineType, GlAccount glAccount, final String description, final BasePK createdBy) {
277        InvoiceLine invoiceLine = null;
278        var invoiceControl = Session.getModelController(InvoiceControl.class);
279        InvoiceLineUseType invoiceLineUseType = invoiceControl.getInvoiceLineUseTypeByName(InvoiceLineUseTypes.GL_ACCOUNT.name());
280        
281        if(glAccount == null) {
282            glAccount = invoiceLineType.getLastDetail().getDefaultGlAccount();
283        }
284        
285        if(glAccount != null) {
286            invoiceLine = invoiceControl.createInvoiceLine(invoice, invoiceLineSequence, parentInvoiceLine, invoiceLineType, invoiceLineUseType, amount, description, createdBy);
287
288            invoiceControl.createInvoiceLineGlAccount(invoiceLine, glAccount, createdBy);
289        } else {
290            eea.addExecutionError(ExecutionErrors.MissingRequiredGlAccount.name());
291        }
292        
293        return invoiceLine;
294    }
295    
296}