XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

STHApplication

【MAUI】寄当家——安卓端交易管理系统

公开
关注 0 Fork 0 Star 0
UTF-8
using JiDangJia.Core;

namespace JiDangJia.Views;

public sealed class FundingPage : ContentPage
{
    private readonly BusinessService service;
    private readonly VerticalStackLayout body=Ui.Stack(),list=Ui.Stack();
    private readonly Picker provider=new(){Title="资方类型"},state=new(){Title="资金状态"};
    private readonly Entry search=new(){Placeholder="搜索资方名称",ClearButtonVisibility=ClearButtonVisibility.WhileEditing};
    private readonly Dictionary<string,string> providers=new Dictionary<string,string>{{"ALL","全部资方"}}.Concat(FundingCost.Providers).ToDictionary(p=>p.Key,p=>p.Value);
    public FundingPage(BusinessService service)
    {
        this.service=service;Title="资金成本";body.Padding=18;
        provider.ItemsSource=providers.Values.ToList();provider.SelectedIndex=0;state.ItemsSource=new[]{"全部状态","使用中","已停止计息"};state.SelectedIndex=0;
        body.Add(Ui.Card(Ui.Stack(Ui.Section("资方资金"),Ui.Text("银行信贷、个人及其他资方的借入本金与利息。",12,Ui.Muted),Ui.Text("自有资金无需登记利息;借入和归还本金不计为经营收入或资金成本。",11,Ui.Muted),Ui.Button("+ 新增资方资金",()=>Navigation.PushAsync(new FundingFormPage(service))))));
        body.Add(Ui.Card(Ui.Stack(Ui.Spread(provider,state),search)));body.Add(list);Content=new ScrollView{Content=body};
        provider.SelectedIndexChanged+=(_,_)=>Load();state.SelectedIndexChanged+=(_,_)=>Load();search.TextChanged+=(_,_)=>Load();
    }
    protected override void OnAppearing(){base.OnAppearing();Load();}
    private void Load()
    {
        list.Clear();var rows=service.FundingAccounts().Where(r=>(provider.SelectedIndex<=0||r.Text("providerType")==providers.Keys.ElementAt(provider.SelectedIndex))&&(state.SelectedIndex==0||state.SelectedIndex==1&&r.Text("endDate").Length==0||state.SelectedIndex==2&&r.Text("endDate").Length>0)&&(string.IsNullOrWhiteSpace(search.Text)||r.Text("name").Contains(search.Text,StringComparison.OrdinalIgnoreCase))).ToList();
        var previews=rows.Select(r=>service.FundingPreview(r.Id,DateTime.Today)).ToList();
        var settled=rows.Sum(r=>service.Db.Query("SELECT COALESCE(SUM(amount),0) amount FROM FundingSettlement WHERE accountId=@p0 AND deletedAt IS NULL",r.Id).First().Money("amount"));
        var summary=Ui.Stack(Ui.Section("筛选汇总",rows.Count+" 笔资金"),Ui.Spread(Ui.Text("使用中资方本金",12,Ui.Muted),Ui.Text(Display.Money(rows.Where(r=>r.Text("endDate").Length==0).Sum(r=>r.Money("principal"))),18,Ui.Bronze,true)),Ui.Spread(Ui.Text("已结算资金成本",12,Ui.Muted),Ui.Text(Display.Money(settled),17,Ui.Red,true)),Ui.Spread(Ui.Text("待结算利息(截至今日)",12,Ui.Muted),Ui.Text(Display.Money(previews.Sum(p=>p.Amount)),17,Ui.Bronze,true)),Ui.Text("待结算利息为估算;点击结算后生成支出,按支出发生日期计入财务与利润。",11,Ui.Muted));
        list.Add(Ui.Card(summary));
        if(rows.Count==0){list.Add(Ui.Card(Ui.Empty("暂无资方资金","登记本金、利率与起息日后,自动计算利息")));return;}
        foreach(var p in previews)
        {
            var r=p.Account;var card=Ui.Stack(Ui.Spread(Ui.Text(r.Text("name"),18,bold:true),Ui.Pill(r.Text("endDate").Length==0?"使用中":"已停止计息")),Ui.Text(FundingCost.Providers[r.Text("providerType")]+" · "+FundingCost.Rule(r),12,Ui.Bronze),Ui.Spread(Ui.Text("本金",12,Ui.Muted),Ui.Text(Display.Money(r.Money("principal")),18,Ui.Bronze,true)),Ui.Text("起息 "+r.Text("startDate")+" · 已结算至 "+Display.Day(p.Start),11,Ui.Muted),Ui.Divider(),Ui.Spread(Ui.Text("待结算利息",12,Ui.Muted),Ui.Text(Display.Money(p.Amount),17,Ui.Red,true)));
            if(p.HasTrash)card.Add(Ui.Text("有结算在回收站,处理后可继续结算。",11,Ui.Red));
            list.Add(Ui.Tap(Ui.Card(card),()=>Navigation.PushAsync(new FundingDetailPage(service,r.Id))));
        }
    }
}

