using Microsoft.Data.Sqlite; namespace JiDangJia.Core; public static class FinanceCategories { // Imported and automatic flows retain their codes. Manual selection shows // each name once per direction, including the old duplicate aliases. public static List Read(Database db,string direction,bool enabledOnly=true)=>db.Read(c=>Read(c,direction,enabledOnly)); internal static List Read(SqliteConnection c,string direction,bool enabledOnly=true)=>Database.Query(c, "SELECT * FROM FinanceCategory WHERE type=@p0 ORDER BY sortOrder,createdAt,id",direction) .Where(r=>!enabledOnly||r.Int("enabled")==1) .GroupBy(r=>r.Text("label").Trim(),StringComparer.OrdinalIgnoreCase) .Select(g=>g.OrderByDescending(r=>r.Int("enabled")).ThenBy(r=>LegacyAlias(r.Text("code"))).ThenBy(r=>r.Int("sortOrder")).First()).ToList(); private static bool LegacyAlias(string code)=>code is "RECYCLE_COST" or "OTHER_INCOME" or "OTHER_EXPENSE"; public static (string Major,string Detail) Classification(string code)=>code switch { "INTEREST" or "STORAGE_FEE" or "C_FEE" or "PENALTY_IN" or "PRINCIPAL_IN" or "PAWN_OUT" or "DEAD_SALE"=>("PAWN","UNCLASSIFIED"), "RECYCLE_OUT" or "RECYCLE_COST" or "RECOVERY"=>("RECYCLE","UNCLASSIFIED"), "STOCK_OUT" or "SALE_IN" or "SALE_REFUND" or "STOCK_REFUND"=>("SALE","UNCLASSIFIED"), "RENT" or "UTILITIES" or "SALARY" or "FUNDING_COST"=>("STORE",code), _=>("OTHER",code.StartsWith("CUSTOM_",StringComparison.Ordinal)?code:"UNCLASSIFIED") }; } public sealed partial class BusinessService { public string SaveFinanceCategory(DbRow value)=>db.Write(c=> { var label=value.Text("label").Trim();var type=value.Text("type"); if(label.Length is 0 or >30)throw new ArgumentException("分类名称请填写 1 至 30 个字符"); if(type is not ("INCOME" or "EXPENSE"))throw new ArgumentException("请选择收入或支出"); if(value.Money("sortOrder")<0||value.Money("sortOrder")>9999||value.Money("sortOrder")%1!=0)throw new ArgumentException("排序请填写 0 至 9999 的整数"); var old=value.Id.Length>0?Database.Query(c,"SELECT * FROM FinanceCategory WHERE id=@p0",value.Id).FirstOrDefault():null; if(value.Id.Length>0&&old is null)throw new ArgumentException("分类不存在,请刷新列表"); if(old is not null&&type!=old.Text("type"))throw new ArgumentException("已有分类的收支方向不能更改,请另建分类"); var group=old is null?new List():Database.Query(c,"SELECT * FROM FinanceCategory WHERE type=@p0 AND TRIM(label)=@p1 COLLATE NOCASE",type,old.Text("label").Trim()); var same=Database.Query(c,"SELECT * FROM FinanceCategory WHERE type=@p0 AND TRIM(label)=@p1 COLLATE NOCASE",type,label); if(same.Any(r=>!group.Any(g=>g.Id==r.Id)))throw new ArgumentException("已有同名分类,请修改或启用已有分类"); if(old is null)group.Add(new(){["id"]=Database.Id("fc"),["shopId"]=Shop.Id,["code"]=Database.Id("CUSTOM").ToUpperInvariant(),["type"]=type,["createdAt"]=Database.Now}); foreach(var row in group) { row["label"]=label;row["sortOrder"]=value.Int("sortOrder");row["enabled"]=value.Int("enabled")==1?1:0;row["updatedAt"]=Database.Now; Database.Upsert(c,"FinanceCategory",row); } var id=old?.Id??group[0].Id;Database.Log(c,"SAVE","FinanceCategory",id,label);return id; }); public void EnableFinanceCategory(string id,bool enabled) { var row=db.Find("FinanceCategory",id)??throw new ArgumentException("分类不存在,请刷新列表"); row["enabled"]=enabled?1:0;SaveFinanceCategory(row); } }