using Microsoft.EntityFrameworkCore; using RMuseum.DbContext; using RMuseum.Models.Accounting; using RSecurityBackend.Models.Generic; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DNTPersianUtils.Core; using RSecurityBackend.Services.Implementation; using RSecurityBackend.Services; using Microsoft.Extensions.Configuration; using RMuseum.Models.Ganjoor.ViewModels; using System.Globalization; using RMuseum.Models.Accounting.ViewModels; namespace RMuseum.Services.Implementation { /// /// donation service /// public class DonationService : IDonationService { /// /// new donation /// /// /// /// public async Task> AddDonation(Guid editingUserId, GanjoorDonationViewModel donation) { try { var d = new GanjoorDonation() { DateString = $"{donation.RecordDate.ToPersianYearMonthDay().Day.ToPersianNumbers()}م {PersianCulture.GetPersianMonthName(donation.RecordDate.ToPersianYearMonthDay().Month)} {donation.RecordDate.ToPersianYearMonthDay().Year.ToPersianNumbers()}", RecordDate = donation.RecordDate, Amount = donation.Amount, Unit = donation.Unit, AmountString = donation.Amount.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers(), DonorName = donation.DonorName, Remaining = donation.Amount, ExpenditureDesc = "", ImportedRecord = false }; if (!string.IsNullOrEmpty(donation.Unit)) d.AmountString = $"{d.AmountString} {d.Unit}"; _context.GanjoorDonations.Add(d); await _context.SaveChangesAsync(); if(!string.IsNullOrEmpty(d.Unit) && d.Amount > 0) { var expenses = await _context.GanjoorExpenses .Include(e => e.DonationExpenditures) .Where(e => e.Unit == d.Unit && (e.Amount - e.DonationExpenditures.DefaultIfEmpty().Sum(x => x.Amount)) > 0) .OrderBy(e => e.Id) .ToListAsync(); foreach (var expense in expenses) { if (d.Remaining <= 0) break; var remaining = expense.Amount - expense.DonationExpenditures.DefaultIfEmpty().Sum(x => x.Amount); if(remaining > d.Remaining) { remaining = d.Remaining; } DonationExpenditure n = new DonationExpenditure() { Amount = remaining, GanjoorDonationId = d.Id }; expense.DonationExpenditures.Add(n); _context.GanjoorExpenses.Update(expense); var amount = d.Remaining == d.Amount && remaining == d.Remaining ? "" : d.Remaining == remaining ? "" : $"مبلغ {remaining.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {d.Unit} آن "; var part = expense.DonationExpenditures.Count == 0 && remaining == expense.Amount ? "" : "بخشی از "; if (!string.IsNullOrEmpty(d.ExpenditureDesc)) d.ExpenditureDesc += " "; DateTime expenseDate = expense.ExpenseDate; var dateString = $"{expenseDate.ToPersianYearMonthDay().Day.ToPersianNumbers()}م {PersianCulture.GetPersianMonthName(expenseDate.ToPersianYearMonthDay().Month)} {expenseDate.ToPersianYearMonthDay().Year.ToPersianNumbers()}"; d.ExpenditureDesc += $"{amount}جهت تأمین {part}هزینهٔ {expense.Description} به مبلغ {expense.Amount.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {d.Unit} صرف شد ({dateString})."; d.Remaining -= remaining; _context.GanjoorDonations.Update(d); await _context.SaveChangesAsync(); } } await RegenerateDonationsPage(editingUserId);//ignore possible errors here! donation.Id = d.Id; donation.DateString = d.DateString; donation.AmountString = d.AmountString; donation.Remaining = d.Remaining; donation.ExpenditureDesc = d.ExpenditureDesc; donation.ImportedRecord = d.ImportedRecord; return new RServiceResult(donation); } catch(Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// new expense /// /// /// /// public async Task> AddExpense(Guid editingUserId, GanjoorExpense expense) { try { expense.DonationExpenditures = new List();//fix swagger posting a list which causes a donation to be added _context.GanjoorExpenses.Add(expense); await _context.SaveChangesAsync(); if(!string.IsNullOrEmpty(expense.Unit) && expense.Amount > 0) { var expenseRemaining = expense.Amount; var donations = await _context.GanjoorDonations.Where(d => d.Unit == expense.Unit && d.Remaining > 0).OrderBy(d => d.Id).ToListAsync(); foreach(var d in donations) { if (expenseRemaining <= 0) break; var remaining = expenseRemaining; if (remaining > d.Remaining) { remaining = d.Remaining; } DonationExpenditure n = new DonationExpenditure() { Amount = remaining, GanjoorDonationId = d.Id }; expense.DonationExpenditures.Add(n); _context.GanjoorExpenses.Update(expense); var amount = d.Remaining == d.Amount && remaining == d.Remaining ? "" : d.Remaining == remaining ? "باقیماندهٔ آن " : $"مبلغ {remaining.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {d.Unit} آن "; var part = expense.DonationExpenditures.Count == 1 && remaining == expenseRemaining ? "" : "بخشی از "; if (!string.IsNullOrEmpty(d.ExpenditureDesc)) d.ExpenditureDesc += " "; DateTime expenseDate = expense.ExpenseDate; var dateString = $"{expenseDate.ToPersianYearMonthDay().Day.ToPersianNumbers()}م {PersianCulture.GetPersianMonthName(expenseDate.ToPersianYearMonthDay().Month)} {expenseDate.ToPersianYearMonthDay().Year.ToPersianNumbers()}"; d.ExpenditureDesc += $"{amount}جهت تأمین {part}هزینهٔ {expense.Description} به مبلغ {expense.Amount.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {d.Unit} صرف شد ({dateString})."; d.Remaining -= remaining; _context.GanjoorDonations.Update(d); await _context.SaveChangesAsync(); expenseRemaining -= remaining; } } await RegenerateDonationsPage(editingUserId);//ignore possible errors here! return new RServiceResult(expense); } catch(Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// donation page row /// private class DonationPageRow { /// /// date /// public string Date { get; set; } /// /// amount /// public string Amount { get; set; } /// /// donor /// public string Donor { get; set; } /// /// usage /// public string Usage { get; set; } /// /// remaining /// public string Remaining { get; set; } } private async Task DoInitializeRecords(RMuseumDbContext context) { LongRunningJobProgressServiceEF jobProgressServiceEF = new LongRunningJobProgressServiceEF(context); var job = (await jobProgressServiceEF.NewJob("DonationService::DoInitializeRecords", "Processing")).Result; try { string htmlText = await context.GanjoorPages.Where(p => p.UrlSlug == "donate").Select(p => p.HtmlText).AsNoTracking().SingleAsync(); List rows = new List(); int nStartIndex = htmlText.IndexOf(""); int rowNumber = 0; while (nStartIndex != -1) { rowNumber++; DonationPageRow row = new DonationPageRow(); nStartIndex += "".Length; row.Date = htmlText.Substring(nStartIndex, htmlText.IndexOf("", nStartIndex) - nStartIndex); nStartIndex = htmlText.IndexOf("", nStartIndex); if (nStartIndex == -1) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, $"{rowNumber} : damount"); return; } nStartIndex += "".Length; row.Amount = htmlText.Substring(nStartIndex, htmlText.IndexOf("", nStartIndex) - nStartIndex); nStartIndex = htmlText.IndexOf("", nStartIndex); if (nStartIndex == -1) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, $"{rowNumber} : ddonator"); return; } nStartIndex += "".Length; row.Donor = htmlText.Substring(nStartIndex, htmlText.IndexOf("", nStartIndex) - nStartIndex); nStartIndex = htmlText.IndexOf("", nStartIndex); if (nStartIndex == -1) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, $"{rowNumber} : dusage"); return; } nStartIndex += "".Length; row.Usage = htmlText.Substring(nStartIndex, htmlText.IndexOf("", nStartIndex) - nStartIndex); nStartIndex = htmlText.IndexOf("", nStartIndex); if (nStartIndex == -1) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, $"{rowNumber} : drem"); return; } nStartIndex += "".Length; row.Remaining = htmlText.Substring(nStartIndex, htmlText.IndexOf("", nStartIndex) - nStartIndex); rows.Add(row); nStartIndex = htmlText.IndexOf("", nStartIndex); } DateTime recordDate = DateTime.Now.AddDays(-2); for (int i = rows.Count - 1; i >= 0; i--) { GanjoorDonation donation = new GanjoorDonation() { ImportedRecord = true, DateString = rows[i].Date, RecordDate = recordDate, AmountString = rows[i].Amount, DonorName = rows[i].Donor, //needs to be cleaned from href values ExpenditureDesc = rows[i].Usage, Remaining = 0 }; if ( rows[i].Remaining != "۲۰ دلار" //one record && rows[i].Remaining.ToEnglishNumbers() != "0" ) { donation.Remaining = decimal.Parse(rows[i].Remaining.Replace("٬", "").Replace("تومان", "").Trim().ToEnglishNumbers()); } if (donation.AmountString.Contains("تومان")) { donation.Amount = decimal.Parse(donation.AmountString.Replace("٬", "").Replace("تومان", "").Trim().ToEnglishNumbers()); donation.Unit = "تومان"; } if (donation.AmountString.Contains("دلار")) { donation.Amount = decimal.Parse(donation.AmountString.Replace("٬", "").Replace("دلار", "").Trim().ToEnglishNumbers()); donation.Unit = "دلار"; } if (donation.ExpenditureDesc == "هنوز هزینه نشده.") { donation.ExpenditureDesc = ""; } context.GanjoorDonations.Add(donation); await context.SaveChangesAsync(); //in order to make Id columns filled in desired order } await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); } catch (Exception exp) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); } } /// /// parse html of https://ganjoor.net/donate/ and fill the records /// /// public async Task> InitializeRecords() { if (await _context.GanjoorDonations.AnyAsync()) return new RServiceResult(true); try { _backgroundTaskQueue.QueueBackgroundWorkItem ( async token => { using (RMuseumDbContext context = new RMuseumDbContext(new DbContextOptions())) //this is long running job, so _context might be already been freed/collected by GC { await DoInitializeRecords(context); } } ); } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } return new RServiceResult(true); } /// /// regenerate donations page /// /// /// public async Task> RegenerateDonationsPage(Guid editingUserId) { try { var dbPage = await _context.GanjoorPages.Where(p => p.UrlSlug == "donate").SingleAsync(); var donations = await _context.GanjoorDonations.OrderByDescending(d => d.Id).ToArrayAsync(); var remSum = await _context.GanjoorDonations.Where(d => d.Unit == "تومان").SumAsync(d => d.Remaining); string htmlText = ""; DateTime dateLastDonation = donations.Length > 0 ? donations[0].RecordDate : DateTime.MinValue; DateTime dateLastExpense = DateTime.MinValue; var lastExpense = await _context.GanjoorExpenses.OrderByDescending(e => e.ExpenseDate).FirstOrDefaultAsync(); if(lastExpense != null) { dateLastExpense = lastExpense.ExpenseDate; } DateTime last = dateLastDonation > dateLastExpense ? dateLastDonation : dateLastExpense; var dateString = $"{last.ToPersianYearMonthDay().Day.ToPersianNumbers()}م {PersianCulture.GetPersianMonthName(last.ToPersianYearMonthDay().Month)} {last.ToPersianYearMonthDay().Year.ToPersianNumbers()}"; htmlText += $"

{Environment.NewLine}"; htmlText += $"در صورت تمایل به کمک مالی به گنجور از طریق کارت عابربانک؛ لطفاً کمکهای خود را به کارت شمارهٔ 6219-8610-2780-4979 (بانک سامان) به نام حمیدرضا محمدی واریز نمایید. علاوه بر آن از طریق اینترنت‌بانک سامان می‌توانید کمکهای خود را به شماره حساب ۸۲۸-۸۰۰-۸۷۳۳۳۰-۱ (شمارهٔ شبا: IR03-0560-0828-8000-0873-3300-01) واریز نمایید. لطفاً از طریق تماس با نشانی ganjoor@ganjoor.net مشخصات خودتان و مبلغ واریزی را اطلاع دهید (نام کمک دهندگان و نوع استفاده‌ای که از کمک آنها شده به مرور در همین صفحه به اطلاع خواهد رسید).{Environment.NewLine}"; htmlText += $"

{Environment.NewLine}"; htmlText += $"

{Environment.NewLine}"; htmlText += $"مبالغ واریزی جهت پرداخت هزینه‌های جاری (میزبانی وب و ...)، گسترش امکانات و همینطور پایگاه داده‌های سایت مورد استفاده قرار خواهد گرفت.{Environment.NewLine}"; htmlText += $"

{Environment.NewLine}"; htmlText += $"

{Environment.NewLine}"; htmlText += $"باقیماندهٔ قابل هزینهٔ کمکهای دریافتی تا {donations[0].DateString} برابر {remSum.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} تومان است.{Environment.NewLine}"; htmlText += $"

{Environment.NewLine}"; var expenses = await _context.GanjoorExpenses .Include(e => e.DonationExpenditures) .Where(e => !string.IsNullOrEmpty(e.Unit) && (e.Amount - e.DonationExpenditures.DefaultIfEmpty().Sum(x => x.Amount)) > 0) .OrderBy(e => e.Id) .ToListAsync(); if(expenses.Count > 0) { htmlText += $"

هزینه‌های پوشش داده نشده

{Environment.NewLine}"; htmlText += $"

کمکهای آتی دریافتی صرف پوشش هزینه‌های زیر خواهد شد:

{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; for (int i = 0; i < expenses.Count; i++) { var expense = expenses[i]; string cssClass = i % 2 == 0 ? " class=\"e\"" : ""; var expenseDateString = $"{expense.ExpenseDate.ToPersianYearMonthDay().Day.ToPersianNumbers()}م {PersianCulture.GetPersianMonthName(expense.ExpenseDate.ToPersianYearMonthDay().Month)} {expense.ExpenseDate.ToPersianYearMonthDay().Year.ToPersianNumbers()}"; var expenseRem = expense.Amount; if (expense.DonationExpenditures.Count > 0) expenseRem -= expense.DonationExpenditures.Sum(x => x.Amount); htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; } htmlText += $"
ردیفتاریخمبلغعنوانمانده
{(i + 1).ToPersianNumbers()}{expenseDateString}{expense.Amount.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {expense.Unit}{expense.Description}{expenseRem.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {expense.Unit}
{Environment.NewLine}"; } htmlText += $"

کمکهای دریافت شده تا به حال

{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; for (int i=0; i{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; htmlText += $"{Environment.NewLine}"; } htmlText += $"
ردیفتاریخمبلغاهدا کنندهمحل هزینهمانده
{(donations.Length - i).ToPersianNumbers()}{donation.DateString}{donation.AmountString}{donation.DonorName}{(string.IsNullOrEmpty(donation.ExpenditureDesc) ? "هنوز هزینه نشده." : donation.ExpenditureDesc)}{(donation.Remaining == 0 || string.IsNullOrEmpty(donation.Unit) ? donation.Remaining.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers() : $"{donation.Remaining.ToString("N0", new CultureInfo("fa-IR")).ToPersianNumbers()} {donation.Unit}")}
{Environment.NewLine}"; await _ganjoorService.ModifyPage(dbPage.Id, editingUserId, new GanjoorModifyPageViewModel() { Title = dbPage.Title, HtmlText = htmlText, Note = "تولید خودکار صفحهٔ کمکهای مالی", UrlSlug = dbPage.UrlSlug, } ); return new RServiceResult(true); } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Database Context /// private readonly RMuseumDbContext _context; /// /// Background Task Queue Instance /// private readonly IBackgroundTaskQueue _backgroundTaskQueue; /// /// Ganjoor Service /// private readonly IGanjoorService _ganjoorService; /// /// configuration /// private readonly IConfiguration _configuration; /// /// constructor /// /// /// /// /// public DonationService(RMuseumDbContext context, IBackgroundTaskQueue backgroundTaskQueue, IGanjoorService ganjoorService, IConfiguration configuration) { _context = context; _backgroundTaskQueue = backgroundTaskQueue; _ganjoorService = ganjoorService; _configuration = configuration; } } }