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.subscription.server.logic;
018
019import com.echothree.model.control.chain.common.ChainKinds;
020import com.echothree.model.control.chain.common.ChainTypes;
021import com.echothree.model.control.chain.common.ChainEntityRoleTypes;
022import com.echothree.model.control.chain.server.control.ChainControl;
023import com.echothree.model.control.chain.server.logic.ChainInstanceLogic;
024import com.echothree.model.control.chain.server.logic.ChainLogic;
025import com.echothree.model.control.chain.server.logic.ChainTypeLogic;
026import com.echothree.model.control.subscription.server.control.SubscriptionControl;
027import com.echothree.model.data.chain.server.entity.Chain;
028import com.echothree.model.data.chain.server.entity.ChainInstance;
029import com.echothree.model.data.subscription.server.entity.Subscription;
030import com.echothree.util.common.persistence.BasePK;
031import com.echothree.util.server.control.BaseLogic;
032import com.echothree.util.server.message.ExecutionErrorAccumulator;
033import com.echothree.util.server.persistence.Session;
034import javax.enterprise.context.ApplicationScoped;
035import javax.enterprise.inject.spi.CDI;
036import javax.inject.Inject;
037
038@ApplicationScoped
039public class SubscriptionChainLogic
040        extends BaseLogic {
041
042    @Inject
043    protected ChainLogic chainLogic;
044
045    @Inject
046    protected ChainTypeLogic chainTypeLogic;
047
048    @Inject
049    protected ChainInstanceLogic chainInstanceLogic;
050
051    protected SubscriptionChainLogic() {
052        super();
053    }
054
055    public static SubscriptionChainLogic getInstance() {
056        return CDI.current().select(SubscriptionChainLogic.class).get();
057    }
058    
059    /** createChainInstance is different than the version in BaseChainLogic, it requires a Subscription parameter. Subscriptions
060     * are allowed to override the Chain being used in each SubscriptionType, so this takes that into account when choosing a
061     * Chain. If there is no SubscriptionTypeChain, then we fall back to the default for the ChainType, following the normal
062     * rules that take OfferChainTypes into account as well. If the chainTypeName == RENEWAL, then we immediately fall back to the
063     * default for the ChainType.
064     * 
065     * TODO: RENEWAL should really be based on the amount of time remaining in the Subscription, and not just fall back to the
066     * default. getSubscriptionTypeChainsBySubscriptionTypeAndChainType(...) could order by the remaining time, and then search
067     * for the one that's the best fit.
068     */
069    protected ChainInstance createChainInstance(final ExecutionErrorAccumulator eea, final String chainKindName, final String chainTypeName,
070            final Subscription subscription, final BasePK createdBy) {
071        var subscriptionControl = Session.getModelController(SubscriptionControl.class);
072        var subscriptionType = subscription.getLastDetail().getSubscriptionType();
073        var chainType = chainTypeLogic.getChainTypeByName(eea, chainKindName, chainTypeName);
074        var party = subscription.getLastDetail().getParty();
075        var subscriptionTypeChains = chainTypeName.equals(ChainTypes.RENEWAL.name()) ? null
076                : subscriptionControl.getSubscriptionTypeChainsBySubscriptionTypeAndChainType(subscriptionType, chainType);
077        Chain chain;
078        ChainInstance chainInstance = null;
079        
080        if(subscriptionTypeChains == null || subscriptionTypeChains.isEmpty()) {
081            chain = chainLogic.getChain(eea, chainType, party);
082        } else {
083            chain = subscriptionTypeChains.iterator().next().getChain();
084        }
085        
086        if(chain != null) {
087            chainInstance = chainInstanceLogic.createChainInstance(eea, chainType, party, createdBy);
088        }
089        
090        return chainInstance;
091    }
092    
093    protected ChainInstance createSubscriptionChainInstance(final ExecutionErrorAccumulator eea, final String chainTypeName, final Subscription subscription,
094            final BasePK createdBy) {
095        var chainInstance = createChainInstance(eea, ChainKinds.SUBSCRIPTION.name(), chainTypeName, subscription, createdBy);
096        
097        if(chainInstance != null) {
098            var chainControl = Session.getModelController(ChainControl.class);
099            var chainType = chainInstance.getLastDetail().getChain().getLastDetail().getChainType();
100        
101            chainControl.createChainInstanceEntityRole(chainInstance, chainControl.getChainEntityRoleTypeByName(chainType,
102                    ChainEntityRoleTypes.SUBSCRIPTION.name()), subscription.getPrimaryKey(), createdBy);
103        }
104        
105        return chainInstance;
106    }
107    
108    public ChainInstance createSubscriptionInitialChainInstance(final ExecutionErrorAccumulator eea, final Subscription subscription,
109            final BasePK createdBy) {
110        return createSubscriptionChainInstance(eea, ChainTypes.INITIAL.name(), subscription, createdBy);
111    }
112    
113    public ChainInstance createSubscriptionExpirationWarningChainInstance(final ExecutionErrorAccumulator eea, final Subscription subscription,
114            final BasePK createdBy) {
115        return createSubscriptionChainInstance(eea, ChainTypes.EXPIRATION_WARNING.name(), subscription, createdBy);
116    }
117    
118    /** TODO: Consider the RemainingTime in the SubscriptionDetail when choosing the Chain to use.
119     */
120    public ChainInstance createSubscriptionRenewalChainInstance(final ExecutionErrorAccumulator eea, final Subscription subscription,
121            final BasePK createdBy) {
122        return createSubscriptionChainInstance(eea, ChainTypes.RENEWAL.name(), subscription, createdBy);
123    }
124    
125    public ChainInstance createSubscriptionExpirationChainInstance(final ExecutionErrorAccumulator eea, final Subscription subscription,
126            final BasePK createdBy) {
127        return createSubscriptionChainInstance(eea, ChainTypes.EXPIRATION.name(), subscription, createdBy);
128    }
129    
130}