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

STHApplication

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

公开
关注 0 Fork 0 Star 0
UTF-8
using JiDangJia.Core;
using System.Globalization;
using System.Text.Json;

namespace JiDangJia.Views;

public sealed class ItemEditor
{
    private readonly BusinessService service;
    private readonly string kind;
    private readonly Dictionary<string,string> types;
    private readonly FieldSet identity;
    private readonly FieldSet tail;
    private readonly VerticalStackLayout detailArea=Ui.Stack();
    private readonly Dictionary<string,(FieldSet fields,HashSet<string> extra)> cache=new();
    private string current="";
    private bool updatingAmount;
    private bool amountOverridden;
    private readonly Button recalculate;
    private readonly DbRow restored;
    public Dictionary<string,PhotoField> Photos=>tail.Photos;
    public VerticalStackLayout View {get;}
    public Picker TypePicker => (Picker)identity.Inputs["type"];
    public string Type => types.Keys.ElementAtOrDefault(TypePicker.SelectedIndex)??"OTHER";
    public string RateType=>Type=="METAL"?cache[current].fields.Read(false).Text("material","GOLD"):Type;
    public event Action? TypeChanged;
    private bool Intake => kind is "RECYCLE" or "PURCHASE" or "RENTAL" or "STOCK_OTHER";
    public ItemEditor(BusinessService service,string kind,DbRow? initial=null)
    {
        this.service=service;this.kind=kind;types=ItemCatalog.Choices(kind);
        restored=initial is null?new():new(initial);
        if(restored.Text("extraAttributes").Length>0)try{using var attributes=JsonDocument.Parse(restored.Text("extraAttributes"));foreach(var pair in attributes.RootElement.EnumerateObject())restored[pair.Name]=pair.Value.Clone();}catch(JsonException){}
        identity=new FieldSet(initial??new DbRow{["type"]=types.Keys.First()},[new("name","物品名称",Required:true),new("type","物品类型",FieldKind.Choice,true,types)]);
        var definitions=new List<Field>();
        if(kind=="PAWN")definitions.Add(new("appraisalPrice","估价(元)",FieldKind.Number));
        if(Intake)definitions.Add(new("amount",kind=="RECYCLE"?"回收金额(元)":"进货成本(元)",FieldKind.Number,true));
        if(kind=="RECYCLE")definitions.Add(new("quotedPrice","客户报价(元)",FieldKind.Number));
        if(kind is "RENT" or "RENTAL" or "STOCK_OTHER")definitions.Add(new("itemCode","物品编码",Hint:"用于识别具体物品,可填写门店现有编码"));
        definitions.Add(new("images","物品照片",FieldKind.Photos));
        tail=new FieldSet(initial??new DbRow{["amount"]=0,["appraisalPrice"]=0,["itemCode"]=ItemCatalog.NewCode()},definitions);
        recalculate=Ui.Button("按克重和单价重算",()=>{amountOverridden=false;CalculateAmount();return Task.CompletedTask;},false);
        if(Intake && tail.Inputs["amount"] is Entry amount)amount.TextChanged+=(_,_)=>{if(!updatingAmount)amountOverridden=true;};
        View=Ui.Stack(identity.View,detailArea,tail.View,recalculate);TypePicker.SelectedIndexChanged+=(_,_)=>{Render();TypeChanged?.Invoke();};Render();
    }
    private void Render()
    {
        current=Type;detailArea.Clear();
        if(!cache.TryGetValue(current,out var entry))
        {
            var definitions=Definitions(current);var extra=definitions.Where(f=>!Standard.Contains(f.Key)).Select(f=>f.Key).ToHashSet();
            AddCustom(definitions,extra,current);
            var initial=restored.Text("type")==current?new DbRow(restored):new DbRow();if(current=="METAL"){if(!initial.ContainsKey("weight"))initial["weight"]=0;if(!initial.ContainsKey("unitPrice"))initial["unitPrice"]=0;if(!initial.ContainsKey("material"))initial["material"]="GOLD";}else if(current=="JEWELRY"&&!initial.ContainsKey("weight"))initial["weight"]=0;
            var fields=new FieldSet(initial,definitions);
            if(current=="METAL"&&fields.Inputs["material"] is Picker material)material.SelectedIndexChanged+=(_,_)=>TypeChanged?.Invoke();
            fields.Changed+=CalculateAmount;cache[current]=entry=(fields,extra);
        }
        detailArea.Add(entry.fields.View);recalculate.IsVisible=Intake&&current=="METAL";
        CalculateAmount();
    }
    private List<Field> Definitions(string type) => type switch
    {
        "METAL"=>new List<Field>{new("material","物品材质",FieldKind.Choice,true,ItemCatalog.Materials),new("weight","克重(克)",FieldKind.Number),new("spec","规格 / 成色")}
            .Concat(Intake?new[]{new Field("unitPrice",kind=="RECYCLE"?"回收单价(元/克)":"进货单价(元/克)",FieldKind.Number)}:Array.Empty<Field>())
            .Concat(new Field[]{new("brand","品牌"),new("serialNo","序列号"),new("accessory","附件")}).ToList(),
        "PROPERTY"=>[new("address","房产地址"),new("area","面积(平方米)",FieldKind.Number),new("certNo","产权证号")],
        "VEHICLE"=>[new("plateNo","车牌号"),new("vin","车架号"),new("mileage","里程(公里)",FieldKind.Number),new("brand","品牌"),new("model","车型"),new("accessory","随车资料 / 附件")],
        "WATCH"=>[new("brand","品牌"),new("model","型号"),new("serialNo","序列号"),new("accessory","附件")],
        "LUXURY"=>[new("brand","品牌"),new("model","型号"),new("serialNo","序列号"),new("condition","品相"),new("accessory","附件")],
        "JEWELRY"=>[new("material","材质"),new("weight","重量(克)",FieldKind.Number),new("spec","规格 / 成色"),new("certificate","证书编号"),new("brand","品牌"),new("serialNo","序列号"),new("accessory","附件")],
        "ELECTRONICS"=>[new("brand","品牌"),new("model","型号"),new("serialNo","序列号 / IMEI"),new("storage","存储容量"),new("accessory","附件")],
        _=>[new("brand","品牌"),new("model","型号"),new("serialNo","序列号"),new("accessory","附件")]
    };
    private static readonly HashSet<string> Standard=["name","type","material","weight","unitPrice","spec","brand","serialNo","accessory","images","amount","appraisalPrice","quotedPrice","itemCode","extraAttributes"];
    private void AddCustom(List<Field> definitions,HashSet<string> extra,string type)
    {
        var config=service.Db.Query("SELECT attributes FROM PawnItemTypeRate WHERE itemType=@p0",type).FirstOrDefault()?.Text("attributes");
        if(type=="METAL"&&string.IsNullOrWhiteSpace(config))config=service.Db.Query("SELECT attributes FROM PawnItemTypeRate WHERE itemType='GOLD'").FirstOrDefault()?.Text("attributes");
        if(string.IsNullOrWhiteSpace(config))return;
        try
        {
            using var doc=JsonDocument.Parse(config);if(doc.RootElement.ValueKind!=JsonValueKind.Array)return;
            foreach(var attr in doc.RootElement.EnumerateArray())
            {
                if(attr.ValueKind!=JsonValueKind.Object || !attr.TryGetProperty("key",out var k))continue;var key=k.ToString();
                if(key.Length==0 || Standard.Contains(key) || definitions.Any(f=>f.Key==key) || key=="purity"&&definitions.Any(f=>f.Key=="spec"))continue;
                var kind=attr.TryGetProperty("type",out var t)?t.ToString():"text";
                var fieldKind=kind switch{"number"=>FieldKind.Number,"date"=>FieldKind.Date,"select"=>FieldKind.Choice,"textarea"=>FieldKind.Notes,_=>FieldKind.Text};
                Dictionary<string,string>? options=null;
                if(fieldKind==FieldKind.Choice)
                {
                    options=new();if(attr.TryGetProperty("options",out var values)&&values.ValueKind==JsonValueKind.Array)foreach(var v in values.EnumerateArray())
                    {var value=v.ValueKind==JsonValueKind.Object&&v.TryGetProperty("value",out var ov)?ov.ToString():v.ToString();options[value]=v.ValueKind==JsonValueKind.Object&&v.TryGetProperty("label",out var ol)?ol.ToString():value;}
                }
                definitions.Add(new(key,attr.TryGetProperty("label",out var label)?label.ToString():key,fieldKind,attr.TryGetProperty("required",out var required)&&required.ValueKind==JsonValueKind.True,options));extra.Add(key);
            }
        }catch(JsonException){ }
    }
    private void CalculateAmount()
    {
        if(!Intake || current!="METAL" || amountOverridden || !cache.TryGetValue(current,out var entry))return;
        var row=entry.fields.Read(false);var weight=row.Money("weight");var price=row.Money("unitPrice");
        if(weight<0 || weight>999999999 || price<0 || price>999999999)return;
        updatingAmount=true;
        try{((Entry)tail.Inputs["amount"]).Text=Fees.Round(weight*price).ToString("0.##",CultureInfo.InvariantCulture);}
        finally{updatingAmount=false;}
    }
    public DbRow Read(bool validate=true)
    {
        var row=identity.Read(validate);var detail=cache[current].fields.Read(validate);foreach(var pair in detail)row[pair.Key]=pair.Value;
        foreach(var pair in tail.Read(validate))row[pair.Key]=pair.Value;
        row["extraAttributes"]=JsonSerializer.Serialize(detail.Where(p=>cache[current].extra.Contains(p.Key)).ToDictionary(p=>p.Key,p=>p.Value));
        return row;
    }
}