Ponizej znajduje się kod napisany w C# (.NET 6). Zmodyfikuj go tak, aby w przypadku ustawienia DeferralMonths wyliczanie bonusu było opóźnione poprzez dodanie offsetu do WorkStartTimestamp w metodzie AdjustUserInServiceStartDate.

/// UserBonusStatus.cs

using System;
using Medica.Authentication.Users;

namespace Medica.HumanResources.Bonuses;

public class UserBonusStatus
{
    private readonly DateTime? _adjustedInServiceStartDate;
    private readonly decimal _currentBonusLimit;
    private readonly BonusRoleConfig _bonusRoleConfig;
    private readonly int _yearsInServiceTillLastUpdate;
    private readonly decimal _partTimeRatio;

    private UserBonusStatus(DateTime? adjustedInServiceStartDate, decimal currentBonusLimit, int yearsInServiceTillLastUpdate, BonusRoleConfig bonusRoleConfig, decimal partTimeRatio)
    {
        _adjustedInServiceStartDate = adjustedInServiceStartDate;
        _currentBonusLimit = currentBonusLimit;
        _yearsInServiceTillLastUpdate = yearsInServiceTillLastUpdate;
        _bonusRoleConfig = bonusRoleConfig;
        _partTimeRatio = partTimeRatio;
    }

    public static UserBonusStatus Create(User user, BonusRoleConfig bonusRoleConfig)
    {
        var currentBonusLimit = Math.Max(user.CurrentBonusLimit, bonusRoleConfig.MinValue);
        var adjustedInServiceStartDate = user.WorkStartTimestamp is { } workStartTimestamp
            ? (DateTime?)AdjustUserInServiceStartDate(workStartTimestamp)
            : null;

        var yearsInServiceTillLastUpdate = user.LastBonusUpdateTimestamp is { } lastBonusUpdateTimestamp && adjustedInServiceStartDate is { } adjustedInServiceStartDateVal
            ? YearsInService(lastBonusUpdateTimestamp, adjustedInServiceStartDateVal)
            : 0;
        return new UserBonusStatus(adjustedInServiceStartDate, currentBonusLimit, yearsInServiceTillLastUpdate, bonusRoleConfig, user.SalaryRatio());
    }

    public decimal BonusLimit(DateTime targetDate)
    {
        if (_adjustedInServiceStartDate is { } startDate && startDate > targetDate) return Decimal.Zero;

        var yearsInServiceTillNow = YearsInService(targetDate);
        var gapYears = Math.Max(0, yearsInServiceTillNow - _yearsInServiceTillLastUpdate);
        return Math.Min(_bonusRoleConfig.MaxValue, _currentBonusLimit + gapYears * _bonusRoleConfig.YearlyIncreaseValue);
    }

    public decimal PartTimeBonusLimit(DateTime targetDate)
    {
        return BonusLimit(targetDate) * _partTimeRatio;
    }

    private static int YearsInService(DateTime targetDate, DateTime adjustedInServiceStartTime)
    {
        if (adjustedInServiceStartTime > targetDate) return 0;
        var diff = targetDate.Year - adjustedInServiceStartTime.Year;
        return adjustedInServiceStartTime.Month > targetDate.Month || (adjustedInServiceStartTime.Month == targetDate.Month && adjustedInServiceStartTime.Day > targetDate.Day) ? diff - 1 : diff;
    }


    private int YearsInService(DateTime targetDate)
    {
        return _adjustedInServiceStartDate is { } adjustedInServiceStartDate
            ? YearsInService(targetDate, adjustedInServiceStartDate)
            : 0;
    }


    private static DateTime AdjustUserInServiceStartDate(DateTime rawTime)
    {
        if (rawTime.Day <= 3)
        {
            return new DateTime(rawTime.Year, rawTime.Month, 1);
        }

        var month = rawTime.Month < 12 ? rawTime.Month + 1 : 1;
        var year = rawTime.Month < 12 ? rawTime.Year : rawTime.Year + 1;
        return new DateTime(year, month, 1);
    }
}

/// ActiveQuarter.cs

using System;
using Medica.Authentication.Users;

namespace Medica.HumanResources.Bonuses;

public class ActiveQuarter
{
    public decimal CurrentBalance { get; }
    public decimal FinalBalance { get; }
    public decimal CurrentPaidAmount { get; }
    public decimal FinalPaidAmount { get; }
    public decimal Remnant { get; }

    private ActiveQuarter(decimal currentBalance, decimal finalBalance, decimal currentPaidAmount, decimal finalPaidAmount, decimal remnant)
    {
        CurrentBalance = currentBalance;
        FinalBalance = finalBalance;
        CurrentPaidAmount = currentPaidAmount;
        FinalPaidAmount = finalPaidAmount;
        Remnant = remnant;
    }

