diff --git a/29WxOrderWpf/29WxOrderWpf.csproj b/29WxOrderWpf/29WxOrderWpf.csproj index 4694fc4..6965a2c 100644 --- a/29WxOrderWpf/29WxOrderWpf.csproj +++ b/29WxOrderWpf/29WxOrderWpf.csproj @@ -68,7 +68,7 @@ true - ..\..\KJD Cef138\Bin\ + ..\..\KJD\Bin\ TRACE true pdbonly @@ -140,6 +140,12 @@ + + + + + + @@ -153,6 +159,9 @@ PDDBuyerSelectCtrl.xaml + + DialogWindow.xaml + OrderListRowCtrl.xaml @@ -168,6 +177,18 @@ ShopListCtrl.xaml + + RevenueLineChart.xaml + + + RevenuePieChart.xaml + + + + + ShopRevenueStatisticsView.xaml + + UcWaterMarkTextBox.xaml @@ -202,6 +223,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -222,6 +247,26 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/29WxOrderWpf/App.xaml b/29WxOrderWpf/App.xaml index b8a97d1..bcd645e 100644 --- a/29WxOrderWpf/App.xaml +++ b/29WxOrderWpf/App.xaml @@ -28,8 +28,8 @@ - - + + diff --git a/29WxOrderWpf/Common/DebugVisibility.cs b/29WxOrderWpf/Common/DebugVisibility.cs new file mode 100644 index 0000000..cc604a7 --- /dev/null +++ b/29WxOrderWpf/Common/DebugVisibility.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace _29WxOrderWpf.Common +{ + public static class DebugVisibility + { + // 定义一个附加属性 + public static readonly DependencyProperty EnableProperty = + DependencyProperty.RegisterAttached( + "Enable", + typeof(bool), + typeof(DebugVisibility), + new FrameworkPropertyMetadata( + true, FrameworkPropertyMetadataOptions.AffectsArrange, + OnEnableChanged, OnEnableCoerceCall)); + + private static object OnEnableCoerceCall(DependencyObject d, object baseValue) + { + if (d is UIElement element) + { +#if DEBUG + // Debug 模式下,根据属性值决定可见性 + element.Visibility = (bool)baseValue ? Visibility.Visible : Visibility.Collapsed; +#else + // Release 模式下,隐藏所有标记了 IsDebug 的元素 + element.Visibility = Visibility.Collapsed; +#endif + } + + return baseValue; + } + + private static void OnEnableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is UIElement element) + { +#if DEBUG + // Debug 模式下,根据属性值决定可见性 + element.Visibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed; +#else + // Release 模式下,隐藏所有标记了 IsDebug 的元素 + element.Visibility = Visibility.Collapsed; +#endif + } + } + + public static bool GetEnable(DependencyObject obj) => (bool)obj.GetValue(EnableProperty); + public static void SetEnable(DependencyObject obj, bool value) => obj.SetValue(EnableProperty, value); + + } +} diff --git a/29WxOrderWpf/Common/DelegateCommand.cs b/29WxOrderWpf/Common/DelegateCommand.cs new file mode 100644 index 0000000..498d8db --- /dev/null +++ b/29WxOrderWpf/Common/DelegateCommand.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq.Expressions; +using System.Windows.Input; + +namespace _29WxOrderWpf.Commands +{ + /// + /// An whose delegates do not take any parameters for and . + /// + /// + /// + public class DelegateCommand : DelegateCommandBase + { + Action _executeMethod; + Func _canExecuteMethod; + + /// + /// Creates a new instance of with the to invoke on execution. + /// + /// The to invoke when is called. + public DelegateCommand(Action executeMethod) + : this(executeMethod, () => true) + { + + } + + /// + /// Creates a new instance of with the to invoke on execution + /// and a to query for determining if the command can execute. + /// + /// The to invoke when is called. + /// The to invoke when is called + public DelegateCommand(Action executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Delegate Command Delegates Cannot Be Null"); + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + /// Executes the command. + /// + public void Execute() + { + _executeMethod(); + } + + /// + /// Determines if the command can be executed. + /// + /// Returns if the command can execute,otherwise returns . + public bool CanExecute() + { + return _canExecuteMethod(); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override void Execute(object parameter) + { + Execute(); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute(); + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + _canExecuteMethod = canExecuteExpression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + } +} diff --git a/29WxOrderWpf/Common/DelegateCommandBase.cs b/29WxOrderWpf/Common/DelegateCommandBase.cs new file mode 100644 index 0000000..3f26b66 --- /dev/null +++ b/29WxOrderWpf/Common/DelegateCommandBase.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace _29WxOrderWpf.Commands +{ + public abstract class DelegateCommandBase : ICommand + { + private bool _isActive; + + private SynchronizationContext _synchronizationContext; + private readonly HashSet _observedPropertiesExpressions = new HashSet(); + + /// + /// Creates a new instance of a , specifying both the execute action and the can execute function. + /// + protected DelegateCommandBase() + { + _synchronizationContext = SynchronizationContext.Current; + } + + /// + /// Occurs when changes occur that affect whether or not the command should execute. + /// + public virtual event EventHandler CanExecuteChanged; + + /// + /// Raises so every + /// command invoker can requery . + /// + protected virtual void OnCanExecuteChanged() + { + var handler = CanExecuteChanged; + if (handler != null) + { + if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current) + _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null); + else + handler.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Raises so every command invoker + /// can requery to check if the command can execute. + /// + /// Note that this will trigger the execution of once for each invoker. + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] + public void RaiseCanExecuteChanged() + { + OnCanExecuteChanged(); + } + + void ICommand.Execute(object parameter) + { + Execute(parameter); + } + + bool ICommand.CanExecute(object parameter) + { + return CanExecute(parameter); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected abstract void Execute(object parameter); + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected abstract bool CanExecute(object parameter); + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The object type containing the property specified in the expression. + /// The property expression. Example: ObservesProperty(() => PropertyName). + protected internal void ObservesPropertyInternal(Expression> propertyExpression) + { + if (_observedPropertiesExpressions.Contains(propertyExpression.ToString())) + { + throw new ArgumentException($"{propertyExpression.ToString()} is already being observed.", + nameof(propertyExpression)); + } + else + { + _observedPropertiesExpressions.Add(propertyExpression.ToString()); + } + } + + #region IsActive + + /// + /// Gets or sets a value indicating whether the object is active. + /// + /// if the object is active; otherwise . + public bool IsActive + { + get { return _isActive; } + set + { + if (_isActive != value) + { + _isActive = value; + OnIsActiveChanged(); + } + } + } + + /// + /// Fired if the property changes. + /// + public virtual event EventHandler IsActiveChanged; + + /// + /// This raises the event. + /// + protected virtual void OnIsActiveChanged() + { + IsActiveChanged?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} diff --git a/29WxOrderWpf/Common/DelegateCommand{T}.cs b/29WxOrderWpf/Common/DelegateCommand{T}.cs new file mode 100644 index 0000000..8e29aa6 --- /dev/null +++ b/29WxOrderWpf/Common/DelegateCommand{T}.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Windows.Input; + +namespace _29WxOrderWpf.Commands +{ + + public class DelegateCommand : DelegateCommandBase + { + readonly Action _executeMethod; + Func _canExecuteMethod; + + /// + /// Initializes a new instance of . + /// + /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate. + /// will always return true. + public DelegateCommand(Action executeMethod) + : this(executeMethod, (o) => true) + { + } + + /// + /// Initializes a new instance of . + /// + /// Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate. + /// Delegate to execute when CanExecute is called on the command. This can be null. + /// When both and are . + public DelegateCommand(Action executeMethod, Func canExecuteMethod) + : base() + { + if (executeMethod == null || canExecuteMethod == null) + throw new ArgumentNullException(nameof(executeMethod), "Delegate Command Delegates Cannot Be Null"); + + TypeInfo genericTypeInfo = typeof(T).GetTypeInfo(); + + // DelegateCommand allows object or Nullable<>. + // note: Nullable<> is a struct so we cannot use a class constraint. + if (genericTypeInfo.IsValueType) + { + if (!genericTypeInfo.IsGenericType || !typeof(Nullable<>).GetTypeInfo().IsAssignableFrom(genericTypeInfo.GetGenericTypeDefinition().GetTypeInfo())) + { + throw new InvalidCastException("Delegate CommandInvalid Generic Payload Type"); + } + } + + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + } + + /// + ///Executes the command and invokes the provided during construction. + /// + ///Data used by the command. + public void Execute(T parameter) + { + _executeMethod(parameter); + } + + /// + ///Determines if the command can execute by invoked the provided during construction. + /// + ///Data used by the command to determine if it can execute. + /// + /// if this command can be executed; otherwise, . + /// + public bool CanExecute(T parameter) + { + return _canExecuteMethod(parameter); + } + + /// + /// Handle the internal invocation of + /// + /// Command Parameter + protected override void Execute(object parameter) + { + Execute((T)parameter); + } + + /// + /// Handle the internal invocation of + /// + /// + /// if the Command Can Execute, otherwise + protected override bool CanExecute(object parameter) + { + return CanExecute((T)parameter); + } + + /// + /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The type of the return value of the method that this delegate encapsulates + /// The property expression. Example: ObservesProperty(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesProperty(Expression> propertyExpression) + { + ObservesPropertyInternal(propertyExpression); + return this; + } + + /// + /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications. + /// + /// The property expression. Example: ObservesCanExecute(() => PropertyName). + /// The current instance of DelegateCommand + public DelegateCommand ObservesCanExecute(Expression> canExecuteExpression) + { + Expression> expression = Expression.Lambda>(canExecuteExpression.Body, Expression.Parameter(typeof(T), "o")); + _canExecuteMethod = expression.Compile(); + ObservesPropertyInternal(canExecuteExpression); + return this; + } + } + + +} diff --git a/29WxOrderWpf/Common/DialogWindow.xaml b/29WxOrderWpf/Common/DialogWindow.xaml new file mode 100644 index 0000000..74fa3bf --- /dev/null +++ b/29WxOrderWpf/Common/DialogWindow.xaml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/29WxOrderWpf/Common/DialogWindow.xaml.cs b/29WxOrderWpf/Common/DialogWindow.xaml.cs new file mode 100644 index 0000000..5190abe --- /dev/null +++ b/29WxOrderWpf/Common/DialogWindow.xaml.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + + + +namespace _29WxOrderWpf.Common +{ + /// + /// DialogWindow.xaml 的交互逻辑 + /// + public partial class DialogWindow : Window + { + private readonly Style dialogWindowStyle; + private bool isConstructorSuccessful = false; + public DialogWindow() + { + InitializeComponent(); + Owner = Application.Current.MainWindow; + isConstructorSuccessful = true; + dialogWindowStyle = (Style)TryFindResource("DialogWindowStyle"); + + Loaded += DialogWindow_Loaded; + } + private void DialogWindow_Loaded(object sender, RoutedEventArgs e) + { + Loaded -= DialogWindow_Loaded; + + } + protected override void OnContentChanged(object oldContent, object newContent) + { + this.Style = dialogWindowStyle; + if (newContent is System.Windows.Controls.UserControl control) + { + + Setter widthSetter = this.Style.Setters.OfType().FirstOrDefault(a => a.Property == WidthProperty); + if (widthSetter == null) + { + if (double.IsNaN(control.Width)) + { + this.Width = DialogWindowProperty.GetWidth(control); + } + else + { + this.Width = control.Width; + control.Width = double.NaN; + } + } + + + Setter heightSetter = this.Style.Setters.OfType().FirstOrDefault(a => a.Property == HeightProperty); + if (heightSetter == null) + { + if (double.IsNaN(control.Height)) + { + this.Height = DialogWindowProperty.GetHeight(control); + } + else + { + this.Height = control.Height; + control.Height = double.NaN; + } + } + + + ResizeMode resizeExt = DialogWindowProperty.GetResizeMode(control); + if (resizeExt != ResizeMode.CanResize) + { + this.ResizeMode = resizeExt; + } + + } + base.OnContentChanged(oldContent, newContent); + } + + protected override void OnStyleChanged(Style oldStyle, Style newStyle) + { + if (newStyle.TargetType == typeof(Window)) + { + if (isConstructorSuccessful) + { + //dialogWindowStyle.Merge(newStyle); + } + return; + } + base.OnStyleChanged(oldStyle, newStyle); + } + } +} diff --git a/29WxOrderWpf/Common/DialogWindowProperty.cs b/29WxOrderWpf/Common/DialogWindowProperty.cs new file mode 100644 index 0000000..4c287d3 --- /dev/null +++ b/29WxOrderWpf/Common/DialogWindowProperty.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace _29WxOrderWpf.Common +{ + /// + /// 弹出窗口属性值 + /// + public class DialogWindowProperty + { + public static readonly DependencyProperty WindowStartupLocationProperty = DependencyProperty.RegisterAttached( + "WindowStartupLocation", typeof(WindowStartupLocation), typeof(DialogWindowProperty), new PropertyMetadata(WindowStartupLocation.CenterScreen)); + public static void SetWindowStartupLocation(DependencyObject element, WindowStartupLocation value) + => element.SetValue(WindowStartupLocationProperty, value); + public static WindowStartupLocation GetWindowStartupLocation(DependencyObject element) + => (WindowStartupLocation)element.GetValue(WindowStartupLocationProperty); + + + public static readonly DependencyProperty ResizeModeProperty = DependencyProperty.RegisterAttached( + "ResizeMode", typeof(ResizeMode), typeof(DialogWindowProperty), new PropertyMetadata(ResizeMode.CanResize)); + public static void SetResizeMode(DependencyObject element, ResizeMode value) + => element.SetValue(ResizeModeProperty, value); + public static ResizeMode GetResizeMode(DependencyObject element) + => (ResizeMode)element.GetValue(ResizeModeProperty); + + + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached( + "Width", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetWidth(DependencyObject element, double value) + => element.SetValue(WidthProperty, value); + public static double GetWidth(DependencyObject element) + => (double)element.GetValue(WidthProperty); + + + + public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached( + "Height", typeof(double), typeof(DialogWindowProperty), new PropertyMetadata(double.NaN)); + public static void SetHeight(DependencyObject element, double value) + => element.SetValue(HeightProperty, value); + public static double GetHeight(DependencyObject element) + => (double)element.GetValue(HeightProperty); + + + } +} diff --git a/29WxOrderWpf/Common/Result.cs b/29WxOrderWpf/Common/Result.cs new file mode 100644 index 0000000..43f7e9e --- /dev/null +++ b/29WxOrderWpf/Common/Result.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace _29WxOrderWpf.Common +{ + public class Result + { + public Result() + { + + } + public Result(int code, string msg) + { + Code = code; + Msg = msg; + } + + public int Code { get; set; } + public string Msg { get; set; } + + } + + public class Result : Result + { + public TData Data { get; set; } + } + +} diff --git a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml index b41a87f..20e4fae 100644 --- a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml +++ b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:c="clr-namespace:_29WxOrderWpf.Common" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_29WxOrderWpf.Ctrls.OrderCtrls" xmlns:utils="clr-namespace:_29WxOrderWpf.Utils" @@ -37,7 +38,8 @@ - + + @@ -93,7 +95,12 @@ + + + diff --git a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml.cs b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml.cs index a454768..04092b1 100644 --- a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml.cs +++ b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml.cs @@ -1,5 +1,7 @@ using _29WxOrderWpf.BaseWindows; +using _29WxOrderWpf.Common; using _29WxOrderWpf.Ctrls.BuyerCtrls; +using _29WxOrderWpf.Ctrls.Statistics; using _29WxOrderWpf.Utils; using AllBase; using AllBase.Extension; @@ -37,1545 +39,1556 @@ using static System.Net.Mime.MediaTypeNames; namespace _29WxOrderWpf.Ctrls.OrderCtrls { - /// - /// OrderListCtrl.xaml 的交互逻辑 - /// - public partial class OrderListCtrl : UserControl - { - public CheckBox chkSelectAll = null; - - public bool IsAutoReadOrder = true; - public DateTime nextGetOrderTime = DateTime.MinValue; - - public string strSearchAll = ""; - public List lstFilterText { get; set; } - - public OrderListCtrl() - { - InitializeComponent(); - DataContext = this; - } - - private void Window_Loaded(object sender, RoutedEventArgs e) - { - lbxOrder.ItemsSource = GlobalConfig.lstFilterOrder; - - dateStart.SelectedDate = DateTime.Now.Date.AddDays(-7); - dateEnd.SelectedDate = DateTime.Now.Date.AddDays(1).AddSeconds(-1); - - borderFilterRemarkSelect.Visibility = Visibility.Collapsed; - - AutoGetOrder(); - } - - public void GetOrderTest() - { - for (int i = 0; i < 10000; i++) - { - //lstOrder.Add(new oms_wei_order_info() { id = 1, IsSelect = false, order_id = ("3737033423685556736".ParseToLong() + Randomx.GetId(0,100)).ParseToString(), shop_name = "媛卡曦工厂优选" }); - GlobalConfig.lstOrder.Add(new oms_wei_order_info() { id = 1, IsSelect = false, order_id = Randomx.GetId(1000, 10000).ParseToString(), shop_name = "媛卡曦工厂优选" }); - } - //GlobalConfig.lstOrder.SortDesc(o => o.order_id.ParseToLong()); - } - - public void AutoGetOrder() - { - Task.Run(() => - { - Thread.Sleep(5000); - while (true) - { - if (!IsAutoReadOrder) - { - Thread.Sleep(1000); - Dispatcher.Invoke(new Action(() => - { - btnSearch.Content = "自动读单暂停中"; - imgLoading.Visibility = Visibility.Collapsed; - })); - continue; - } - - if (DateTime.Now > nextGetOrderTime) - { - GetOrder(); - - nextGetOrderTime = DateTime.Now.AddMinutes(25); - } - Thread.Sleep(1000); - Dispatcher.Invoke(new Action(() => - { - var ticks = nextGetOrderTime - DateTime.Now; - btnSearch.Content = "读单 " + ticks.Minutes.ToString("00") + ":" + ticks.Seconds.ToString("00"); - })); - } - }); - } - - - public bool hasReadOrder = false; - /// - /// - /// - /// 按过滤条件读取读取服务端订单 - public void GetOrder(string orderIds = "", bool filterGetCondition = false) - { - Dispatcher.Invoke(new Action(() => - { - imgLoading.Visibility = Visibility.Visible; - btnSearch.IsEnabled = false; - })); - int page = 0; - int totalPage = 1; - int pageSize = 50; - - while (true && (IsAutoReadOrder || !string.IsNullOrEmpty(orderIds))) - { - if (page >= totalPage) - { - break; - } - Dispatcher.Invoke(new Action(() => - { - imgLoading.Visibility = Visibility.Visible; - btnSearch.IsEnabled = false; - })); - - page++; - var url = BaseClass.Build29WxObjString("order/get_erp_order_list"); - var data = UserLogin.GetSignaturex() + "&page=" + page; - if (!string.IsNullOrEmpty(orderIds)) - data += "&erp_order_id_in=" + orderIds; - if (page > 1) - data += "&is_not_count=true"; - data += "&page_size=" + pageSize; - var shop_id = string.Join(",", GlobalConfig.ShopLoginList.Select(s => s.ErpShopId).Where(s => !s.IsNullOrEmpty())); - //指定订单id的查询,不再查询店铺id - if (string.IsNullOrEmpty(orderIds)) - data += "&erp_shop_id_in=" + shop_id; - Dispatcher.Invoke(new Action(() => - { - // 按过滤条件读取 - if (filterGetCondition) - { - if (!string.IsNullOrEmpty(txtShopName.Text.Trim())) - { - var shopNames = txtShopName.Text.Trim().Split(';').Where(s => !s.IsNullOrEmpty()).ToList(); - shop_id = string.Join(",", GlobalConfig.ShopLoginList.Where(s => shopNames.Contains(s.ShopAccount)).Select(s => s.ErpShopId).Where(s => !s.IsNullOrEmpty())); - } - } - var startTime = BaseClass.DateTimeToTimestampSeconds(dateStart.SelectedDate.Value); - var endTime = BaseClass.DateTimeToTimestampSeconds(dateEnd.SelectedDate.Value.AddDays(1)); - if (!chkEndDate.IsChecked.ParseToBool()) - endTime = BaseClass.DateTimeToTimestampSeconds(DateTime.Now.Date.AddDays(1)); - data += "&start_time=" + startTime; - data += "&end_time=" + endTime; - })); - Thread.Sleep(200); - 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")) - { - BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html, "msg")); - break; - } - var seconds = BaseRegex.GetDecimalJson(ref html, "seconds"); - - var jArr = JObject.Parse(html)["data"]; - if (jArr.Count() == 0) - break; - - var order_ids = string.Join(",", jArr.Select(a => a["order_id"].ParseToString())); - - // 读取订单详情 - if (string.IsNullOrEmpty(order_ids)) - Thread.Sleep(100); - List orderItems = GetOrderItemInfo(order_ids); - - // 读取售后单 - if (string.IsNullOrEmpty(order_ids)) - Thread.Sleep(100); - List refunds = GetOrderRefundInfo(order_ids); - - // 读订单扩展 - if (string.IsNullOrEmpty(order_ids)) - Thread.Sleep(100); - List orderItemExts = GetOrderItemExts(order_ids); - if (string.IsNullOrEmpty(order_ids)) - { - foreach (var oie in orderItemExts) - { - // 移除订单物流异常信息,每次批量读单最后要去微信获取 - oie.abnormal_text = ""; - } - } - - // 读取中转仓拍单订单 - var url3 = BaseClass.Build29WxObjString("order/wx_get_transit_order"); - var data3 = UserLogin.GetSignaturex(); - data3 += "&order_no=" + order_ids; - 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")) - { - BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html3, "msg")); - break; - } - List transitOrderList = new List(); - try - { - transitOrderList = JsonHelper.DeserializeJsonToList(JObject.Parse(html3)["data"].ParseToString()); - } - catch { } - - // 读取暂时记录的买手订单,后面想办法放到中转订单库里去 - var url4 = BaseClass.Build29WxObjString("order/wx_get_buyer_order_list"); - var data4 = UserLogin.GetSignaturex(); - data4 += "&wx_order_id_in=" + order_ids; - var html4 = GetHtml.PostUtf8(url4, data4); - if (string.IsNullOrEmpty(html4) || !html4.Contains("\"code\":200")) - html4 = GetHtml.PostUtf8(url4, data4); - if (string.IsNullOrEmpty(html4) || !html4.Contains("\"code\":200")) - { - Dispatcher.Invoke(new Action(() => - { - BaseMsgWindow.ShowError("订单读取失败(买手) " + BaseRegex.GetStringJson(ref html4, "msg")); - })); - break; - } - List pddBuyerOrders = new List(); - try - { - pddBuyerOrders = JsonHelper.DeserializeJsonToList(JObject.Parse(html4)["data"].ParseToString()) - .OrderByDescending(p => p.TransitCreateTime).ToList(); - } - catch { } - - // 读取暂时记录的买手售后单,后面想办法放到中转订单库里去 - var url6 = BaseClass.Build29WxObjString("order/wx_get_buyer_after_sale_list"); - var data6 = UserLogin.GetSignaturex(); - data6 += "&wx_order_id_in=" + order_ids; - List pddBuyerAfterSales = new List(); - if (!string.IsNullOrEmpty(order_ids)) - { - var html6 = GetHtml.PostUtf8(url6, data6); - if (string.IsNullOrEmpty(html6) || !html6.Contains("\"code\":200")) - html6 = GetHtml.PostUtf8(url6, data6); - if (string.IsNullOrEmpty(html6) || !html6.Contains("\"code\":200")) - { - BaseMsgWindow.ShowError("订单读取失败(买手售后) " + BaseRegex.GetStringJson(ref html6, "msg")); - break; - } - try - { - pddBuyerAfterSales = JsonHelper.DeserializeJsonToList(JObject.Parse(html6)["data"].ParseToString()) - .OrderByDescending(p => p.TransitCreateTime).ToList(); - } - catch - { - } - } - - // 读取中转仓处理售后单 - var url5 = BaseClass.Build29WxObjString("order/wx_get_transit_warehouse_aftersale_list"); - var data5 = UserLogin.GetSignaturex(); - data5 += "&wx_order_id_in=" + order_ids; - var html5 = GetHtml.PostUtf8(url5, data5); - if (string.IsNullOrEmpty(html5) || !html5.Contains("\"code\":200")) - html5 = GetHtml.PostUtf8(url5, data5); - if (string.IsNullOrEmpty(html5) || !html5.Contains("\"code\":200")) - { - Dispatcher.Invoke(new Action(() => - { - BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html5, "msg")); - })); - break; - } - List transitAfterSaleList = new List(); - try - { - transitAfterSaleList = JsonHelper.DeserializeJsonToList(JObject.Parse(html5)["data"].ParseToString()) - .OrderByDescending(p => p.created_time).ToList(); - } - catch { } - - - // 读取关联拍单商品 - var urlMap = BaseClass.Build29WxObjString("order/wx_get_buy_prod_map"); - var dataMap = UserLogin.GetSignaturex() - + "&spu_codes=" + string.Join(",", orderItems.Select(oi => GetHtml.UrlEncodeUtf8(oi.out_product_id))) + "," + string.Join(",", orderItems.Select(oi => oi.product_id)); - dataMap += "&main_user_name=" + BaseClass.MainUserName; - var resultMap = GetHtml.PostUtf8(urlMap, dataMap); - if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200")) - resultMap = GetHtml.PostUtf8(urlMap, dataMap); - if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200")) - { - Dispatcher.Invoke(new Action(() => - { - BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref resultMap, "msg")); - })); - break; - } - List byuMapList = new List(); - try { byuMapList = JsonHelper.DeserializeJsonToList(JObject.Parse(resultMap)["data"].ParseToString()); } catch { } - - - // 装载订单 - foreach (JObject jObj in jArr) - { - try - { - oms_wei_order_info order = null; - order = JsonHelper.DeserializeJsonToObject(JsonHelper.SerializeObject(jObj)); - - //装载订单商品详情 - order.orderItems = new ObservableCollection(); - foreach (var item in orderItems.Where(oi => oi.order_id == order.order_id)) - { - // 订单商品 - item.ItemOrder = order; - order.orderItems.Add(item); - - item.OrderItemExt = orderItemExts.FirstOrDefault(ext => ext.order_id == item.order_id && item.sku_id == item.sku_id); - - // 已放到赋值函数中处理 - // 更新店铺是否加入代发情况 - //item.Shop = GlobalConfig.ShopLoginList.FirstOrDefault(s => s.ErpShopId == item.order_id); - //Dispatcher.Invoke(new Action(() => { item.UpdateOrderItemBuyButton(); })); - - // 关联拍单关系 // 危险物品禁止拍单,仅关联安全商品 - if (!GlobalConfig.lstDangerItemWord.Any(d => (item.title).ParseToString().Contains(d))) - { - item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.out_product_id); - if (item.BuyMap == null) - item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.out_product_id); - if (item.BuyMap == null) - item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.product_id); - if (item.BuyMap == null) - item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.product_id); - } - - // 中转仓订单 - if (item.ItemOrder.orderItems.Count > 1) - item.ItemTransitOrders.AddRange(transitOrderList.Where(t => t.order_no == item.order_id && t.order_sku_id == item.sku_id)); - else - item.ItemTransitOrders.AddRange(transitOrderList.Where(t => t.order_no == item.order_id)); - // 中转仓退货包裹订单 - item.ItemTransitOrderAfterSales = transitAfterSaleList.Where(a => item.ItemTransitOrders.Any(t => t.out_order_no == a.order_code)).ToList(); - - // 中转仓买手订单(wx_send_type等于0) - item.PddBuyerOrders = pddBuyerOrders.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 0).ToList(); - // 中转仓买手退货订单(wx_send_type等于0) - item.PddBuyerAfterSales = pddBuyerAfterSales.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 0).ToList(); - // 直发买手订单(wx_send_type等于1) - item.PddBuyerOrders = pddBuyerOrders.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 1).ToList(); - // 直发买手退款订单(wx_send_type等于1) - item.DirectPddBuyerAfterSales = pddBuyerAfterSales.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 1).ToList(); - } - - // 装载订单售后详情 - order.refundItems = new ObservableCollection(refunds.Where(of => of.order_id == order.order_id)); - - // 读取订单后立即赋值,防止滚动时实时赋值造成卡顿 - foreach (var orderItem in order.orderItems) - { - orderItem.SetOrderItemTextProperty(); - } - order.SetOrderTextProperty(); - - // 删除再添加,触发更新。 todo: 如果已存在相同订单,则直接修改更新 - var orderExist = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == order.order_id); - while (orderExist != null) - { - GlobalConfig.lstOrder.Remove(orderExist); - orderExist = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == order.order_id); - } - GlobalConfig.lstOrder.Add(order); - - Dispatcher.Invoke(new Action(() => - { - FilterOneOrder(order); - })); - } - catch (Exception ex) - { - } - } - - Dispatcher.Invoke(new Action(() => - { - disPlayOrderTabOrderCount(); - })); - - var totalCount = BaseRegex.GetIntJson(ref html, "count"); - if (totalCount > 0) - totalPage = Math.Ceiling(1m * totalCount / pageSize).ParseToInt(); - else - { - - } - } - - if (string.IsNullOrEmpty(orderIds)) - GetShopWaybillExceptionOrder(); - - if (string.IsNullOrEmpty(orderIds)) - hasReadOrder = true; - - Dispatcher.Invoke(new Action(() => - { - imgLoading.Visibility = Visibility.Collapsed; - btnSearch.IsEnabled = true; - })); - } - - /// - /// 读取订单详情信息 - /// - /// - /// - public static List GetOrderItemInfo(string order_ids) - { - if (string.IsNullOrEmpty(order_ids)) - return new List(); - var url = BaseClass.Build29WxObjString("order/get_erp_order_item_list"); - var data = UserLogin.GetSignaturex(); - data += "&erp_order_id_in=" + order_ids; - data += "&page_size=" + 2500; - 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")) - { - BaseMsgWindow.ShowAnimateMessage("订单详情读取失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); - } - List orderItems = new List(); - try - { - orderItems = JsonHelper.DeserializeJsonToList(JObject.Parse(html)["data"].ParseToString()); - if (orderItems == null) - orderItems = new List(); - } - catch (Exception ex) - { - orderItems = new List(); - BaseMsgWindow.ShowAnimateMessage("订单详情读取失败 " + ex.Message, "Error", 5); - } - return orderItems; - } - - public static List GetOrderItemExts(string order_ids) - { - List existOrderExtList = new List(); - if (!string.IsNullOrEmpty(order_ids)) - { - var url = BaseClass.Build29WxObjString("order/wx_get_order_item_ext_info"); - var data = UserLogin.GetSignaturex(); - data += "&wx_order_id_in=" + order_ids; - var html = GetHtml.PostUtf8(url, data); - if (!html.Contains("\"code\":200")) - html = GetHtml.PostUtf8(url, data); - if (!html.Contains("\"code\":200")) - { - BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); - } - existOrderExtList = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(html)["data"].ParseToString()); - } - return existOrderExtList; - } - - /// - /// 读取售后单 - /// - /// - /// - public static List GetOrderRefundInfo(string order_ids) - { - if (string.IsNullOrEmpty(order_ids)) - return new List(); - Thread.Sleep(100); - var url2 = BaseClass.Build29WxObjString("order/get_erp_refund_list"); - var data2 = UserLogin.GetSignaturex(); - data2 += "&erp_order_id_in=" + order_ids; - var html2 = GetHtml.PostUtf8(url2, data2); - if (string.IsNullOrEmpty(html2) || !html2.Contains("\"code\":200")) - html2 = GetHtml.PostUtf8(url2, data2); - if (string.IsNullOrEmpty(html2) || !html2.Contains("\"code\":200")) - { - //BaseMsgWindow.ShowError(MainWindow.Instance, "订单读取失败 " + BaseRegex.GetStringJson(ref html, "msg")); - //break; - } - List refunds = new List(); - try { refunds = JsonHelper.DeserializeJsonToList(JObject.Parse(html2)["data"].ParseToString()); } catch { } - return refunds; - } - - public void GetShopWaybillExceptionOrder() - { - foreach (var shop in GlobalConfig.ShopLoginList) - { - if (shop.LoginStatus == "登录成功") - ShopBLL.CheckShopLogin(shop); - if (shop.LoginStatus != "登录成功") - continue; - - var rst = WxSimAbnormalOrderSeach(shop, 100); - //Thread.Sleep(Randomx.GetId(50, 150)); - if (rst.Contains("\"code\":0")) - { - try - { - JObject jObj = JsonHelper.JObjectParse(rst); - var wxOrderIdIn = string.Join(",", jObj["orderList"].Select(j => j["commonInfo"]["orderId"].ParseToString())); - var wxSkuIdIn = string.Join(",", jObj["orderList"].Select(j => (j["orderProductInfo"].FirstOrDefault()?["skuId"]).ParseToString())); - - // 异常单 - List existOrderExtList = new List(); - if (!string.IsNullOrEmpty(wxOrderIdIn) && !string.IsNullOrEmpty(wxSkuIdIn)) - { - var url = BaseClass.Build29WxObjString("order/wx_get_order_item_ext_info"); - var data = UserLogin.GetSignaturex(); - data += "&wx_order_id_in=" + wxOrderIdIn; - data += "&wx_sku_id_in=" + wxSkuIdIn; - var html = GetHtml.PostUtf8(url, data); - if (!html.Contains("\"code\":200")) - html = GetHtml.PostUtf8(url, data); - if (!html.Contains("\"code\":200")) - { - BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); - continue; - } - existOrderExtList = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(html)["data"].ParseToString()); - } - - // 先把已经物流异常但未查询到的订单改为正常 - var waybillExceptionOrders = GlobalConfig.lstOrder.Where(o => o.shop_id.ParseToString() == shop.ErpShopId && o.orderItems.Any(oi => oi.ShopWaybillExceptionStr == "物流异常")).ToList(); - foreach (var waybillExceptionOrder in waybillExceptionOrders) - { - if (!existOrderExtList.Any(oExt => oExt.order_id == waybillExceptionOrder.order_id)) - { - foreach (var orderItem in waybillExceptionOrder.orderItems) - { - if (orderItem.OrderItemExt != null) - { - orderItem.OrderItemExt.abnormal_text = ""; - orderItem.ShopWaybillExceptionStr = ""; - } - } - - // 更新显示 - Dispatcher.Invoke(new Action(() => - { - foreach (var oi in waybillExceptionOrder.orderItems) - oi.SetOrderItemTextProperty(); - waybillExceptionOrder.SetOrderTextProperty(); - - MainWindow.Instance.orderListCtrl.FilterOneOrder(waybillExceptionOrder, true); - })); - - var orderId = waybillExceptionOrder.order_id; - order_item_ext orderExt = new order_item_ext() { order_id = orderId, shop_app_id = shop.ShopId, shop_name = shop.ShopAccount, abnormal_text = "物流异常" }; - - var url1 = BaseClass.Build29WxObjString("order/wx_mdy_order_item_ext_info"); - var data1 = UserLogin.GetSignaturex(); - data1 += "&wx_order_id=" + orderExt.order_id; - data1 += "&abnormal_text="; - var html1 = GetHtml.PostUtf8(url1, data1); - if (string.IsNullOrEmpty(html1) || !html1.Contains("\"code\":200")) - html1 = GetHtml.PostUtf8(url1, data1); - } - } - - foreach (var od in jObj["orderList"]) - { - var oStr = JsonHelper.SerializeObject(od); - - //var orderId = o["commonInfo"]["orderId"].ParseToString(); - var orderId = BaseRegex.GetStringJson(ref oStr, "orderId"); - - if (orderId == "3737914129536416512") - { - - } - var skuId = BaseRegex.GetStringJson(ref oStr, "skuId"); - - // 存在状态完全一样的订单异常信息就跳过 - order_item_ext orderExt = new order_item_ext() { order_id = orderId, sku_id = skuId, shop_app_id = shop.ShopId, shop_name = shop.ShopAccount, abnormal_text = "物流异常" }; - - if (!existOrderExtList.Any(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id && eoe.abnormal_text == orderExt.abnormal_text)) - { - var url1 = BaseClass.Build29WxObjString("order/wx_add_order_item_ext_info"); - if (existOrderExtList.Any(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id)) - url1 = BaseClass.Build29WxObjString("order/wx_mdy_order_item_ext_info"); - var data1 = UserLogin.GetSignaturex(); - data1 += "&wx_order_id=" + orderExt.order_id; - data1 += "&wx_sku_id=" + orderExt.sku_id; - data1 += "&shop_app_id=" + orderExt.shop_app_id; - data1 += "&shop_name=" + orderExt.shop_name; - data1 += "&abnormal_text=" + orderExt.abnormal_text; - var html1 = GetHtml.PostUtf8(url1, data1); - if (string.IsNullOrEmpty(html1) || !html1.Contains("\"code\":200")) - { - BaseMsgWindow.ShowAnimateMessage("订单扩展信息写入失败 " + BaseRegex.GetStringJson(ref html1, "msg"), "Error", 5); - break; - } - } - else - { - orderExt = existOrderExtList.FirstOrDefault(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id && eoe.abnormal_text == orderExt.abnormal_text); - } - - // 更新订单附加信息 - if (orderExt.order_id == "3737845591459133952") - { - - } - var order = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == orderExt.order_id); - if (order != null) - { - var orderItem = order.orderItems.FirstOrDefault(oi => oi.sku_id == orderExt.sku_id); - if (orderItem != null) - { - orderItem.OrderItemExt = orderExt; - Dispatcher.Invoke(new Action(() => - { - foreach (var oi in order.orderItems) - oi.SetOrderItemTextProperty(); - order.SetOrderTextProperty(); - - MainWindow.Instance.orderListCtrl.FilterOneOrder(order, true); - })); - } - } - } - } - catch (Exception ex) - { - BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + ex.Message, "Error", 5); - } - } - } - } - - public void disPlayOrderTabOrderCount() - { - - var orders = string.Join("','", GlobalConfig.lstFilterOrder.Select(o => o.order_id)); - var orders1 = string.Join(",", GlobalConfig.lstFilterOrder.Select(o => o.order_id)); - - - - var tabItems = this.tabOrderType.Items.Cast(); - var tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待采购")); - var userOrderLst = GlobalConfig.lstOrder.Where(o => GlobalConfig.ShopLoginList.Any(s => s.ErpShopId.ParseToLong() == o.shop_id)).ToList(); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待采购"))); - //tabItem.Header = "待采购(" + userOrderLst.Where(r => r.IsWaitBuy).Count() + "|" + filteLstTmp.Where(r => r.IsWaitBuy).Count() + ")"; - tabItem.Header = "待采购(" + userOrderLst.Where(r => r.IsWaitBuy).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待发货")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待发货"))); - //tabItem.Header = "待发货(" + userOrderLst.Where(r => r.IsWaitSend).Count() + "|" + filteLstTmp.Where(r => r.IsWaitSend).Count() + ")"; - tabItem.Header = "待发货(" + userOrderLst.Where(r => r.IsWaitSend).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待收货")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待收货"))); - //tabItem.Header = "待收货(" + userOrderLst.Where(r => r.IsWaitReceive).Count() + "|" + filteLstTmp.Where(r => r.IsWaitReceive).Count() + ")"; - tabItem.Header = "待收货(" + userOrderLst.Where(r => r.IsWaitReceive).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("售后完成单")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "售后完成单"))); - //tabItem.Header = "售后完成单(" + userOrderLst.Where(r => r.IsRefund).Count() + "|" + filteLstTmp.Where(r => r.IsRefund).Count() + ")"; - tabItem.Header = "售后完成单(" + userOrderLst.Where(r => r.IsRefund).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("需处理售后单")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "需处理售后单"))); - //tabItem.Header = "需处理售后单(" + userOrderLst.Where(r => r.IsEmergency).Count() + "|" + filteLstTmp.Where(r => r.IsEmergency).Count() + ")"; - tabItem.Header = "需处理售后单(" + userOrderLst.Where(r => r.IsEmergency).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("异常单")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "异常单"))); - //tabItem.Header = "异常单(" + userOrderLst.Where(r => r.isAbnormal).Count() + "|" + filteLstTmp.Where(r => r.isAbnormal).Count() + ")"; - tabItem.Header = "异常单(" + userOrderLst.Where(r => r.isAbnormal).Count() + "|" + filteLstTmp.Count() + ")"; - } - tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("全部订单")); - if (tabItem != null) - { - var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "全部订单"))); - tabItem.Header = "全部订单(" + userOrderLst.Count() + "|" + filteLstTmp.Count() + ")"; - } - //txtMsg.Text = "当前条件共计 " + GlobalConfig.lstFilterOrder.Count() + " 条订单记录"; - } - - public void FilterOrders() - { - try - { - if (this.lbxOrder == null) - return; - - Dispatcher.Invoke(new Action(() => - { - if (GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单")) - GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.RefundDatetime)); - else if (GlobalConfig.OrderTabHeaderStr.StartsWith("异常单")) - GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.update_time)); - else - GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.create_time)); - this.lbxOrder.ItemsSource = GlobalConfig.lstFilterOrder; - - disPlayOrderTabOrderCount(); - })); - } - catch (Exception ex) - { - } - } - - public void FilterOneOrder(oms_wei_order_info order, bool updateTabCount = false) - { - var orderExist = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.order_id == order.order_id); - while (orderExist != null) - { - GlobalConfig.lstFilterOrder.Remove(orderExist); - orderExist = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.order_id == order.order_id); - } - - if (!GetOrderIsFilter(order)) - { - oms_wei_order_info nextOrder = null; - if (GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单")) - nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.RefundDatetime < order.RefundDatetime); - else if (GlobalConfig.OrderTabHeaderStr.StartsWith("异常单")) - nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.update_time < order.update_time); - else - nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.create_time < order.create_time); - - if (nextOrder != null) - { - GlobalConfig.lstFilterOrder.Insert(GlobalConfig.lstFilterOrder.IndexOf(nextOrder), order); - } - else - { - GlobalConfig.lstFilterOrder.Add(order); - } - } - - if (updateTabCount) - { - Dispatcher.Invoke(new Action(() => - { - disPlayOrderTabOrderCount(); - })); - } - } - - bool GetOrderIsFilter(oms_wei_order_info order, string filterHeadStr = null) - { - if (order == null) - return true; - - if (!GlobalConfig.ShopLoginList.Any(s => s.ErpShopId.ParseToLong() == order.shop_id)) - return true; - - if (!string.IsNullOrEmpty(strSearchAll)) - { - bool isFilter = true; - // 买手物流 - if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.express_1_waybill.ParseToString().Contains(strSearchAll)))) - isFilter = false; - // 仓库物流 - else if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.express_2_waybill.ParseToString().Contains(strSearchAll)))) - isFilter = false; - // 退货物流 - else if (order.refundItems.Any(ri => ri.return_waybill_id.ParseToString().Contains(strSearchAll))) - isFilter = false; - // 退货单号 - else if (order.refundItems.Any(ri => ri.after_sale_order_id.ParseToString().Contains(strSearchAll))) - isFilter = false; - // 仓库采购单号 - else if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.out_order_no.ParseToString().Contains(strSearchAll)))) - isFilter = false; - // 仓库退款单号 - else if (order.orderItems.Any(oi => oi.ItemTransitOrderAfterSales.Any(t => t.refund_waybill.ParseToString().Contains(strSearchAll)))) - isFilter = false; - if (isFilter) - return true; - } - - // 过滤店铺名称 - if (!string.IsNullOrEmpty(txtShopName.Text.Trim()) && !(";" + txtShopName.Text.Trim() + ";").Contains(";" + order.shop_name + ";")) - { - return true; - } - // 过滤商品名称 - if (!string.IsNullOrEmpty(txtGoodsName.Text.Trim()) && !order.orderItems.Any(it => it.title.Contains(txtGoodsName.Text.Trim()))) - { - return true; - } - // 过滤id - if (!string.IsNullOrEmpty(txtOrderId.Text.Trim()) && !(";" + txtOrderId.Text.Trim().Replace(' ', ';').Replace(',', ';') + ";").Contains(";" + order.order_id + ";")) - { - return true; - } - // 过滤多子单订单 - if (chkNoMultiOrder.IsChecked.ParseToBool() && order.orderItems.Count() > 1) - { - return true; - } - // 过滤非中转仓订单 - if (chkOnlyWarehouseOrder.IsChecked.ParseToBool() && !order.IsWaitBuy && - order.orderItems.All(oi => oi.ItemTransitOrderWithVoid == null) && order.orderItems.All(oi => oi.OrderItemExt?.buy_type != "直发")) - { - return true; - } - // 过滤备注 - if (chkFilterRemark.IsChecked == true && !string.IsNullOrEmpty(txtFilterRemark.Text)) - { - if (cmbFilterRemark.SelectedItem is ComboBoxItem comboBoxItem) - { - if (comboBoxItem.Content is TextBlock tb) - { - if (tb.Text == "店铺备注包含" && !lstFilterText.Contains(order.shop_remark)) - return true; - else if (tb.Text == "软件备注包含" && !lstFilterText.Contains(order.soft_remark)) - return true; - else if (tb.Text == "店铺备注不含" && lstFilterText.Contains(order.shop_remark)) - return true; - else if (tb.Text == "软件备注不含" && lstFilterText.Contains(order.soft_remark)) - return true; - } - } - } - if (!chkDirectOrder.IsChecked == true && order.OrderBuyType == "直发") - return true; - if (!chkTransitOrder.IsChecked == true && order.OrderBuyType == "云仓") - return true; - if (!chkTransitChangeDirectOrder.IsChecked == true && order.OrderBuyType == "云改直") - return true; - - // 过滤时间范围 - if (cmbTimeType.SelectedItem is ComboBoxItem cbi) - { - if (cbi.Content is TextBlock tb) - { - if (tb.Text == "更新时间") - { - if (order.UpdateDateTime < dateStart.SelectedDate.Value) - return true; - if (chkEndDate.IsChecked == true && order.UpdateDateTime > dateEnd.SelectedDate.Value) - return true; - } - if (tb.Text == "创建时间") - { - if (order.CreateDateTime < dateStart.SelectedDate.Value) - return true; - if (chkEndDate.IsChecked == true && order.CreateDateTime > dateEnd.SelectedDate.Value) - return true; - } - } - } - - // 过滤标签页 - if (filterHeadStr.IsNullOrEmpty()) - filterHeadStr = GlobalConfig.OrderTabHeaderStr; - - if (filterHeadStr.StartsWith("全部订单")) - { - return false; - } - if (filterHeadStr.StartsWith("待采购")) - { - if (!order.IsWaitBuy) - return true; - //if (order.IsRefund) - // return true; - if (chkMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap == null)) - return true; - if (chkNoMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap != null)) - return true; - } - if (filterHeadStr.StartsWith("待发货") && !order.IsWaitSend) - { - return true; - } - if (filterHeadStr.StartsWith("待收货")) - { - if (!order.IsWaitReceive) - return true; - - if (chkWaitReceiveBuyerIsNotSend.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手未发货")) - return true; - if (chkWaitReceiveBuyerIsSend.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手已发货")) - return true; - if (chkWaitReceiveBuyerIsInWarehouse.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手已送达")) - return true; - - if (chkWaitReceiveWarehouseIsNotPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转待取号")) - return true; - if (chkWaitReceiveWarehouseIsPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转提前取号" && oi.WarehouseWaybillStatusStr != "中转已取号" && oi.WarehouseWaybillStatusStr != "中转已入库")) - return true; - if (chkWaitReceiveWarehouseIsSendOut.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转已出库")) - return true; - } - if (filterHeadStr.StartsWith("异常单") && !order.isAbnormal) - { - return true; - } - if (filterHeadStr.StartsWith("需处理售后单")) - { - if (!order.IsEmergency) - return true; - - if (chkEmergencyBuyerWaitAfterSale.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("需手动申请售后"))) - return true; - else if (chkEmergencyBuyerWaitSendBack.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("待用户发货"))) - return true; - else if (chkEmergencyBuyerWaitSupplierRefund.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("商家处理中"))) - return true; - else if (chkEmergencyBuyerRefundFail.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("退款失败"))) - return true; - - if (chkEmergencyShopCustomRefundHasWaybill.IsChecked == true && order.orderItems.Any(oi => oi.ReturnWaybillIdStr.IsNullOrEmpty())) - return true; - else if (chkEmergencyShopCustomRefundNoWaybill.IsChecked == true && order.orderItems.All(oi => !oi.ReturnWaybillIdStr.IsNullOrEmpty())) - return true; - - if (order.order_id == "3737838342857424896") - { - - } - - // 退款完成 - if (chkEmergencyShopAfterSaleSuccess.IsChecked == true && order.DeliveryDeadlineDtatusStr != "退款完成") - return true; - // 退款中 - else if (chkEmergencyShopInAfterSale.IsChecked == true && (order.DeliveryDeadlineDtatusStr != "退款中")) - return true; - - //// 退货退款 - //if (chkEmergencyReturn.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.RefundStatusStr).ParseToString().Contains("退货")) - // return true; - //// 仅退款 - //else if (chkEmergencyRefund.IsChecked == true && (order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.RefundStatusStr).ParseToString().Contains("退货")) - // return true; - // 退货退款 - if (chkEmergencyReturn.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.type).ParseToString().Contains("RETURN")) - return true; - // 仅退款 - else if (chkEmergencyRefund.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.type).ParseToString().Contains("REFUND")) - return true; - } - if (filterHeadStr.StartsWith("售后完成单")) - { - if (!order.IsRefund) - return true; - - if (chkAftersaleIsSend.IsChecked == true && !order.DeliveryDeadlineStr.Contains("已发货")) - return true; - else if (chkAftersaleIsNotSend.IsChecked == true && !order.DeliveryDeadlineStr.Contains("未发货")) - return true; - - if (chkAftersaleBuyerIsBuy.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr.IsNullOrEmpty())) - return true; - if (chkAftersaleWarehouseIsNotPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转待取号" && !oi.WarehouseWaybillStatusStr.Contains("取号异常"))) - return true; - if (chkAftersaleWarehouseIsPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转提前取号" && oi.WarehouseWaybillStatusStr != "中转已取号" && - oi.WarehouseWaybillStatusStr != "中转已入库" && oi.WarehouseWaybillStatusStr != "中转已出库")) - return true; - } - - return false; - } - - private void btnMultiShopChoice_Click(object sender, RoutedEventArgs e) - { - MainWindow.Instance.shopListCtrl.IsVisibleChanged -= ShopListCtrl_IsVisibleChanged; - MainWindow.Instance.shopListCtrl.Visibility = Visibility.Visible; - MainWindow.Instance.shopListCtrl.IsVisibleChanged += ShopListCtrl_IsVisibleChanged; - } - - private void ShopListCtrl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) - { - if (!MainWindow.Instance.shopListCtrl.IsVisible && MainWindow.Instance.shopListCtrl.IsAcceptChoiceShop == true) - { - var list = MainWindow.Instance.shopListCtrl.lbxShop.ItemsSource as ObservableCollection; - if (list != null) - this.txtShopName.Text = string.Join(";", list.Where(s => s.IsSelect == true).Select(s => s.ShopAccount)); - - FilterOrders(); - - } - MainWindow.Instance.shopListCtrl.IsVisibleChanged -= ShopListCtrl_IsVisibleChanged; - } - - private void btnSearch_Click(object sender, RoutedEventArgs e) - { - this.btnSearch.IsEnabled = false; - - Task.Run(() => - { - Thread.Sleep(5000); - Dispatcher.Invoke(new Action(() => { this.btnSearch.IsEnabled = true; })); - }); - Task.Run(() => - { - while (!MainWindow.Instance.hasReadShop) - { - Thread.Sleep(100); - } - - GetOrder(); - nextGetOrderTime = DateTime.Now.AddMinutes(25); - }); - } - - private void btnFilter_Click(object sender, RoutedEventArgs e) - { - FilterOrders(); - } - - private void dgOrderHeader_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e) - { - } - - private void dgOrderHeader_LayoutUpdated(object sender, EventArgs e) - { - foreach (var item in lbxOrder.Items) - { - ListBoxItem container = (ListBoxItem)lbxOrder.ItemContainerGenerator.ContainerFromIndex(lbxOrder.Items.IndexOf(item)); - if (container != null) - { - // 找到grid控件并修改列大小 - var grids = UiCtrlUtils.FindVisualChildAll(container, "gridListItem"); - foreach (var grid in grids) - { - for (int i = 0; i < grid.ColumnDefinitions.Count; i++) - { - if (dgOrderHeader.Columns.Count > i) - { - // 最后一列因滚动条变窄,已在表头补偿 - if (i == grid.ColumnDefinitions.Count - 1) - grid.ColumnDefinitions[i].Width = new GridLength(Math.Max(0, dgOrderHeader.Columns[i].ActualWidth - 0)); - else - grid.ColumnDefinitions[i].Width = new GridLength(dgOrderHeader.Columns[i].ActualWidth); - } - } - } - } - } - } - - private void ChkSelectAll_Checked(object sender, RoutedEventArgs e) - { - foreach (var order in GlobalConfig.lstOrder.ToList()) - { - order.IsSelect = true; - } - } - - private void ChkSelectAll_Unchecked(object sender, RoutedEventArgs e) - { - foreach (var order in GlobalConfig.lstOrder.ToList()) - { - order.IsSelect = false; - } - } - - private void ChkSelectAll_Loaded(object sender, RoutedEventArgs e) - { - chkSelectAll = sender as CheckBox; - } - - public void UpdateChkAllBox () - { - chkSelectAll.Checked -= ChkSelectAll_Checked; - chkSelectAll.Unchecked -= ChkSelectAll_Unchecked; - if (GlobalConfig.lstFilterOrder.All(s => s.IsSelect)) - chkSelectAll.IsChecked = true; - else if (GlobalConfig.lstFilterOrder.All(s => !s.IsSelect)) - chkSelectAll.IsChecked = false; - else - chkSelectAll.IsChecked = null; - chkSelectAll.Checked += ChkSelectAll_Checked; - chkSelectAll.Unchecked += ChkSelectAll_Unchecked; - } - - private void txtSearchAll_TextChanged(object sender, TextChangedEventArgs e) - { - strSearchAll = txtSearchAll.Text.Trim(); - } - - private void tabOrderType_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - GlobalConfig.OrderTabHeaderStr = ((tabOrderType.SelectedItem as TabItem)?.Header).ParseToString(); - FilterOrders(); - } - - private void chkMapProdOnly_Click(object sender, RoutedEventArgs e) - { - if (chkMapProdOnly.IsChecked == true) - chkNoMapProdOnly.IsChecked = false; - FilterOrders(); - } - private void chkNoMapProdOnly_Click(object sender, RoutedEventArgs e) - { - if (chkNoMapProdOnly.IsChecked == true) - chkMapProdOnly.IsChecked = false; - FilterOrders(); - } - - public static string addNotes(ShopLoginInfo shop, string orderId, string notes, int tagColor = -1) - { - string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/common/cgi/batchMerchantNotes?token=&lang=zh_CN"; - string body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotes"":""" + notes + @""",""merchantNotesTagColor"":" + tagColor + @"}]}"; - if (tagColor == -1) - body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotes"":""" + notes + @"""}]}"; - - string refere = "https://store.weixin.qq.com/shop/order/list"; - var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, refere); - return result; - } - public static string addNotesFlag(ShopLoginInfo shop, string orderId, string notes, int tagColor = 0) - { - string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/common/cgi/batchMerchantNotes?token=&lang=zh_CN"; - string body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotesTagColor"":" + tagColor + @"}]}"; - - string refere = "https://store.weixin.qq.com/shop/order/list"; - var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, refere); - return result; - } - - private void chkAutoReadBuyerOrder_CheckedChanged(object sender, RoutedEventArgs e) - { - IsAutoReadOrder = chkAutoReadOrder.IsChecked == true; - } - - private void TabItem_SizeChanged(object sender, SizeChangedEventArgs e) - { - var tabItems = this.tabOrderType.Items.Cast(); - wpFilter.Margin = new Thickness(tabItems.Sum(t => t.ActualWidth) + 18, wpFilter.Margin.Top, wpFilter.Margin.Right, wpFilter.Margin.Bottom); - - Point position1 = txtFilterRemark.TranslatePoint(new Point(0, 0), gridOrderListCtrl); - Point position2 = txtFilterRemark.TranslatePoint(new Point(0, 0), wpFilter); - borderFilterRemarkSelect.Margin = new Thickness(tabItems.Sum(t => t.ActualWidth) + 16 + position2.X, position1.Y + txtFilterRemark.ActualHeight + 3, 0, 0); - } - - private void chkSubFilterSonditions_Checked(object sender, RoutedEventArgs e) - { - FilterOrders(); - } - - /// - /// 待处理订单买手售后状态过滤 - /// - private void chkEmergencyBuyerAfterSale_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkEmergencyBuyerWaitAfterSale.IsChecked = chkEmergencyBuyerWaitSendBack.IsChecked = chkEmergencyBuyerWaitSupplierRefund.IsChecked = chkEmergencyBuyerRefundFail.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - /// - /// 待处理订单店铺买家售后物流状态过滤 - /// - private void chkEmergencyShopCustomRefundWaybill_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkEmergencyShopCustomRefundHasWaybill.IsChecked = chkEmergencyShopCustomRefundNoWaybill.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - /// - /// 待处理订单店铺售后状态过滤 - /// - /// - /// - private void chkEmergencyShopAfterSale_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkEmergencyShopAfterSaleSuccess.IsChecked = chkEmergencyShopInAfterSale.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - /// - /// 待收货 买手发货状态 - /// - /// - /// - private void chkWaitReceiveBuyerStatus_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkWaitReceiveBuyerIsSend.IsChecked = chkWaitReceiveBuyerIsNotSend.IsChecked = chkWaitReceiveBuyerIsInWarehouse.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - private void chkWaitReceiveWarehouseStatus_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkWaitReceiveWarehouseIsNotPickup.IsChecked = chkWaitReceiveWarehouseIsPickup.IsChecked = chkWaitReceiveWarehouseIsSendOut.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - private void chkAftersaleSendStatus_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkAftersaleIsSend.IsChecked = chkAftersaleIsNotSend.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - private void chkAftersaleBuyerStatus_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkAftersaleBuyerIsBuy.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - private void chkAftersaleWarehouseStatus_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkAftersaleWarehouseIsPickup.IsChecked = chkAftersaleWarehouseIsNotPickup.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - - private async void btnBatchRefund_Click(object sender, RoutedEventArgs e) - { - var orders = GlobalConfig.lstFilterOrder.Where(o => o.IsSelect == true); - List tmpOrderItems = new List(); - foreach (var order in orders) - { - foreach (var orderItem in order.orderItems) - { - if (orderItem.ItemOrder?.OrderBuyType == "云仓") - if (orderItem != null && !string.IsNullOrEmpty(orderItem.ItemTransitOrderWithVoid?.out_order_no) && orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() && - (orderItem.TransitRefundStatusStr.Contains("需手动申请售后") || orderItem.TransitRefundStatusStr.Contains("待用户发货"))) - tmpOrderItems.Add(orderItem); - - if (orderItem.ItemOrder?.OrderBuyType == "直发" || orderItem.ItemOrder?.OrderBuyType == "云改直") - if (orderItem != null && !string.IsNullOrEmpty(orderItem.ItemOrder?.out_order_no) && orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() && - ((orderItem.ItemOrder?.out_after_sale_status).ParseToString().Contains("需手动申请售后") || (orderItem.ItemOrder?.out_after_sale_status).ParseToString().Contains("待用户发货"))) - tmpOrderItems.Add(orderItem); - } - } - if (tmpOrderItems.Count == 0) - { - BaseMsgWindow.ShowAnimateMessage("未选中需要需手动申请售后的订单", "Error", 3); - return; - } - - await Task.Run(() => - { - int index = 0; - foreach (var tmpOrderItem in tmpOrderItems) - { - Dispatcher.Invoke(new Action(() => { txtMsg.Text = "正在处理买手退货,总计:" + tmpOrderItems.Count + ",已处理:" + index++; })); - - var pddOrderSn = ""; - if (tmpOrderItem.ItemOrder?.OrderBuyType == "云仓") - pddOrderSn = tmpOrderItem.ItemTransitOrderWithVoid?.out_order_no; - else if (tmpOrderItem.ItemOrder?.OrderBuyType == "直发" || tmpOrderItem.ItemOrder?.OrderBuyType == "云改直") - pddOrderSn = tmpOrderItem.ItemOrder?.out_order_no; - - BuyerLoginInfo buyer = null; - if (tmpOrderItem.ItemOrder?.OrderBuyType == "云仓") - Dispatcher.Invoke(new Action(() => { buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(b => b.buyer_nick == tmpOrderItem.ItemTransitOrderWithVoid.buyer_account); })); - else if (tmpOrderItem.ItemOrder?.OrderBuyType == "直发" || tmpOrderItem.ItemOrder?.OrderBuyType == "云改直") - Dispatcher.Invoke(new Action(() => { buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(b => b.buyer_nick == tmpOrderItem.ItemOrder?.out_account); })); - if (buyer == null) - { - BaseMsgWindow.ShowAnimateMessage("买手单:" + pddOrderSn + " 买手不在当前账户下,跳过", "Error", 3); - } - - // 检测一下买手号 - if (buyer.status == "在线") - { - PddMobileLoginForm.CheckBuyerLogin(buyer); - } - if (buyer.status != "在线") - { - BaseMsgWindow.ShowAnimateMessage("买手单:" + pddOrderSn + " 买手号掉线,跳过", "Error", 3); - continue; - } - - MainWindow.Instance.orderListCtrl.GetOrder(tmpOrderItem.order_id); - - oms_wei_order_info order = null; - Dispatcher.Invoke(new Action(() => { order = GlobalConfig.lstOrder.FirstOrDefault(oi => oi.order_id == tmpOrderItem.order_id); })); - if (order == null) - continue; - oms_wei_order_item_info orderItem = null; - Dispatcher.Invoke(new Action(() => { orderItem = order.orderItems.FirstOrDefault(oi => oi.order_id == tmpOrderItem.order_id && oi.sku_id == tmpOrderItem.sku_id); })); - if (orderItem == null) - continue; - - string refundStatusStr = ""; - string outOrderNo = ""; - if (order.OrderBuyType == "云仓") - { - outOrderNo = (orderItem.ItemTransitOrderWithVoid?.out_order_no).ParseToString(); - refundStatusStr = (orderItem?.TransitRefundStatusStr).ParseToString(); - } - else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") - { - outOrderNo = (order.out_order_no).ParseToString(); - refundStatusStr = (order.out_after_sale_status).ParseToString(); - } - if (orderItem == null || string.IsNullOrEmpty(outOrderNo) || !orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() || - (!refundStatusStr.Contains("需手动申请售后") && !refundStatusStr.Contains("待用户发货"))) - continue; - - PddBuyerOrderLiteInfo pddBuyerOrderLiteInfo = new PddBuyerOrderLiteInfo(); - pddBuyerOrderLiteInfo.ShopOrderIdStr = orderItem.order_id; - pddBuyerOrderLiteInfo.order_sn = pddOrderSn; - - if (order.OrderBuyType == "云仓") - pddBuyerOrderLiteInfo.order_amount = orderItem.ItemTransitOrderWithVoid?.out_amount; - else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") - pddBuyerOrderLiteInfo.order_amount = order.out_amount; - - order_pdd_buyer_after_sale pddBuyerAfterSale = new order_pdd_buyer_after_sale() - { - wx_order_id = orderItem.order_id, - wx_sku_id = orderItem.sku_id, - TransitCreateTime = orderItem.ItemTransitOrderWithVoid?.created_time == null ? DateTime.MinValue : orderItem.ItemTransitOrderWithVoid.created_time, - pre_allocated_order_no = orderItem.ItemTransitOrderWithVoid?.pre_allocated_order_no, - buyerId = buyer.buyer_id, - buyerName = buyer.buyer_name, - orderSn = pddOrderSn, - orderAmount = pddBuyerOrderLiteInfo.order_amount, - refundAmount = pddBuyerOrderLiteInfo.order_amount, - orderDate = pddOrderSn.ParseToString().Split('-').FirstOrDefault().ParseToInt(), - }; - if (orderItem.PddBuyerAfterSale != null) - pddBuyerAfterSale = orderItem.PddBuyerAfterSale; - - bool isSimPddAfterSaleSuccess = true; - if (refundStatusStr.Contains("需手动申请售后")) - { - isSimPddAfterSaleSuccess = false; - // pddBuyerAfterSale作为参数传入,获取退货退款金额 - var rst = PDDBuyerOrderReadCtrl.SimPddAfterSaleWithReturnGoods(buyer, pddBuyerOrderLiteInfo, pddBuyerAfterSale); - - var afterSalesStatus = BaseRegex.GetIntJson(ref rst, "after_sales_status"); - var afterSalesId = BaseRegex.GetLongJson(ref rst, "after_sales_id"); - pddBuyerAfterSale.afterSalesId = afterSalesId.ToString(); - pddBuyerAfterSale.statusText = "待用户发货"; - - if (afterSalesStatus == 0) - { - var msg = BaseRegex.GetStringJsonByRegs(ref rst, rst, "msg", "err_msg", "error_msg", "errorMsg", "errMsg"); - BaseMsgWindow.ShowAnimateMessage(msg, "Error", 3); - Thread.Sleep(5000); - } - else - { - if (order.OrderBuyType == "云仓") - { - var e57Rst = PDDBuyerOrderReadCtrl.SendTransitAfterSaleE57(pddBuyerOrderLiteInfo, "待用户发货"); - if (e57Rst.Contains("0") || e57Rst.Contains("200")) - { - BaseMsgWindow.ShowAnimateMessage("买手退款信息更新成功", "Msg", 3); - isSimPddAfterSaleSuccess = true; - Thread.Sleep(Randomx.GetId(10000, 12001)); - } - else - { - var msg = BaseRegex.GetStringXml(ref e57Rst, "msg"); - BaseMsgWindow.ShowAnimateMessage("买手退款信息更新失败,服务端返回 " + msg, "Error", 3); - } - } - else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") - { - var e57Rst = PDDBuyerOrderReadCtrl.SendAfterSaleE57NeedManual(pddBuyerOrderLiteInfo, "待用户发货"); - if (e57Rst.Contains("0") || e57Rst.Contains("200")) - { - BaseMsgWindow.ShowAnimateMessage("买手退款信息更新成功", "Msg", 3); - isSimPddAfterSaleSuccess = true; - Thread.Sleep(Randomx.GetId(10000, 12001)); - } - else - { - var msg = BaseRegex.GetStringXml(ref e57Rst, "msg"); - BaseMsgWindow.ShowAnimateMessage("买手退款信息更新失败,服务端返回 " + msg, "Error", 3); - } - } - } - } - if (isSimPddAfterSaleSuccess) - { - var addressRst = YkdBuyerOrder.GetAfterSaleAddress(buyer, pddOrderSn, pddBuyerAfterSale.afterSalesId.ParseToString()); - var merchantReceiverInfo = BaseRegex.GetOneJsonObjStrJson(ref addressRst, "merchantReceiverInfo"); - string address = ""; - if (merchantReceiverInfo != null) - { - address = BaseRegex.GetStringJson(ref merchantReceiverInfo, "copyValue"); - } - - var url3 = BaseClass.Build29WxObjString("order/wx_add_mdy_buyer_after_sale"); - var data3 = UserLogin.GetSignaturex(); - data3 += "&wx_order_id=" + pddBuyerAfterSale.wx_order_id; - data3 += "&wx_sku_id=" + pddBuyerAfterSale.wx_sku_id; - if (pddBuyerAfterSale.TransitCreateTime != DateTime.MinValue) - data3 += "&TransitCreateTime=" + pddBuyerAfterSale.TransitCreateTime.ToString("yyyy-MM-dd HH:mm:ss"); - data3 += "&pre_allocated_order_no=" + pddBuyerAfterSale.pre_allocated_order_no; - data3 += "&buyerId=" + pddBuyerAfterSale.buyerId; - data3 += "&buyerName=" + pddBuyerAfterSale.buyerName; - data3 += "&orderAmount=" + pddBuyerAfterSale.orderAmount; - data3 += "&orderSn=" + pddBuyerAfterSale.orderSn; - data3 += "&afterSalesId=" + pddBuyerAfterSale.afterSalesId; - data3 += "&orderDate=" + pddBuyerAfterSale.orderDate; - //data3 += "&TransitPickupTime=" + pddBuyerOrder.TransitPickupTime.ToString("yyyy-MM-dd HH:mm:ss"); - data3 += "&refundAmount=" + pddBuyerAfterSale.refundAmount; - data3 += "&statusText=" + pddBuyerAfterSale.statusText; - if (!string.IsNullOrEmpty(address)) - data3 += "&afterSaleAddress=" + address; - if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") // 云仓 0 直发 1 - data3 += "&wx_send_type=1"; - //data3 += "&is_mdy=true"; - var html3 = GetHtml.PostUtf8(url3, data3); - if (string.IsNullOrEmpty(html3) || !html3.Contains("\"code\":200")) - html3 = GetHtml.PostUtf8(url3, data3); - } - MainWindow.Instance.orderListCtrl.GetOrder(tmpOrderItem.order_id); - Dispatcher.Invoke(new Action(() => { txtMsg.Text = "正在处理买手退货,总计:" + tmpOrderItems.Count + ",已处理:" + index; })); - } - - Dispatcher.Invoke(new Action(() => { txtMsg.Text = "买手退货处理完成,总计:" + tmpOrderItems.Count + ",已处理:" + index; })); - }); - } - - - public static string WxSimAbnormalOrderSeach(ShopLoginInfo shop, int page = 1) - { - string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/list/cgi/orderSearch?token=&lang=zh_CN"; - string referer = "https://store.weixin.qq.com/shop/order/list"; - string body = @"{""isSearch"":false,""pageSize"":100,""overTime"":0,""pengingShipOneDay"":0,""pengingShipSixHour"":0,""expediteDelivery"":0," + - @"""stockoutPunishAlert"":0,""appointmentInstall"":0,""waybillStatus"":3,""isUserBookingDelivery"":0,""pageNum"":" + page + @",""onAftersaleOrderExist"":0,""orderStatus"":30}"; - var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, referer); - return result; - } - - private void btnMultiRemark_Click(object sender, RoutedEventArgs e) - { - wpFilterRemarkSelect.Children.Clear(); - List txts = txtFilterRemark.Text.Trim().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).ToList(); - var remarks = GlobalConfig.lstOrder.Select(o => o.shop_remark).Distinct().ToList(); - remarks.AddRange(GlobalConfig.lstOrder.Select(o => o.soft_remark).Distinct().ToList()); - remarks = remarks.Distinct().ToList(); - foreach (var remark in remarks) - { - if (string.IsNullOrWhiteSpace(remark)) - continue; - wpFilterRemarkSelect.Children.Add(new CheckBox() { Content = remark, IsChecked = txts.Contains(remark), Margin = new Thickness(2) }); - } - - Point position = txtFilterRemark.TranslatePoint(new Point(0, 0), gridOrderListCtrl); - borderFilterRemarkSelect.Margin = new Thickness(position.X - 2, position.Y + txtFilterRemark.ActualHeight + 3, 0, 0); - - if (borderFilterRemarkSelect.Visibility == Visibility.Visible) - borderFilterRemarkSelect.Visibility = Visibility.Collapsed; - else - borderFilterRemarkSelect.Visibility = Visibility.Visible; - } - - private void btnCancelFilterRemark_Click(object sender, RoutedEventArgs e) - { - borderFilterRemarkSelect.Visibility = Visibility.Collapsed; - } - - private void btnApplyFilterRemark_Click(object sender, RoutedEventArgs e) - { - string txt = ""; - foreach (UIElement element in wpFilterRemarkSelect.Children) - { - if (element is CheckBox box && box.IsChecked == true) - { - txt += box.Content.ParseToString() + ";"; - } - } - txtFilterRemark.Text = txt.TrimEnd(';'); - borderFilterRemarkSelect.Visibility = Visibility.Collapsed; - } - - private void btnFilterRemarkSelectAll_Click(object sender, RoutedEventArgs e) - { - foreach (UIElement element in wpFilterRemarkSelect.Children) - { - if (element is CheckBox box) - { - box.IsChecked = true; - } - } - } - - private void btnFilterRemarkUnSelectAll_Click(object sender, RoutedEventArgs e) - { - foreach (UIElement element in wpFilterRemarkSelect.Children) - { - if (element is CheckBox box) - { - box.IsChecked = false; - } - } - } - - private void txtFilterRemark_TextChanged(object sender, TextChangedEventArgs e) - { - lstFilterText = txtFilterRemark.Text.Trim().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); - } - - private void chkEmergencyReturnOrRefund_Click(object sender, RoutedEventArgs e) - { - if (sender is CheckBox chk) - { - var isCheck = !chk.IsChecked.ParseToBool(); - chkEmergencyReturn.IsChecked = chkEmergencyRefund.IsChecked = false; - chk.IsChecked = !isCheck; - } - FilterOrders(); - } - } + /// + /// OrderListCtrl.xaml 的交互逻辑 + /// + public partial class OrderListCtrl : UserControl + { + public CheckBox chkSelectAll = null; + + public bool IsAutoReadOrder = true; + public DateTime nextGetOrderTime = DateTime.MinValue; + + public string strSearchAll = ""; + public List lstFilterText { get; set; } + + public OrderListCtrl() + { + InitializeComponent(); + DataContext = this; + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + lbxOrder.ItemsSource = GlobalConfig.lstFilterOrder; + + dateStart.SelectedDate = DateTime.Now.Date.AddDays(-7); + dateEnd.SelectedDate = DateTime.Now.Date.AddDays(1).AddSeconds(-1); + + borderFilterRemarkSelect.Visibility = Visibility.Collapsed; + + AutoGetOrder(); + } + + public void GetOrderTest() + { + for (int i = 0; i < 10000; i++) + { + //lstOrder.Add(new oms_wei_order_info() { id = 1, IsSelect = false, order_id = ("3737033423685556736".ParseToLong() + Randomx.GetId(0,100)).ParseToString(), shop_name = "媛卡曦工厂优选" }); + GlobalConfig.lstOrder.Add(new oms_wei_order_info() { id = 1, IsSelect = false, order_id = Randomx.GetId(1000, 10000).ParseToString(), shop_name = "媛卡曦工厂优选" }); + } + //GlobalConfig.lstOrder.SortDesc(o => o.order_id.ParseToLong()); + } + + public void AutoGetOrder() + { + Task.Run(() => + { + Thread.Sleep(5000); + while (true) + { + if (!IsAutoReadOrder) + { + Thread.Sleep(1000); + Dispatcher.Invoke(new Action(() => + { + btnSearch.Content = "自动读单暂停中"; + imgLoading.Visibility = Visibility.Collapsed; + })); + continue; + } + + if (DateTime.Now > nextGetOrderTime) + { + GetOrder(); + + nextGetOrderTime = DateTime.Now.AddMinutes(25); + } + Thread.Sleep(1000); + Dispatcher.Invoke(new Action(() => + { + var ticks = nextGetOrderTime - DateTime.Now; + btnSearch.Content = "读单 " + ticks.Minutes.ToString("00") + ":" + ticks.Seconds.ToString("00"); + })); + } + }); + } + + + public bool hasReadOrder = false; + /// + /// + /// + /// 按过滤条件读取读取服务端订单 + public void GetOrder(string orderIds = "", bool filterGetCondition = false) + { + Dispatcher.Invoke(new Action(() => + { + imgLoading.Visibility = Visibility.Visible; + btnSearch.IsEnabled = false; + })); + int page = 0; + int totalPage = 1; + int pageSize = 50; + + while (true && (IsAutoReadOrder || !string.IsNullOrEmpty(orderIds))) + { + if (page >= totalPage) + { + break; + } + Dispatcher.Invoke(new Action(() => + { + imgLoading.Visibility = Visibility.Visible; + btnSearch.IsEnabled = false; + })); + + page++; + var url = BaseClass.Build29WxObjString("order/get_erp_order_list"); + var data = UserLogin.GetSignaturex() + "&page=" + page; + if (!string.IsNullOrEmpty(orderIds)) + data += "&erp_order_id_in=" + orderIds; + if (page > 1) + data += "&is_not_count=true"; + data += "&page_size=" + pageSize; + var shop_id = string.Join(",", GlobalConfig.ShopLoginList.Select(s => s.ErpShopId).Where(s => !s.IsNullOrEmpty())); + //指定订单id的查询,不再查询店铺id + if (string.IsNullOrEmpty(orderIds)) + data += "&erp_shop_id_in=" + shop_id; + Dispatcher.Invoke(new Action(() => + { + // 按过滤条件读取 + if (filterGetCondition) + { + if (!string.IsNullOrEmpty(txtShopName.Text.Trim())) + { + var shopNames = txtShopName.Text.Trim().Split(';').Where(s => !s.IsNullOrEmpty()).ToList(); + shop_id = string.Join(",", GlobalConfig.ShopLoginList.Where(s => shopNames.Contains(s.ShopAccount)).Select(s => s.ErpShopId).Where(s => !s.IsNullOrEmpty())); + } + } + var startTime = BaseClass.DateTimeToTimestampSeconds(dateStart.SelectedDate.Value); + var endTime = BaseClass.DateTimeToTimestampSeconds(dateEnd.SelectedDate.Value.AddDays(1)); + if (!chkEndDate.IsChecked.ParseToBool()) + endTime = BaseClass.DateTimeToTimestampSeconds(DateTime.Now.Date.AddDays(1)); + data += "&start_time=" + startTime; + data += "&end_time=" + endTime; + })); + Thread.Sleep(200); + 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")) + { + BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html, "msg")); + break; + } + var seconds = BaseRegex.GetDecimalJson(ref html, "seconds"); + + var jArr = JObject.Parse(html)["data"]; + if (jArr.Count() == 0) + break; + + var order_ids = string.Join(",", jArr.Select(a => a["order_id"].ParseToString())); + + // 读取订单详情 + if (string.IsNullOrEmpty(order_ids)) + Thread.Sleep(100); + List orderItems = GetOrderItemInfo(order_ids); + + // 读取售后单 + if (string.IsNullOrEmpty(order_ids)) + Thread.Sleep(100); + List refunds = GetOrderRefundInfo(order_ids); + + // 读订单扩展 + if (string.IsNullOrEmpty(order_ids)) + Thread.Sleep(100); + List orderItemExts = GetOrderItemExts(order_ids); + if (string.IsNullOrEmpty(order_ids)) + { + foreach (var oie in orderItemExts) + { + // 移除订单物流异常信息,每次批量读单最后要去微信获取 + oie.abnormal_text = ""; + } + } + + // 读取中转仓拍单订单 + var url3 = BaseClass.Build29WxObjString("order/wx_get_transit_order"); + var data3 = UserLogin.GetSignaturex(); + data3 += "&order_no=" + order_ids; + 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")) + { + BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html3, "msg")); + break; + } + List transitOrderList = new List(); + try + { + transitOrderList = JsonHelper.DeserializeJsonToList(JObject.Parse(html3)["data"].ParseToString()); + } + catch { } + + // 读取暂时记录的买手订单,后面想办法放到中转订单库里去 + var url4 = BaseClass.Build29WxObjString("order/wx_get_buyer_order_list"); + var data4 = UserLogin.GetSignaturex(); + data4 += "&wx_order_id_in=" + order_ids; + var html4 = GetHtml.PostUtf8(url4, data4); + if (string.IsNullOrEmpty(html4) || !html4.Contains("\"code\":200")) + html4 = GetHtml.PostUtf8(url4, data4); + if (string.IsNullOrEmpty(html4) || !html4.Contains("\"code\":200")) + { + Dispatcher.Invoke(new Action(() => + { + BaseMsgWindow.ShowError("订单读取失败(买手) " + BaseRegex.GetStringJson(ref html4, "msg")); + })); + break; + } + List pddBuyerOrders = new List(); + try + { + pddBuyerOrders = JsonHelper.DeserializeJsonToList(JObject.Parse(html4)["data"].ParseToString()) + .OrderByDescending(p => p.TransitCreateTime).ToList(); + } + catch { } + + // 读取暂时记录的买手售后单,后面想办法放到中转订单库里去 + var url6 = BaseClass.Build29WxObjString("order/wx_get_buyer_after_sale_list"); + var data6 = UserLogin.GetSignaturex(); + data6 += "&wx_order_id_in=" + order_ids; + List pddBuyerAfterSales = new List(); + if (!string.IsNullOrEmpty(order_ids)) + { + var html6 = GetHtml.PostUtf8(url6, data6); + if (string.IsNullOrEmpty(html6) || !html6.Contains("\"code\":200")) + html6 = GetHtml.PostUtf8(url6, data6); + if (string.IsNullOrEmpty(html6) || !html6.Contains("\"code\":200")) + { + BaseMsgWindow.ShowError("订单读取失败(买手售后) " + BaseRegex.GetStringJson(ref html6, "msg")); + break; + } + try + { + pddBuyerAfterSales = JsonHelper.DeserializeJsonToList(JObject.Parse(html6)["data"].ParseToString()) + .OrderByDescending(p => p.TransitCreateTime).ToList(); + } + catch + { + } + } + + // 读取中转仓处理售后单 + var url5 = BaseClass.Build29WxObjString("order/wx_get_transit_warehouse_aftersale_list"); + var data5 = UserLogin.GetSignaturex(); + data5 += "&wx_order_id_in=" + order_ids; + var html5 = GetHtml.PostUtf8(url5, data5); + if (string.IsNullOrEmpty(html5) || !html5.Contains("\"code\":200")) + html5 = GetHtml.PostUtf8(url5, data5); + if (string.IsNullOrEmpty(html5) || !html5.Contains("\"code\":200")) + { + Dispatcher.Invoke(new Action(() => + { + BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref html5, "msg")); + })); + break; + } + List transitAfterSaleList = new List(); + try + { + transitAfterSaleList = JsonHelper.DeserializeJsonToList(JObject.Parse(html5)["data"].ParseToString()) + .OrderByDescending(p => p.created_time).ToList(); + } + catch { } + + + // 读取关联拍单商品 + var urlMap = BaseClass.Build29WxObjString("order/wx_get_buy_prod_map"); + var dataMap = UserLogin.GetSignaturex() + + "&spu_codes=" + string.Join(",", orderItems.Select(oi => GetHtml.UrlEncodeUtf8(oi.out_product_id))) + "," + string.Join(",", orderItems.Select(oi => oi.product_id)); + dataMap += "&main_user_name=" + BaseClass.MainUserName; + var resultMap = GetHtml.PostUtf8(urlMap, dataMap); + if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200")) + resultMap = GetHtml.PostUtf8(urlMap, dataMap); + if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200")) + { + Dispatcher.Invoke(new Action(() => + { + BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref resultMap, "msg")); + })); + break; + } + List byuMapList = new List(); + try { byuMapList = JsonHelper.DeserializeJsonToList(JObject.Parse(resultMap)["data"].ParseToString()); } catch { } + + + // 装载订单 + foreach (JObject jObj in jArr) + { + try + { + oms_wei_order_info order = null; + order = JsonHelper.DeserializeJsonToObject(JsonHelper.SerializeObject(jObj)); + + //装载订单商品详情 + order.orderItems = new ObservableCollection(); + foreach (var item in orderItems.Where(oi => oi.order_id == order.order_id)) + { + // 订单商品 + item.ItemOrder = order; + order.orderItems.Add(item); + + item.OrderItemExt = orderItemExts.FirstOrDefault(ext => ext.order_id == item.order_id && item.sku_id == item.sku_id); + + // 已放到赋值函数中处理 + // 更新店铺是否加入代发情况 + //item.Shop = GlobalConfig.ShopLoginList.FirstOrDefault(s => s.ErpShopId == item.order_id); + //Dispatcher.Invoke(new Action(() => { item.UpdateOrderItemBuyButton(); })); + + // 关联拍单关系 // 危险物品禁止拍单,仅关联安全商品 + if (!GlobalConfig.lstDangerItemWord.Any(d => (item.title).ParseToString().Contains(d))) + { + item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.out_product_id); + if (item.BuyMap == null) + item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.out_product_id); + if (item.BuyMap == null) + item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.product_id); + if (item.BuyMap == null) + item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.product_id); + } + + // 中转仓订单 + if (item.ItemOrder.orderItems.Count > 1) + item.ItemTransitOrders.AddRange(transitOrderList.Where(t => t.order_no == item.order_id && t.order_sku_id == item.sku_id)); + else + item.ItemTransitOrders.AddRange(transitOrderList.Where(t => t.order_no == item.order_id)); + // 中转仓退货包裹订单 + item.ItemTransitOrderAfterSales = transitAfterSaleList.Where(a => item.ItemTransitOrders.Any(t => t.out_order_no == a.order_code)).ToList(); + + // 中转仓买手订单(wx_send_type等于0) + item.PddBuyerOrders = pddBuyerOrders.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 0).ToList(); + // 中转仓买手退货订单(wx_send_type等于0) + item.PddBuyerAfterSales = pddBuyerAfterSales.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 0).ToList(); + // 直发买手订单(wx_send_type等于1) + item.PddBuyerOrders = pddBuyerOrders.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 1).ToList(); + // 直发买手退款订单(wx_send_type等于1) + item.DirectPddBuyerAfterSales = pddBuyerAfterSales.Where(po => po.wx_order_id == item.order_id && po.wx_sku_id == item.sku_id && po.wx_send_type == 1).ToList(); + } + + // 装载订单售后详情 + order.refundItems = new ObservableCollection(refunds.Where(of => of.order_id == order.order_id)); + + // 读取订单后立即赋值,防止滚动时实时赋值造成卡顿 + foreach (var orderItem in order.orderItems) + { + orderItem.SetOrderItemTextProperty(); + } + order.SetOrderTextProperty(); + + // 删除再添加,触发更新。 todo: 如果已存在相同订单,则直接修改更新 + var orderExist = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == order.order_id); + while (orderExist != null) + { + GlobalConfig.lstOrder.Remove(orderExist); + orderExist = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == order.order_id); + } + GlobalConfig.lstOrder.Add(order); + + Dispatcher.Invoke(new Action(() => + { + FilterOneOrder(order); + })); + } + catch (Exception ex) + { + } + } + + Dispatcher.Invoke(new Action(() => + { + disPlayOrderTabOrderCount(); + })); + + var totalCount = BaseRegex.GetIntJson(ref html, "count"); + if (totalCount > 0) + totalPage = Math.Ceiling(1m * totalCount / pageSize).ParseToInt(); + else + { + + } + } + + if (string.IsNullOrEmpty(orderIds)) + GetShopWaybillExceptionOrder(); + + if (string.IsNullOrEmpty(orderIds)) + hasReadOrder = true; + + Dispatcher.Invoke(new Action(() => + { + imgLoading.Visibility = Visibility.Collapsed; + btnSearch.IsEnabled = true; + })); + } + + /// + /// 读取订单详情信息 + /// + /// + /// + public static List GetOrderItemInfo(string order_ids) + { + if (string.IsNullOrEmpty(order_ids)) + return new List(); + var url = BaseClass.Build29WxObjString("order/get_erp_order_item_list"); + var data = UserLogin.GetSignaturex(); + data += "&erp_order_id_in=" + order_ids; + data += "&page_size=" + 2500; + 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")) + { + BaseMsgWindow.ShowAnimateMessage("订单详情读取失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); + } + List orderItems = new List(); + try + { + orderItems = JsonHelper.DeserializeJsonToList(JObject.Parse(html)["data"].ParseToString()); + if (orderItems == null) + orderItems = new List(); + } + catch (Exception ex) + { + orderItems = new List(); + BaseMsgWindow.ShowAnimateMessage("订单详情读取失败 " + ex.Message, "Error", 5); + } + return orderItems; + } + + public static List GetOrderItemExts(string order_ids) + { + List existOrderExtList = new List(); + if (!string.IsNullOrEmpty(order_ids)) + { + var url = BaseClass.Build29WxObjString("order/wx_get_order_item_ext_info"); + var data = UserLogin.GetSignaturex(); + data += "&wx_order_id_in=" + order_ids; + var html = GetHtml.PostUtf8(url, data); + if (!html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (!html.Contains("\"code\":200")) + { + BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); + } + existOrderExtList = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(html)["data"].ParseToString()); + } + return existOrderExtList; + } + + /// + /// 读取售后单 + /// + /// + /// + public static List GetOrderRefundInfo(string order_ids) + { + if (string.IsNullOrEmpty(order_ids)) + return new List(); + Thread.Sleep(100); + var url2 = BaseClass.Build29WxObjString("order/get_erp_refund_list"); + var data2 = UserLogin.GetSignaturex(); + data2 += "&erp_order_id_in=" + order_ids; + var html2 = GetHtml.PostUtf8(url2, data2); + if (string.IsNullOrEmpty(html2) || !html2.Contains("\"code\":200")) + html2 = GetHtml.PostUtf8(url2, data2); + if (string.IsNullOrEmpty(html2) || !html2.Contains("\"code\":200")) + { + //BaseMsgWindow.ShowError(MainWindow.Instance, "订单读取失败 " + BaseRegex.GetStringJson(ref html, "msg")); + //break; + } + List refunds = new List(); + try { refunds = JsonHelper.DeserializeJsonToList(JObject.Parse(html2)["data"].ParseToString()); } catch { } + return refunds; + } + + public void GetShopWaybillExceptionOrder() + { + foreach (var shop in GlobalConfig.ShopLoginList) + { + if (shop.LoginStatus == "登录成功") + ShopBLL.CheckShopLogin(shop); + if (shop.LoginStatus != "登录成功") + continue; + + var rst = WxSimAbnormalOrderSeach(shop, 100); + //Thread.Sleep(Randomx.GetId(50, 150)); + if (rst.Contains("\"code\":0")) + { + try + { + JObject jObj = JsonHelper.JObjectParse(rst); + var wxOrderIdIn = string.Join(",", jObj["orderList"].Select(j => j["commonInfo"]["orderId"].ParseToString())); + var wxSkuIdIn = string.Join(",", jObj["orderList"].Select(j => (j["orderProductInfo"].FirstOrDefault()?["skuId"]).ParseToString())); + + // 异常单 + List existOrderExtList = new List(); + if (!string.IsNullOrEmpty(wxOrderIdIn) && !string.IsNullOrEmpty(wxSkuIdIn)) + { + var url = BaseClass.Build29WxObjString("order/wx_get_order_item_ext_info"); + var data = UserLogin.GetSignaturex(); + data += "&wx_order_id_in=" + wxOrderIdIn; + data += "&wx_sku_id_in=" + wxSkuIdIn; + var html = GetHtml.PostUtf8(url, data); + if (!html.Contains("\"code\":200")) + html = GetHtml.PostUtf8(url, data); + if (!html.Contains("\"code\":200")) + { + BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + BaseRegex.GetStringJson(ref html, "msg"), "Error", 5); + continue; + } + existOrderExtList = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(html)["data"].ParseToString()); + } + + // 先把已经物流异常但未查询到的订单改为正常 + var waybillExceptionOrders = GlobalConfig.lstOrder.Where(o => o.shop_id.ParseToString() == shop.ErpShopId && o.orderItems.Any(oi => oi.ShopWaybillExceptionStr == "物流异常")).ToList(); + foreach (var waybillExceptionOrder in waybillExceptionOrders) + { + if (!existOrderExtList.Any(oExt => oExt.order_id == waybillExceptionOrder.order_id)) + { + foreach (var orderItem in waybillExceptionOrder.orderItems) + { + if (orderItem.OrderItemExt != null) + { + orderItem.OrderItemExt.abnormal_text = ""; + orderItem.ShopWaybillExceptionStr = ""; + } + } + + // 更新显示 + Dispatcher.Invoke(new Action(() => + { + foreach (var oi in waybillExceptionOrder.orderItems) + oi.SetOrderItemTextProperty(); + waybillExceptionOrder.SetOrderTextProperty(); + + MainWindow.Instance.orderListCtrl.FilterOneOrder(waybillExceptionOrder, true); + })); + + var orderId = waybillExceptionOrder.order_id; + order_item_ext orderExt = new order_item_ext() { order_id = orderId, shop_app_id = shop.ShopId, shop_name = shop.ShopAccount, abnormal_text = "物流异常" }; + + var url1 = BaseClass.Build29WxObjString("order/wx_mdy_order_item_ext_info"); + var data1 = UserLogin.GetSignaturex(); + data1 += "&wx_order_id=" + orderExt.order_id; + data1 += "&abnormal_text="; + var html1 = GetHtml.PostUtf8(url1, data1); + if (string.IsNullOrEmpty(html1) || !html1.Contains("\"code\":200")) + html1 = GetHtml.PostUtf8(url1, data1); + } + } + + foreach (var od in jObj["orderList"]) + { + var oStr = JsonHelper.SerializeObject(od); + + //var orderId = o["commonInfo"]["orderId"].ParseToString(); + var orderId = BaseRegex.GetStringJson(ref oStr, "orderId"); + + if (orderId == "3737914129536416512") + { + + } + var skuId = BaseRegex.GetStringJson(ref oStr, "skuId"); + + // 存在状态完全一样的订单异常信息就跳过 + order_item_ext orderExt = new order_item_ext() { order_id = orderId, sku_id = skuId, shop_app_id = shop.ShopId, shop_name = shop.ShopAccount, abnormal_text = "物流异常" }; + + if (!existOrderExtList.Any(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id && eoe.abnormal_text == orderExt.abnormal_text)) + { + var url1 = BaseClass.Build29WxObjString("order/wx_add_order_item_ext_info"); + if (existOrderExtList.Any(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id)) + url1 = BaseClass.Build29WxObjString("order/wx_mdy_order_item_ext_info"); + var data1 = UserLogin.GetSignaturex(); + data1 += "&wx_order_id=" + orderExt.order_id; + data1 += "&wx_sku_id=" + orderExt.sku_id; + data1 += "&shop_app_id=" + orderExt.shop_app_id; + data1 += "&shop_name=" + orderExt.shop_name; + data1 += "&abnormal_text=" + orderExt.abnormal_text; + var html1 = GetHtml.PostUtf8(url1, data1); + if (string.IsNullOrEmpty(html1) || !html1.Contains("\"code\":200")) + { + BaseMsgWindow.ShowAnimateMessage("订单扩展信息写入失败 " + BaseRegex.GetStringJson(ref html1, "msg"), "Error", 5); + break; + } + } + else + { + orderExt = existOrderExtList.FirstOrDefault(eoe => eoe.order_id == orderExt.order_id && eoe.sku_id == orderExt.sku_id && eoe.abnormal_text == orderExt.abnormal_text); + } + + // 更新订单附加信息 + if (orderExt.order_id == "3737845591459133952") + { + + } + var order = GlobalConfig.lstOrder.FirstOrDefault(o => o.order_id == orderExt.order_id); + if (order != null) + { + var orderItem = order.orderItems.FirstOrDefault(oi => oi.sku_id == orderExt.sku_id); + if (orderItem != null) + { + orderItem.OrderItemExt = orderExt; + Dispatcher.Invoke(new Action(() => + { + foreach (var oi in order.orderItems) + oi.SetOrderItemTextProperty(); + order.SetOrderTextProperty(); + + MainWindow.Instance.orderListCtrl.FilterOneOrder(order, true); + })); + } + } + } + } + catch (Exception ex) + { + BaseMsgWindow.ShowAnimateMessage("订单扩展信息查询失败 " + ex.Message, "Error", 5); + } + } + } + } + + public void disPlayOrderTabOrderCount() + { + + var orders = string.Join("','", GlobalConfig.lstFilterOrder.Select(o => o.order_id)); + var orders1 = string.Join(",", GlobalConfig.lstFilterOrder.Select(o => o.order_id)); + + + + var tabItems = this.tabOrderType.Items.Cast(); + var tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待采购")); + var userOrderLst = GlobalConfig.lstOrder.Where(o => GlobalConfig.ShopLoginList.Any(s => s.ErpShopId.ParseToLong() == o.shop_id)).ToList(); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待采购"))); + //tabItem.Header = "待采购(" + userOrderLst.Where(r => r.IsWaitBuy).Count() + "|" + filteLstTmp.Where(r => r.IsWaitBuy).Count() + ")"; + tabItem.Header = "待采购(" + userOrderLst.Where(r => r.IsWaitBuy).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待发货")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待发货"))); + //tabItem.Header = "待发货(" + userOrderLst.Where(r => r.IsWaitSend).Count() + "|" + filteLstTmp.Where(r => r.IsWaitSend).Count() + ")"; + tabItem.Header = "待发货(" + userOrderLst.Where(r => r.IsWaitSend).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("待收货")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "待收货"))); + //tabItem.Header = "待收货(" + userOrderLst.Where(r => r.IsWaitReceive).Count() + "|" + filteLstTmp.Where(r => r.IsWaitReceive).Count() + ")"; + tabItem.Header = "待收货(" + userOrderLst.Where(r => r.IsWaitReceive).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("售后完成单")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "售后完成单"))); + //tabItem.Header = "售后完成单(" + userOrderLst.Where(r => r.IsRefund).Count() + "|" + filteLstTmp.Where(r => r.IsRefund).Count() + ")"; + tabItem.Header = "售后完成单(" + userOrderLst.Where(r => r.IsRefund).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("需处理售后单")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "需处理售后单"))); + //tabItem.Header = "需处理售后单(" + userOrderLst.Where(r => r.IsEmergency).Count() + "|" + filteLstTmp.Where(r => r.IsEmergency).Count() + ")"; + tabItem.Header = "需处理售后单(" + userOrderLst.Where(r => r.IsEmergency).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("异常单")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "异常单"))); + //tabItem.Header = "异常单(" + userOrderLst.Where(r => r.isAbnormal).Count() + "|" + filteLstTmp.Where(r => r.isAbnormal).Count() + ")"; + tabItem.Header = "异常单(" + userOrderLst.Where(r => r.isAbnormal).Count() + "|" + filteLstTmp.Count() + ")"; + } + tabItem = tabItems.FirstOrDefault(t => t.Header.ParseToString().StartsWith("全部订单")); + if (tabItem != null) + { + var filteLstTmp = new ObservableCollection(userOrderLst.Where(o => !GetOrderIsFilter(o, "全部订单"))); + tabItem.Header = "全部订单(" + userOrderLst.Count() + "|" + filteLstTmp.Count() + ")"; + } + //txtMsg.Text = "当前条件共计 " + GlobalConfig.lstFilterOrder.Count() + " 条订单记录"; + } + + public void FilterOrders() + { + try + { + if (this.lbxOrder == null) + return; + + Dispatcher.Invoke(new Action(() => + { + if (GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单")) + GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.RefundDatetime)); + else if (GlobalConfig.OrderTabHeaderStr.StartsWith("异常单")) + GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.update_time)); + else + GlobalConfig.lstFilterOrder = new ObservableCollectionThreadSafe(GlobalConfig.lstOrder.Where(o => !GetOrderIsFilter(o)).OrderByDescending(o => o.create_time)); + this.lbxOrder.ItemsSource = GlobalConfig.lstFilterOrder; + + disPlayOrderTabOrderCount(); + })); + } + catch (Exception ex) + { + } + } + + public void FilterOneOrder(oms_wei_order_info order, bool updateTabCount = false) + { + var orderExist = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.order_id == order.order_id); + while (orderExist != null) + { + GlobalConfig.lstFilterOrder.Remove(orderExist); + orderExist = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.order_id == order.order_id); + } + + if (!GetOrderIsFilter(order)) + { + oms_wei_order_info nextOrder = null; + if (GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单")) + nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.RefundDatetime < order.RefundDatetime); + else if (GlobalConfig.OrderTabHeaderStr.StartsWith("异常单")) + nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.update_time < order.update_time); + else + nextOrder = GlobalConfig.lstFilterOrder.FirstOrDefault(o => o.create_time < order.create_time); + + if (nextOrder != null) + { + GlobalConfig.lstFilterOrder.Insert(GlobalConfig.lstFilterOrder.IndexOf(nextOrder), order); + } + else + { + GlobalConfig.lstFilterOrder.Add(order); + } + } + + if (updateTabCount) + { + Dispatcher.Invoke(new Action(() => + { + disPlayOrderTabOrderCount(); + })); + } + } + + bool GetOrderIsFilter(oms_wei_order_info order, string filterHeadStr = null) + { + if (order == null) + return true; + + if (!GlobalConfig.ShopLoginList.Any(s => s.ErpShopId.ParseToLong() == order.shop_id)) + return true; + + if (!string.IsNullOrEmpty(strSearchAll)) + { + bool isFilter = true; + // 买手物流 + if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.express_1_waybill.ParseToString().Contains(strSearchAll)))) + isFilter = false; + // 仓库物流 + else if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.express_2_waybill.ParseToString().Contains(strSearchAll)))) + isFilter = false; + // 退货物流 + else if (order.refundItems.Any(ri => ri.return_waybill_id.ParseToString().Contains(strSearchAll))) + isFilter = false; + // 退货单号 + else if (order.refundItems.Any(ri => ri.after_sale_order_id.ParseToString().Contains(strSearchAll))) + isFilter = false; + // 仓库采购单号 + else if (order.orderItems.Any(oi => oi.ItemTransitOrders.Any(t => t.out_order_no.ParseToString().Contains(strSearchAll)))) + isFilter = false; + // 仓库退款单号 + else if (order.orderItems.Any(oi => oi.ItemTransitOrderAfterSales.Any(t => t.refund_waybill.ParseToString().Contains(strSearchAll)))) + isFilter = false; + if (isFilter) + return true; + } + + // 过滤店铺名称 + if (!string.IsNullOrEmpty(txtShopName.Text.Trim()) && !(";" + txtShopName.Text.Trim() + ";").Contains(";" + order.shop_name + ";")) + { + return true; + } + // 过滤商品名称 + if (!string.IsNullOrEmpty(txtGoodsName.Text.Trim()) && !order.orderItems.Any(it => it.title.Contains(txtGoodsName.Text.Trim()))) + { + return true; + } + // 过滤id + if (!string.IsNullOrEmpty(txtOrderId.Text.Trim()) && !(";" + txtOrderId.Text.Trim().Replace(' ', ';').Replace(',', ';') + ";").Contains(";" + order.order_id + ";")) + { + return true; + } + // 过滤多子单订单 + if (chkNoMultiOrder.IsChecked.ParseToBool() && order.orderItems.Count() > 1) + { + return true; + } + // 过滤非中转仓订单 + if (chkOnlyWarehouseOrder.IsChecked.ParseToBool() && !order.IsWaitBuy && + order.orderItems.All(oi => oi.ItemTransitOrderWithVoid == null) && order.orderItems.All(oi => oi.OrderItemExt?.buy_type != "直发")) + { + return true; + } + // 过滤备注 + if (chkFilterRemark.IsChecked == true && !string.IsNullOrEmpty(txtFilterRemark.Text)) + { + if (cmbFilterRemark.SelectedItem is ComboBoxItem comboBoxItem) + { + if (comboBoxItem.Content is TextBlock tb) + { + if (tb.Text == "店铺备注包含" && !lstFilterText.Contains(order.shop_remark)) + return true; + else if (tb.Text == "软件备注包含" && !lstFilterText.Contains(order.soft_remark)) + return true; + else if (tb.Text == "店铺备注不含" && lstFilterText.Contains(order.shop_remark)) + return true; + else if (tb.Text == "软件备注不含" && lstFilterText.Contains(order.soft_remark)) + return true; + } + } + } + if (!chkDirectOrder.IsChecked == true && order.OrderBuyType == "直发") + return true; + if (!chkTransitOrder.IsChecked == true && order.OrderBuyType == "云仓") + return true; + if (!chkTransitChangeDirectOrder.IsChecked == true && order.OrderBuyType == "云改直") + return true; + + // 过滤时间范围 + if (cmbTimeType.SelectedItem is ComboBoxItem cbi) + { + if (cbi.Content is TextBlock tb) + { + if (tb.Text == "更新时间") + { + if (order.UpdateDateTime < dateStart.SelectedDate.Value) + return true; + if (chkEndDate.IsChecked == true && order.UpdateDateTime > dateEnd.SelectedDate.Value) + return true; + } + if (tb.Text == "创建时间") + { + if (order.CreateDateTime < dateStart.SelectedDate.Value) + return true; + if (chkEndDate.IsChecked == true && order.CreateDateTime > dateEnd.SelectedDate.Value) + return true; + } + } + } + + // 过滤标签页 + if (filterHeadStr.IsNullOrEmpty()) + filterHeadStr = GlobalConfig.OrderTabHeaderStr; + + if (filterHeadStr.StartsWith("全部订单")) + { + return false; + } + if (filterHeadStr.StartsWith("待采购")) + { + if (!order.IsWaitBuy) + return true; + //if (order.IsRefund) + // return true; + if (chkMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap == null)) + return true; + if (chkNoMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap != null)) + return true; + } + if (filterHeadStr.StartsWith("待发货") && !order.IsWaitSend) + { + return true; + } + if (filterHeadStr.StartsWith("待收货")) + { + if (!order.IsWaitReceive) + return true; + + if (chkWaitReceiveBuyerIsNotSend.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手未发货")) + return true; + if (chkWaitReceiveBuyerIsSend.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手已发货")) + return true; + if (chkWaitReceiveBuyerIsInWarehouse.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr != "买手已送达")) + return true; + + if (chkWaitReceiveWarehouseIsNotPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转待取号")) + return true; + if (chkWaitReceiveWarehouseIsPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转提前取号" && oi.WarehouseWaybillStatusStr != "中转已取号" && oi.WarehouseWaybillStatusStr != "中转已入库")) + return true; + if (chkWaitReceiveWarehouseIsSendOut.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转已出库")) + return true; + } + if (filterHeadStr.StartsWith("异常单") && !order.isAbnormal) + { + return true; + } + if (filterHeadStr.StartsWith("需处理售后单")) + { + if (!order.IsEmergency) + return true; + + if (chkEmergencyBuyerWaitAfterSale.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("需手动申请售后"))) + return true; + else if (chkEmergencyBuyerWaitSendBack.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("待用户发货"))) + return true; + else if (chkEmergencyBuyerWaitSupplierRefund.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("商家处理中"))) + return true; + else if (chkEmergencyBuyerRefundFail.IsChecked == true && order.orderItems.All(oi => !oi.TransitRefundStatusStr.Contains("退款失败"))) + return true; + + if (chkEmergencyShopCustomRefundHasWaybill.IsChecked == true && order.orderItems.Any(oi => oi.ReturnWaybillIdStr.IsNullOrEmpty())) + return true; + else if (chkEmergencyShopCustomRefundNoWaybill.IsChecked == true && order.orderItems.All(oi => !oi.ReturnWaybillIdStr.IsNullOrEmpty())) + return true; + + if (order.order_id == "3737838342857424896") + { + + } + + // 退款完成 + if (chkEmergencyShopAfterSaleSuccess.IsChecked == true && order.DeliveryDeadlineDtatusStr != "退款完成") + return true; + // 退款中 + else if (chkEmergencyShopInAfterSale.IsChecked == true && (order.DeliveryDeadlineDtatusStr != "退款中")) + return true; + + //// 退货退款 + //if (chkEmergencyReturn.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.RefundStatusStr).ParseToString().Contains("退货")) + // return true; + //// 仅退款 + //else if (chkEmergencyRefund.IsChecked == true && (order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.RefundStatusStr).ParseToString().Contains("退货")) + // return true; + // 退货退款 + if (chkEmergencyReturn.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.type).ParseToString().Contains("RETURN")) + return true; + // 仅退款 + else if (chkEmergencyRefund.IsChecked == true && !(order.refundItems.OrderByDescending(or => or.create_time).FirstOrDefault()?.type).ParseToString().Contains("REFUND")) + return true; + } + if (filterHeadStr.StartsWith("售后完成单")) + { + if (!order.IsRefund) + return true; + + if (chkAftersaleIsSend.IsChecked == true && !order.DeliveryDeadlineStr.Contains("已发货")) + return true; + else if (chkAftersaleIsNotSend.IsChecked == true && !order.DeliveryDeadlineStr.Contains("未发货")) + return true; + + if (chkAftersaleBuyerIsBuy.IsChecked == true && order.orderItems.All(oi => oi.BuyerTransitWaybillStatusStr.IsNullOrEmpty())) + return true; + if (chkAftersaleWarehouseIsNotPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转待取号" && !oi.WarehouseWaybillStatusStr.Contains("取号异常"))) + return true; + if (chkAftersaleWarehouseIsPickup.IsChecked == true && order.orderItems.All(oi => oi.WarehouseWaybillStatusStr != "中转提前取号" && oi.WarehouseWaybillStatusStr != "中转已取号" && + oi.WarehouseWaybillStatusStr != "中转已入库" && oi.WarehouseWaybillStatusStr != "中转已出库")) + return true; + } + + return false; + } + + private void btnMultiShopChoice_Click(object sender, RoutedEventArgs e) + { + MainWindow.Instance.shopListCtrl.IsVisibleChanged -= ShopListCtrl_IsVisibleChanged; + MainWindow.Instance.shopListCtrl.Visibility = Visibility.Visible; + MainWindow.Instance.shopListCtrl.IsVisibleChanged += ShopListCtrl_IsVisibleChanged; + } + + private void ShopListCtrl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (!MainWindow.Instance.shopListCtrl.IsVisible && MainWindow.Instance.shopListCtrl.IsAcceptChoiceShop == true) + { + var list = MainWindow.Instance.shopListCtrl.lbxShop.ItemsSource as ObservableCollection; + if (list != null) + this.txtShopName.Text = string.Join(";", list.Where(s => s.IsSelect == true).Select(s => s.ShopAccount)); + + FilterOrders(); + + } + MainWindow.Instance.shopListCtrl.IsVisibleChanged -= ShopListCtrl_IsVisibleChanged; + } + + private void btnSearch_Click(object sender, RoutedEventArgs e) + { + this.btnSearch.IsEnabled = false; + + Task.Run(() => + { + Thread.Sleep(5000); + Dispatcher.Invoke(new Action(() => { this.btnSearch.IsEnabled = true; })); + }); + Task.Run(() => + { + while (!MainWindow.Instance.hasReadShop) + { + Thread.Sleep(100); + } + + GetOrder(); + nextGetOrderTime = DateTime.Now.AddMinutes(25); + }); + } + + private void btnFilter_Click(object sender, RoutedEventArgs e) + { + FilterOrders(); + } + + private void dgOrderHeader_ColumnDisplayIndexChanged(object sender, DataGridColumnEventArgs e) + { + } + + private void dgOrderHeader_LayoutUpdated(object sender, EventArgs e) + { + foreach (var item in lbxOrder.Items) + { + ListBoxItem container = (ListBoxItem)lbxOrder.ItemContainerGenerator.ContainerFromIndex(lbxOrder.Items.IndexOf(item)); + if (container != null) + { + // 找到grid控件并修改列大小 + var grids = UiCtrlUtils.FindVisualChildAll(container, "gridListItem"); + foreach (var grid in grids) + { + for (int i = 0; i < grid.ColumnDefinitions.Count; i++) + { + if (dgOrderHeader.Columns.Count > i) + { + // 最后一列因滚动条变窄,已在表头补偿 + if (i == grid.ColumnDefinitions.Count - 1) + grid.ColumnDefinitions[i].Width = new GridLength(Math.Max(0, dgOrderHeader.Columns[i].ActualWidth - 0)); + else + grid.ColumnDefinitions[i].Width = new GridLength(dgOrderHeader.Columns[i].ActualWidth); + } + } + } + } + } + } + + private void ChkSelectAll_Checked(object sender, RoutedEventArgs e) + { + foreach (var order in GlobalConfig.lstOrder.ToList()) + { + order.IsSelect = true; + } + } + + private void ChkSelectAll_Unchecked(object sender, RoutedEventArgs e) + { + foreach (var order in GlobalConfig.lstOrder.ToList()) + { + order.IsSelect = false; + } + } + + private void ChkSelectAll_Loaded(object sender, RoutedEventArgs e) + { + chkSelectAll = sender as CheckBox; + } + + public void UpdateChkAllBox() + { + chkSelectAll.Checked -= ChkSelectAll_Checked; + chkSelectAll.Unchecked -= ChkSelectAll_Unchecked; + if (GlobalConfig.lstFilterOrder.All(s => s.IsSelect)) + chkSelectAll.IsChecked = true; + else if (GlobalConfig.lstFilterOrder.All(s => !s.IsSelect)) + chkSelectAll.IsChecked = false; + else + chkSelectAll.IsChecked = null; + chkSelectAll.Checked += ChkSelectAll_Checked; + chkSelectAll.Unchecked += ChkSelectAll_Unchecked; + } + + private void txtSearchAll_TextChanged(object sender, TextChangedEventArgs e) + { + strSearchAll = txtSearchAll.Text.Trim(); + } + + private void tabOrderType_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + GlobalConfig.OrderTabHeaderStr = ((tabOrderType.SelectedItem as TabItem)?.Header).ParseToString(); + FilterOrders(); + } + + private void chkMapProdOnly_Click(object sender, RoutedEventArgs e) + { + if (chkMapProdOnly.IsChecked == true) + chkNoMapProdOnly.IsChecked = false; + FilterOrders(); + } + private void chkNoMapProdOnly_Click(object sender, RoutedEventArgs e) + { + if (chkNoMapProdOnly.IsChecked == true) + chkMapProdOnly.IsChecked = false; + FilterOrders(); + } + + public static string addNotes(ShopLoginInfo shop, string orderId, string notes, int tagColor = -1) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/common/cgi/batchMerchantNotes?token=&lang=zh_CN"; + string body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotes"":""" + notes + @""",""merchantNotesTagColor"":" + tagColor + @"}]}"; + if (tagColor == -1) + body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotes"":""" + notes + @"""}]}"; + + string refere = "https://store.weixin.qq.com/shop/order/list"; + var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, refere); + return result; + } + public static string addNotesFlag(ShopLoginInfo shop, string orderId, string notes, int tagColor = 0) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/common/cgi/batchMerchantNotes?token=&lang=zh_CN"; + string body = @"{""items"":[{""orderId"":""" + orderId + @""",""merchantNotesTagColor"":" + tagColor + @"}]}"; + + string refere = "https://store.weixin.qq.com/shop/order/list"; + var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, refere); + return result; + } + + private void chkAutoReadBuyerOrder_CheckedChanged(object sender, RoutedEventArgs e) + { + IsAutoReadOrder = chkAutoReadOrder.IsChecked == true; + } + + private void TabItem_SizeChanged(object sender, SizeChangedEventArgs e) + { + var tabItems = this.tabOrderType.Items.Cast(); + wpFilter.Margin = new Thickness(tabItems.Sum(t => t.ActualWidth) + 18, wpFilter.Margin.Top, wpFilter.Margin.Right, wpFilter.Margin.Bottom); + + Point position1 = txtFilterRemark.TranslatePoint(new Point(0, 0), gridOrderListCtrl); + Point position2 = txtFilterRemark.TranslatePoint(new Point(0, 0), wpFilter); + borderFilterRemarkSelect.Margin = new Thickness(tabItems.Sum(t => t.ActualWidth) + 16 + position2.X, position1.Y + txtFilterRemark.ActualHeight + 3, 0, 0); + } + + private void chkSubFilterSonditions_Checked(object sender, RoutedEventArgs e) + { + FilterOrders(); + } + + /// + /// 待处理订单买手售后状态过滤 + /// + private void chkEmergencyBuyerAfterSale_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkEmergencyBuyerWaitAfterSale.IsChecked = chkEmergencyBuyerWaitSendBack.IsChecked = chkEmergencyBuyerWaitSupplierRefund.IsChecked = chkEmergencyBuyerRefundFail.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + /// + /// 待处理订单店铺买家售后物流状态过滤 + /// + private void chkEmergencyShopCustomRefundWaybill_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkEmergencyShopCustomRefundHasWaybill.IsChecked = chkEmergencyShopCustomRefundNoWaybill.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + /// + /// 待处理订单店铺售后状态过滤 + /// + /// + /// + private void chkEmergencyShopAfterSale_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkEmergencyShopAfterSaleSuccess.IsChecked = chkEmergencyShopInAfterSale.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + /// + /// 待收货 买手发货状态 + /// + /// + /// + private void chkWaitReceiveBuyerStatus_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkWaitReceiveBuyerIsSend.IsChecked = chkWaitReceiveBuyerIsNotSend.IsChecked = chkWaitReceiveBuyerIsInWarehouse.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private void chkWaitReceiveWarehouseStatus_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkWaitReceiveWarehouseIsNotPickup.IsChecked = chkWaitReceiveWarehouseIsPickup.IsChecked = chkWaitReceiveWarehouseIsSendOut.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private void chkAftersaleSendStatus_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkAftersaleIsSend.IsChecked = chkAftersaleIsNotSend.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private void chkAftersaleBuyerStatus_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkAftersaleBuyerIsBuy.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private void chkAftersaleWarehouseStatus_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkAftersaleWarehouseIsPickup.IsChecked = chkAftersaleWarehouseIsNotPickup.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private async void btnBatchRefund_Click(object sender, RoutedEventArgs e) + { + var orders = GlobalConfig.lstFilterOrder.Where(o => o.IsSelect == true); + List tmpOrderItems = new List(); + foreach (var order in orders) + { + foreach (var orderItem in order.orderItems) + { + if (orderItem.ItemOrder?.OrderBuyType == "云仓") + if (orderItem != null && !string.IsNullOrEmpty(orderItem.ItemTransitOrderWithVoid?.out_order_no) && orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() && + (orderItem.TransitRefundStatusStr.Contains("需手动申请售后") || orderItem.TransitRefundStatusStr.Contains("待用户发货"))) + tmpOrderItems.Add(orderItem); + + if (orderItem.ItemOrder?.OrderBuyType == "直发" || orderItem.ItemOrder?.OrderBuyType == "云改直") + if (orderItem != null && !string.IsNullOrEmpty(orderItem.ItemOrder?.out_order_no) && orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() && + ((orderItem.ItemOrder?.out_after_sale_status).ParseToString().Contains("需手动申请售后") || (orderItem.ItemOrder?.out_after_sale_status).ParseToString().Contains("待用户发货"))) + tmpOrderItems.Add(orderItem); + } + } + if (tmpOrderItems.Count == 0) + { + BaseMsgWindow.ShowAnimateMessage("未选中需要需手动申请售后的订单", "Error", 3); + return; + } + + await Task.Run(() => + { + int index = 0; + foreach (var tmpOrderItem in tmpOrderItems) + { + Dispatcher.Invoke(new Action(() => { txtMsg.Text = "正在处理买手退货,总计:" + tmpOrderItems.Count + ",已处理:" + index++; })); + + var pddOrderSn = ""; + if (tmpOrderItem.ItemOrder?.OrderBuyType == "云仓") + pddOrderSn = tmpOrderItem.ItemTransitOrderWithVoid?.out_order_no; + else if (tmpOrderItem.ItemOrder?.OrderBuyType == "直发" || tmpOrderItem.ItemOrder?.OrderBuyType == "云改直") + pddOrderSn = tmpOrderItem.ItemOrder?.out_order_no; + + BuyerLoginInfo buyer = null; + if (tmpOrderItem.ItemOrder?.OrderBuyType == "云仓") + Dispatcher.Invoke(new Action(() => { buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(b => b.buyer_nick == tmpOrderItem.ItemTransitOrderWithVoid.buyer_account); })); + else if (tmpOrderItem.ItemOrder?.OrderBuyType == "直发" || tmpOrderItem.ItemOrder?.OrderBuyType == "云改直") + Dispatcher.Invoke(new Action(() => { buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(b => b.buyer_nick == tmpOrderItem.ItemOrder?.out_account); })); + if (buyer == null) + { + BaseMsgWindow.ShowAnimateMessage("买手单:" + pddOrderSn + " 买手不在当前账户下,跳过", "Error", 3); + } + + // 检测一下买手号 + if (buyer.status == "在线") + { + PddMobileLoginForm.CheckBuyerLogin(buyer); + } + if (buyer.status != "在线") + { + BaseMsgWindow.ShowAnimateMessage("买手单:" + pddOrderSn + " 买手号掉线,跳过", "Error", 3); + continue; + } + + MainWindow.Instance.orderListCtrl.GetOrder(tmpOrderItem.order_id); + + oms_wei_order_info order = null; + Dispatcher.Invoke(new Action(() => { order = GlobalConfig.lstOrder.FirstOrDefault(oi => oi.order_id == tmpOrderItem.order_id); })); + if (order == null) + continue; + oms_wei_order_item_info orderItem = null; + Dispatcher.Invoke(new Action(() => { orderItem = order.orderItems.FirstOrDefault(oi => oi.order_id == tmpOrderItem.order_id && oi.sku_id == tmpOrderItem.sku_id); })); + if (orderItem == null) + continue; + + string refundStatusStr = ""; + string outOrderNo = ""; + if (order.OrderBuyType == "云仓") + { + outOrderNo = (orderItem.ItemTransitOrderWithVoid?.out_order_no).ParseToString(); + refundStatusStr = (orderItem?.TransitRefundStatusStr).ParseToString(); + } + else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") + { + outOrderNo = (order.out_order_no).ParseToString(); + refundStatusStr = (order.out_after_sale_status).ParseToString(); + } + if (orderItem == null || string.IsNullOrEmpty(outOrderNo) || !orderItem.PddBuyerAfterSaleAddressStr.IsNullOrEmpty() || + (!refundStatusStr.Contains("需手动申请售后") && !refundStatusStr.Contains("待用户发货"))) + continue; + + PddBuyerOrderLiteInfo pddBuyerOrderLiteInfo = new PddBuyerOrderLiteInfo(); + pddBuyerOrderLiteInfo.ShopOrderIdStr = orderItem.order_id; + pddBuyerOrderLiteInfo.order_sn = pddOrderSn; + + if (order.OrderBuyType == "云仓") + pddBuyerOrderLiteInfo.order_amount = orderItem.ItemTransitOrderWithVoid?.out_amount; + else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") + pddBuyerOrderLiteInfo.order_amount = order.out_amount; + + order_pdd_buyer_after_sale pddBuyerAfterSale = new order_pdd_buyer_after_sale() + { + wx_order_id = orderItem.order_id, + wx_sku_id = orderItem.sku_id, + TransitCreateTime = orderItem.ItemTransitOrderWithVoid?.created_time == null ? DateTime.MinValue : orderItem.ItemTransitOrderWithVoid.created_time, + pre_allocated_order_no = orderItem.ItemTransitOrderWithVoid?.pre_allocated_order_no, + buyerId = buyer.buyer_id, + buyerName = buyer.buyer_name, + orderSn = pddOrderSn, + orderAmount = pddBuyerOrderLiteInfo.order_amount, + refundAmount = pddBuyerOrderLiteInfo.order_amount, + orderDate = pddOrderSn.ParseToString().Split('-').FirstOrDefault().ParseToInt(), + }; + if (orderItem.PddBuyerAfterSale != null) + pddBuyerAfterSale = orderItem.PddBuyerAfterSale; + + bool isSimPddAfterSaleSuccess = true; + if (refundStatusStr.Contains("需手动申请售后")) + { + isSimPddAfterSaleSuccess = false; + // pddBuyerAfterSale作为参数传入,获取退货退款金额 + var rst = PDDBuyerOrderReadCtrl.SimPddAfterSaleWithReturnGoods(buyer, pddBuyerOrderLiteInfo, pddBuyerAfterSale); + + var afterSalesStatus = BaseRegex.GetIntJson(ref rst, "after_sales_status"); + var afterSalesId = BaseRegex.GetLongJson(ref rst, "after_sales_id"); + pddBuyerAfterSale.afterSalesId = afterSalesId.ToString(); + pddBuyerAfterSale.statusText = "待用户发货"; + + if (afterSalesStatus == 0) + { + var msg = BaseRegex.GetStringJsonByRegs(ref rst, rst, "msg", "err_msg", "error_msg", "errorMsg", "errMsg"); + BaseMsgWindow.ShowAnimateMessage(msg, "Error", 3); + Thread.Sleep(5000); + } + else + { + if (order.OrderBuyType == "云仓") + { + var e57Rst = PDDBuyerOrderReadCtrl.SendTransitAfterSaleE57(pddBuyerOrderLiteInfo, "待用户发货"); + if (e57Rst.Contains("0") || e57Rst.Contains("200")) + { + BaseMsgWindow.ShowAnimateMessage("买手退款信息更新成功", "Msg", 3); + isSimPddAfterSaleSuccess = true; + Thread.Sleep(Randomx.GetId(10000, 12001)); + } + else + { + var msg = BaseRegex.GetStringXml(ref e57Rst, "msg"); + BaseMsgWindow.ShowAnimateMessage("买手退款信息更新失败,服务端返回 " + msg, "Error", 3); + } + } + else if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") + { + var e57Rst = PDDBuyerOrderReadCtrl.SendAfterSaleE57NeedManual(pddBuyerOrderLiteInfo, "待用户发货"); + if (e57Rst.Contains("0") || e57Rst.Contains("200")) + { + BaseMsgWindow.ShowAnimateMessage("买手退款信息更新成功", "Msg", 3); + isSimPddAfterSaleSuccess = true; + Thread.Sleep(Randomx.GetId(10000, 12001)); + } + else + { + var msg = BaseRegex.GetStringXml(ref e57Rst, "msg"); + BaseMsgWindow.ShowAnimateMessage("买手退款信息更新失败,服务端返回 " + msg, "Error", 3); + } + } + } + } + if (isSimPddAfterSaleSuccess) + { + var addressRst = YkdBuyerOrder.GetAfterSaleAddress(buyer, pddOrderSn, pddBuyerAfterSale.afterSalesId.ParseToString()); + var merchantReceiverInfo = BaseRegex.GetOneJsonObjStrJson(ref addressRst, "merchantReceiverInfo"); + string address = ""; + if (merchantReceiverInfo != null) + { + address = BaseRegex.GetStringJson(ref merchantReceiverInfo, "copyValue"); + } + + var url3 = BaseClass.Build29WxObjString("order/wx_add_mdy_buyer_after_sale"); + var data3 = UserLogin.GetSignaturex(); + data3 += "&wx_order_id=" + pddBuyerAfterSale.wx_order_id; + data3 += "&wx_sku_id=" + pddBuyerAfterSale.wx_sku_id; + if (pddBuyerAfterSale.TransitCreateTime != DateTime.MinValue) + data3 += "&TransitCreateTime=" + pddBuyerAfterSale.TransitCreateTime.ToString("yyyy-MM-dd HH:mm:ss"); + data3 += "&pre_allocated_order_no=" + pddBuyerAfterSale.pre_allocated_order_no; + data3 += "&buyerId=" + pddBuyerAfterSale.buyerId; + data3 += "&buyerName=" + pddBuyerAfterSale.buyerName; + data3 += "&orderAmount=" + pddBuyerAfterSale.orderAmount; + data3 += "&orderSn=" + pddBuyerAfterSale.orderSn; + data3 += "&afterSalesId=" + pddBuyerAfterSale.afterSalesId; + data3 += "&orderDate=" + pddBuyerAfterSale.orderDate; + //data3 += "&TransitPickupTime=" + pddBuyerOrder.TransitPickupTime.ToString("yyyy-MM-dd HH:mm:ss"); + data3 += "&refundAmount=" + pddBuyerAfterSale.refundAmount; + data3 += "&statusText=" + pddBuyerAfterSale.statusText; + if (!string.IsNullOrEmpty(address)) + data3 += "&afterSaleAddress=" + address; + if (order.OrderBuyType == "直发" || order.OrderBuyType == "云改直") // 云仓 0 直发 1 + data3 += "&wx_send_type=1"; + //data3 += "&is_mdy=true"; + var html3 = GetHtml.PostUtf8(url3, data3); + if (string.IsNullOrEmpty(html3) || !html3.Contains("\"code\":200")) + html3 = GetHtml.PostUtf8(url3, data3); + } + MainWindow.Instance.orderListCtrl.GetOrder(tmpOrderItem.order_id); + Dispatcher.Invoke(new Action(() => { txtMsg.Text = "正在处理买手退货,总计:" + tmpOrderItems.Count + ",已处理:" + index; })); + } + + Dispatcher.Invoke(new Action(() => { txtMsg.Text = "买手退货处理完成,总计:" + tmpOrderItems.Count + ",已处理:" + index; })); + }); + } + + + public static string WxSimAbnormalOrderSeach(ShopLoginInfo shop, int page = 1) + { + string url = "https://store.weixin.qq.com/shop-faas/mmchannelstradeorder/list/cgi/orderSearch?token=&lang=zh_CN"; + string referer = "https://store.weixin.qq.com/shop/order/list"; + string body = @"{""isSearch"":false,""pageSize"":100,""overTime"":0,""pengingShipOneDay"":0,""pengingShipSixHour"":0,""expediteDelivery"":0," + + @"""stockoutPunishAlert"":0,""appointmentInstall"":0,""waybillStatus"":3,""isUserBookingDelivery"":0,""pageNum"":" + page + @",""onAftersaleOrderExist"":0,""orderStatus"":30}"; + var result = HttpResponseWXBLL.WeiXinPostResponse(shop, url, body, referer); + return result; + } + + private void btnMultiRemark_Click(object sender, RoutedEventArgs e) + { + wpFilterRemarkSelect.Children.Clear(); + List txts = txtFilterRemark.Text.Trim().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).ToList(); + var remarks = GlobalConfig.lstOrder.Select(o => o.shop_remark).Distinct().ToList(); + remarks.AddRange(GlobalConfig.lstOrder.Select(o => o.soft_remark).Distinct().ToList()); + remarks = remarks.Distinct().ToList(); + foreach (var remark in remarks) + { + if (string.IsNullOrWhiteSpace(remark)) + continue; + wpFilterRemarkSelect.Children.Add(new CheckBox() { Content = remark, IsChecked = txts.Contains(remark), Margin = new Thickness(2) }); + } + + Point position = txtFilterRemark.TranslatePoint(new Point(0, 0), gridOrderListCtrl); + borderFilterRemarkSelect.Margin = new Thickness(position.X - 2, position.Y + txtFilterRemark.ActualHeight + 3, 0, 0); + + if (borderFilterRemarkSelect.Visibility == Visibility.Visible) + borderFilterRemarkSelect.Visibility = Visibility.Collapsed; + else + borderFilterRemarkSelect.Visibility = Visibility.Visible; + } + + private void btnCancelFilterRemark_Click(object sender, RoutedEventArgs e) + { + borderFilterRemarkSelect.Visibility = Visibility.Collapsed; + } + + private void btnApplyFilterRemark_Click(object sender, RoutedEventArgs e) + { + string txt = ""; + foreach (UIElement element in wpFilterRemarkSelect.Children) + { + if (element is CheckBox box && box.IsChecked == true) + { + txt += box.Content.ParseToString() + ";"; + } + } + txtFilterRemark.Text = txt.TrimEnd(';'); + borderFilterRemarkSelect.Visibility = Visibility.Collapsed; + } + + private void btnFilterRemarkSelectAll_Click(object sender, RoutedEventArgs e) + { + foreach (UIElement element in wpFilterRemarkSelect.Children) + { + if (element is CheckBox box) + { + box.IsChecked = true; + } + } + } + + private void btnFilterRemarkUnSelectAll_Click(object sender, RoutedEventArgs e) + { + foreach (UIElement element in wpFilterRemarkSelect.Children) + { + if (element is CheckBox box) + { + box.IsChecked = false; + } + } + } + + private void txtFilterRemark_TextChanged(object sender, TextChangedEventArgs e) + { + lstFilterText = txtFilterRemark.Text.Trim().Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); + } + + private void chkEmergencyReturnOrRefund_Click(object sender, RoutedEventArgs e) + { + if (sender is CheckBox chk) + { + var isCheck = !chk.IsChecked.ParseToBool(); + chkEmergencyReturn.IsChecked = chkEmergencyRefund.IsChecked = false; + chk.IsChecked = !isCheck; + } + FilterOrders(); + } + + private void btn_ShowShopRevenueStatisticsView(object sender, RoutedEventArgs e) + { + new DialogWindow() + { + Title = "统计", + Content = new ShopRevenueStatisticsView(), + + }.ShowDialog(); + + } + } } diff --git a/29WxOrderWpf/Ctrls/Statistics/Charts/Chart.xaml b/29WxOrderWpf/Ctrls/Statistics/Charts/Chart.xaml new file mode 100644 index 0000000..dd17c18 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Charts/Chart.xaml @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml new file mode 100644 index 0000000..b4d7f4b --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml.cs b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml.cs new file mode 100644 index 0000000..a5b7a0b --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenueLineChart.xaml.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using _29WxOrderWpf.Ctrls.Statistics.Models; + +namespace _29WxOrderWpf.Ctrls.Statistics.Charts +{ + /// + /// 销售趋势折线图(轻量自绘,无第三方依赖)。 + /// 绑定 (IList<RevenueTrendPoint>)即可,数据变化时自动重绘。 + /// 鼠标悬停在数据点圆圈上时显示该点数据(日期 / 销售额 / 订单数)。 + /// + public partial class RevenueLineChart : UserControl + { + public static readonly DependencyProperty PointsProperty = + DependencyProperty.Register("Points", typeof(IList), typeof(RevenueLineChart), + new PropertyMetadata(null, OnRenderPropertyChanged)); + + /// 趋势数据点 + public IList Points + { + get { return (IList)GetValue(PointsProperty); } + set { SetValue(PointsProperty, value); } + } + + public static readonly DependencyProperty LineBrushProperty = + DependencyProperty.Register("LineBrush", typeof(Brush), typeof(RevenueLineChart), + new PropertyMetadata(new SolidColorBrush(Color.FromRgb(22, 119, 255)), OnRenderPropertyChanged)); + + /// 折线颜色 + public Brush LineBrush + { + get { return (Brush)GetValue(LineBrushProperty); } + set { SetValue(LineBrushProperty, value); } + } + + public static readonly DependencyProperty LineThicknessProperty = + DependencyProperty.Register("LineThickness", typeof(double), typeof(RevenueLineChart), + new PropertyMetadata(2d, OnRenderPropertyChanged)); + + /// 折线粗细 + public double LineThickness + { + get { return (double)GetValue(LineThicknessProperty); } + set { SetValue(LineThicknessProperty, value); } + } + + public RevenueLineChart() + { + InitializeComponent(); + Loaded += delegate { Render(); }; + SizeChanged += delegate { Render(); }; + } + + private static void OnRenderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((RevenueLineChart)d).Render(); + } + + private void Render() + { + LayoutRoot.Children.Clear(); + var points = Points; + if (points == null || points.Count == 0 || ActualWidth <= 20 || ActualHeight <= 20) + return; + + const double marginLeft = 52; + const double marginRight = 16; + const double marginTop = 18; + const double marginBottom = 30; + + double plotWidth = Math.Max(10, ActualWidth - marginLeft - marginRight); + double plotHeight = Math.Max(10, ActualHeight - marginTop - marginBottom); + + var values = points.Select(p => (double)p.SalesAmount).ToList(); + double maxValue = values.Max(); + if (maxValue <= 0) maxValue = 1; + double yMax = maxValue * 1.15; + + var lineBrush = LineBrush ?? new SolidColorBrush(Color.FromRgb(22, 119, 255)); + Color lineColor = lineBrush is SolidColorBrush ? ((SolidColorBrush)lineBrush).Color : Color.FromRgb(22, 119, 255); + + int count = points.Count; + double stepX = count <= 1 ? 0 : plotWidth / (count - 1); + + Func getX = i => marginLeft + stepX * i; + Func getY = v => marginTop + plotHeight * (1 - v / yMax); + + // 1. 横向网格线 + Y 轴刻度 + for (int k = 0; k <= 4; k++) + { + double ratio = k / 4.0; + double y = marginTop + plotHeight * ratio; + double v = yMax * (1 - ratio); + + LayoutRoot.Children.Add(new Line + { + X1 = marginLeft, Y1 = y, X2 = marginLeft + plotWidth, Y2 = y, + Stroke = new SolidColorBrush(Color.FromRgb(238, 238, 238)), + StrokeThickness = 1 + }); + + var yLabel = new TextBlock + { + Text = FormatValue(v), + FontSize = 11, + Foreground = new SolidColorBrush(Color.FromRgb(153, 153, 153)), + Width = marginLeft - 6, + TextAlignment = TextAlignment.Right + }; + LayoutRoot.Children.Add(yLabel); + Canvas.SetLeft(yLabel, 0); + Canvas.SetTop(yLabel, y - 8); + } + + // 2. 面积填充 + if (count > 1) + { + var figure = new PathFigure { StartPoint = new Point(getX(0), getY(values[0])) }; + for (int i = 1; i < count; i++) + figure.Segments.Add(new LineSegment { Point = new Point(getX(i), getY(values[i])) }); + figure.Segments.Add(new LineSegment { Point = new Point(getX(count - 1), marginTop + plotHeight) }); + figure.Segments.Add(new LineSegment { Point = new Point(getX(0), marginTop + plotHeight) }); + figure.IsClosed = true; + + var gradient = new LinearGradientBrush + { + StartPoint = new Point(0, 0), + EndPoint = new Point(0, 1) + }; + gradient.GradientStops.Add(new GradientStop(Color.FromArgb(64, lineColor.R, lineColor.G, lineColor.B), 0)); + gradient.GradientStops.Add(new GradientStop(Color.FromArgb(8, lineColor.R, lineColor.G, lineColor.B), 1)); + + LayoutRoot.Children.Add(new Path + { + Data = new PathGeometry { Figures = { figure } }, + Fill = gradient + }); + } + + // 3. 折线 + var polylinePoints = new PointCollection(); + for (int i = 0; i < count; i++) + polylinePoints.Add(new Point(getX(i), getY(values[i]))); + + LayoutRoot.Children.Add(new Polyline + { + Points = polylinePoints, + Stroke = lineBrush, + StrokeThickness = LineThickness, + StrokeLineJoin = PenLineJoin.Round + }); + + // 4. 数据点(点较多时省略;悬停显示该点数据) + if (count <= 31) + { + for (int i = 0; i < count; i++) + { + var dot = new Ellipse + { + ToolTip = CreateTooltip(points[i]) + }; + dot. Style = (Style)FindResource("LineEllipse"); + //// 悬停时高亮圆点 + //dot.MouseEnter += (s, e) => dot.Fill = lineBrush; + //dot.MouseLeave += (s, e) => dot.Fill = Brushes.White; + + LayoutRoot.Children.Add(dot); + Canvas.SetLeft(dot, getX(i) - 3.5); + Canvas.SetTop(dot, getY(values[i]) - 3.5); + } + } + + // 5. X 轴日期刻度(最多显示 8 个) + int labelStep = Math.Max(1, (int)Math.Ceiling(count / 8.0)); + for (int i = 0; i < count; i += labelStep) + { + var tb = new TextBlock + { + Text = points[i].DateText, + FontSize = 11, + Foreground = new SolidColorBrush(Color.FromRgb(153, 153, 153)), + TextAlignment = TextAlignment.Center, + Width = 60 + }; + LayoutRoot.Children.Add(tb); + double x = getX(i) - 30; + x = Math.Max(0, Math.Min(ActualWidth - 60, x)); + Canvas.SetLeft(tb, x); + Canvas.SetTop(tb, marginTop + plotHeight + 6); + } + } + + /// 生成数据点悬浮提示(日期 / 销售额 / 订单数) + private static object CreateTooltip(RevenueTrendPoint p) + { + var panel = new StackPanel(); + var txtBrush = new SolidColorBrush(Color.FromRgb(51, 51, 51)); + panel.Children.Add(new TextBlock + { + Text = p.Date.ToString("yyyy-MM-dd"), + FontSize = 11, + FontWeight = FontWeights.SemiBold, + Foreground = txtBrush + }); + panel.Children.Add(new TextBlock + { + Text = "销售额:" + FormatValue((double)p.SalesAmount) + "元", + FontSize = 11, + Foreground = txtBrush + }); + panel.Children.Add(new TextBlock + { + Text = "订单数:" + p.OrderCount + " 单", + FontSize = 11, + Foreground = txtBrush + }); + + return new Border + { + Background = Brushes.White, + CornerRadius = new CornerRadius(4), + Padding = new Thickness(8, 5, 8, 5), + Child = panel + }; + } + + private static string FormatValue(double value) + { + double abs = Math.Abs(value); + if (abs >= 100000000) + return (value / 100000000d).ToString("0.#") + "亿"; + if (abs >= 10000) + return (value / 10000d).ToString("0.#") + "万"; + return value.ToString("#,##0"); + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml new file mode 100644 index 0000000..53d6213 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml.cs b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml.cs new file mode 100644 index 0000000..91f61b7 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Charts/RevenuePieChart.xaml.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using _29WxOrderWpf.Ctrls.Statistics.Models; + +namespace _29WxOrderWpf.Ctrls.Statistics.Charts +{ + /// + /// 店铺销售占比饼图(轻量自绘,无第三方依赖)。 + /// 绑定 (IList<ShopSalesProportionItem>)即可,数据变化时自动重绘。 + /// 设置 (>0)可只显示前 N 项,其余项合并为一个“”扇区(百分比为剩余总和,整体仍为 100%)。 + /// 设置 可在环形图中央留白处显示标题( 可切换为顶部),不会遮挡扇形; + /// 图例通过 可放置到 4 个角,饼图区域由独立 panel 包裹,方便整体挪动和调整大小。 + /// + public partial class RevenuePieChart : UserControl + { + /// 标题显示位置 + public enum PieTitlePlacement + { + /// 居中显示在环形图中央留白处(默认,不遮挡扇形) + Center, + + /// 显示在图表顶部 + Top + } + + /// 图例显示位置(4 个角) + public enum PieLegendPosition + { + /// 左上角 + TopLeft, + + /// 右上角(默认) + TopRight, + + /// 左下角 + BottomLeft, + + /// 右下角 + BottomRight + } + + /// 待绘制扇区(含颜色) + private sealed class DisplaySlice + { + public ShopSalesProportionItem Item; + public Brush Brush; + } + + /// ECharts 默认配色 + private static readonly Color[] Palette = + { + Color.FromRgb(84, 112, 198), + Color.FromRgb(145, 204, 117), + Color.FromRgb(250, 200, 88), + Color.FromRgb(238, 102, 102), + Color.FromRgb(115, 192, 222), + Color.FromRgb(59, 162, 114), + Color.FromRgb(252, 132, 82), + Color.FromRgb(154, 96, 180), + Color.FromRgb(234, 124, 204) + }; + + /// “其他”合并项默认颜色(灰色) + private static readonly Color OtherColor = Color.FromRgb(192, 196, 204); + + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register("ItemsSource", typeof(IList), typeof(RevenuePieChart), + new PropertyMetadata(null, OnRenderPropertyChanged)); + + /// 占比数据 + public IList ItemsSource + { + get { return (IList)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public static readonly DependencyProperty MaxItemsProperty = + DependencyProperty.Register("MaxItems", typeof(int), typeof(RevenuePieChart), + new PropertyMetadata(0, OnRenderPropertyChanged)); + + /// 最多显示的独立扇区数(>0 时其余项合并为“其他”;<=0 显示全部) + public int MaxItems + { + get { return (int)GetValue(MaxItemsProperty); } + set { SetValue(MaxItemsProperty, value); } + } + + public static readonly DependencyProperty OtherLabelProperty = + DependencyProperty.Register("OtherLabel", typeof(string), typeof(RevenuePieChart), + new PropertyMetadata("其他", OnRenderPropertyChanged)); + + /// 合并项的显示名称(默认“其他”) + public string OtherLabel + { + get { return (string)GetValue(OtherLabelProperty); } + set { SetValue(OtherLabelProperty, value); } + } + + public static readonly DependencyProperty OtherBrushProperty = + DependencyProperty.Register("OtherBrush", typeof(Brush), typeof(RevenuePieChart), + new PropertyMetadata(new SolidColorBrush(OtherColor), OnRenderPropertyChanged)); + + /// 合并项的颜色(默认灰色) + public Brush OtherBrush + { + get { return (Brush)GetValue(OtherBrushProperty); } + set { SetValue(OtherBrushProperty, value); } + } + + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(RevenuePieChart), + new PropertyMetadata(null, OnRenderPropertyChanged)); + + /// 标题文本(空/Null 不显示) + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + public static readonly DependencyProperty TitlePlacementProperty = + DependencyProperty.Register("TitlePlacement", typeof(PieTitlePlacement), typeof(RevenuePieChart), + new PropertyMetadata(PieTitlePlacement.Center, OnRenderPropertyChanged)); + + /// 标题显示位置(默认居中显示在环形图留白处,不遮挡扇形) + public PieTitlePlacement TitlePlacement + { + get { return (PieTitlePlacement)GetValue(TitlePlacementProperty); } + set { SetValue(TitlePlacementProperty, value); } + } + + public static readonly DependencyProperty TitleFontSizeProperty = + DependencyProperty.Register("TitleFontSize", typeof(double), typeof(RevenuePieChart), + new PropertyMetadata(14d, OnRenderPropertyChanged)); + + /// 标题字号 + public double TitleFontSize + { + get { return (double)GetValue(TitleFontSizeProperty); } + set { SetValue(TitleFontSizeProperty, value); } + } + + public static readonly DependencyProperty TitleBrushProperty = + DependencyProperty.Register("TitleBrush", typeof(Brush), typeof(RevenuePieChart), + new PropertyMetadata(new SolidColorBrush(Color.FromRgb(51, 51, 51)), OnRenderPropertyChanged)); + + /// 标题颜色 + public Brush TitleBrush + { + get { return (Brush)GetValue(TitleBrushProperty); } + set { SetValue(TitleBrushProperty, value); } + } + + public static readonly DependencyProperty LegendPositionProperty = + DependencyProperty.Register("LegendPosition", typeof(PieLegendPosition), typeof(RevenuePieChart), + new PropertyMetadata(PieLegendPosition.TopRight, OnRenderPropertyChanged)); + + /// 图例显示位置(默认右上角,与饼图分列放置,不遮挡) + public PieLegendPosition LegendPosition + { + get { return (PieLegendPosition)GetValue(LegendPositionProperty); } + set { SetValue(LegendPositionProperty, value); } + } + + public RevenuePieChart() + { + InitializeComponent(); + Loaded += delegate { Render(); }; + SizeChanged += delegate { Render(); }; + } + + private static void OnRenderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((RevenuePieChart)d).Render(); + } + + private void Render() + { + LayoutRoot.Children.Clear(); + var items = ItemsSource; + if (items == null || items.Count == 0 || ActualWidth <= 20 || ActualHeight <= 20) + return; + + double width = ActualWidth; + double height = ActualHeight; + + // 图例列宽:图例占左/右一列,饼图占另一列,天然不会互相遮挡 + double legendWidth = Math.Min(210, width * 0.38); + bool legendOnLeft = LegendPosition == PieLegendPosition.TopLeft + || LegendPosition == PieLegendPosition.BottomLeft; + + double pieLeft = legendOnLeft ? legendWidth : 0; + double pieWidth = Math.Max(10, width - legendWidth); + double pieHeight = height; + + // 顶部标题:先预留高度,饼图相应下移,避免遮挡 + double topTitleHeight = 0; + if (TitlePlacement == PieTitlePlacement.Top && !string.IsNullOrEmpty(Title)) + { + var probe = CreateTitleBlock(pieWidth); + probe.Measure(new Size(pieWidth, double.PositiveInfinity)); + topTitleHeight = Math.Ceiling(probe.DesiredSize.Height) + 8; + } + + // 饼图区域用独立 Canvas 包裹,方便整体挪动(SetLeft/SetTop)和修改大小(Width/Height) + var piePanel = new Canvas { Width = pieWidth, Height = pieHeight }; + LayoutRoot.Children.Add(piePanel); + Canvas.SetLeft(piePanel, pieLeft); + Canvas.SetTop(piePanel, 0); + + double cx = pieWidth / 2; + double cy = (topTitleHeight + pieHeight) / 2; + double radius = Math.Max(10, Math.Min(pieWidth, pieHeight - topTitleHeight) / 2 - 18); + + // 0. 生成实际绘制的扇区列表(MaxItems > 0 时其余项合并为“其他”) + var slices = BuildSlices(items); + + // 1. 绘制扇形 + double startAngle = -90; // 从正上方开始,顺时针 + foreach (var slice in slices) + { + double sweep = slice.Item.Percentage / 100d * 360d; + if (sweep < 0) sweep = 0; + var brush = slice.Brush; + + if (sweep >= 359.9) + { + var circle = new Ellipse + { + Width = radius * 2, + Height = radius * 2, + Fill = brush + }; + piePanel.Children.Add(circle); + Canvas.SetLeft(circle, cx - radius); + Canvas.SetTop(circle, cy - radius); + } + else if (sweep > 0.01) + { + piePanel.Children.Add(CreateSlice(cx, cy, radius, startAngle, sweep, brush)); + } + startAngle += sweep; + } + + // 2. 中间留白圆心 + double holeRadius = radius * 0.55; + var hole = new Ellipse + { + Width = holeRadius * 2, + Height = holeRadius * 2, + Fill = Brushes.White + }; + piePanel.Children.Add(hole); + Canvas.SetLeft(hole, cx - holeRadius); + Canvas.SetTop(hole, cy - holeRadius); + + // 3. 标题(居中显示在环形图留白处,宽度限制在留白直径内,不遮挡扇形) + if (!string.IsNullOrEmpty(Title)) + { + double maxW = TitlePlacement == PieTitlePlacement.Center + ? Math.Max(20, holeRadius * 2 - 8) + : pieWidth; + var titleBlock = CreateTitleBlock(maxW); + titleBlock.Measure(new Size(maxW, double.PositiveInfinity)); + double w = Math.Min(titleBlock.DesiredSize.Width, maxW); + double h = titleBlock.DesiredSize.Height; + + piePanel.Children.Add(titleBlock); + Canvas.SetLeft(titleBlock, cx - w / 2); + Canvas.SetTop(titleBlock, TitlePlacement == PieTitlePlacement.Center ? cy - h / 2 : 0); + } + + // 4. 图例(4 个角可设置,与饼图分列,不遮挡) + var legendPanel = CreateLegendPanel(slices, legendWidth - 16); + legendPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + double legendW = legendPanel.DesiredSize.Width; + double legendH = legendPanel.DesiredSize.Height; + + const double padding = 8; + double lx, ly; + if (LegendPosition == PieLegendPosition.TopLeft || LegendPosition == PieLegendPosition.TopRight) + ly = padding; + else + ly = Math.Max(padding, height - legendH - padding); + lx = legendOnLeft ? padding : width - legendW - padding; + + LayoutRoot.Children.Add(legendPanel); + Canvas.SetLeft(legendPanel, lx); + Canvas.SetTop(legendPanel, ly); + } + + /// + /// 根据 生成实际绘制的扇区列表: + /// 按占比从大到小取前 N 项单独显示,其余项合并为一个“其他”扇区(百分比为剩余总和,整体仍为 100%)。 + /// + private List BuildSlices(IList items) + { + var result = new List(); + if (items == null || items.Count == 0) + return result; + + var ordered = items + .OrderByDescending(i => i.Percentage) + .ThenByDescending(i => i.SalesAmount) + .ToList(); + + int max = MaxItems > 0 ? MaxItems : ordered.Count; + if (max >= ordered.Count) + { + for (int i = 0; i < ordered.Count; i++) + result.Add(new DisplaySlice + { + Item = ordered[i], + Brush = new SolidColorBrush(Palette[i % Palette.Length]) + }); + return result; + } + + for (int i = 0; i < max; i++) + result.Add(new DisplaySlice + { + Item = ordered[i], + Brush = new SolidColorBrush(Palette[i % Palette.Length]) + }); + + var rest = ordered.Skip(max).ToList(); + result.Add(new DisplaySlice + { + Item = new ShopSalesProportionItem + { + ShopName = string.IsNullOrEmpty(OtherLabel) ? "其他" : OtherLabel, + Percentage = rest.Sum(r => r.Percentage), + SalesAmount = rest.Sum(r => r.SalesAmount) + }, + Brush = OtherBrush ?? new SolidColorBrush(OtherColor) + }); + return result; + } + + private TextBlock CreateTitleBlock(double maxWidth) + { + var brush = TitleBrush ?? new SolidColorBrush(Color.FromRgb(51, 51, 51)); + return new TextBlock + { + Text = Title, + FontSize = TitleFontSize, + FontWeight = FontWeights.Bold, + Foreground = brush, + TextAlignment = TextAlignment.Center, + TextWrapping = TextWrapping.Wrap, + TextTrimming = TextTrimming.CharacterEllipsis, + MaxWidth = maxWidth + }; + } + + private static StackPanel CreateLegendPanel(IList slices, double maxWidth) + { + double maxLegendWidth = Math.Max(60, maxWidth); + var legendPanel = new StackPanel { MaxWidth = maxLegendWidth }; + foreach (var slice in slices) + { + var legendRow = new StackPanel + { + Orientation = Orientation.Horizontal, + Margin = new Thickness(0, 4, 0, 4) + }; + legendRow.Children.Add(new Rectangle + { + Width = 12, + Height = 12, + Fill = slice.Brush + }); + + var text = new TextBlock + { + Text = slice.Item.ShopName + " " + slice.Item.Percentage.ToString("0.0") + "%", + FontSize = 12, + Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102)), + Margin = new Thickness(6, 0, 0, 0), + VerticalAlignment = VerticalAlignment.Center, + MaxWidth = Math.Max(40, maxLegendWidth - 18), + TextTrimming = TextTrimming.CharacterEllipsis + }; + legendRow.Children.Add(text); + legendPanel.Children.Add(legendRow); + } + return legendPanel; + } + + private static Path CreateSlice(double cx, double cy, double radius, double startAngle, double sweep, Brush fill) + { + var start = PointOnCircle(cx, cy, radius, startAngle); + var end = PointOnCircle(cx, cy, radius, startAngle + sweep); + + var figure = new PathFigure { StartPoint = new Point(cx, cy), IsClosed = true, IsFilled = true }; + figure.Segments.Add(new LineSegment { Point = start }); + figure.Segments.Add(new ArcSegment + { + Point = end, + Size = new Size(radius, radius), + IsLargeArc = sweep > 180, + SweepDirection = SweepDirection.Clockwise + }); + + return new Path + { + Data = new PathGeometry { Figures = { figure } }, + Fill = fill, + Stroke = Brushes.White, + StrokeThickness = 1 + }; + } + + private static Point PointOnCircle(double cx, double cy, double radius, double angle) + { + double rad = angle * Math.PI / 180d; + return new Point(cx + radius * Math.Cos(rad), cy + radius * Math.Sin(rad)); + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/General.xaml b/29WxOrderWpf/Ctrls/Statistics/General.xaml new file mode 100644 index 0000000..2ab7af3 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/General.xaml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs new file mode 100644 index 0000000..8eebad0 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs @@ -0,0 +1,256 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace _29WxOrderWpf.Ctrls.Statistics.Models +{ + /// + /// 店铺营收统计 - 查询参数 + /// + public class ShopRevenueStatisticsQuery + { + /// 开始时间 + public DateTime StartDate { get; set; } + + /// 结束时间 + public DateTime EndDate { get; set; } + + /// 店铺 app_key(A.app_key),空/Null 表示全部店铺 + public string ShopKey { get; set; } + } + + /// + /// 店铺营收统计 - 接口返回的原始明细行。 + /// 对应接口返回字段: + /// A.app_key, B.create_time, B.order_id, B.status, + /// B.product_price, B.order_price, B.purchase_price, B.freight, + /// B.predict_commission_fee, B.commission_amount, B.discounted_price, B.profit, + /// B.purchase_status, c.refund_status + /// 其中 A 为店铺表,B 为订单表,c 为售后/退款表。 + /// 金额字段单位均为“分”,展示时需除以 100。 + /// + public class ShopRevenueStatisticsRow + { + /// A.app_key 店铺 app_key + [JsonProperty("app_key")] + public string AppKey { get; set; } + + /// B.create_time 订单创建时间(接口为秒级时间戳,由 UnixDateTimeConverter 转换为 DateTime) + [JsonProperty("create_time")] + [JsonConverter(typeof(UnixDateTimeConverter))] + public DateTime CreateTime { get; set; } + + /// B.order_id 订单号 + [JsonProperty("order_id")] + public string OrderId { get; set; } + + /// B.status 订单状态:10-待付款;20-待发货;21-部分发货;30-待收货;100-完成;200-全部商品售后之后订单取消;250-未付款用户主动取消或超时未付款订单自动取消 + public int Status { get; set; } + + /// B.product_price 商品总价(分) + [JsonProperty("product_price")] + public int ProductPrice { get; set; } + + /// B.order_price 订单金额(分),计算公式:order_price = original_order_price - discounted_price - deduction_price - change_down_price + [JsonProperty("order_price")] + public int OrderPrice { get; set; } + + /// B.purchase_price 采购价(分) + [JsonProperty("purchase_price")] + public int PurchasePrice { get; set; } + + /// B.freight 运费(分) + public int Freight { get; set; } + + /// B.predict_commission_fee 平台服务费(微信小店:预计技术服务费)(分) + [JsonProperty("predict_commission_fee")] + public int PredictCommissionFee { get; set; } + + /// B.commission_amount 达人佣金(微信小店:分账金额)(分) + [JsonProperty("commission_amount")] + public int CommissionAmount { get; set; } + + /// B.discounted_price 优惠券优惠金额(分) + [JsonProperty("discounted_price")] + public int DiscountedPrice { get; set; } + + /// B.profit 利润(分),计算公式:实付金额 - 采购价 - 平台服务费 - 达人佣金(采购价、达人佣金为空值则按 0) + public int Profit { get; set; } + + /// B.purchase_status 采购状态:0-未采购;1-采购(默认值 0,推送仓库时修改为 1) + [JsonProperty("purchase_status")] + public int PurchaseStatus { get; set; } + + /// c.refund_status 退款状态(4/11 视为已退款,与 RevenueStatisticsAggregator.IsRefunded 保持一致) + [JsonProperty("refund_status")] + public int RefundStatus { get; set; } + + /// 店铺名称(由 app_key 关联店铺表得到,展示用,非接口原始字段) + [JsonProperty("shop_name")] + public string ShopName { get; set; } + + } + + /// + /// 顶部汇总卡片数据(金额单位:元) + /// + public class ShopRevenueSummary + { + /// 销售额(最终有效销售额) + public decimal SalesAmount { get; set; } + + /// 有效订单数(已扣除退款订单) + public int ValidOrderCount { get; set; } + + /// 利润(实际利润) + public decimal Profit { get; set; } + + /// 退款金额(统计期间退款) + public decimal RefundAmount { get; set; } + + /// 商品销量(有效销量) + public int ProductSalesVolume { get; set; } + + /// 客户数(成交客户) + public int CustomerCount { get; set; } + + /// 客单价(平均每单金额) + public decimal AvgOrderAmount { get; set; } + + /// 退款率(退款订单占比,百分数,如 2.31 表示 2.31%) + public decimal RefundRate { get; set; } + } + + /// + /// 销售趋势数据点(金额单位:元) + /// + public class RevenueTrendPoint + { + /// 日期 + public DateTime Date { get; set; } + + /// 日期文本(MM-dd) + public string DateText { get { return Date.ToString("MM-dd"); } } + + /// 当日销售额(元) + public decimal SalesAmount { get; set; } + + /// 当日有效订单数 + public int OrderCount { get; set; } + } + + /// + /// 店铺销售占比数据(金额单位:元) + /// + public class ShopSalesProportionItem + { + /// 店铺名称 + public string ShopName { get; set; } + + /// 销售额(元) + public decimal SalesAmount { get; set; } + + /// 占比(0~100) + public double Percentage { get; set; } + } + + /// + /// 店铺统计表格行(金额单位:元) + /// + public class ShopStatisticsItem + { + /// 店铺名称 + public string ShopName { get; set; } + + /// 订单数 + public int OrderCount { get; set; } + + /// 销售额(元) + public decimal SalesAmount { get; set; } + + /// 利润(元) + public decimal Profit { get; set; } + + /// 退款金额(元) + public decimal RefundAmount { get; set; } + + /// 客户数 + public int CustomerCount { get; set; } + + /// 客单价(元) + public decimal AvgOrderAmount { get; set; } + } + + /// + /// 店铺下拉选项 + /// + public class ShopOption + { + /// 店铺 app_key(A.app_key) + public string AppKey { get; set; } + + /// 店铺名称 + public string ShopName { get; set; } + } + + /// + /// 时间范围下拉选项 + /// + public class TimeRangeOption + { + /// 显示文本(如:今天、昨天、本周、本月、最近10天、自定义) + public string Text { get; set; } + + /// 值(today / yesterday / week / month / last10 / custom) + public string Value { get; set; } + } + + class UnixDateTimeConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(DateTime); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // 读取数值(支持 long 和 int 类型) + if (reader.TokenType == JsonToken.Integer) + { + long milliseconds = (long)reader.Value; + // Unix 纪元:1970-01-01 UTC + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + if (milliseconds.ToString().Length == 13) + { + return epoch.AddMilliseconds(milliseconds); + } + return epoch.AddSeconds(milliseconds); + + + } + // 如果传入的是字符串(如 "1700000000000"),也支持 + else if (reader.TokenType == JsonToken.String) + { + if (long.TryParse((string)reader.Value, out long milliseconds)) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + if (milliseconds.ToString().Length == 13) + { + return epoch.AddMilliseconds(milliseconds); + } + return epoch.AddSeconds(milliseconds); + } + } + throw new JsonSerializationException($"无法将 {reader.Value} 转换为 DateTime"); + } + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // 序列化时转回时间戳 + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + long milliseconds = (long)((DateTime)value - epoch).TotalMilliseconds; + writer.WriteValue(milliseconds); + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/Statistics/Services/ShopRevenueStatisticsService.cs b/29WxOrderWpf/Ctrls/Statistics/Services/ShopRevenueStatisticsService.cs new file mode 100644 index 0000000..acc57c9 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/Services/ShopRevenueStatisticsService.cs @@ -0,0 +1,443 @@ +using _29WxOrderWpf.Common; +using _29WxOrderWpf.Ctrls.Statistics.Models; +using AllBase; +using AllBase.Helper; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Work; + +namespace _29WxOrderWpf.Ctrls.Statistics.Services +{ + /// + /// 店铺营收统计 - 数据服务调用接口。 + /// 页面只依赖此接口,接入真实数据源时新建实现类(如对接 HTTP 接口 / SQL 查询), + /// 并通过 ShopRevenueStatisticsViewModel 构造函数注入即可,无需改动页面。 + /// + public interface IShopRevenueStatisticsService + { + /// + /// 查询原始统计明细(接口返回的 A.* / B.* / c.* 原始字段)。 + /// 真实实现通常通过 SQL / HTTP 一次性返回这些行,再由聚合器计算。 + /// + Task> QueryRowsAsync(ShopRevenueStatisticsQuery query); + + /// 获取顶部汇总卡片数据 + Task GetSummaryAsync(ShopRevenueStatisticsQuery query); + + /// 获取销售趋势(按天) + Task> GetSalesTrendAsync(ShopRevenueStatisticsQuery query); + + /// 获取店铺销售占比(饼图) + Task> GetShopSalesProportionAsync(ShopRevenueStatisticsQuery query); + + /// 获取店铺统计表格 + Task> GetShopStatisticsAsync(ShopRevenueStatisticsQuery query); + + /// 获取店铺下拉列表(用于查询条件) + Task> GetShopOptionsAsync(); + } + + /// + /// 统计聚合器:把接口返回的原始明细行聚合成页面所需的汇总/趋势/占比/表格数据。 + /// 聚合口径说明(依据接口字段描述): + /// - 订单状态 status:10-待付款;20-待发货;21-部分发货;30-待收货;100-完成; + /// 200-全部商品售后之后订单取消;250-未付款用户主动取消或超时未付款订单自动取消。 + /// - 有效订单:已付款(20/21/30/100)且未退款;10/250 未付款不计入销售额。 + /// - 已退款:c.refund_status 为 4/11,或订单状态为 200(全部商品售后后取消)。 + /// - 金额字段单位均为“分”,除以 100 转为元。 + /// + public static class RevenueStatisticsAggregator + { + /// 订单状态 - 待付款 + public const int StatusPendingPay = 10; + + /// 订单状态 - 待发货 + public const int StatusWaitSend = 20; + + /// 订单状态 - 部分发货 + public const int StatusPartSend = 21; + + /// 订单状态 - 待收货 + public const int StatusWaitReceive = 30; + + /// 订单状态 - 完成 + public const int StatusFinished = 100; + + /// 订单状态 - 全部商品售后之后订单取消 + public const int StatusCancelAfterAfterSale = 200; + + /// 订单状态 - 未付款用户主动取消/超时未付款自动取消 + public const int StatusCancelNotPay = 250; + + /// 是否已付款的订单(20/21/30/100;10 待付款、250 未付款取消不计入) + public static bool IsPaidOrder(ShopRevenueStatisticsRow row) + { + if (row == null) return false; + return row.Status == StatusWaitSend + || row.Status == StatusPartSend + || row.Status == StatusWaitReceive + || row.Status == StatusFinished; + } + + /// 是否已退款(c.refund_status 为 4/11,或订单状态为 200 全部商品售后后取消) + public static bool IsRefunded(ShopRevenueStatisticsRow row) + { + if (row == null) return false; + return row.RefundStatus == 4 || row.RefundStatus == 11 || row.Status == StatusCancelAfterAfterSale; + } + + /// 是否有效订单(已付款且未退款) + public static bool IsValidOrder(ShopRevenueStatisticsRow row) + { + return IsPaidOrder(row) && !IsRefunded(row); + } + + /// 汇总卡片 + public static ShopRevenueSummary GetSummary(IEnumerable rows) + { + var list = (rows ?? new List()).ToList(); + var valid = list.Where(IsValidOrder).ToList(); + var refunded = list.Where(IsRefunded).ToList(); + + decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m; + decimal profit = valid.Sum(r => (decimal)r.Profit) / 100m; + decimal refundAmount = refunded.Sum(r => (decimal)r.OrderPrice) / 100m; + int validOrderCount = valid.Select(r => r.OrderId).Distinct().Count(); + // 总订单数(退款率分母)= 已付款订单 + 已退款订单(含售后取消 200),不含未付款的 10/250 + int totalOrderCount = list.Where(r => IsPaidOrder(r) || IsRefunded(r)).Select(r => r.OrderId).Distinct().Count(); + int refundOrderCount = refunded.Select(r => r.OrderId).Distinct().Count(); + + return new ShopRevenueSummary + { + SalesAmount = salesAmount, + ValidOrderCount = validOrderCount, + Profit = profit, + RefundAmount = refundAmount, + ProductSalesVolume = valid.Count, + CustomerCount = validOrderCount, + AvgOrderAmount = validOrderCount == 0 ? 0m : salesAmount / validOrderCount, + RefundRate = totalOrderCount == 0 ? 0m : (decimal)refundOrderCount * 100m / totalOrderCount + }; + } + + /// 销售趋势(按天,缺失日期补 0,仅统计有效订单) + public static List GetSalesTrend(IEnumerable rows, DateTime startDate, DateTime endDate) + { + var list = (rows ?? new List()).Where(IsValidOrder).ToList(); + var map = list + .GroupBy(r => r.CreateTime.Date) + .ToDictionary( + g => g.Key, + g => new RevenueTrendPoint + { + Date = g.Key, + SalesAmount = g.Sum(r => (decimal)r.OrderPrice) / 100m, + OrderCount = g.Select(r => r.OrderId).Distinct().Count() + }); + + var result = new List(); + for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1)) + { + if (map.TryGetValue(d, out var point)) + result.Add(point); + else + result.Add(new RevenueTrendPoint { Date = d, SalesAmount = 0m, OrderCount = 0 }); + } + return result; + } + + /// 店铺销售占比(饼图,仅统计有效订单) + public static List GetShopSalesProportion(IEnumerable rows) + { + var list = (rows ?? new List()).Where(IsValidOrder).ToList(); + decimal total = list.Sum(r => (decimal)r.OrderPrice) / 100m; + + return list + .GroupBy(r => r.ShopName) + .Select(g => new ShopSalesProportionItem + { + ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key, + SalesAmount = g.Sum(r => (decimal)r.OrderPrice) / 100m, + Percentage = total == 0m ? 0d : (double)(g.Sum(r => (decimal)r.OrderPrice) / 100m / total * 100m) + }) + .OrderByDescending(i => i.SalesAmount) + .ToList(); + } + + /// 店铺统计表格 + public static List GetShopStatistics(IEnumerable rows) + { + var list = (rows ?? new List()).ToList(); + + return list + .GroupBy(r => r.ShopName) + .Select(g => + { + var valid = g.Where(IsValidOrder).ToList(); + var refunded = g.Where(IsRefunded).ToList(); + decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m; + int orderCount = valid.Select(r => r.OrderId).Distinct().Count(); + return new ShopStatisticsItem + { + ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key, + OrderCount = orderCount, + SalesAmount = salesAmount, + Profit = valid.Sum(r => (decimal)r.Profit) / 100m, + RefundAmount = refunded.Sum(r => (decimal)r.OrderPrice) / 100m, + CustomerCount = orderCount, + AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount + }; + }) + .OrderByDescending(i => i.SalesAmount) + .ToList(); + } + } + + /// + /// 默认实现:返回模拟数据,用于页面演示/联调。 + /// 接入真实接口时,新建实现类并注入即可(见 IShopRevenueStatisticsService 注释)。 + /// + public class ShopRevenueStatisticsService : IShopRevenueStatisticsService + { + /// + /// 明细数据缓存。 + /// key = 店铺id|yyyyMMdd(店铺 × 天),value = 该店铺当天的明细行。 + /// 查询按“店铺 × 日期”命中;完全未命中时所有店铺 + 整个时间范围一次请求, + /// 部分命中时只请求缺失的店铺/日期段。 + /// + private readonly Dictionary> _rowCache = + new Dictionary>(); + private readonly object _cacheLock = new object(); + + private sealed class DateRange + { + public DateTime Start; + public DateTime End; + } + + private static string CacheKey(string shopId, DateTime day) + { + return shopId + "|" + day.ToString("yyyyMMdd"); + } + + public async Task> QueryRowsAsync(ShopRevenueStatisticsQuery query) + { + // 1. 展开本次查询涉及的店铺(ShopKey 为空 = 全部店铺) + List shopIds; + if (string.IsNullOrEmpty(query.ShopKey)) + shopIds = (await GetShopOptionsAsync()).Select(a => a.AppKey).ToList(); + else + shopIds = new List { query.ShopKey }; + if (shopIds.Count == 0) + return new List(); + + var startDay = query.StartDate.Date; + var endDay = query.EndDate.Date; + int totalDays = (int)(endDay - startDay).Days + 1; + + // 2. 命中检测:按 店铺 × 天 找出缺失项 + var missingByShop = new Dictionary>(); + int missingCount = 0; + lock (_cacheLock) + { + foreach (var shopId in shopIds) + { + var missingDays = new List(); + for (var d = startDay; d <= endDay; d = d.AddDays(1)) + { + if (!_rowCache.ContainsKey(CacheKey(shopId, d))) + missingDays.Add(d); + } + missingCount += missingDays.Count; + missingByShop[shopId] = missingDays; + } + } + + // 3. 全部命中:直接从缓存拼装返回 + if (missingCount == 0) + return GetCachedRows(shopIds, startDay, endDay); + + // 4. 完全未命中:所有店铺 + 整个时间范围一次请求 + if (missingCount == shopIds.Count * totalDays) + { + var allRows = await RequestRowsAsync(shopIds, startDay, endDay); + CacheResponse(shopIds, startDay, endDay, allRows, null); + var cached = GetCachedRows(shopIds, startDay, endDay); + // 若接口行内未返回 app_key 导致无法入库,则直接返回本次请求结果 + return cached.Count > 0 ? cached : allRows; + } + + // 5. 部分命中:只请求未命中的(店铺 × 连续缺失日期段) + foreach (var pair in missingByShop) + { + if (pair.Value.Count == 0) + continue; + foreach (var run in BuildRuns(pair.Value)) + { + var rows = await RequestRowsAsync(new List { pair.Key }, run.Start, run.End); + CacheResponse(new List { pair.Key }, run.Start, run.End, rows, pair.Key); + } + } + + return GetCachedRows(shopIds, startDay, endDay); + } + + /// 按 店铺 × 日期 从缓存拼装结果 + private List GetCachedRows(List shopIds, DateTime startDate, DateTime endDate) + { + var result = new List(); + lock (_cacheLock) + { + for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1)) + { + foreach (var shopId in shopIds) + { + if (_rowCache.TryGetValue(CacheKey(shopId, d), out var list)) + result.AddRange(list); + } + } + } + return result; + } + + /// 请求接口一次(可传多个店铺),返回原始明细行 + private async Task> RequestRowsAsync(List shopIds, DateTime startDate, DateTime endDate) + { + var url = BaseClass.Build29WxObjString("shop_revenue_statistics/get_shop_order_amount"); + var sb = new StringBuilder(); + sb.Append(UserLogin.GetSignaturex()); + sb.Append("&shop_ids=").Append(string.Join(",", shopIds)); + sb.Append("&start_time=").Append(BaseClass.ConvertDateTimeLong(startDate.Date)); + sb.Append("&end_time=").Append(BaseClass.ConvertDateTimeLong(endDate.Date.AddDays(1).AddTicks(-1))); + var html = await GetHtml.PostUtf8Async(url, sb.ToString()); + + var ret = JsonConvert.DeserializeObject>>(html, new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + if (ret.Code != 200) + { + throw new Exception(ret.Msg); + } + return ret.Data ?? new List(); + } + + /// + /// 把一次请求的结果写入缓存(按 店铺id|日期 分桶)。 + /// 除有数据的 店铺×天 外,会把本次请求覆盖但无数据的 店铺×天 也标记为已缓存(空列表), + /// 避免“该店铺当天无数据”被反复判定为未命中而重复请求数据库。 + /// + private void CacheResponse(List shopIds, DateTime startDate, DateTime endDate, List rows, string fallbackShopId) + { + if (rows == null) + rows = new List(); + + // 能否按 店铺×天 归属: + // 单店铺请求一定可以(用请求的店铺兜底);多店铺请求要求行内带 app_key; + // 响应为空时也视为可归属(说明覆盖的 店铺×天 都无数据)。 + bool canAttribute = shopIds.Count == 1 + || rows.Count == 0 + || rows.Any(r => !string.IsNullOrEmpty(r.AppKey)); + + lock (_cacheLock) + { + if (canAttribute) + { + // 覆盖的 店铺×天 全部落缓存键(无数据的天记为空的已缓存) + for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1)) + { + foreach (var shopId in shopIds) + { + var key = CacheKey(shopId, d); + if (!_rowCache.TryGetValue(key, out var list)) + { + list = new List(); + _rowCache[key] = list; + } + } + } + } + + // 有数据的行按 店铺×天 追加 + foreach (var row in rows) + { + // 行内未带 app_key 时,用本次请求的店铺兜底 + var shopId = string.IsNullOrEmpty(row.AppKey) ? fallbackShopId : row.AppKey; + if (string.IsNullOrEmpty(shopId)) + continue; + var key = CacheKey(shopId, row.CreateTime.Date); + if (!_rowCache.TryGetValue(key, out var list)) + { + list = new List(); + _rowCache[key] = list; + } + list.Add(row); + } + } + } + + /// 把缺失日期合并为连续日期段(每段一次请求) + private static List BuildRuns(List missingDays) + { + var sorted = missingDays.OrderBy(d => d).ToList(); + var runs = new List(); + int i = 0; + while (i < sorted.Count) + { + var start = sorted[i]; + var end = start; + i++; + while (i < sorted.Count && sorted[i] == end.AddDays(1)) + { + end = sorted[i]; + i++; + } + runs.Add(new DateRange { Start = start, End = end }); + } + return runs; + } + + public async Task GetSummaryAsync(ShopRevenueStatisticsQuery query) + { + var rows = await QueryRowsAsync(query); + return RevenueStatisticsAggregator.GetSummary(rows); + } + + public async Task> GetSalesTrendAsync(ShopRevenueStatisticsQuery query) + { + var rows = await QueryRowsAsync(query); + return RevenueStatisticsAggregator.GetSalesTrend(rows, query.StartDate, query.EndDate); + } + + public async Task> GetShopSalesProportionAsync(ShopRevenueStatisticsQuery query) + { + var rows = await QueryRowsAsync(query); + return RevenueStatisticsAggregator.GetShopSalesProportion(rows); + } + + public async Task> GetShopStatisticsAsync(ShopRevenueStatisticsQuery query) + { + var rows = await QueryRowsAsync(query); + return RevenueStatisticsAggregator.GetShopStatistics(rows); + } + + public Task> GetShopOptionsAsync() + { + var list = GlobalConfig.ShopLoginList.Select(a => + new ShopOption() + { + AppKey = a.ShopId, + ShopName = a.ShopAccount + }).ToList(); + + return Task.FromResult(list); + } + + } +} diff --git a/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml b/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml new file mode 100644 index 0000000..a7cb480 --- /dev/null +++ b/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +