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.accounting.common.AccountingConstants;
020import com.echothree.model.control.accounting.server.logic.GlAccountLogic;
021import com.echothree.model.control.core.server.control.CoreControl;
022import com.echothree.model.control.invoice.common.InvoiceTypes;
023import com.echothree.model.control.invoice.common.choice.PurchaseInvoiceStatusChoicesBean;
024import com.echothree.model.control.invoice.common.workflow.PurchaseInvoiceStatusConstants;
025import com.echothree.model.control.invoice.server.control.InvoiceControl;
026import com.echothree.model.control.vendor.server.control.VendorControl;
027import com.echothree.model.control.workflow.server.control.WorkflowControl;
028import com.echothree.model.data.accounting.server.entity.Currency;
029import com.echothree.model.data.accounting.server.entity.GlAccount;
030import com.echothree.model.data.contact.server.entity.PartyContactMechanism;
031import com.echothree.model.data.core.server.entity.EntityInstance;
032import com.echothree.model.data.invoice.server.entity.Invoice;
033import com.echothree.model.data.invoice.server.entity.InvoiceLine;
034import com.echothree.model.data.invoice.server.entity.InvoiceLineType;
035import com.echothree.model.data.party.common.pk.PartyPK;
036import com.echothree.model.data.party.server.entity.Language;
037import com.echothree.model.data.party.server.entity.Party;
038import com.echothree.model.data.shipment.server.entity.FreeOnBoard;
039import com.echothree.model.data.term.server.entity.Term;
040import com.echothree.model.data.vendor.server.entity.Vendor;
041import com.echothree.model.data.workflow.server.entity.WorkflowDestination;
042import com.echothree.model.data.workflow.server.entity.WorkflowEntityStatus;
043import com.echothree.util.common.message.ExecutionErrors;
044import com.echothree.util.common.persistence.BasePK;
045import com.echothree.util.server.message.ExecutionErrorAccumulator;
046import com.echothree.util.server.persistence.Session;
047
048public class PurchaseInvoiceLogic {
049
050    private PurchaseInvoiceLogic() {
051        super();
052    }
053
054    private static class PurchaseInvoiceLogicHolder {
055        static PurchaseInvoiceLogic instance = new PurchaseInvoiceLogic();
056    }
057
058    public static PurchaseInvoiceLogic getInstance() {
059        return PurchaseInvoiceLogicHolder.instance;
060    }
061    
062    public Invoice getInvoiceByName(String invoiceName) {
063        var invoiceControl = Session.getModelController(InvoiceControl.class);
064        
065        return invoiceControl.getInvoiceByNameUsingNames(InvoiceTypes.PURCHASE_INVOICE.name(), invoiceName);
066    }
067    
068    protected void validateReference(final ExecutionErrorAccumulator eea, final Party billFrom, final String reference, final Vendor vendor) {
069        if(vendor.getRequireReference() && reference == null) {
070            eea.addExecutionError(ExecutionErrors.PurchaseInvoiceReferenceRequired.name());
071        } else if(reference != null) {
072            var invoiceControl = Session.getModelController(InvoiceControl.class);
073            
074            if(!vendor.getAllowReferenceDuplicates() && invoiceControl.countInvoicesByInvoiceFromAndReference(billFrom, reference) != 0) {
075                eea.addExecutionError(ExecutionErrors.PurchaseInvoiceDuplicateReference.name());
076            } else {
077                String referenceValidationPattern = vendor.getReferenceValidationPattern();
078                
079                if(referenceValidationPattern != null && !reference.matches(referenceValidationPattern)) {
080                    eea.addExecutionError(ExecutionErrors.InvalidPurchaseInvoiceReference.name());
081                }
082            }
083        }
084    }
085
086    protected GlAccount getApGlAccount(final ExecutionErrorAccumulator eea, final Vendor vendor) {
087        GlAccount vendorApGlAccount = vendor.getApGlAccount();
088        GlAccount vendorTypeDefaultApGlAccount = vendor.getVendorType().getLastDetail().getDefaultApGlAccount();
089        GlAccount[] glAccounts = { vendorApGlAccount, vendorTypeDefaultApGlAccount };
090
091        return GlAccountLogic.getInstance().getDefaultGlAccountByCategory(eea, new GlAccount[]{
092                    vendor.getApGlAccount(),
093                    vendor.getVendorType().getLastDetail().getDefaultApGlAccount()
094                }, AccountingConstants.GlAccountCategory_ACCOUNTS_PAYABLE, ExecutionErrors.UnknownDefaultApGlAccount.name());
095    }
096    
097    public Invoice createInvoice(final Session session, final ExecutionErrorAccumulator eea, final Party billFrom,
098            final PartyContactMechanism billFromPartyContactMechanism, final Party billTo, final PartyContactMechanism billToPartyContactMechanism,
099            final Currency currency, final Term term, final FreeOnBoard freeOnBoard, final String reference,
100            final String description, final Long invoicedTime, final Long dueTime,
101            final Long paidTime, final String workflowEntranceName, final BasePK createdBy) {
102        Invoice invoice = null;
103        var vendorControl = Session.getModelController(VendorControl.class);
104        Vendor vendor = vendorControl.getVendor(billFrom);
105        GlAccount glAccount = getApGlAccount(eea, vendor);
106        
107        validateReference(eea, billFrom, reference, vendor);
108        
109        if(eea == null || !eea.hasExecutionErrors()) {
110            invoice = InvoiceLogic.getInstance().createInvoice(session, eea, InvoiceTypes.PURCHASE_INVOICE.name(), billFrom,
111                    billFromPartyContactMechanism, billTo, billToPartyContactMechanism, currency, glAccount, term,
112                    freeOnBoard, reference, description, invoicedTime, dueTime, paidTime, createdBy);
113
114            if(!eea.hasExecutionErrors() && workflowEntranceName != null) {
115                var coreControl = Session.getModelController(CoreControl.class);
116                var workflowControl = Session.getModelController(WorkflowControl.class);
117                EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(invoice.getPrimaryKey());
118
119                workflowControl.addEntityToWorkflowUsingNames(null, PurchaseInvoiceStatusConstants.Workflow_PURCHASE_INVOICE_STATUS, workflowEntranceName,
120                        entityInstance, null, null, createdBy);
121            }
122        }
123        
124        return invoice;
125    }
126    
127    public InvoiceLine createInvoiceLine(final ExecutionErrorAccumulator eea, final Invoice invoice, final Integer invoiceLineSequence, final InvoiceLine parentInvoiceLine,
128            final Long amount, final InvoiceLineType invoiceLineType, final GlAccount glAccount, final String description, final BasePK createdBy) {
129        var coreControl = Session.getModelController(CoreControl.class);
130        var workflowControl = Session.getModelController(WorkflowControl.class);
131        InvoiceLine invoiceLine = null;
132        EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(invoice.getPrimaryKey());
133        WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(PurchaseInvoiceStatusConstants.Workflow_PURCHASE_INVOICE_STATUS, entityInstance);
134        String workflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName();
135
136        if(workflowStepName.equals(PurchaseInvoiceStatusConstants.WorkflowStep_ENTRY)) {
137            invoiceLine = InvoiceLogic.getInstance().createInvoiceLine(eea, invoice, invoiceLineSequence, parentInvoiceLine, amount, invoiceLineType, glAccount, description, createdBy);
138        } else {
139            eea.addExecutionError(ExecutionErrors.InvalidPurchaseInvoiceStatus.name(), invoice.getLastDetail().getInvoiceName(), workflowStepName);
140        }
141        
142        return invoiceLine;
143    }
144    
145    public PurchaseInvoiceStatusChoicesBean getPurchaseInvoiceStatusChoices(final String defaultInvoiceStatusChoice, final Language language, final boolean allowNullChoice,
146            final Invoice invoice, final PartyPK partyPK) {
147        var workflowControl = Session.getModelController(WorkflowControl.class);
148        PurchaseInvoiceStatusChoicesBean purchaseInvoiceStatusChoicesBean = new PurchaseInvoiceStatusChoicesBean();
149        
150        if(invoice == null) {
151            workflowControl.getWorkflowEntranceChoices(purchaseInvoiceStatusChoicesBean, defaultInvoiceStatusChoice, language, allowNullChoice,
152                    workflowControl.getWorkflowByName(PurchaseInvoiceStatusConstants.Workflow_PURCHASE_INVOICE_STATUS), partyPK);
153        } else {
154            var coreControl = Session.getModelController(CoreControl.class);
155            EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(invoice.getPrimaryKey());
156            WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(PurchaseInvoiceStatusConstants.Workflow_PURCHASE_INVOICE_STATUS, entityInstance);
157            
158            workflowControl.getWorkflowDestinationChoices(purchaseInvoiceStatusChoicesBean, defaultInvoiceStatusChoice, language, allowNullChoice, workflowEntityStatus.getWorkflowStep(), partyPK);
159        }
160        
161        return purchaseInvoiceStatusChoicesBean;
162    }
163    
164    public void setPurchaseInvoiceStatus(final ExecutionErrorAccumulator eea, final Invoice invoice, final String invoiceStatusChoice, final PartyPK modifiedBy) {
165        var coreControl = Session.getModelController(CoreControl.class);
166        var workflowControl = Session.getModelController(WorkflowControl.class);
167        EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(invoice.getPrimaryKey());
168        WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(PurchaseInvoiceStatusConstants.Workflow_PURCHASE_INVOICE_STATUS,
169                entityInstance);
170        WorkflowDestination workflowDestination = invoiceStatusChoice == null? null: workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), invoiceStatusChoice);
171        
172        if(workflowDestination != null || invoiceStatusChoice == null) {
173            workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, null, modifiedBy);
174        } else {
175            eea.addExecutionError(ExecutionErrors.UnknownPurchaseInvoiceStatusChoice.name(), invoiceStatusChoice);
176        }
177    }
178    
179}