    public static ActiveQuarter Create(DateTime thisDate, decimal alterationsBalance, User user, BonusRoleConfig roleConfig)
    {
        var quarter = Quarter.FromDate(thisDate);

        var userBonusStatus = UserBonusStatus.Create(user, roleConfig);

        decimal bonusLimit = decimal.Zero;
        var firstMonthLimit = userBonusStatus.PartTimeBonusLimit(quarter.StartDate);
        var secondMonthLimit = userBonusStatus.PartTimeBonusLimit(quarter.SecondMonth.StartDate);
        var thirdMonthLimit = userBonusStatus.PartTimeBonusLimit(quarter.ThirdMonth.StartDate);

        if (thisDate >= quarter.SecondMonth.StartDate) bonusLimit += firstMonthLimit;
        if (thisDate >= quarter.ThirdMonth.StartDate) bonusLimit += secondMonthLimit;
        if (thisDate >= quarter.EndDate) bonusLimit += thirdMonthLimit;

        var currentBalance = bonusLimit + alterationsBalance;
        var finalBalance = alterationsBalance + firstMonthLimit + secondMonthLimit + thirdMonthLimit;

        var currentPaidAmount = Math.Max(0, currentBalance);
        var finalPaidAmount = Math.Max(0, finalBalance);

        var remnant = finalBalance > 0 ? 0 : finalBalance;

        return new ActiveQuarter(currentBalance, finalBalance, currentPaidAmount, finalPaidAmount, remnant);
    }
}

/// BonusService.cs

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Medica.Authentication.Users;
using Medica.Helpers;
using Medica.OrgStructure;
using Medica.Schedule;

namespace Medica.HumanResources.Bonuses;

public class BonusService : IBonusService
{
    private static readonly DateTime _featureEnabledTimeStamp = new DateTime(2020, 07, 01);
    private const int _maxAlterationAmount = 10000;
    private readonly IBonusDbAdapter _dbAdapter;
    private readonly IBonusRoleConfigResolver _roleConfigResolver;
    private readonly Func<DateTime> _currentTimeProvider;
    private readonly ISchedulerService _emailService;
    private readonly IOrgStructureService _orgStructureService;

    public BonusService(IBonusDbAdapter dbAdapter, IBonusRoleConfigResolver roleConfigResolver, ISchedulerService emailService, IOrgStructureService orgStructureService) : this(dbAdapter, roleConfigResolver, emailService, orgStructureService, () => DateTime.Now)
    {
    }

    public BonusService(IBonusDbAdapter dbAdapter, IBonusRoleConfigResolver roleConfigResolver, ISchedulerService emailService, IOrgStructureService orgStructureService, Func<DateTime> currentTimeProvider)
    {
        _dbAdapter = dbAdapter;
        _roleConfigResolver = roleConfigResolver;
        _emailService = emailService;
        _currentTimeProvider = currentTimeProvider;
        _orgStructureService = orgStructureService;
    }

    public async Task<Either<BonusAlteration>> AddBonusAlterationAsync(BonusAlteration bonusAlteration)
    {
        return await AddUpdateBonusAlterationAsync(bonusAlteration, alteration => _dbAdapter.AddBonusAlterationAsync(alteration))
            .FlatMapAsync(SendAddAlterationEmail);
    }

    public async Task<Either<BonusAlteration>> UpdateBonusAlterationAsync(BonusAlteration bonusAlteration, long? validOwnerId = null)
    {
        return await AddUpdateBonusAlterationAsync(bonusAlteration, alteration => _dbAdapter.UpdateBonusAlterationAsync(alteration))
            .FlatMapAsync(alteration => VerifyRequester(alteration, validOwnerId))
            .FlatMapAsync(SendUpdateAlterationEmail);
    }

    private async Task<Either<BonusAlteration>> AddUpdateBonusAlterationAsync(BonusAlteration bonusAlteration,
        Func<BonusAlteration, Task<Either<BonusAlteration>>> methodAsync)
    {
        var userId = bonusAlteration.UserId;
        var resolvedRole = await _roleConfigResolver.Resolve(userId);
        return await resolvedRole.FlatMapAsync(
            async config =>
            {
                var validationError = await ValidateAlterationAsync(bonusAlteration, config);
                if (validationError is { } error) return Either<BonusAlteration>.Error(error);
                return await methodAsync(bonusAlteration);
            });
    }

    public async Task<Either> DeleteAlterationAsync(long id, long? validOwnerId = null)
    {
        return await _dbAdapter.GetAlterationAsync(id)
            .FlatMapAsync(alteration => VerifyRequester(alteration, validOwnerId))
            .FlatMapAsync(DeleteAlterationAsync)
            .FlatMapAsync(SendDeleteAlterationEmail)
            .IgnoreValue();
    }

    private async Task<Either<BonusAlteration>> DeleteAlterationAsync(BonusAlteration alteration)
    {
        if (alteration.OptimaId != null || alteration.RequiredActionId != null)
        {
            alteration.Amount = Decimal.Zero;
            return await _dbAdapter.UpdateBonusAlterationAsync(alteration);
        }

        var thisQuarter = Quarter.FromDate(_currentTimeProvider());
        if (alteration.CreationTime < thisQuarter.StartDate)
            return Either<BonusAlteration>.UserFriendlyError(
                "Cannot delete bonus alteration from non-active quarter",
                "Nie można usunąć wpisu z poprzedniego kwartału");
        return await _dbAdapter.DeleteAlterationAsync(alteration.Id).FlatMapAsync(alteration.AsSuccess);
    }

    public async Task<Either<IList<BonusAlteration>>> GetAlterations(long userId, Quarter quarter)
    {
        return await _dbAdapter.GetBonusAlterationsAsync(userId, quarter.StartDate, quarter.EndDate);
    }

    public async Task<Either<IList<BonusQuarterSummary>>> GetSummaries(User user, DateTime targetDate)
    {

        var resolvedRoleConfig = await _roleConfigResolver.Resolve(user.Id);
        return await resolvedRoleConfig.Match(
            async config => await GetSummariesForRole(user, targetDate, config),
            error => Task.FromResult(Either<IList<BonusQuarterSummary>>.Error(error)));
    }

    public async Task<Either<IList<User>>> GetSubordinatesWithBonusFeatureAsync(long supervisorId)
    {
        return await _orgStructureService.GetSubordinatesAsync(supervisorId).FlatMapAsync(FilterSubordinates);
    }

    private async Task<Either<IList<User>>> FilterSubordinates(IList<User> inputList)
    {
        var list = new List<User>();
        foreach (var user in inputList)
        {
            var resolveResult = await _roleConfigResolver.TryResolve(user.Id, true);
            if (resolveResult.IsFailure) return Either<IList<User>>.Error(resolveResult);
            resolveResult.MatchSome(roleConfig =>
            {
                if (roleConfig?.IsActive == true) list.Add(user);
            });
        }

        return list.AsSuccess();
    }

    public async Task<Either<BonusQuarterSummary>> GetCurrentQuarterSummary(User user, DateTime targetDate)
    {
        var resolvedRoleConfig = await _roleConfigResolver.Resolve(user.Id);
        return await resolvedRoleConfig.Match(
            async config => await GetCurrentQuarterSummary(user, targetDate, config),
            error => Task.FromResult(Either<BonusQuarterSummary>.Error(error)));
    }

    private async Task<Either<IList<BonusQuarterSummary>>> GetSummariesForRole(User user, DateTime targetDate, BonusRoleConfig config)
    {
        var pastSummariesResult = await _dbAdapter.GetAllSummariesAsync(user.Id);
        return await pastSummariesResult.Match(async pastSummaries =>
            {
                var currentQuarterSummaryResult = await ComputeQuarterSummary(user, targetDate, config, pastSummaries);
                return currentQuarterSummaryResult.Match(
                    currentQuarterSummary =>
                    {
                        var summaryStartTime = user.WorkStartTimestamp is { } workStartTimestamp
                            ? new DateTime(Math.Max(_featureEnabledTimeStamp.Ticks, workStartTimestamp.Ticks))
                            : _featureEnabledTimeStamp;
                        var summaries = CombineQuarterSummaries(targetDate, summaryStartTime, pastSummaries, currentQuarterSummary);
                        return summaries.AsSuccess();
                    },
                    Either<IList<BonusQuarterSummary>>.Error);
            },
            error => Task.FromResult(Either<IList<BonusQuarterSummary>>.Error(error)));
    }

