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