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<DbRow> Read(Database db,string direction,bool enabledOnly=true)=>db.Read(c=>Read(c,direction,enabledOnly));
internal static List<DbRow> 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<DbRow>():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);
}
}
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<DbRow> Read(Database db,string direction,bool enabledOnly=true)=>db.Read(c=>Read(c,direction,enabledOnly));
internal static List<DbRow> 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<DbRow>():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);
}
}