    private static List<BonusQuarterSummary> CombineQuarterSummaries(DateTime targetDate, DateTime summaryStartTime, IList<BonusQuarterSummary> list, BonusQuarterSummary currentQuarterSummary)
    {
        var pastSummariesDict = list.ToDictionary(
            v => new Quarter(v.Year, v.Quarter),
            v => new BonusQuarterSummaryData(v.PaidAmount, v.Balance));
        var pastQuarters = Quarter.PastQuartersRange(summaryStartTime, targetDate);

        var summaries = pastQuarters.Select(quarter =>
        {
            var isDataAvailable = pastSummariesDict.TryGetValue(quarter, out var data);
            return new BonusQuarterSummary
            {
                Balance = isDataAvailable ? data!.Balance : decimal.Zero,
                PaidAmount = isDataAvailable ? data!.PaidAmount : decimal.Zero,
                Quarter = quarter.QuarterIdx,
                Year = quarter.Year
            };
        }).ToList();

        summaries.Insert(0, currentQuarterSummary);
        return summaries;
    }

    public async Task<Either<BonusQuarterSummary>> GetCurrentQuarterSummary(User user, DateTime targetDate, BonusRoleConfig config)
    {
        var pastSummaries = await _dbAdapter.GetAllSummariesAsync(user.Id);
        return await pastSummaries.Match(async list => await ComputeQuarterSummary(user, targetDate, config, list),
            error => Task.FromResult(Either<BonusQuarterSummary>.Error(error)));
    }

    private async Task<Either<BonusQuarterSummary>> ComputeQuarterSummary(User user, DateTime targetDate, BonusRoleConfig config, IList<BonusQuarterSummary> list)
    {
        var targetQuarter = Quarter.FromDate(targetDate);
        var currentBalanceResult = await GetCurrentQuarterInputBalance(user.Id, targetQuarter, list);
        return currentBalanceResult.Match(
            currentBalance =>
            {
                var activeQuarter = ActiveQuarter.Create(targetDate, currentBalance, user, config);
                var summary = new BonusQuarterSummary()
                {
                    Balance = activeQuarter.FinalBalance,
                    PaidAmount = activeQuarter.CurrentPaidAmount,
                    Quarter = targetQuarter.QuarterIdx,
                    Year = targetQuarter.Year,
                    UserId = user.Id
                };
                return summary.AsSuccess();
            },

            Either<BonusQuarterSummary>.Error);
    }

    private async Task<Either<decimal>> GetCurrentQuarterInputBalance(long userId, Quarter targetQuarter, IList<BonusQuarterSummary> previousSummaries)
    {
        return await GetPreviousQuartersBalance(targetQuarter, previousSummaries).Match(async previousBalance =>
            {
                var bonusAlterationsResult = await _dbAdapter.GetBonusAlterationsAsync(userId, targetQuarter.StartDate, targetQuarter.EndDate);
                return bonusAlterationsResult.Match(
                    list => Either.Success(previousBalance + list.Select(item => item.Amount).Sum()),
                    Either<decimal>.Error);

            },
            error => Task.FromResult(Either<decimal>.Error(error)));
    }

    private Either<decimal> GetPreviousQuartersBalance(Quarter targetQuarter, IList<BonusQuarterSummary> bonusQuarterSummaries)
    {
        if (bonusQuarterSummaries.Count == 0)
        {
            return Either.Success(decimal.Zero);
        }

        var lastSummary = bonusQuarterSummaries[0];
        var lastQuarter = new Quarter(lastSummary.Year, lastSummary.Quarter);
        return lastQuarter.IsBefore(targetQuarter)
            ? Either.Success(lastSummary.Balance - lastSummary.PaidAmount)
            : Either<decimal>.InternalError("Quarter requested as current already has locked summary");
    }

    private async Task<AppServiceError?> ValidateAlterationAsync(BonusAlteration bonusAlteration, BonusRoleConfig roleConfig)
    {
        var now = _currentTimeProvider();
        var userId = bonusAlteration.UserId;
        var currentQuarter = Quarter.FromDate(now);
        if (bonusAlteration.Amount == Decimal.Zero)
        {
            return AppServiceError.UserFriendly("Bonus alteration amount cannot be zero", "Kwota musi być różna od zera");
        }

        if (bonusAlteration.CreationTime > now)
        {
            return AppServiceError.UserFriendly("Bonus alteration cannot be set into the future", "Data wpisu z przyszłości");
        }

        if (bonusAlteration.CreationTime < currentQuarter.StartDate)
        {
            return AppServiceError.UserFriendly("Bonus alteration can be added only to current quarter", "Data wpisu nie jest w bieżącym kwartale");
        }

        if (bonusAlteration.Amount < 0)
        {
            return Math.Abs(bonusAlteration.Amount) > _maxAlterationAmount
                ? AppServiceError.UserFriendly("Bonus alteration amount out of range", "Niepoprawna kwota")
                : null;
        }

        var currentQuarterAlterations = await _dbAdapter.GetBonusAlterationsAsync(userId, currentQuarter.StartDate, currentQuarter.EndDate);
        return currentQuarterAlterations.Match(
            list =>
            {
                var currentRewardsSum = list
                    .Where(item => item.Amount > 0)
                    .Select(item => item.Amount)
                    .Sum();
                return currentRewardsSum + bonusAlteration.Amount > roleConfig.MaxExtraBonusValue
                    ? AppServiceError.UserFriendly(
                        $"Bonus alteration [{bonusAlteration.Amount}] together with existing rewards [{currentRewardsSum}] exceeds configured limit [{roleConfig.MaxExtraBonusValue}]",
                        "Limit bonusów dodatkowych dla danego stanowiska / roli został przekroczony")
                    : null;
            }, error => error);

    }