public sealed class FundingFormPage : ContentPage
{
    public FundingFormPage(BusinessService service,string? id=null)
    {
        Title=id is null?"新增资方资金":"编辑资方资金";
        var row=id is null?new DbRow{["providerType"]="BANK",["ratePeriod"]="ANNUAL",["dayBasis"]=365,["startDate"]=Display.Day(DateTime.Today)}:service.Db.Find("FundingAccount",id)??throw new InvalidOperationException("资金记录不存在");
        var bases=FundingCost.Bases(row.Text("ratePeriod"));
        var fields=new FieldSet(row,[new("name","资方名称",Required:true,Hint:"银行名称或资方姓名"),new("providerType","资方类型",FieldKind.Choice,true,FundingCost.Providers),new("principal","资方本金(元)",FieldKind.Number,true),new("ratePeriod","利率周期",FieldKind.Choice,true,FundingCost.RatePeriods),new("rate","利率(%)",FieldKind.Number,true,Hint:"如年利率 4.8%,填写 4.8"),new("dayBasis","计息口径",FieldKind.Choice,true,bases),new("startDate","起息日期",FieldKind.Date,true),new("remark","备注",FieldKind.Notes)]);
        var period=(Picker)fields.Inputs["ratePeriod"];var basis=(Picker)fields.Inputs["dayBasis"];
        period.SelectedIndexChanged+=(_,_)=>{bases.Clear();foreach(var pair in FundingCost.Bases(period.SelectedIndex==1?"MONTHLY":"ANNUAL"))bases[pair.Key]=pair.Value;basis.ItemsSource=bases.Values.ToList();basis.SelectedIndex=0;};
        ((Picker)fields.Inputs["providerType"]).SelectedIndexChanged+=(_,_)=>{if(id is null)period.SelectedIndex=((Picker)fields.Inputs["providerType"]).SelectedIndex==1?1:0;};
        if(id is not null&&service.Db.Query("SELECT id FROM FundingSettlement WHERE accountId=@p0",id).Count>0)
            foreach(var key in new[]{"principal","rate","ratePeriod","dayBasis","startDate"})fields.Inputs[key].IsEnabled=false;
        var body=Ui.Stack(Ui.Card(fields.View),Ui.Card(Ui.Stack(Ui.Section("计息规则"),Ui.Text("单利按实际使用天数计算:本金 × 利率 ÷ 100 ÷ 计息口径天数 × 使用天数。起息日计息,结算截止日不计息。",12,Ui.Muted),Ui.Text("年利率可选 365 或 360 天;月利率按 30 天。请按与资方约定的口径选择。",12,Ui.Muted),Ui.Text("本金或利率变化时,先结清并停止本笔计息,再新增资金记录。",12,Ui.Muted))),Ui.Button("保存资方资金",async()=>{var input=fields.Read();var key=await Task.Run(()=>service.SaveFunding(input));await DisplayAlertAsync("已保存","资方资金已保存,结算利息时才产生支出。","完成");if(id is null){await Navigation.PushAsync(new FundingDetailPage(service,key));Navigation.RemovePage(this);}else await Navigation.PopAsync();}));
        body.Padding=18;Content=new ScrollView{Content=body};
    }
}

