diff --git a/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs index 88b3386..103eb5e 100644 --- a/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs +++ b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs @@ -23,6 +23,10 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models /// 空/Null 表示全部店铺(服务层会取全部店铺的 ErpShopId)。 /// public List ShopIds { get; set; } + /// + /// 是否统计快递费用 + /// + public bool StatPackages { get; set; } } /// @@ -111,6 +115,10 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models /// 被拒绝金额(分) /// public long RejectedRefund { get; set; } + /// + /// 包裹费用 + /// + public long PackageAmount { get; set; } } /// @@ -199,6 +207,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models /// public int UnrefundedAmount { get; set; } + public long PackageAmount { get; set; } /// /// 买手订单信息(一个订单号对应多个买手订单) /// @@ -288,6 +297,10 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models public decimal BuyerRefundSuccessedAmount { get; set; } /// 买手号剩余退款金额(尚未成功退款的金额,元) public decimal BuyerRefundRemainingAmount => BuyerRefundAmount - BuyerRefundSuccessedAmount; + /// + /// 包裹费用 + /// + public decimal PackageAmount { get; set; } } /// @@ -355,6 +368,10 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models /// 平均利润率(利润 / 采购金额,百分数,如 12.34 表示 12.34%) public decimal AverageProfitRate { get; set; } + /// + /// 包裹费用 + /// + public decimal PackageAmount { get; set; } } /// diff --git a/29WxOrderWpf/Ctrls/Statistics/RevenueStatisticsSearchBar.xaml b/29WxOrderWpf/Ctrls/Statistics/RevenueStatisticsSearchBar.xaml index 8910410..0875bfe 100644 --- a/29WxOrderWpf/Ctrls/Statistics/RevenueStatisticsSearchBar.xaml +++ b/29WxOrderWpf/Ctrls/Statistics/RevenueStatisticsSearchBar.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.Statistics.ViewModels" + xmlns:wpf="WPFCommon" d:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d"> @@ -48,9 +49,9 @@ Width="110" Height="32" VerticalContentAlignment="Center" + BorderThickness="1" DisplayMemberPath="Text" IsReadOnly="True" - BorderThickness="1" ItemsSource="{Binding TimeRangeOptions}" SelectedItem="{Binding SelectedTimeRange, Mode=TwoWay}" Style="{StaticResource RoundedComboBox}" /> @@ -60,9 +61,10 @@ FontSize="14" Foreground="#666666" Text="日期" /> - - --> + + + - public static int VersionCount = 388; + public static int VersionCount = 389; //public static string ServerUrl { get; set; } = "http://1.13.195.131:9000"; public static string ServerUrl diff --git a/Work/_29Tools/Forms/UserForms/WXGetBalanceQrCodeForm.cs b/Work/_29Tools/Forms/UserForms/WXGetBalanceQrCodeForm.cs index fadb885..e521ddf 100644 --- a/Work/_29Tools/Forms/UserForms/WXGetBalanceQrCodeForm.cs +++ b/Work/_29Tools/Forms/UserForms/WXGetBalanceQrCodeForm.cs @@ -141,57 +141,75 @@ namespace Work._29Tools.Forms.UserForms } private List paymentList; - public async void GetBalanceList() + private string GetBalanceFilters() { - int pageCount = 1; - int pageSize = 50; + var data3 = "&user_name_in=" + BaseClass.UserName; + data3 += "&create_time_begin=" + dtpTimeBegin.Value.ToString("yyyy-MM-dd HH:mm:ss"); + data3 += "&create_time_end=" + dtpTimeEnd.Value.ToString("yyyy-MM-dd HH:mm:ss"); + if (cmbType.Text == "充值") + data3 += "&type_in=1,30"; + else if (cmbType.Text == "管理员充值") + data3 += "&type_in=1"; + else if (cmbType.Text == "支付宝充值") + data3 += "&type_in=30"; + else if (cmbType.Text == "礼物单支出/礼物单退费") + data3 += "&type_in=16,34"; + else if (cmbType.Text == "拍单扣除/作废返还") + data3 += "&type_in=20,23"; + else if (cmbType.Text == "超重扣费支出") + data3 += "&type_in=24,28,33,37,41"; + else if (cmbType.Text == "退货包裹扣费支出/返还") + data3 += "&type_in=26,38"; + else if (cmbType.Text == "额外包裹扣费支出/返还") + data3 += "&type_in=35,36"; + else if (cmbType.Text == "云仓退货扣费支出") + data3 += "&type_in=31"; + if (!string.IsNullOrEmpty(txtOutOrderId.Text.Trim())) + data3 += "&order_id_in=" + txtOutOrderId.Text.Trim(); + if (!string.IsNullOrEmpty(txtShopOrderNo.Text.Trim())) + data3 += "&remark_fulltext=" + txtShopOrderNo.Text.Trim(); + return data3; + } + + private static List ReadBalancePage(string filters, int page, int pageSize, out int count, out decimal seconds) + { + var url = BaseClass.Build29WxObjString("user/get_user_balance"); + var data = UserLogin.GetSignaturex() + filters + "&page=" + page + "&page_size=" + pageSize; + var html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (string.IsNullOrEmpty(html) || !html.Contains("\"code\":200")) + throw new InvalidOperationException($"第 {page} 页查询失败:" + (string.IsNullOrEmpty(html) ? "服务器无响应" : BaseRegex.GetStringJsonByRegs(ref html, html, "msg"))); + + var result = JObject.Parse(html); + count = (int)result["count"]; + seconds = BaseRegex.GetDecimalJson(ref html, "seconds"); + return JsonConvert.DeserializeObject>(result["data"]?.ToString() ?? "null") ?? throw new InvalidOperationException($"第 {page} 页缺少消费记录"); + } + + public void GetBalanceList() + { + int page = 1, pageSize = 50; + string filters = null; Invoke(new Action(() => { this.dgvPayment.Rows.Clear(); - })); - var url3 = BaseClass.Build29WxObjString("user/get_user_balance"); - var data3 = UserLogin.GetSignaturex(); - Invoke(new Action(() => - { - data3 += "&user_name_in=" + BaseClass.UserName; - data3 += "&create_time_begin=" + dtpTimeBegin.Value.ToString("yyyy-MM-dd HH:mm:ss"); - data3 += "&create_time_end=" + dtpTimeEnd.Value.ToString("yyyy-MM-dd HH:mm:ss"); - if (cmbType.Text == "充值") - data3 += "&type_in=1,30"; - else if (cmbType.Text == "管理员充值") - data3 += "&type_in=1"; - else if (cmbType.Text == "支付宝充值") - data3 += "&type_in=30"; - else if (cmbType.Text == "礼物单支出/礼物单退费") - data3 += "&type_in=16,34"; - else if (cmbType.Text == "拍单扣除/作废返还") - data3 += "&type_in=20,23"; - else if (cmbType.Text == "超重扣费支出") - data3 += "&type_in=24,28,33,37,41"; - else if (cmbType.Text == "退货包裹扣费支出/返还") - data3 += "&type_in=26,38"; - else if (cmbType.Text == "额外包裹扣费支出/返还") - data3 += "&type_in=35,36"; - else if (cmbType.Text == "云仓退货扣费支出") - data3 += "&type_in=31"; - if (!string.IsNullOrEmpty(txtOutOrderId.Text.Trim())) - data3 += "&order_id_in=" + txtOutOrderId.Text.Trim(); - if (!string.IsNullOrEmpty(txtShopOrderNo.Text.Trim())) - data3 += "&remark_fulltext=" + txtShopOrderNo.Text.Trim(); - data3 += "&page=" + ucSplitPage1.CurrentPage; - data3 += "&page_size=" + ucSplitPage1.PageSize; + paymentList = null; + filters = GetBalanceFilters(); + page = ucSplitPage1.CurrentPage; pageSize = ucSplitPage1.PageSize; })); - var html3 = GetHtml.PostUtf8(url3, data3); - if (string.IsNullOrEmpty(html3) || !html3.Contains("\"code\":200")) - html3 = GetHtml.PostUtf8(url3, data3); - if (string.IsNullOrEmpty(html3) || !html3.Contains("\"code\":200")) + int count; + decimal seconds; + try { - BaseMsgForm.ShowError("查询失败:" + BaseRegex.GetStringJsonByRegs(ref html3, html3, "msg")); + paymentList = ReadBalancePage(filters, page, pageSize, out count, out seconds); + } + catch (Exception ex) + { + BaseMsgForm.ShowError("查询失败:" + ex.Message); return; } - paymentList = new List(); - paymentList = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(html3)["data"].ParseToString()); foreach (var payment in paymentList) { if (!this.Created) @@ -210,12 +228,11 @@ namespace Work._29Tools.Forms.UserForms this.dgvPayment.Rows[rowIndex].Height = 60; })); } - int count = BaseRegex.GetIntJson(ref html3, "count"); - pageCount = Math.Ceiling(1.0 * count / pageSize).ParseToInt(); + int pageCount = Math.Ceiling(1.0 * count / pageSize).ParseToInt(); pageCount = Math.Max(pageCount, 1); ucSplitPage1.TotalPage = pageCount; #if DEBUG - Invoke(new Action(() => { statusBarUserControl1.lblTaskStatus.Text = BaseRegex.GetDecimalJson(ref html3, "seconds") + "秒"; })); + Invoke(new Action(() => { statusBarUserControl1.lblTaskStatus.Text = seconds + "秒"; })); #endif Invoke(new Action(() => { @@ -275,7 +292,20 @@ namespace Work._29Tools.Forms.UserForms private void exportRecord_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - if (paymentList != null) + var menu = new ContextMenuStrip(); + menu.Items.Add("导出当页", null, async (s, args) => await ExportRecords(false)).Enabled = paymentList != null; + menu.Items.Add("导出全部", null, async (s, args) => await ExportRecords(true)); + menu.Closed += (s, args) => BeginInvoke(new Action(menu.Dispose)); + menu.Show(exportRecord, 0, exportRecord.Height); + } + + private async Task ExportRecords(bool allPages) + { + var records = paymentList; + var filters = GetBalanceFilters(); + int pageSize = ucSplitPage1.PageSize; + exportRecord.Enabled = false; + try { using (SaveFileDialog saveFileDialog = new SaveFileDialog()) { @@ -285,40 +315,86 @@ namespace Work._29Tools.Forms.UserForms saveFileDialog.RestoreDirectory = true; if (saveFileDialog.ShowDialog() == DialogResult.OK) { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("类型,订单ID,金额,余额,创建时间,备注"); - - foreach (var item in paymentList) + if (allPages) { - //var rowIndex = this.dgvPayment.Rows.Add( - // false, - // ((EnumOrderPaymentType)payment.type.ParseToInt()).ParseToString(), - // item.order_id, - // item.amount, - // item.balance, - // item.create_time.ToString("yyyy-MM-dd HH:mm:ss"), - // item.remark - //); - string type = "未知"; - if (item.type.HasValue) - { - if (Enum.TryParse(item.type.Value.ToString(), out EnumOrderPaymentType v)) - { - type = v.ToString(); - } - } + statusBarUserControl1.pgbTaskProgress.Maximum = 100; + statusBarUserControl1.InitProgressBar(); + statusBarUserControl1.UpdateProgressBarStatus("正在读取第 1 页…"); + records = await ReadAllBalancePages(filters, pageSize); + statusBarUserControl1.UpdateProgressBarStatus($"正在保存 {records.Count} 条记录…"); + } - - sb.AppendLine($"{type},{item.order_id},{item.amount},{item.balance},{item.create_time.ToString("yyyy-MM-dd HH:mm:ss")},{item.remark}"); + var fileName = saveFileDialog.FileName; + await Task.Run(() => SaveBalanceRecords(fileName, records)); + if (allPages && !IsDisposed) + { + statusBarUserControl1.UpdateProgressBar(100); + statusBarUserControl1.FinishProgressBar($"导出完成,共 {records.Count} 条记录"); } - File.WriteAllText(saveFileDialog.FileName, sb.ToString(), Encoding.UTF8); MessageBox.Show("导出成功"); } } } + catch (Exception ex) + { + if (allPages && !IsDisposed) + statusBarUserControl1.UpdateProgressBarStatus("导出失败:" + ex.Message); + BaseMsgForm.ShowError("导出失败:" + ex.Message); + } + finally + { + if (!IsDisposed) + exportRecord.Enabled = true; + } } + private static void SaveBalanceRecords(string fileName, List records) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("类型,订单ID,金额,余额,创建时间,备注"); + + foreach (var item in records) + { + string type = "未知"; + if (item.type.HasValue) + { + if (Enum.TryParse(item.type.Value.ToString(), out EnumOrderPaymentType v)) + { + type = v.ToString(); + } + } + + + sb.AppendLine($"{type},{item.order_id},{item.amount},{item.balance},{item.create_time.ToString("yyyy-MM-dd HH:mm:ss")},{item.remark}"); + } + File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8); + } + + private async Task> ReadAllBalancePages(string filters, int pageSize) + { + var records = new List(); + int expectedCount = 0; + for (int page = 1; ; page++) + { + int count = 0; + var batch = await Task.Run(() => ReadBalancePage(filters, page, pageSize, out count, out _)); + if (page == 1) + expectedCount = count; + if (count != expectedCount || batch.Count != Math.Min(pageSize, expectedCount - records.Count)) + throw new InvalidOperationException($"第 {page} 页记录数有变化,请重新导出。"); + + records.AddRange(batch); + int totalPages = Math.Max(1, (int)Math.Ceiling((double)expectedCount / pageSize)); + statusBarUserControl1.UpdateProgressBar((int)(99L * page / totalPages)); + statusBarUserControl1.UpdateProgressBarStatus($"已读取 {page}/{totalPages} 页,共 {records.Count}/{expectedCount} 条"); + if (records.Count == expectedCount) + { + Debug.Assert(page == totalPages); + return records; + } + } + } private async Task GetUserAmountSummary() { diff --git a/WxOrder.Framework/Commons/DialogWindow.xaml.cs b/WxOrder.Framework/Commons/DialogWindow.xaml.cs index f63a547..fd5fa6d 100644 --- a/WxOrder.Framework/Commons/DialogWindow.xaml.cs +++ b/WxOrder.Framework/Commons/DialogWindow.xaml.cs @@ -24,7 +24,9 @@ namespace WxOrder.Framework.Commons public DialogWindow(Window owner = null) { InitializeComponent(); - Owner = owner ?? Application.Current.MainWindow; + Owner = owner ?? Application.Current.Windows + .OfType() + .FirstOrDefault(w => w.IsActive) ?? Application.Current.MainWindow; isConstructorSuccessful = true; dialogWindowStyle = (Style)TryFindResource("DialogWindowStyle"); Loaded += DialogWindow_Loaded;