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}