public sealed class FundingDetailPage : ContentPage
{
    private readonly BusinessService service;private readonly string id;
    private readonly VerticalStackLayout body=Ui.Stack();
    public FundingDetailPage(BusinessService service,string id){this.service=service;this.id=id;Title="资方资金详情";body.Padding=18;Content=new ScrollView{Content=body};}
    protected override void OnAppearing(){base.OnAppearing();Load();}
    private void Load()
    {
        body.Clear();var r=service.Db.Find("FundingAccount",id);if(r is null||r.Text("deletedAt").Length>0){body.Add(Ui.Empty("资金记录已移入回收站","可在设置的回收站中还原"));return;}
        var p=service.FundingPreview(id,DateTime.Today);bool closed=r.Text("endDate").Length>0;
        body.Add(Ui.Card(Ui.Stack(Ui.Spread(Ui.Text(r.Text("name"),22,bold:true),Ui.Pill(closed?"已停止计息":"使用中")),Ui.Text(FundingCost.Providers[r.Text("providerType")],13,Ui.Muted),Ui.Text(Display.Money(r.Money("principal")),30,Ui.Bronze,true),Ui.Text(FundingCost.Rule(r),13,Ui.Bronze),Ui.Text("起息日期:"+r.Text("startDate"),12,Ui.Muted),Ui.Text("已结算至:"+Display.Day(p.Start)+"(当日不含)",12,Ui.Muted),Ui.Text(closed?"停止计息日期:"+r.Text("endDate"):"起息日计息,结算日不计息;单利,按实际天数计算。",12,Ui.Muted),Ui.Text(r.Text("remark"),12,Ui.Muted))));
        body.Add(Ui.Card(Ui.Stack(Ui.Section("待结算利息"),Ui.Text(Display.Money(p.Amount),28,Ui.Red,true),Ui.Text(Display.Day(p.Start)+" 至 "+Display.Day(p.End)+"(不含) · "+p.Days+" 天",12,Ui.Muted),Ui.Text("待结算金额尚未计入财务支出。",11,Ui.Muted))));
        if(p.HasTrash)body.Add(Ui.Button("处理回收站中的结算",()=>Navigation.PushAsync(new TrashPage(service)),false));
        else if(!closed||p.Days>0)body.Add(Ui.Button("结算资金成本",()=>Navigation.PushAsync(new FundingSettlePage(service,id))));
        if(!closed)body.Add(Ui.Button("结清并停止计息",()=>Navigation.PushAsync(new FundingSettlePage(service,id,true)),false));
        if(!closed)body.Add(Ui.Button("编辑资方资金",()=>Navigation.PushAsync(new FundingFormPage(service,id)),false));
        var history=Ui.Stack(Ui.Section("结算记录"));var rows=service.Db.Query("SELECT * FROM FundingSettlement WHERE accountId=@p0 AND deletedAt IS NULL ORDER BY periodEnd DESC",id);
        if(rows.Count==0)history.Add(Ui.Text("尚未结算",12,Ui.Muted));
        foreach(var s in rows)
        {
            var item=Ui.Stack(Ui.Spread(Ui.Text(s.Text("periodStart")+" 至 "+s.Text("periodEnd"),12),Ui.Text(Display.Money(s.Money("amount")),16,Ui.Red,true)),Ui.Text($"{s.Int("days")} 天 · 支出日期 {s.Text("occurDate")} · 截止日不含",11,Ui.Muted));
            var finance=service.Db.Query("SELECT id FROM FinanceRecord WHERE ticketId=@p0 AND deletedAt IS NULL",s.Id).FirstOrDefault();
            if(finance is not null)item.Add(Ui.Button("查看支出流水",()=>Navigation.PushAsync(new DetailPage(service,"FinanceRecord",finance.Id)),false));
            else item.Add(Ui.Text("本次利息为零,已保留结算区间;未生成零元支出。",11,Ui.Muted));
            if(s.Id==rows[0].Id)item.Add(Ui.Button("撤销最新结算",async()=>{if(await DisplayAlertAsync("撤销最新结算","结算和对应支出将移入回收站。处理回收站后可重新结算。","移入回收站","取消")){await Task.Run(()=>service.SoftDelete("FundingSettlement",s.Id));Load();}},false));
            history.Add(Ui.Card(item,12));
        }
        body.Add(Ui.Card(history));body.Add(Ui.Button("移入回收站",async()=>{if(await DisplayAlertAsync("移入回收站","这笔资方资金、结算记录和对应支出将一起移入回收站。","移入回收站","取消")){await Task.Run(()=>service.SoftDelete("FundingAccount",id));await Navigation.PopAsync();}},false));
    }
}