    private Either<BonusAlteration> SendAddAlterationEmail(BonusAlteration alteration)
    {
        var operationDescription = alteration.Amount > 0 ? "Została nadana nagroda uznaniowa" : "Zostało nadane pomniejszenie bonusa";
        var operationSummary = "";
        return SendEmail(alteration, operationSummary, operationDescription);
    }

    private Either<BonusAlteration> SendUpdateAlterationEmail(BonusAlteration alteration)
    {
        var operationDescription = alteration.Amount > 0 ? "Nagroda uznaniowa została zaktualizowana" : "Nadane pomniejszenie bonusa zostało zaktualizowane";
        var subjectSuffix = alteration.Amount > 0 ? " - Zmieniona" : " - Zmienione";

        return SendEmail(alteration, subjectSuffix, operationDescription);
    }

    private Either<BonusAlteration> VerifyRequester(BonusAlteration alteration, long? validRequesterId)
    {
        if (validRequesterId is { } id && alteration.CreatorUserId != id)
        {
            return Either<BonusAlteration>.UserFriendlyError(
                $"Creator user id is of invalid value. Expected: {validRequesterId}. Actual: {alteration.CreatorUserId}",
                "Brak uprawnień do wykonania żądanej operacji");
        }

        return alteration.AsSuccess();
    }

    private Either<BonusAlteration> SendDeleteAlterationEmail(BonusAlteration alteration)
    {
        var operationDescription = alteration.Amount > 0 ? "Nagroda uznaniowa została anulowana" : "Nadane pomniejszenie bonusa zostało anulowane";
        var subjectSuffix = alteration.Amount > 0 ? " - Anulowana" : " - Anulowane";
        return SendEmail(alteration, subjectSuffix, operationDescription);
    }

    private Either<BonusAlteration> SendEmail(BonusAlteration alteration, string subjectSuffix, string operationDescription)
    {
        var alterationType = alteration.Amount > 0 ? "Nagroda uznaniowa" : "Pomniejszenie bonusa";
        var amountFormatted = $"{Math.Abs(alteration.Amount):0.00} zł";
        var subject = $"{alterationType} - {alteration.Title} ({amountFormatted}){subjectSuffix}";
        var bodyBuilder = new StringBuilder();
        bodyBuilder.Append($"<h2>{alterationType}</h2>");

        bodyBuilder.Append($"<p>{operationDescription}. Wyskość: {amountFormatted},  Powód</p>");
        bodyBuilder.Append($"<h3>{alteration.Title}</h3>");
        if (!string.IsNullOrWhiteSpace(alteration.Description))
        {
            bodyBuilder.Append($"<p>{alteration.Description}</p>");
        }
        bodyBuilder.Append($"<p>Identyfikator wpisu: {alteration.Id}</p>");
        var body = bodyBuilder.ToString();
        var emailsOpt = alteration.User.GetAllEmailAddresses();

        if (emailsOpt is { } emails && emails.Length > 0)
        {
            try
            {
                foreach (var email in emails)
                {
                    if (EmailHelper.IsValidEmail(email)) _emailService.ScheduleEmail(email.ToLower(), subject, body);
                }
            }
            catch (Exception ex)
            {
                return Either<BonusAlteration>.InternalError(ex);
            }
        }

        return alteration.AsSuccess();
    }
}

/// BonusRoleConfig.cs

using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;

namespace Medica.HumanResources.Bonuses;

public class BonusRoleConfig : FullAuditedEntity<long>, IMustHaveTenant
{
    public long RoleId { get; set; }

    public bool IsActive { get; set; }
    public decimal MinValue { get; set; }
    public decimal MaxValue { get; set; }
    public decimal YearlyIncreaseValue { get; set; }
    public decimal MaxExtraBonusValue { get; set; }
    public decimal SickLeaveDecrement { get; set; }
    public decimal ChildCareDecrement { get; set; }
    public int DeferralMonths { get; set; }
    public int TenantId { get; set; }
}