- 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
436 lines
20 KiB
C#
436 lines
20 KiB
C#
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; }
|
||
}
|
||
}
|