Files
Complex/Complex.Application/Services/Basic/ChargeCycleService.cs
alireza e4fe2389a4 feat: add unit filtering and improve unit info handling in charge cycles
- Add optional UnitId parameter to CostCyclsList method for filtering cycles by specific unit
- Replace UnitIds/UnitNames lists with structured UnitInfoDto containing Id and Name
- Clean unit names by removing "GUID#" prefix when present
- Update CostCycleDto to use UnitInfoDto list instead of separate Id and Name lists
- Fix unit assignment logic in Add and Edit methods to use new DTO structure
2026-06-10 20:17:46 +03:30

436 lines
20 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AutoMapper;
using Complex.Application.Complex;
using Complex.Application.Utility;
using Complex.Common.Dto;
using Complex.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Complex.Application.Services.Basic
{
public class CostService
{
private readonly IComplexDBContext _complexDBContext;
private IMapper _mapper;
private ILogger _logger;
public CostService(IComplexDBContext complexDBContext, IMapper mapper, ILogger<CostService> logger)
{
_complexDBContext = complexDBContext;
_mapper = mapper;
_logger = logger;
}
//public ResultDto<List<ChargeCycleDto>> ComplexChargeCycle(Guid ComplexId)
//{
// var chargeCycles = _complexDBContext.ChargeCycles.Include(c => c.UnitChargeTypes).ThenInclude(c => c.Unit)
// .Where(c => c.ComplexId == ComplexId).ToList();
// var chargeCycleObj = chargeCycles.Select(c => _mapper.Map<ChargeCycleDto>(c)).ToList();
// foreach (var c in chargeCycleObj)
// {
// c.Units = chargeCycles.Single(a => a.Id == c.Id).UnitChargeTypes.Where(t => !t.ChargeTypeId.HasValue)
// .Select(a => a.Unit).Select(u => _mapper.Map<UnitDto>(u)).ToList();
// }
// return new ResultDto<List<ChargeCycleDto>>
// {
// Data = chargeCycleObj,
// IsSuccess = true,
// };
//}
/// <summary>
/// لیست مدیریت شارژ یا هر گونه درآمد و مخارج گرفته می شود
/// </summary>
/// <param name="ComplexId"></param>
/// <param name="StartDate"></param>
/// <param name="EndDate"></param>
/// <param name="IncomeCostTitleId"></param>
/// <returns></returns>
public ResultDto<List<CostCycleDto>> CostCyclsList(Guid ComplexId, DateOnly? StartDate = null,
DateOnly? EndDate = null, int? IncomeCostTitleId = null, Guid? UnitId = null)
{
if (IncomeCostTitleId.HasValue && StartDate.HasValue)
StartDate = GetStartOfMonth(StartDate.Value);
var ChargeCycleQuery = _complexDBContext.CostCycles.Include(c => c.Units).Where(c => c.ComplexId == ComplexId).AsQueryable();
if (StartDate.HasValue)
ChargeCycleQuery = ChargeCycleQuery.Where(c => c.StartDate >= StartDate);
if (EndDate.HasValue)
ChargeCycleQuery = ChargeCycleQuery.Where(c => c.EndDate <= EndDate);
if (IncomeCostTitleId.HasValue)
ChargeCycleQuery = ChargeCycleQuery.Where(c => c.IncomeCostTitleId == IncomeCostTitleId);
else/*مدیریت شارژ ها فقط*/
ChargeCycleQuery = ChargeCycleQuery.Where(c => c.IncomeCostTitleId == null);
if (UnitId.HasValue)
ChargeCycleQuery = ChargeCycleQuery.Where(c => c.Units == null || c.Units.Any(u => u.Id == UnitId.Value));
var ChargeCycleList = ChargeCycleQuery.ToList().Select(c => new CostCycleDto
{
ComplexId = c.ComplexId,
Amount = c.Amount,
EncouragementAmount = c.EncouragementAmount,
EncouragementDay = c.EncouragementDay,
EndDate = c.EndDate,
StartDate = c.StartDate,
Id = c.Id,
Title = c.Title,
Units = c.Units?.Select(u =>
{
var name = u.UnitName ?? "";
// Some units store "GUID#Name" format; extract just the name part
var hashIndex = name.IndexOf('#');
var cleanName = hashIndex >= 0 ? name[(hashIndex + 1)..] : name;
return new UnitInfoDto
{
Id = u.Id,
Name = cleanName
};
}).ToList(),
IncomeCostTitleId = c.IncomeCostTitleId,
IncomeCostTitle = c.IncomeCostTitle?.Title
}).ToList();
return new ResultDto<List<CostCycleDto>>
{
IsSuccess = true,
RowCount = ChargeCycleList.Count,
Data = ChargeCycleList
};
}
public ResultDto<bool> EditCostCycle(CostCycleDto costCycleDto)
{
var cycle = _complexDBContext.CostCycles.Single(a => a.Id == costCycleDto.Id);
var isUsed = _complexDBContext.Transactions.Any(c => c.CostCycleId == costCycleDto.Id);
if (isUsed)
return new ResultDto<bool>
{
IsSuccess = false,
Message = "این آیتم استفاده شده است. شما می توانید این آیتم را حذف نمایید",
Data = false
};
cycle = _mapper.Map<CostCycle>(costCycleDto);
cycle.IncomeCostTitleId = costCycleDto.IncomeCostTitleId;
if (costCycleDto.Units != null)
{
var unitIds = costCycleDto.Units.Select(u => u.Id).ToList();
cycle.Units = _complexDBContext.Units.Where(u => unitIds.Contains(u.Id)
&& u.ComplexId == costCycleDto.ComplexId).ToList();
}
foreach (var u in cycle.Units)
{
var define = DefineUnitCharge(unitId: u.Id, costCycle: cycle);
if (!define.IsSuccess)
return new ResultDto<bool> { Data = false, IsSuccess = true, Message = define.Message };
}
_complexDBContext.SaveChanges();
return new ResultDto<bool> { Data = true, IsSuccess = true, Message = "ویرایش با موفقیت انجام شد" };
}
private DateOnly GetStartOfMonth(DateOnly date)
{
var persianDate = PersianDate.GetPDate(date.ToDateTime(new TimeOnly(1, 1)));
return date.AddDays(-persianDate.Day + 1);
}
public ResultDto<Guid> InsertCostCycle(CostCycleDto costCycleDto)
{
//اگر مدیریت شارژ هست پس تاریخ ها باید از یکم تا پایان ماه بشود
//البته باید در نرم افزار فقط امکان انتخاب ماه را داشته باشد
//&^*&^*&^&*^*&^*&^&*^*&^*&^&*^*&^*&^*&^&*^&*^*&&^*&^*&^&*^*^*&^*&^*&^&*^*&
//
if (costCycleDto.IncomeCostTitleId == null)
costCycleDto.StartDate = GetStartOfMonth(costCycleDto.StartDate);
var newCycle = _mapper.Map<CostCycle>(costCycleDto);
if (costCycleDto.Units != null)
{
var unitIds = costCycleDto.Units.Select(u => u.Id).ToList();
newCycle.Units = _complexDBContext.Units.Where(u => unitIds.Contains(u.Id) && costCycleDto.ComplexId == u.ComplexId).ToList();
}
//if(newCycle.Units==null)
//{
// //باید چک کرد در این بازه ایا با یکی دیگر که همه واحدها را در نظر میگیرد هم پوشانی نداشته باشد
// اما چون در پایین چک میشه پس مشکلی ندارد
// var IsPublicCostExist=_complexDBContext.CostCycles.Where(c=>
// c.ComplexId== newCycle.ComplexId && )
//}
_complexDBContext.CostCycles.Add(newCycle);
foreach (var u in newCycle.Units)
{
var define = DefineUnitCharge(unitId: u.Id, costCycle: newCycle, isNew: true);
if (!define.IsSuccess)
return new ResultDto<Guid> { IsSuccess = false, Message = define.Message };
}
_complexDBContext.SaveChanges();
return new ResultDto<Guid> { Data = newCycle.Id, IsSuccess = true, Message = "ثبت با موفقیت انجام شد" };
}
public ResultDto DefineUnitCharge(Guid unitId, CostCycle costCycle, bool isNew = false)
{
//var cycle = _complexDBContext.CostCycles.Where(c => c.Id == costCycleId).FirstOrDefault();
var currentDate = DateOnly.FromDateTime(DateTime.Now);
///ایتم هایی که برای بعد از این تاریخ هست باید حذف شوند و مجدد محاسبه شوند
///اما ایتم هایی که هم تاریخ مدیریت شارژ و هم تاریخ امروز به بعد
///البته در مدیریت شارژ تعریفی نباید اجازه داد تاریخ شروع از آن روز به قبل باشد
if (!isNew)
{
var validDateTimeForDel = currentDate;
//validDateTimeForDel.AddHours(-validDateTimeForDel.Hour);
//validDateTimeForDel.AddMinutes(-validDateTimeForDel.Minute);
//validDateTimeForDel.AddSeconds(-validDateTimeForDel.Second);
var itemsForDelete = _complexDBContext.UnitCosts.Where(t =>
t.CostCycleId == costCycle.Id &&
t.UnitId == unitId && t.Date >= validDateTimeForDel && !t.TransactionId.HasValue);
/*
نکته: !t.TransactionId.HasValue
.مشکل اینجاست که اگر برای اینده باشه و پرداخت کرده باشه باید یک راهکاری اندیشید چون حذف نمیشه!مشکل دارد
*/
_complexDBContext.UnitCosts.RemoveRange(itemsForDelete);
}
//اگر شارژ باشد فقط یک بار در ماه هست اما غیر از ان به هر هزینه تعریفی میتواند
//که در ماه میتواند چندین هزینه تعریف کند
//اگر برای شارژ باشد
//if (!cycle.IncomeCostTitleId.HasValue)
// deleteCharges = deleteCharges.Where(t => t.ChargeCycleId == ChargeCycleId);
//else
//deleteCharges = deleteCharges.Where(t => t.ChargeCycleId == cycle.Id);
//همه ماهانه هست و روز خاصی نگرفتیم برای اصل و پرداخت نشده ها حذف شده
//پس روز اخر ملاک هست
if (!costCycle.IncomeCostTitleId.HasValue)
while (currentDate <= costCycle.EndDate)
{
//میبیند برای این واحد ایا چیزی ثبت شده است یا خیر با دوره واحد دیگر
//چون بالا برای دوره واحدی که به این تابع امده حذف را انجام دادیم
var oldUnitCost = _complexDBContext.UnitCosts.Include(uc => uc.CostCycle).ThenInclude(c => c.Units)
.Where(uc =>
uc.UnitId == unitId &&
uc.CostCycle.IncomeCostTitleId == null &&
uc.Date.Month == currentDate.Month).FirstOrDefault();
if (oldUnitCost != null)
{
//cycle.Units != null====>پس این واحد هم اختصاصیش بوده*/
//اگر این اختصاصی هست و قبلش اختصاصی هم بوده خطا بده
if (costCycle.Units != null && oldUnitCost.CostCycle.Units != null)
return new ResultDto
{
IsSuccess = false,
Message = "برای برخی از واحدها در این بازه زمانی قبلا به صورت اختصاصی شارژ تعیین شده است"
};
//اگر این اختصاصی هست و قبلش غیراختصاصی بوده باید پاک بشه البته برای همین واحد است که حذف میشه
if (costCycle.Units != null && oldUnitCost.CostCycle.Units == null)
_complexDBContext.UnitCosts.Remove(oldUnitCost);
//اگر این غیر اختصاصی هست و قبلش غیر اختصاصی بوده قبلیش باید پاک بشه یا خطا بدهیم
//اما خطا نمیدهیم چون ممکنه بازه ها متفاوت باشند ولی اشتراک داشته باشند. بهتره جای دیگه چک بشه
//if (cycle.Units == null && oldUnitCost.CostCycle.Units == null)
//اگر این غیر اختصاصی هست و قبلیش اختصاصی بوده که نباید ثبت بشه
if (costCycle.Units == null && oldUnitCost.CostCycle.Units != null)
return new ResultDto { IsSuccess = true, Message = "نیاز به تعریف مجدد برای این واحد نبود" };
if (costCycle.Units == null && oldUnitCost.CostCycle.Units == null)
return new ResultDto { IsSuccess = false, Message = "شما در یک بازه زمانی نمیتوانید بیش از یک مورد عمومی تعریف کنید" };
}
//--------------
var newUC = new UnitCost
{
CostCycleId = costCycle.Id,
UnitId = unitId,
Date = currentDate,
TransactionId = null
};
currentDate = currentDate.AddMonths(1);
_complexDBContext.UnitCosts.Add(newUC);
}
else
{
var newUC = new UnitCost
{
CostCycleId = costCycle.Id,
UnitId = unitId,
Date = currentDate,
TransactionId = null
};
_complexDBContext.UnitCosts.Add(newUC);
}
// _complexDBContext.SaveChanges();
return new ResultDto
{
Message = "ثبت با موفقیت انجام شد",
IsSuccess = true,
};
}
/// <summary>
/// بدهی یک واحد
/// </summary>
/// <param name="unitId"></param>
/// <param name="incomeCostTitleId"></param>
/// <returns></returns>
public ResultDto<CostOfUnitInfoDto> CalculateCostForUnit(Guid unitId, int? incomeCostTitleId)
{
var currentDate = DateOnly.FromDateTime(DateTime.Now);
var unitCost = _complexDBContext.UnitCosts.Where(uc => unitId == uc.UnitId &&
!uc.TransactionId.HasValue
&& uc.CostCycle.IncomeCostTitleId == incomeCostTitleId && uc.Date <= currentDate);
var LastPaymentDate = _complexDBContext.UnitCosts.Where(uc => unitId == uc.UnitId &&
uc.TransactionId.HasValue/*پرداخت شده000000000000*/
&& uc.CostCycle.IncomeCostTitleId == incomeCostTitleId && uc.Date <= currentDate)
.OrderBy(uc => uc.Date).LastOrDefault()?.Date;
var costTitle = "شارژ";
if (incomeCostTitleId.HasValue)
costTitle = _complexDBContext.IncomeCostTitles.First(i => i.Id == incomeCostTitleId).Title;
return new ResultDto<CostOfUnitInfoDto>
{
Data = new CostOfUnitInfoDto
{
CostMount = unitCost.Sum(c => c.CostCycle.Amount),
CostTitle = costTitle,
LastPaymentDate = LastPaymentDate,
UnitId = unitId
},
IsSuccess = true
};
}
/// <summary>
/// لیست بدهی های یک واحد
/// </summary>
/// <param name="unitId"></param>
/// <returns></returns>
public ResultDto<List<CostOfUnitInfoDto>> CalculateAllCostForUnits(List<Guid> unitIds)
{
var currentDate = DateOnly.FromDateTime(DateTime.Now);
var unitCharges = _complexDBContext.UnitCosts.Include(uc => uc.CostCycle).ThenInclude(cc => cc.IncomeCostTitle)
.Where(uc => unitIds.Contains(uc.UnitId) &&
!uc.TransactionId.HasValue
&& uc.Date <= currentDate).ToList();
var resultList = new List<CostOfUnitInfoDto>();
foreach (var unitid in unitIds)
{
resultList.AddRange(unitCharges.Where(u => u.UnitId == unitid).Select(uc => new CostOfUnitInfoDto
{
CostMount = uc.CostCycle.Amount,
CostTitle = uc.CostCycle.IncomeCostTitleId.HasValue ? uc.CostCycle.IncomeCostTitle?.Title : "شارژ",
Date = uc.Date,
UnitId = unitid,
CostCount = unitCharges.Where(u => u.UnitId == unitid).Count()
}).ToList());
}
return new ResultDto<List<CostOfUnitInfoDto>>
{
Data = resultList,
IsSuccess = true
};
//{
// Data = unitCharges.Sum(c => c.CostCycle.Amount),
// Message = "تمام بدهی"
//};
}
/// <summary>
/// تاریخچه پرداختی ها و باقیمانده از آن
/// </summary>
/// <param name="unitId"></param>
/// <param name="incomeCostTitleId"></param>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
public ResultDto<List<CostOfUnitInfoDto>> CostsForUnits(List<Guid> unitIds, int? incomeCostTitleId,
DateOnly startDate, DateOnly endDate)
{
var unitCharges = _complexDBContext.UnitCosts.Include(uc => uc.CostCycle).ThenInclude(cc => cc.IncomeCostTitle)
.Where(uc => unitIds.Contains(uc.UnitId)
//!uc.TransactionId.HasValue
&& uc.Date >= startDate
&& uc.Date <= endDate).ToList();//.GroupBy(g => g.UnitId);
var resultList = new List<CostOfUnitInfoDto>();
foreach (var uid in unitIds)
{
resultList.AddRange(unitCharges.Where(u => u.UnitId == uid).Select(uc => new CostOfUnitInfoDto
{
UnitCostId = uc.Id,
CostMount = uc.CostCycle.Amount,
CostTitle = uc.CostCycle.IncomeCostTitleId.HasValue ? uc.CostCycle.IncomeCostTitle?.Title : "شارژ",
Date = uc.Date,
UnitId = uid,
CostCount = unitCharges.Where(u => u.UnitId == uid).Count()
}).ToList());
}
return new ResultDto<List<CostOfUnitInfoDto>>
{
Data = resultList,
IsSuccess = true
};
}
}
public class CostOfUnitInfoDto
{
public long UnitCostId { set; get; }
public Guid UnitId { get; set; }
public int CostCount { get; set; }
public long CostMount { get; set; }
//public long CostCount { set; get; }
public DateOnly? LastPaymentDate { get; set; }
public DateOnly? Date { get; set; }
public string? CostTitle { get; set; }
public DateOnly? PaymentDate { set; get; }
}
public class UnitInfoDto
{
public Guid Id { set; get; }
public string Name { set; get; }
}
public class CostCycleDto
{
public Guid Id { set; get; }
public Guid ComplexId { set; get; }
public string Title { set; get; }
public DateOnly StartDate { set; get; }
public DateOnly EndDate { set; get; }
public long Amount { set; get; } = 0;
/// <summary>
/// روز از ماه برای تشویقی
/// </summary>
public int EncouragementDay { set; get; } = 0;
/// <summary>
/// مبلغ اجاره با احتساب تشویقی
/// </summary>
public long EncouragementAmount { set; get; } = 0;
/// <summary>
/// اگر برای واحدهای خاصی بود
/// </summary>
public virtual List<UnitInfoDto>? Units { set; get; }
/// <summary>
/// null یعنی شارژ
/// </summary>
public int? IncomeCostTitleId { set; get; }
public string? IncomeCostTitle { set; get; }
}
}