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