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}