public sealed class FundingSettlePage : ContentPage
{
    public FundingSettlePage(BusinessService service,string id,bool finish=false)
    {
        Title=finish?"结清并停止计息":"结算资金成本";var initial=service.FundingPreview(id,DateTime.Today);
        var end=new DatePicker{Date=initial.End,MinimumDate=initial.Start,MaximumDate=DateTime.Today,Format="yyyy-MM-dd",HeightRequest=46};
        var paid=new DatePicker{Date=DateTime.Today,MinimumDate=initial.Start,MaximumDate=DateTime.Today,Format="yyyy-MM-dd",HeightRequest=46};
        var notes=new Editor{Placeholder="填写结算备注",MaximumHeightRequest=150,MinimumHeightRequest=80,MaxLength=1000};
        var preview=Ui.Stack();FundingPreview? current=null;
        void Refresh()
        {
            preview.Clear();current=null;
            try
            {
                var p=service.FundingPreview(id,end.Date??initial.End);current=p;
                preview.Add(Ui.Section("本次资金成本"));preview.Add(Ui.Text(Display.Money(p.Amount),30,Ui.Red,true));preview.Add(Ui.Text(Display.Day(p.Start)+" 至 "+Display.Day(p.End)+"(不含) · "+p.Days+" 天",12,Ui.Muted));preview.Add(Ui.Text(FundingCost.Rule(p.Account),12,Ui.Bronze));
                preview.Add(Ui.Text("本金 × 利率 ÷ 100 ÷ "+p.Account.Int("dayBasis")+" × 天数;累计利息按分取整后扣除已结算利息。",11,Ui.Muted));
                if(p.HasTrash)preview.Add(Ui.Text("请先处理回收站中的资金结算。",12,Ui.Red));
            }
            catch(ArgumentException ex){preview.Add(Ui.Text(ex.Message,12,Ui.Red));}
        }
        end.DateSelected+=(_,_)=>Refresh();Refresh();
        var body=Ui.Stack(Ui.Card(Ui.Stack(Ui.Section(initial.Account.Text("name")),Ui.Text("计息至(当日不计息)",12,Ui.Muted),end,Ui.Text("支出发生日期",12,Ui.Muted),paid,Ui.Text("结算备注",12,Ui.Muted),notes)),Ui.Card(preview),Ui.Button(finish?"确认结清并停止计息":"确认结算并生成支出",async()=>
        {
            Refresh();var p=current??throw new ArgumentException("请调整结算日期");
            if(p.HasTrash)throw new InvalidOperationException("请先处理回收站中的资金结算");
            if(p.Days==0&&!finish)throw new ArgumentException("请选择晚于已结算截止日的日期");
            var message=$"{p.Account.Text("name")}\n{Display.Day(p.Start)} 至 {Display.Day(p.End)}(不含)\n{p.Days} 天,资金成本 {Display.Money(p.Amount)}\n支出日期 {Display.Day(paid.Date??DateTime.Today)}"+(finish?"\n结清后停止此笔资金计息。":"");
            if(!await DisplayAlertAsync("确认资金成本",message,"确认","取消"))return;
            var paymentDate=paid.Date??DateTime.Today;var remark=notes.Text??"";
            await Task.Run(()=>service.CompleteFunding(id,p.Start,p.End,paymentDate,remark,finish,p.Amount));
            await DisplayAlertAsync("已结算",p.Amount>0?"已生成资金成本支出,财务和利润已更新。":"已保存结算区间,本次没有新增支出。","完成");await Navigation.PopAsync();
        }),Ui.Text("借入、归还本金另行管理。本页只生成利息支出,已结算区间不会重复记账。",11,Ui.Muted));
        body.Padding=18;Content=new ScrollView{Content=body};
    }
}