using System.Text.Json; using System.Globalization; using Microsoft.Data.Sqlite; namespace JiDangJia.Core; public static class RentalInventory { public static List Items(DbRow order) { try {using var d=JsonDocument.Parse(order.Text("items","[]"));return d.RootElement.EnumerateArray().Select(DbRow.FromJson).ToList();} catch(Exception ex)when(ex is JsonException or InvalidOperationException){return [];} } internal static void Migrate(SqliteConnection c) { foreach(var row in Database.Query(c,"SELECT * FROM BusinessOrder WHERE kind='RENT'")) { if(row.Text("deletedAt").Length>0 && row.Text("trashGroup").Length==0) { row["trashGroup"]="BusinessOrder:"+row.Id; Database.Execute(c,"UPDATE BusinessOrder SET trashGroup=@p0 WHERE id=@p1",row.Text("trashGroup"),row.Id); } var items=Items(row); if(items.Count>0 && items.All(i=>i.Text("stockId").Length>0))continue; Attach(c,row,items,false); Database.Execute(c,"UPDATE BusinessOrder SET items=@p0 WHERE id=@p1",row.Text("items"),row.Id); } } internal static void Attach(SqliteConnection c,DbRow row,List items,bool opening) { if(items.Count==0)items.Add(new(){["name"]=row.Text("itemName"),["type"]=row.Text("itemType","OTHER"),["images"]=row.Text("images")}); var seen=new HashSet(); foreach(var item in items) { var stockId=item.Text("stockId"); if(stockId.Length>0) { if(!seen.Add(stockId))throw new ArgumentException("同一物品不能重复选择"); var stock=opening?BusinessService.AvailableStock(c,stockId):Database.Query(c,"SELECT * FROM RecycleItem WHERE id=@p0",stockId).FirstOrDefault()??throw new InvalidOperationException("旧租赁物品关联的库存不存在"); if(stock.Text("source")!="RENTAL")throw new ArgumentException("请选择租赁库存中的物品"); item.Clear();foreach(var p in stock)item[p.Key]=p.Value;item["stockId"]=stockId; } else { var stock=new DbRow(item); stockId=Database.Id("stock");stock["id"]=stockId;stock["orderId"]="";stock["source"]="RENTAL";stock["originBusinessId"]=row.Id; stock["itemCode"]=item.Text("itemCode").Length>0?item.Text("itemCode"):ItemCatalog.NewCode(); stock["amount"]=item.Money("cost")>0?item.Money("cost"):item.Money("appraisalPrice"); stock["costKnown"]=stock.Money("amount")>0?1:0; stock["createdAt"]=row.Text("createdAt",Database.Now);stock["status"]="IN_STOCK"; if(row.Text("deletedAt").Length>0){stock["deletedAt"]=row.Text("deletedAt");stock["trashGroup"]=row.Text("trashGroup","BusinessOrder:"+row.Id);} Database.Upsert(c,"RecycleItem",stock);item["stockId"]=stockId;item["itemCode"]=stock.Text("itemCode");item["cost"]=stock.Money("amount"); } if(opening || row.Text("status")=="OPEN")Database.Execute(c,"UPDATE RecycleItem SET status='RENTED',rentalOrderId=@p0 WHERE id=@p1",row.Id,stockId); } row["items"]=JsonSerializer.Serialize(items); row["itemName"]=string.Join("、",items.Select(i=>i.Text("name")));row["itemType"]=items[0].Text("type","OTHER"); } public static string Status(DbRow stock,DbRow? rental,DateTime today) { if(stock.Text("status")=="RENTED")return rental is not null && IsOverdue(rental,today)?"OVERDUE_RENT":"RENTED"; return stock.Text("status"); } public static bool IsOverdue(DbRow rental,DateTime today)=>DateTime.TryParse(rental.Text("dueDate"),CultureInfo.InvariantCulture,DateTimeStyles.None,out var due) && due.DateItems(r).Any(i=>i.Text("stockId")==stock.Id)); Database.Execute(c,"UPDATE RecycleItem SET status=@p0,rentalOrderId=@p1,soldAt=NULL WHERE id=@p2",rental is null?(stock.Text("status")=="LISTED"?"LISTED":"IN_STOCK"):"RENTED",rental?.Id,stock.Id); } else if(stock.Text("status")=="SOLD")Database.Execute(c,"UPDATE RecycleItem SET status='IN_STOCK',soldAt=NULL WHERE id=@p0",stock.Id); } } } public sealed partial class BusinessService { internal static DbRow AvailableStock(SqliteConnection c,string id) { var stock=Database.Query(c,"SELECT * FROM RecycleItem WHERE id=@p0 AND deletedAt IS NULL AND status IN ('IN_STOCK','LISTED')",id).FirstOrDefault()??throw new InvalidOperationException("物品已售、租赁中或已删除,不能办理此操作"); if(stock.Text("orderId").Length>0 && Database.Query(c,"SELECT id FROM RecycleOrder WHERE id=@p0 AND status='DONE' AND deletedAt IS NULL",stock.Text("orderId")).Count==0)throw new InvalidOperationException("来源入库单已作废或删除"); if(stock.Text("source")=="DEAD" && Database.Query(c,"SELECT id FROM PawnTicket WHERE id=@p0 AND deletedAt IS NULL",stock.Text("ticketId")).Count==0)throw new InvalidOperationException("来源当单已删除"); if(stock.Text("source")=="RENTAL" && stock.Int("costKnown")==0)throw new InvalidOperationException("旧租赁物品缺少购货成本,请在库存详情补录成本后销售"); return stock; } private static void ReturnRentalItems(SqliteConnection c,DbRow rental) { foreach(var item in RentalInventory.Items(rental))if(item.Text("stockId").Length>0) { if(Database.Query(c,"SELECT id FROM RecycleItem WHERE id=@p0 AND status='RENTED' AND rentalOrderId=@p1 AND deletedAt IS NULL",item.Text("stockId"),rental.Id).Count==0)throw new InvalidOperationException("租赁物品状态已变更,请先核对库存"); Database.Execute(c,"UPDATE RecycleItem SET status='IN_STOCK',rentalOrderId=NULL WHERE id=@p0",item.Text("stockId")); } } public void LostRental(string id,decimal saleAmount) { Positive(saleAmount,"设备售价"); db.Write(c=> { var row=Database.Query(c,"SELECT * FROM BusinessOrder WHERE id=@p0 AND kind='RENT' AND status='OPEN' AND deletedAt IS NULL",id).FirstOrDefault()??throw new InvalidOperationException("租赁单已经结算"); if(!RentalInventory.IsOverdue(row,DateTime.Today))throw new InvalidOperationException("租赁尚未逾期,不能按未归还设备销售结算"); if(saleAmountDatabase.Query(c,"SELECT * FROM RecycleItem WHERE id=@p0 AND status='RENTED' AND rentalOrderId=@p1 AND deletedAt IS NULL",i.Text("stockId"),id).FirstOrDefault()??throw new InvalidOperationException("租赁库存状态已变更")).ToList(); if(stocks.Any(i=>i.Int("costKnown")==0))throw new InvalidOperationException("租赁物品缺少购货成本,请在库存详情补录后按销售结算"); var allocations=FinanceAnalytics.Split(saleAmount,stocks.Select(i=>i.Money("amount")).ToList());var day=Display.Day(DateTime.Today); for(int i=0;i999999999)throw new ArgumentException("购货成本应在 0 至 999,999,999 之间"); db.Write(c=> { var stock=Database.Query(c,"SELECT * FROM RecycleItem WHERE id=@p0 AND source='RENTAL' AND costKnown=0 AND deletedAt IS NULL",id).FirstOrDefault()??throw new InvalidOperationException("仅缺少历史成本的租赁物品可补录"); if(Database.Query(c,"SELECT id FROM SaleRecord WHERE itemId=@p0 AND status='DONE' AND deletedAt IS NULL",id).Count>0)throw new InvalidOperationException("物品已经销售,不能补录成本"); Database.Execute(c,"UPDATE RecycleItem SET amount=@p0,costKnown=1 WHERE id=@p1",Fees.Round(cost),id); Database.Log(c,"RENT_COST","RecycleItem",id,stock.Text("name"));return 0; }); } }