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.period.server.logic; 018 019import com.echothree.model.control.party.server.control.PartyControl; 020import com.echothree.model.control.period.common.PeriodConstants; 021import com.echothree.model.control.period.server.control.PeriodControl; 022import com.echothree.model.data.party.common.pk.PartyPK; 023import com.echothree.model.data.period.server.entity.Period; 024import com.echothree.model.data.period.server.entity.PeriodKind; 025import com.echothree.util.common.message.ExecutionErrors; 026import com.echothree.util.server.message.ExecutionErrorAccumulator; 027import com.echothree.util.server.persistence.EntityPermission; 028import com.echothree.util.server.persistence.Session; 029import java.time.ZoneId; 030import java.time.ZonedDateTime; 031import java.util.Formatter; 032import java.util.Locale; 033import javax.enterprise.context.ApplicationScoped; 034import javax.enterprise.inject.spi.CDI; 035 036@ApplicationScoped 037public class FiscalPeriodLogic { 038 039 protected FiscalPeriodLogic() { 040 super(); 041 } 042 043 public static FiscalPeriodLogic getInstance() { 044 return CDI.current().select(FiscalPeriodLogic.class).get(); 045 } 046 047 private void createMonth(final PeriodKind periodKind, final Period quarterPeriod, final ZonedDateTime yearStart, final int month, 048 final PartyPK createdBy) { 049 var periodControl = Session.getModelController(PeriodControl.class); 050 var year = yearStart.getYear(); 051 var periodName = new StringBuilder().append(year).append('_').append('M'); 052 var monthPeriodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_MONTH); 053 var monthStart = yearStart.withMonth(month); 054 var monthEnd = monthStart.plusMonths(1).minusNanos(1); 055 056 new Formatter(periodName, Locale.US).format("%02d", month); 057 var monthPeriod = PeriodLogic.getInstance().createPeriod(periodKind, periodName.toString(), quarterPeriod, monthPeriodType, 058 monthStart.toInstant().toEpochMilli(), monthEnd.toInstant().toEpochMilli(), createdBy); 059 060 var periodKindDescriptions = periodControl.getPeriodKindDescriptionsByPeriodKind(periodKind); 061 var yearPeriodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_YEAR); 062 periodKindDescriptions.forEach((periodKindDescription) -> { 063 var language = periodKindDescription.getLanguage(); 064 var yearPeriodTypeDescription = periodControl.getPeriodTypeDescription(yearPeriodType, language); 065 if (yearPeriodTypeDescription != null) { 066 var monthPeriodTypeDescription = periodControl.getPeriodTypeDescription(monthPeriodType, language); 067 if (monthPeriodTypeDescription != null) { 068 var description = periodKindDescription.getDescription() + 069 ' ' + yearPeriodTypeDescription.getDescription() + 070 ' ' + year + 071 ' ' + monthPeriodTypeDescription.getDescription() + 072 ' ' + month; 073 074 periodControl.createPeriodDescription(monthPeriod, language, description, createdBy); 075 } 076 } 077 }); 078 } 079 080 private void createQuarter(final PeriodKind periodKind, final Period yearPeriod, final ZonedDateTime yearStart, final int quarter, 081 final PartyPK createdBy) { 082 var periodControl = Session.getModelController(PeriodControl.class); 083 var year = yearStart.getYear(); 084 var periodName = String.valueOf(year) + '_' + 'Q' + quarter; 085 var quarterPeriodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_QUARTER); 086 var monthOfQuarterStart = 1 + (quarter - 1) * 3; 087 var quarterStart = yearStart.withMonth(monthOfQuarterStart); 088 var quarterEnd = quarterStart.plusMonths(3).minusNanos(1); 089 var quarterPeriod = PeriodLogic.getInstance().createPeriod(periodKind, periodName, yearPeriod, quarterPeriodType, 090 quarterStart.toInstant().toEpochMilli(), quarterEnd.toInstant().toEpochMilli(), createdBy); 091 092 var periodKindDescriptions = periodControl.getPeriodKindDescriptionsByPeriodKind(periodKind); 093 var yearPeriodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_YEAR); 094 periodKindDescriptions.forEach((periodKindDescription) -> { 095 var language = periodKindDescription.getLanguage(); 096 var yearPeriodTypeDescription = periodControl.getPeriodTypeDescription(yearPeriodType, language); 097 if (yearPeriodTypeDescription != null) { 098 var quarterPeriodTypeDescription = periodControl.getPeriodTypeDescription(quarterPeriodType, language); 099 if (quarterPeriodTypeDescription != null) { 100 var description = periodKindDescription.getDescription() + 101 ' ' + yearPeriodTypeDescription.getDescription() + 102 ' ' + year + 103 ' ' + quarterPeriodTypeDescription.getDescription() + 104 ' ' + quarter; 105 106 periodControl.createPeriodDescription(quarterPeriod, language, description, createdBy); 107 } 108 } 109 }); 110 111 for(var monthOffset = 0; monthOffset < 3; monthOffset++) { 112 createMonth(periodKind, quarterPeriod, yearStart, monthOfQuarterStart + monthOffset, createdBy); 113 } 114 } 115 116 private Period createYear(final ExecutionErrorAccumulator eea, final Period perpetualPeriod, final int year, 117 final ZoneId zone, final PartyPK createdBy) { 118 var periodControl = Session.getModelController(PeriodControl.class); 119 var periodName = String.valueOf(year); 120 var periodKind = periodControl.getPeriodKindByName(PeriodConstants.PeriodKind_FISCAL); 121 var yearPeriod = periodControl.getPeriodByName(periodKind, periodName); 122 123 if(yearPeriod == null) { 124 var periodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_YEAR); 125 var yearStart = ZonedDateTime.of(year, 1, 1, 0, 0, 0, 0, zone); 126 var yearEnd = yearStart.plusYears(1).minusNanos(1); 127 128 yearPeriod = PeriodLogic.getInstance().createPeriod(periodKind, periodName, perpetualPeriod, periodType, 129 yearStart.toInstant().toEpochMilli(), yearEnd.toInstant().toEpochMilli(), createdBy); 130 131 var periodKindDescriptions = periodControl.getPeriodKindDescriptionsByPeriodKind(periodKind); 132 for(var periodKindDescription : periodKindDescriptions) { 133 var language = periodKindDescription.getLanguage(); 134 var periodTypeDescription = periodControl.getPeriodTypeDescription(periodType, language); 135 136 if(periodTypeDescription != null) { 137 var description = periodKindDescription.getDescription() + 138 ' ' + periodTypeDescription.getDescription() + 139 ' ' + year; 140 141 periodControl.createPeriodDescription(yearPeriod, language, description, createdBy); 142 } 143 } 144 145 for(var quarter = 1; quarter < 5; quarter++) { 146 createQuarter(periodKind, yearPeriod, yearStart, quarter, createdBy); 147 } 148 } else { 149 eea.addExecutionError(ExecutionErrors.DuplicatePeriodName.name(), PeriodConstants.PeriodKind_FISCAL, periodName); 150 } 151 152 return yearPeriod; 153 } 154 155 public Period ensurePerpetual(final ExecutionErrorAccumulator eea, final PartyPK createdBy) { 156 var periodControl = Session.getModelController(PeriodControl.class); 157 var periodKind = periodControl.getPeriodKindByName(PeriodConstants.PeriodKind_FISCAL); 158 var perpetualPeriod = periodControl.getPeriodByName(periodKind, PeriodConstants.Period_PERPETUAL); 159 160 if(perpetualPeriod == null) { 161 var periodType = periodControl.getPeriodTypeByName(periodKind, PeriodConstants.PeriodType_PERPETUAL); 162 163 perpetualPeriod = PeriodLogic.getInstance().createPeriod(periodKind, PeriodConstants.Period_PERPETUAL, null, 164 periodType, 0L, Long.MAX_VALUE, createdBy); 165 166 } 167 168 return perpetualPeriod; 169 } 170 171 public Period createFiscalYear(final ExecutionErrorAccumulator eea, final Integer year, final PartyPK createdBy) { 172 var partyControl = Session.getModelController(PartyControl.class); 173 var defaultPartyCompany = partyControl.getDefaultPartyCompany(); 174 Period fiscalYear = null; 175 176 if(defaultPartyCompany != null) { 177 var perpetualPeriod = ensurePerpetual(eea, createdBy); 178 var javaTimeZoneName = partyControl.getPreferredTimeZone(defaultPartyCompany.getParty()).getLastDetail().getJavaTimeZoneName(); 179 var zone = ZoneId.of(javaTimeZoneName); 180 181 fiscalYear = createYear(eea, perpetualPeriod, year, zone, createdBy); 182 } else { 183 eea.addExecutionError(ExecutionErrors.MissingDefaultCompany.name()); 184 } 185 186 return fiscalYear; 187 } 188 189 private Period getFiscalPeriodByName(final ExecutionErrorAccumulator eea, final String periodName, 190 final EntityPermission entityPermission) { 191 var periodControl = Session.getModelController(PeriodControl.class); 192 var periodKind = periodControl.getPeriodKindByName(PeriodConstants.PeriodKind_FISCAL); 193 var period = periodControl.getPeriodByName(periodKind, periodName, entityPermission); 194 195 if(periodKind != null) { 196 period = periodControl.getPeriodByName(periodKind, periodName, entityPermission); 197 198 if(period == null) { 199 eea.addExecutionError(ExecutionErrors.UnknownPeriodName.name(), PeriodConstants.PeriodKind_FISCAL, periodName); 200 } 201 } else { 202 eea.addExecutionError(ExecutionErrors.UnknownPeriodKindName.name(), PeriodConstants.PeriodKind_FISCAL); 203 } 204 205 return period; 206 } 207 208 public Period getFiscalPeriodByName(final ExecutionErrorAccumulator eea, final String periodName) { 209 return getFiscalPeriodByName(eea, periodName, EntityPermission.READ_ONLY); 210 } 211 212 public Period getFiscalPeriodByNameForUpdate(final ExecutionErrorAccumulator eea, final String periodName) { 213 return getFiscalPeriodByName(eea, periodName, EntityPermission.READ_WRITE); 214 } 215 216}