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}