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