001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 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.InvoiceRoleType;
044import com.echothree.model.data.invoice.server.entity.InvoiceType;
045import com.echothree.model.data.party.server.entity.Party;
046import com.echothree.model.data.sequence.server.entity.Sequence;
047import com.echothree.model.data.sequence.server.entity.SequenceType;
048import com.echothree.model.data.shipment.server.entity.FreeOnBoard;
049import com.echothree.model.data.term.server.entity.Term;
050import com.echothree.util.common.message.ExecutionErrors;
051import com.echothree.util.common.persistence.BasePK;
052import com.echothree.util.server.control.BaseLogic;
053import com.echothree.util.server.message.ExecutionErrorAccumulator;
054import com.echothree.util.server.persistence.Session;
055import javax.enterprise.context.ApplicationScoped;
056import javax.enterprise.inject.spi.CDI;
057
058@ApplicationScoped
059public class InvoiceLogic
060        extends BaseLogic {
061
062    protected InvoiceLogic() {
063        super();
064    }
065
066    public static InvoiceLogic getInstance() {
067        return CDI.current().select(InvoiceLogic.class).get();
068    }
069    
070    public Currency getInvoiceCurrency(final Invoice invoice) {
071        return invoice.getLastDetail().getBillingAccount().getLastDetail().getCurrency();
072    }
073    
074    public InvoiceType getInvoiceTypeByName(final ExecutionErrorAccumulator eea, final String invoiceTypeName) {
075        var invoiceControl = Session.getModelController(InvoiceControl.class);
076        var invoiceType = invoiceControl.getInvoiceTypeByName(invoiceTypeName);
077
078        if(invoiceType == null) {
079            handleExecutionError(UnknownInvoiceTypeNameException.class, eea, ExecutionErrors.UnknownInvoiceTypeName.name(), invoiceTypeName);
080        }
081
082        return invoiceType;
083    }
084
085    public InvoiceRoleType getInvoiceRoleTypeByName(final ExecutionErrorAccumulator eea, final String invoiceRoleTypeName) {
086        var invoiceControl = Session.getModelController(InvoiceControl.class);
087        var invoiceRoleType = invoiceControl.getInvoiceRoleTypeByName(invoiceRoleTypeName);
088
089        if(invoiceRoleType == null) {
090            handleExecutionError(UnknownInvoiceRoleTypeNameException.class, eea, ExecutionErrors.UnknownInvoiceRoleTypeName.name(), invoiceRoleTypeName);
091        }
092
093        return invoiceRoleType;
094    }
095
096    public SequenceType getInvoiceSequenceType(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
097        SequenceType sequenceType;
098        var parentInvoiceType = invoiceType;
099        
100        do {
101            var invoiceTypeDetail = parentInvoiceType.getLastDetail();
102            
103            sequenceType = invoiceTypeDetail.getInvoiceSequenceType();
104            
105            if(sequenceType == null) {
106                parentInvoiceType = invoiceTypeDetail.getParentInvoiceType();
107            } else {
108                break;
109            }
110        } while(parentInvoiceType != null);
111        
112        if(sequenceType == null) {
113            var sequenceControl = Session.getModelController(SequenceControl.class);
114            
115            sequenceType = sequenceControl.getDefaultSequenceType();
116        }
117        
118        if(sequenceType == null) {
119            handleExecutionError(UnknownInvoiceSequenceTypeException.class, eea, ExecutionErrors.UnknownInvoiceSequenceType.name(), invoiceType.getLastDetail().getInvoiceTypeName());
120        }
121        
122        return sequenceType;
123    }
124    
125    public Sequence getInvoiceSequence(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
126        Sequence sequence = null;
127        var sequenceType = getInvoiceSequenceType(eea, invoiceType);
128        
129        if(eea == null || !eea.hasExecutionErrors()) {
130            var sequenceControl = Session.getModelController(SequenceControl.class);
131            
132            sequence = sequenceControl.getDefaultSequence(sequenceType);
133        }
134        
135        if(sequence == null) {
136            handleExecutionError(UnknownInvoiceSequenceException.class, eea, ExecutionErrors.UnknownInvoiceSequence.name(), invoiceType.getLastDetail().getInvoiceTypeName());
137        }
138        
139        return sequence;
140    }
141    
142    public String getInvoiceName(final ExecutionErrorAccumulator eea, final InvoiceType invoiceType) {
143        String invoiceName = null;
144        var sequence = getInvoiceSequence(eea, invoiceType);
145        
146        if(eea == null || !eea.hasExecutionErrors()) {
147            invoiceName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(sequence);
148        }
149        
150        return invoiceName;
151    }
152
153    public Term getInvoiceTerm(final ExecutionErrorAccumulator eea, final Party billFrom, Term term) {
154        if(term == null) {
155            var termControl = Session.getModelController(TermControl.class);
156
157            term = termControl.getPartyTerm(billFrom).getTerm();
158        }
159
160        if(term == null) {
161            eea.addExecutionError(ExecutionErrors.UnknownPartyTerm.name(), billFrom.getLastDetail().getPartyName());
162        }
163
164        return term;
165    }
166
167    public FreeOnBoard getInvoiceFreeOnBoard(final ExecutionErrorAccumulator eea, final Party billFrom, FreeOnBoard freeOnBoard) {
168        if(freeOnBoard == null) {
169            var partyFreeOnBoardControl = Session.getModelController(PartyFreeOnBoardControl.class);
170
171            freeOnBoard = partyFreeOnBoardControl.getPartyFreeOnBoard(billFrom).getFreeOnBoard();
172        }
173
174        if(freeOnBoard == null) {
175            eea.addExecutionError(ExecutionErrors.UnknownPartyFreeOnBoard.name(), billFrom.getLastDetail().getPartyName());
176        }
177
178        return freeOnBoard;
179    }
180
181    public String getTermTypeName(final Term term) {
182        var termDetail = term.getLastDetail();
183        
184        return termDetail.getTermType().getTermTypeName();
185    }
186    
187    public Long getDueTime(final Session session, final Term term, final String termTypeName, Long invoicedTime) {
188        var termControl = Session.getModelController(TermControl.class);
189        Long dueTime;
190        
191        if(termTypeName.equals(TermTypes.STANDARD.name())) {
192            dueTime = invoicedTime + termControl.getStandardTerm(term).getNetDueDays() * (1000 * 60 * 60 * 24);
193        } else if(termTypeName.equals(TermTypes.PREPAID.name())) {
194            dueTime = null;
195        } else { // TODO: TermTypes.DATE_DRIVEN.name()
196            dueTime = session.START_TIME_LONG;
197        }
198        
199        return dueTime;
200    }
201    
202    public Long getPaidTime(final Session session, final String termTypeName) {
203        Long paidTime;
204        
205        if(termTypeName.equals(TermTypes.PREPAID.name())) {
206            paidTime = session.START_TIME_LONG;
207        } else {
208            paidTime = null;
209        }
210        
211        return paidTime;
212    }
213    
214    public Invoice createInvoice(final Session session, final ExecutionErrorAccumulator eea, String invoiceTypeName, final Party billFrom,
215            final PartyContactMechanism billFromPartyContactMechanism, final Party billTo, final PartyContactMechanism billToPartyContactMechanism, Currency currency, final GlAccount glAccount,
216            Term term, FreeOnBoard freeOnBoard, final String reference, final String description, Long invoicedTime, Long dueTime, Long paidTime, final BasePK createdBy) {
217        var partyControl = Session.getModelController(PartyControl.class);
218        Invoice invoice = null;
219        
220        currency = currency == null ? partyControl.getPreferredCurrency(billFrom) : currency;
221        var billingAccount = BillingAccountLogic.getInstance().getBillingAccount(eea, billFrom, billFromPartyContactMechanism, billTo, billToPartyContactMechanism, currency, null,
222                null, createdBy);
223        
224        if(eea == null || !eea.hasExecutionErrors()) {
225            var invoiceControl = Session.getModelController(InvoiceControl.class);
226            var invoiceType = invoiceControl.getInvoiceTypeByName(invoiceTypeName);
227            
228            if(invoiceType != null) {
229                var invoiceName = getInvoiceName(eea, invoiceType);
230
231                invoiceTypeName = invoiceType.getLastDetail().getInvoiceTypeName(); // Clean-up capitalization.
232
233                if(eea == null || !eea.hasExecutionErrors()) {
234                    term = getInvoiceTerm(eea, billFrom, term);
235
236                    // FreeOnBoard is only allowed for SALES_INVOICEs and PURCHASE_INVOICEs.
237                    if(invoiceTypeName.equals(InvoiceTypes.SALES_INVOICE.name()) || invoiceTypeName.equals(InvoiceTypes.PURCHASE_INVOICE.name())) {
238                        freeOnBoard = getInvoiceFreeOnBoard(eea, billFrom, freeOnBoard);
239                    } else if(freeOnBoard != null) {
240                        eea.addExecutionError(ExecutionErrors.FreeOnBoardNotAllowed.name(), freeOnBoard.getLastDetail().getFreeOnBoardName());
241                    }
242
243                    if(eea == null || !eea.hasExecutionErrors()) {
244                        var billingControl = Session.getModelController(BillingControl.class);
245                        var invoicedTimeLogic = InvoiceTimeLogic.getInstance();
246                        var billFromContactMechanism = billingControl.getBillingAccountRoleUsingNames(billingAccount, BillingAccountRoleTypes.BILL_FROM.name()).getPartyContactMechanism();
247                        var billToContactMechanism = billingControl.getBillingAccountRoleUsingNames(billingAccount, BillingAccountRoleTypes.BILL_TO.name()).getPartyContactMechanism();
248                        var termTypeName = getTermTypeName(term);
249
250                        invoicedTime = invoicedTime == null ? session.START_TIME_LONG : invoicedTime;
251                        dueTime = dueTime == null ? getDueTime(session, term, termTypeName, invoicedTime) : dueTime;
252                        paidTime = paidTime == null ? getPaidTime(session, termTypeName) : paidTime;
253
254                        invoice = invoiceControl.createInvoice(invoiceType, invoiceName, billingAccount, glAccount, term, freeOnBoard, reference, description, createdBy);
255                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.INVOICED.name(), invoicedTime, createdBy);
256                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.DUE.name(), dueTime, createdBy);
257                        invoicedTimeLogic.createOrUpdateInvoiceTimeIfNotNull(null, invoice, InvoiceTimeTypes.PAID.name(), paidTime, createdBy);
258
259                        invoiceControl.createInvoiceRoleUsingNames(invoice, billFrom, billFromContactMechanism, InvoiceRoleTypes.INVOICE_FROM.name(), createdBy);
260                        invoiceControl.createInvoiceRoleUsingNames(invoice, billTo, billToContactMechanism, InvoiceRoleTypes.INVOICE_TO.name(), createdBy);
261                    }
262                }
263            } else {
264                eea.addExecutionError(ExecutionErrors.UnknownInvoiceTypeName.name(), invoiceTypeName);
265            }
266        }
267        
268        return invoice;
269    }
270    
271    public InvoiceLine createInvoiceLine(final ExecutionErrorAccumulator eea, final Invoice invoice, final Integer invoiceLineSequence, final InvoiceLine parentInvoiceLine,
272            final Long amount, final InvoiceLineType invoiceLineType, GlAccount glAccount, final String description, final BasePK createdBy) {
273        InvoiceLine invoiceLine = null;
274        var invoiceControl = Session.getModelController(InvoiceControl.class);
275        var invoiceLineUseType = invoiceControl.getInvoiceLineUseTypeByName(InvoiceLineUseTypes.GL_ACCOUNT.name());
276        
277        if(glAccount == null) {
278            glAccount = invoiceLineType.getLastDetail().getDefaultGlAccount();
279        }
280        
281        if(glAccount != null) {
282            invoiceLine = invoiceControl.createInvoiceLine(invoice, invoiceLineSequence, parentInvoiceLine, invoiceLineType, invoiceLineUseType, amount, description, createdBy);
283
284            invoiceControl.createInvoiceLineGlAccount(invoiceLine, glAccount, createdBy);
285        } else {
286            eea.addExecutionError(ExecutionErrors.MissingRequiredGlAccount.name());
287        }
288        
289        return invoiceLine;
290    }
291    
292}