Przeglądaj źródła

带票额度统计

lowkeyman 2 miesięcy temu
rodzic
commit
b6b49d924b

+ 229 - 0
admin/control/refill_tax_quota_stats.php

@@ -0,0 +1,229 @@
+<?php
+class refill_tax_quota_statsControl extends SystemControl
+{
+    private $setting_key;
+    public function __construct()
+    {
+        parent::__construct();
+        $this->setting_key = 'quota_stats_settings';
+    }
+
+    public function indexOp()
+    {
+        Tpl::output('admin_info',$this->getAdminInfo());
+        Tpl::showpage('refill_quota_stats.index');
+    }
+
+    public function stats_settingsOp()
+    {
+        $data = $this->get_stats_settings();
+        exit(json_encode($data));
+    }
+
+    public function stats_settings_saveOp()
+    {
+        $writer = function ($key,$val)
+        {
+            $cache = rcache($this->setting_key, 'refill-');
+            $data = unserialize($cache['data']) ?? [];
+            $data[$key] = $val;
+            wcache($this->setting_key, ['data' => serialize($data)], 'refill-');
+        };
+        $writer($_POST['key'], $_POST['value']);
+
+        $data = $this->get_stats_settings();
+        exit(json_encode($data));
+    }
+
+    private function get_stats_settings(): array
+    {
+        $settings = rcache($this->setting_key, 'refill-');
+        $settings = unserialize($settings['data']);
+
+        $data = [
+            'quota_company' => $settings['quota_company'] ?? [],
+            'quota_merchant' => $settings['quota_merchant'] ?? [],
+            'quota_channel' => $settings['quota_channel'] ?? [],
+            'quota_summary' => $settings['quota_summary'] ?? [],
+            'approval_task' => $settings['approval_task'] ?? [],
+        ];
+        $fields = function ($key)
+        {
+            if (!empty($_GET[$key])) {
+                return explode(',', $_GET[$key]);
+            } elseif (!empty($_POST[$key])) {
+                return explode(',', $_POST[$key]);
+            }
+            return [];
+        };
+
+        $desiredTypes = $fields('key');
+        if (!empty($desiredTypes))
+        {
+            $data = array_filter($data, function($key) use ($desiredTypes) {
+                return in_array($key, $desiredTypes);
+            }, ARRAY_FILTER_USE_KEY);
+        }
+
+        $data['state'] = true;
+        return $data;
+    }
+
+    public function sys_mchsOp()
+    {
+        $model_merchant = Model('merchant');
+
+        $condition['merchant_state'] = 1;
+        $merchant_list = $model_merchant->getMerchantList($condition, 200, 'available_predeposit desc,merchant_state asc,mchid desc', true);
+        $merchant_list = array_map(function($item) {
+            return [
+                'mch_id' => $item['mchid'],
+                'mch_name' => $item['company_name']
+            ];
+        }, $merchant_list);
+
+        $data = [
+            'state' => true,
+            'list' => $merchant_list
+        ];
+        exit(json_encode($data));
+    }
+
+    public function sys_mchs_amountsOp()
+    {
+        if (empty($_GET['mchids'])) {
+            $data = [
+                'state' => false,
+                'list' => []
+            ];
+            exit(json_encode($data));
+        }
+
+        $mchids = explode(',', $_GET['mchids']);
+
+        $condition_counts['mchid'] = ['in', $mchids];
+        $condition_counts['status'] = merchantModel::status_passed;
+        $counts = Model('')->table('refill_evidence')
+            ->field('sum(amount) as amounts,mchid')
+            ->where($condition_counts)
+            ->group('mchid')
+            ->select();
+        $mch_stats = [];
+        foreach ($counts as $count) {
+            $mchid = $count['mchid'];
+            $mch_stats[$mchid] = $count['amounts'];
+        }
+
+        $model_merchant = Model('merchant');
+        $condition_mch['mchid'] = ['in', $mchids];
+        $condition_mch['merchant_state'] = 1;
+        $merchant_list = $model_merchant->getMerchantList($condition_mch, 200, 'available_predeposit desc,merchant_state asc,mchid desc', true);
+        $merchant_list = array_map(function($item) use($mch_stats)
+        {
+            $amounts = 0;
+
+            $mchid = $item['mchid'];
+            if (array_key_exists($mchid, $mch_stats)) {
+                $amounts = $mch_stats[$mchid];
+            }
+
+            return [
+                'mch_id' => $item['mchid'],
+                'mch_name' => $item['company_name'],
+                'amounts' => $amounts
+            ];
+        }, $merchant_list);
+
+        $data = [
+            'state' => true,
+            'list' => $merchant_list
+        ];
+        exit(json_encode($data));
+    }
+
+    public function sys_chansOp()
+    {
+        $provider_model = Model('refill_provider');
+        $condition['opened'] = 1;
+        $provider_items = $provider_model->table('refill_provider,store')
+            ->field('refill_provider.name,refill_provider.qualitys,refill_provider.type,store.store_name,store.store_id')
+            ->join('inner')
+            ->on('store.store_id=refill_provider.store_id')
+            ->where($condition)
+            ->order('opened asc, name asc')
+            ->select();
+        $provider_items = array_map(function ($item) {
+            return [
+                'store_id' => $item['store_id'],
+                'store_name' => $item['store_name']
+            ];
+        }, $provider_items);
+
+        $data = [
+            'state' => true,
+            'list' => $provider_items
+        ];
+        exit(json_encode($data));
+    }
+
+    public function sys_chans_amountsOp()
+    {
+        if (empty($_GET['store_ids'])) {
+            $data = [
+                'state' => false,
+                'list' => []
+            ];
+            exit(json_encode($data));
+        }
+
+        $store_ids = explode(',', $_GET['store_ids']);
+        $provider_model = Model('refill_provider');
+        $condition_provider['opened'] = 1;
+        $condition_provider['refill_provider.store_id'] = ['in', $store_ids];
+        $provider_items = $provider_model->table('refill_provider,store')
+            ->field('refill_provider.name,refill_provider.qualitys,refill_provider.type,store.store_name,store.store_id,refill_provider.provider_id')
+            ->join('inner')
+            ->on('store.store_id=refill_provider.store_id')
+            ->where($condition_provider)
+            ->order('opened asc, name asc')
+            ->select();
+
+        $provider_ids = array_map(function ($item) {
+            return $item['provider_id'];
+        }, $provider_items);
+        $condition_stat['provider_amount.provider_id'] = ['in', $provider_ids];
+        $counts = Model()->table('provider_amount,refill_provider')
+            ->where($condition_stat)
+            ->join('inner')->on('provider_amount.provider_id=refill_provider.provider_id')
+            ->field('sum(amount) as amounts, provider_amount.provider_id')
+            ->group('provider_amount.provider_id')
+            ->select();
+
+        $chan_stats = [];
+        foreach ($counts as $count) {
+            $provider_id = $count['provider_id'];
+            $chan_stats[$provider_id] = $count['amounts'];
+        }
+
+        $provider_items = array_map(function ($item) use($chan_stats)
+        {
+            $amounts = 0;
+
+            $provider_id = $item['provider_id'];
+            if (array_key_exists($provider_id, $chan_stats)) {
+                $amounts = $chan_stats[$provider_id];
+            }
+            return [
+                'store_id' => $item['store_id'],
+                'store_name' => $item['store_name'],
+                'amounts' => $amounts
+            ];
+        }, $provider_items);
+
+        $data = [
+            'state' => true,
+            'list' => $provider_items
+        ];
+        exit(json_encode($data));
+    }
+}

+ 2 - 2
admin/include/limit.php

@@ -60,8 +60,8 @@ $_limit =  array(
         array('name'=> '异常订单列表', 'op'=>null, 'act'=>'refill_error'),
         array('name'=> '服务器磁盘监控', 'op'=>null, 'act'=>'arw_monitor'),
         array('name'=> '对账管理', 'op'=>null, 'act'=>'orderstats'),
-        array('name'=> '上下游金额统计', 'op'=>null, 'act'=>'refill_amount_stats')
-
+        array('name'=> '上下游金额统计', 'op'=>null, 'act'=>'refill_amount_stats'),
+        array('name'=> '带票额度统计', 'op'=>null, 'act'=>'refill_tax_quota_stats'),
     )),
     array('name' => '办卡业务', 'child' => array(
         array('name' => '信息列表', 'op' => null, 'act' => 'retail'),

+ 1 - 0
admin/include/menu.php

@@ -143,6 +143,7 @@ $arr = array(
 					array('args'=>'index,arw_monitor,refill',			'text'=>'服务器磁盘监控'),
 					array('args'=>'index,refill_tax_stats,refill',	    'text'=>'带票对账统计'),
 					array('args'=>'index,refill_amount_stats,refill',	    'text'=>'上下游金额统计'),
+					array('args'=>'index,refill_tax_quota_stats,refill',	    'text'=>'带票额度统计'),
 				)
 			],
 			[

+ 4 - 613
admin/templates/default/js/stats-component.js

@@ -1,53 +1,3 @@
-/*---------------------------------------------参数配置---------------------------------------*/
-const CONSTANTS = {
-    KEY_CHANNEL_NAMES: 'channel_names', //上游的名字,通道分组用
-    KEY_MERCHANT_NAMES: 'merchant_names', //下游的名字,机构分组用
-    KEY_CHANNEL_FAST: 'channel_fast', //上下游余额统计:统计的快充通道
-    KEY_CHANNEL_NORMAL: 'channel_normal', //上下游余额统计:统计的普充通道
-    KEY_MERCHANT: 'merchant', //上下游余额统计:统计的机构
-    KEY_DAILY_CHANNEL: 'daily_channel', //日对账单:统计的通道
-    KEY_DAILY_MERCHANT: 'daily_merchant', //日对账单:统计的机构
-    KEY_MONTHLY_CHANNEL: 'monthly_channel', //月对账单:统计的通道
-    KEY_MONTHLY_MERCHANT: 'monthly_merchant', //月对账单:统计的机构
-    KEY_APPROVAL_TASK: 'approval_task', //审批列表
-};
-
-/*---------------------------------------------公共方法---------------------------------------*/
-function formatDecimals(number, digit= 2) {//格式化金额
-    if (typeof number === 'string') {
-        number = Number(number);
-    }
-    if (number === 0) {
-        return '0';
-    }
-    return parseFloat(number).toFixed(digit);
-}
-
-function isValidKey(obj, key) {
-    if (obj.hasOwnProperty(key)) {
-        if (obj[key] !== null && obj[key] !== undefined && obj[key] !== '') {
-            return true;
-        }
-    }
-    return false;
-}
-
-function deepCloneData(obj) {//数据深拷贝
-    if (obj === null || typeof obj !== 'object') {
-        return obj;
-    }
-
-    let clone = Array.isArray(obj) ? [] : {};
-
-    for (let key in obj) {
-        if (obj.hasOwnProperty(key)) {
-            clone[key] = deepCloneData(obj[key]);
-        }
-    }
-
-    return clone;
-}
-
 function sortSysData(sysData, sortData, key) {//sortData数据前置,sysData中未包含在sortData中的数据放在后面
     let sortedData = deepCloneData(sortData);
     const unselectedData = sysData.filter(function (sys) {
@@ -101,37 +51,6 @@ function sortCustData(sortCustSet, data, groupKey, nameKey) {
     return sortedData;
 }
 
-/**
- * 按指定字段进行排序,非字典序,将间隔开的元素排在一起
- * @param list
- * @param key
- * @returns {*[]}
- */
-function sortByName(list, key) {
-    const dataMap = new Map();
-
-    const result = [...list];
-
-    for (let i = 0; i < result.length; i++) {
-        const dataName = result[i][key];
-        const sortKey = dataName.slice(0, 2);
-
-        if (dataMap.has(sortKey)) {
-            const firstIndex = dataMap.get(sortKey);
-            if (i > firstIndex) {
-                const [item] = result.splice(i, 1);
-                result.splice(firstIndex + 1, 0, item);
-                dataMap.set(sortKey, firstIndex + 1);
-                i--;
-            }
-        } else {
-            dataMap.set(sortKey, i);
-        }
-    }
-
-    return result;
-}
-
 /*
  * 合并二维数据,表格数据合并/通道机构配置项合并
  * 注意data1和data2的位置,data1为本平台的缓存,data2为导入的数据
@@ -173,153 +92,8 @@ function mergeNames(data1, data2) {
     return data1.concat(notin);
 }
 
-function handleInputd2(event) {//限制金额输入框
-    let element = event.target;
-    let value = element.textContent;
-
-    let selection = window.getSelection();
-    let range = selection.getRangeAt(0);
-    let startOffset = range.startOffset;
-
-    value = value.replace(/[^\d.-]/g, '');
-
-    const parts = value.split('.');
-    if (parts.length > 1) {
-        parts[1] = parts[1].slice(0, 2);
-        value = parts.join('.');
-    }
-
-    element.textContent = value;
-
-    let newNode = element.childNodes[0] || element;
-
-    range.setStart(newNode, Math.min(startOffset, value.length));
-    range.setEnd(newNode, Math.min(startOffset, value.length));
-    selection.removeAllRanges();
-    selection.addRange(range);
-}
-
-function handleInputd4(event) {
-    let element = event.target;
-    let value = element.textContent;
-
-    let selection = window.getSelection();
-    let range = selection.getRangeAt(0);
-    let startOffset = range.startOffset;
-
-    value = value.replace(/[^\d.-]/g, '');
-
-    const parts = value.split('.');
-    if (parts.length > 1) {
-        parts[1] = parts[1].slice(0, 4);
-        value = parts.join('.');
-    }
-
-    element.textContent = value;
-
-    let newNode = element.childNodes[0] || element;
-
-    range.setStart(newNode, Math.min(startOffset, value.length));
-    range.setEnd(newNode, Math.min(startOffset, value.length));
-    selection.removeAllRanges();
-    selection.addRange(range);
-}
-
-function normalizeAmount(amount) {
-    return amount.replace(/^0+(\d+)/, '$1').replace(/^0+$/, '0');
-}
-
-function formatDateTime(dateString) {
-    const date = new Date(dateString);
-    const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, '0');
-    const day = String(date.getDate()).padStart(2, '0');
-    const hours = String(date.getHours()).padStart(2, '0');
-    const minutes = String(date.getMinutes()).padStart(2, '0');
-    const seconds = String(date.getSeconds()).padStart(2, '0');
-
-    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
-}
-
-/*
- * 过滤json数组,仅保留需要的字段
- */
-function filterJsonFields(jsonArray, fieldsToKeep) {
-    return jsonArray.map(item => {
-        return fieldsToKeep.reduce((filteredItem, field) => {
-            if (field in item) {
-                filteredItem[field] = item[field];
-            }
-            return filteredItem;
-        }, {});
-    });
-}
-
-function isObjectEmpty(obj) {
-    return Object.keys(obj).length === 0;
-}
-
-function showErr(error) {
-    layer.alert(error, {
-        icon: 2,
-        title: '错误'
-    });
-}
-
-function showWarning(error, callback) {
-    layer.alert(error, {
-        icon: 0,
-        title: '注意',
-        yes: function(index) {
-            if (typeof callback === 'function') {
-                callback();
-            }
-            layer.close(index);
-        }
-    });
-}
-function showSuccess(msg) {
-    layer.msg(msg, {
-        icon: 1,
-        time: 2000
-    });
-}
-
-function isNumeric(value) {
-    return /^-?\d+(\.\d+)?$/.test(value);
-}
-
-/*----------------------------------------------统一请求定义---------------------------------------*/
-class ApiService {
-    constructor(baseUrl) {
-        this.baseUrl = baseUrl;
-    }
-
-    request(endpoint, method, data) {
-        const that = this;
-
-        return new Promise(function(resolve, reject) {
-            $.ajax({
-                url: that.baseUrl + endpoint,
-                type: method,
-                data: data,
-                dataType: 'json',
-                success: function(response) {
-                    try {
-                        const parsedResponse = JSON.parse(JSON.stringify(response));
-                        resolve(parsedResponse);
-                    } catch (e) {
-                        reject(new Error('请求失败 ' + response));
-                    }
-                },
-                error: function(jqXHR, textStatus, errorThrown) {
-                    reject(new Error('请求失败: ' + textStatus + ', ' + errorThrown));
-                }
-            });
-        });
-    }
-
-
+/*----------------------------------------------请求---------------------------------------*/
+class AmountApiService extends BaseService {
     getStatsSettings(keys = []) {
         const endpoint = '?act=refill_amount_stats&op=stats_settings&key=' + keys.join(',');
         return this.request(endpoint, 'GET', {});
@@ -379,244 +153,7 @@ class ApiService {
         return this.request(endpoint, 'POST', params);
     };
 }
-const statsApi = new ApiService('');
-
-/*------------------------------------------------------组件实现:调度类-------------------------------------------*/
-class ComponentBase {
-    constructor(options) {
-        this.el = document.querySelector(options.el);
-        this.components = options.components || {};
-        this.componentData = options.componentData || {};
-        this.events = {};
-        this.componentInstances = {};
-        this.render();
-    }
-
-    render() {
-        this.el.innerHTML = this.compile(this.el.innerHTML);
-    }
-
-    compile(template) {
-        const componentNames = Object.keys(this.components);
-        componentNames.forEach(name => {
-            const regex = new RegExp(`<${name}([^>]*)>(.*?)<\/${name}>`, 'g');
-            template = template.replace(regex, (match, attrs) => {
-                const props = {};
-
-                attrs.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
-                    if (CONSTANTS[value] !== undefined) {
-                        value = CONSTANTS[value];
-                    }
-
-                    props[key] = value;
-                });
-
-                const component = new this.components[name]({
-                    props: props,
-                    data: this.componentData[name] || {},
-                    parent: this
-                });
-
-                this.componentInstances[name] = component;
-
-                return component.render();
-            });
-        });
-
-        return template;
-    }
-
-    on(event, callback) {
-        if (!this.events[event]) {
-            this.events[event] = [];
-        }
-        this.events[event].push(callback);
-    }
-
-    emit(event, ...args) {
-        if (this.events[event]) {
-            this.events[event].forEach(callback => callback(...args));
-        }
-    }
-
-    getComponent(name) {
-        return this.componentInstances[name];
-    }
-}
-
-/*------------------------------------------------------组件实现:组件基类-------------------------------------------*/
-class Component {
-    constructor(options) {
-        this.data = options.data || {};
-        this.props = options.props || {};
-        this.template = options.template || '';
-        this.id = 'component-' + Math.random().toString(36).substr(2, 9);
-        this.props['comp-id'] = this.id;
-    }
-
-    render() {
-        let template = this.template;
-
-        for (const key in this.props) {
-            template = template.replace(new RegExp(`{{${key}}}`, 'g'), this.props[key]);
-        }
-
-        setTimeout(() => {
-            this.bindEvents();
-            this.mounted();
-        }, 0);
-
-        return template;
-    }
-
-    bindEvents() {
-        const rootElement = document.querySelector(`#${this.id}`);
-
-        if (!rootElement) {
-            return;
-        }
-
-        this.unbindEvents();
-
-        const supportedEvents = ['click', 'input', 'blur', 'change'];
-
-        supportedEvents.forEach(eventType => {
-            const elements = rootElement.querySelectorAll(`[v-on\\:${eventType}]`);
-
-            elements.forEach(element => {
-                const method = element.getAttribute(`v-on:${eventType}`);
-                if (method && typeof this[method] === 'function') {
-                    const listener = this[method].bind(this);
-                    element.addEventListener(eventType, listener);
-
-                    if (!this.eventListeners[eventType]) {
-                        this.eventListeners[eventType] = [];
-                    }
-                    this.eventListeners[eventType].push({ element, listener });
-                }
-            });
-        });
-    }
-
-    unbindEvents() {
-        for (const eventType in this.eventListeners) {
-            this.eventListeners[eventType].forEach(({ element, listener }) => {
-                element.removeEventListener(eventType, listener);
-            });
-        }
-        this.eventListeners = {};
-    }
-
-    mounted() {
-
-    }
-}
-
-/*------------------------------------------------------layui表格基类-----------------------------------------------*/
-class LayuiTableBase {
-    constructor(id) {
-        this.tableId = id;
-    }
-
-    /*
-     * 表格中可编辑列的处理
-     */
-    editEvent(editColumns = [], callback) {
-        const tableItem = $('#' + this.tableId);
-
-        const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
-
-        trs.each(function (i, tr) {
-            for (let col of editColumns) {
-                const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
-                if (td.length > 0) {//合并隐藏的不处理
-                    const valDiv = td.find('div');
-                    valDiv.attr('contenteditable', true).css('background-color', 'lightblue');
-
-                    valDiv.off('input', handleInputd4)
-                        .off('keypress')
-                        .off('blur')
-                        .on('input', handleInputd4)
-                        .on('keypress', function (e) {
-                            if (e.keyCode === 13) {
-                                e.preventDefault();
-                                $(this).blur();
-                            }
-                        })
-                        .on('blur', function () {
-                            const val = valDiv.text().trim();
-                            if (!isNumeric(val)) {
-                                valDiv.html('0');
-                            }
-
-                            if (callback !== null && typeof callback === 'function') {
-                                const index = $(this).parent().parent().data('index');
-                                callback(index);
-                            }
-                        });
-                }
-            }
-        });
-    }
-
-    switchEvent(switchColumns = [], callback) {
-        const tableItem = $('#' + this.tableId);
-        const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
-
-        trs.each(function (i, tr) {
-            for (let clo of switchColumns) {
-                const td = $(tr).find('td[data-field="' + clo + '"]').not('.layui-hide');
-                if (td.length > 0) {
-                    const buttons = td.find('.layui-btn');
-
-                    buttons.off('click').on('click', function (e) {
-                        e.preventDefault();
-                        e.stopPropagation();
-
-                        const currTr = $(this).closest('tr');
-                        const index = currTr.data('index');
-
-                        buttons.removeClass('selected').addClass('unselected');
-                        $(this).removeClass('unselected').addClass('selected');
-
-                        if (callback !== null && typeof callback === 'function') {
-                            callback(index);
-                        }
-                    });
-                }
-            }
-        });
-    }
-
-    /*
-     * 表格中金额字段样式,小于0的数值设置为红色
-     */
-    setValueClass(redColumns, boldColumns = []) {
-        const trs = $('#' + this.tableId).next('.layui-table-view').find('.layui-table-body tbody tr');
-        trs.each(function(i, tr) {
-            for(let col of redColumns) {
-                const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
-                if (td.length > 0) {//合并隐藏的不处理
-                    const valDiv = td.find('div');
-                    const val = valDiv.text().trim();
-                    if (parseFloat(val) < 0) {
-                        valDiv.addClass('negative-value');
-                    } else {
-                        valDiv.removeClass('negative-value');
-                    }
-                }
-            }
-
-            for (let boldCol of boldColumns) {
-                const boldTd = $(tr).find('td[data-field="' + boldCol + '"]').not('.layui-hide');
-                if (boldTd.length > 0) {
-                    const boldDiv = boldTd.find('div');
-                    boldDiv.css('font-weight', 'bold');
-                }
-            }
-        });
-    }
-}
+const statsApi = new AmountApiService('');
 
 /*------------------------------------------------------组件:上下游名称设置-------------------------------------------*/
 class StreamNameComponent extends Component {
@@ -1172,7 +709,7 @@ class SelectedMerchantComponent extends Component {
     }
 }
 
-/*-------------------------------------页面全局数据封装,防止出现重复定义-------------------------------*/
+/*-------------------------------------UI数据-------------------------------*/
 class GlobalDataBase {
     constructor() {
         this.data = {};
@@ -1257,149 +794,3 @@ class GlobalDataBase {
         });
     }
 }
-
-class TaskManager {
-    constructor() {
-        this.globalTaskList = [];
-        this.currentTaskList = [];
-    }
-
-    async initializeGlobalTaskList() {
-        this.globalTaskList = await this._fetchTasksFromBackend();
-        this._updateCurrentTaskList();
-    }
-
-    async _fetchTasksFromBackend() {
-        const response = await statsApi.getStatsSettings([CONSTANTS.KEY_APPROVAL_TASK]);
-        let data = [];
-        if (response.state === true) {
-            data = response[CONSTANTS.KEY_APPROVAL_TASK];
-        }
-        return data.map(task => this._normalizeTask(task));
-    }
-
-    async _syncTaskWithBackend(task) {
-        const response = await statsApi.setStatsSettings(CONSTANTS.KEY_APPROVAL_TASK, task);
-        return response.state === true;
-    }
-
-    async _syncTaskListWithBackend() {
-        try {
-            this.globalTaskList = await this._fetchTasksFromBackend();
-            this._updateCurrentTaskList();
-        } catch (error) {
-            console.error("Failed to sync tasks with backend:", error);
-        }
-    }
-
-    _mergeTasks(taskList, newTask) {
-        const taskId = newTask.id;
-        const taskIndex = taskList.findIndex(task => task.id === taskId);
-
-        if (taskIndex !== -1) {
-            taskList[taskIndex] = { ...taskList[taskIndex], ...newTask };
-        } else {
-            taskList.push(newTask);
-        }
-
-        return taskList;
-    }
-
-    _normalizeTask(task) {
-        return {
-            id: task.id || this._generateUniqueId(),
-            type: task.type || this.getTaskType(),
-            time: task.time || new Date().toISOString(),
-            data: task.data || {},
-            status: task.status || 'pending',
-            initiator: task.initiator || '',
-            approver: task.approver || ''
-        };
-    }
-
-    _generateUniqueId() {
-        return 'xxxx-xxxx-4xxx-yxxx-xxxxxx'.replace(/[xy]/g, function(c) {
-            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
-            return v.toString(16);
-        });
-    }
-
-    getTaskType() {
-        throw new Error("Subclasses must implement getTaskType()");
-    }
-
-    _updateCurrentTaskList() {
-        const field = this.getTaskType();
-        this.currentTaskList = this.globalTaskList.filter(task => task.type === field);
-        this._sortCurrentTaskListByTime();
-    }
-
-    _sortCurrentTaskListByTime() {
-        this.currentTaskList.sort((a, b) => new Date(a.time) - new Date(b.time));
-    }
-
-    async addTask(data = {}, initiator = '', approver = '') {
-        const newTask = this._normalizeTask({ data, initiator, approver });
-        const taskTmp = deepCloneData(this.globalTaskList);
-        const updateTask = this._mergeTasks(taskTmp, newTask);
-        if (await this._syncTaskWithBackend(updateTask)) {
-            this.globalTaskList.push(newTask);
-            this._updateCurrentTaskList();
-            return true;
-        }
-
-        return false;
-    }
-
-    getPendingTasks() {
-        return this.currentTaskList;
-    }
-
-    async executeTask(taskId, approvalHandler) {
-        await this._syncTaskListWithBackend();
-
-        const taskIndex = this.currentTaskList.findIndex(task => task.id === taskId);
-        if (taskIndex === -1) {
-            console.warn("Task already processed or not found.");
-            return false;
-        }
-
-        const task = this.currentTaskList[taskIndex];
-
-        await approvalHandler(task);
-
-        this.currentTaskList.splice(taskIndex, 1);
-
-        const globalIndex = this.globalTaskList.findIndex(t => t.id === taskId);
-        if (globalIndex !== -1) {
-            this.globalTaskList.splice(globalIndex, 1);
-        }
-
-        await this._syncTaskWithBackend(this.globalTaskList);
-
-        return true;
-    }
-
-    async updateTask(taskId, updatedData) {
-        const taskIndex = this.globalTaskList.findIndex(task => task.id === taskId);
-        if (taskIndex === -1) {
-            console.warn(`Task with ID ${taskId} not found.`);
-            return false;
-        }
-
-        if (Object.keys(updatedData).length === 0) {
-            this.globalTaskList.splice(taskIndex, 1);
-        } else {
-            this.globalTaskList[taskIndex].data = updatedData;
-        }
-
-        const updateSuccess = await this._syncTaskWithBackend(this.globalTaskList);
-        if (updateSuccess) {
-            this._updateCurrentTaskList();
-            return true;
-        } else {
-            console.error(`Failed to update task ${taskId} in the backend.`);
-            return false;
-        }
-    }
-}

Plik diff jest za duży
+ 1194 - 0
admin/templates/default/js/stats-quota.js


+ 650 - 0
admin/templates/default/js/stats-utils.js

@@ -0,0 +1,650 @@
+/*---------------------------------------------参数配置---------------------------------------*/
+const CONSTANTS = {
+    //审批列表
+    KEY_APPROVAL_TASK: 'approval_task',
+
+    // 余额统计
+    KEY_CHANNEL_NAMES: 'channel_names', //上游的名字,通道分组用
+    KEY_MERCHANT_NAMES: 'merchant_names', //下游的名字,机构分组用
+    KEY_CHANNEL_FAST: 'channel_fast', //上下游余额统计:统计的快充通道
+    KEY_CHANNEL_NORMAL: 'channel_normal', //上下游余额统计:统计的普充通道
+    KEY_MERCHANT: 'merchant', //上下游余额统计:统计的机构
+    KEY_DAILY_CHANNEL: 'daily_channel', //日对账单:统计的通道
+    KEY_DAILY_MERCHANT: 'daily_merchant', //日对账单:统计的机构
+    KEY_MONTHLY_CHANNEL: 'monthly_channel', //月对账单:统计的通道
+    KEY_MONTHLY_MERCHANT: 'monthly_merchant', //月对账单:统计的机构
+
+    //带票额度统计
+    KEY_QUOTA_COMPANY: 'quota_company', //带票额度统计:主体
+    KEY_QUOTA_MERCHANT: 'quota_merchant', //带票额度统计:机构
+    KEY_QUOTA_CHANNEL: 'quota_channel', //带票额度统计:通道
+    KEY_QUOTA_SUMMARY: 'quota_summary', //带票额度统计:综合
+};
+
+/*---------------------------------------------公共方法---------------------------------------*/
+function formatDecimals(number, digit= 2) {//格式化金额
+    if (typeof number === 'string') {
+        number = Number(number);
+    }
+    if (number === 0) {
+        return '0';
+    }
+    return parseFloat(number).toFixed(digit);
+}
+
+function isValidKey(obj, key) {
+    if (obj.hasOwnProperty(key)) {
+        if (obj[key] !== null && obj[key] !== undefined && obj[key] !== '') {
+            return true;
+        }
+    }
+    return false;
+}
+
+function deepCloneData(obj) {//数据深拷贝
+    if (obj === null || typeof obj !== 'object') {
+        return obj;
+    }
+
+    let clone = Array.isArray(obj) ? [] : {};
+
+    for (let key in obj) {
+        if (obj.hasOwnProperty(key)) {
+            clone[key] = deepCloneData(obj[key]);
+        }
+    }
+
+    return clone;
+}
+
+function handleInputd2(event) {//限制金额输入框
+    let element = event.target;
+    let value = element.textContent;
+
+    let selection = window.getSelection();
+    let range = selection.getRangeAt(0);
+    let startOffset = range.startOffset;
+
+    value = value.replace(/[^\d.-]/g, '');
+
+    const parts = value.split('.');
+    if (parts.length > 1) {
+        parts[1] = parts[1].slice(0, 2);
+        value = parts.join('.');
+    }
+
+    element.textContent = value;
+
+    let newNode = element.childNodes[0] || element;
+
+    range.setStart(newNode, Math.min(startOffset, value.length));
+    range.setEnd(newNode, Math.min(startOffset, value.length));
+    selection.removeAllRanges();
+    selection.addRange(range);
+}
+
+function handleInputd4(event) {
+    let element = event.target;
+    let value = element.textContent;
+
+    let selection = window.getSelection();
+    let range = selection.getRangeAt(0);
+    let startOffset = range.startOffset;
+
+    value = value.replace(/[^\d.-]/g, '');
+
+    const parts = value.split('.');
+    if (parts.length > 1) {
+        parts[1] = parts[1].slice(0, 4);
+        value = parts.join('.');
+    }
+
+    element.textContent = value;
+
+    let newNode = element.childNodes[0] || element;
+
+    range.setStart(newNode, Math.min(startOffset, value.length));
+    range.setEnd(newNode, Math.min(startOffset, value.length));
+    selection.removeAllRanges();
+    selection.addRange(range);
+}
+
+function handleInputNormal2(event) {
+    let element = event.target;
+    let value = element.value;
+
+    let cursorPos = element.selectionStart;
+
+    value = value.replace(/[^\d.-]/g, '');
+
+    const parts = value.split('.');
+    if (parts.length > 1) {
+        parts[1] = parts[1].slice(0, 2);
+        value = parts.join('.');
+    }
+
+    element.value = value;
+
+    element.setSelectionRange(cursorPos, cursorPos);
+}
+
+function normalizeAmount(amount) {
+    return amount.replace(/^0+(\d+)/, '$1').replace(/^0+$/, '0');
+}
+
+function formatDateTime(dateString) {
+    const date = new Date(dateString);
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, '0');
+    const day = String(date.getDate()).padStart(2, '0');
+    const hours = String(date.getHours()).padStart(2, '0');
+    const minutes = String(date.getMinutes()).padStart(2, '0');
+    const seconds = String(date.getSeconds()).padStart(2, '0');
+
+    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+function isValidDate(dateString) { //校验日期是否合法
+    const regex = /^\d{4}-\d{2}-\d{2}$/;
+    if (!regex.test(dateString)) {
+        return false;
+    }
+
+    const date = new Date(dateString);
+    if (Number.isNaN(date.getTime())) {
+        return false;
+    }
+
+    const [year, month, day] = dateString.split('-').map(Number);
+    return date.getFullYear() === year && date.getMonth() + 1 === month && date.getDate() === day;
+}
+
+/*
+ * 过滤json数组,仅保留需要的字段
+ */
+function filterJsonFields(jsonArray, fieldsToKeep) {
+    return jsonArray.map(item => {
+        return fieldsToKeep.reduce((filteredItem, field) => {
+            if (field in item) {
+                filteredItem[field] = item[field];
+            }
+            return filteredItem;
+        }, {});
+    });
+}
+
+/**
+ * 按指定字段进行排序,非字典序,将间隔开的元素排在一起
+ * @param list
+ * @param key
+ * @returns {*[]}
+ */
+function sortByName(list, key) {
+    const dataMap = new Map();
+
+    const result = [...list];
+
+    for (let i = 0; i < result.length; i++) {
+        const dataName = result[i][key];
+        const sortKey = dataName.slice(0, 2);
+
+        if (dataMap.has(sortKey)) {
+            const firstIndex = dataMap.get(sortKey);
+            if (i > firstIndex) {
+                const [item] = result.splice(i, 1);
+                result.splice(firstIndex + 1, 0, item);
+                dataMap.set(sortKey, firstIndex + 1);
+                i--;
+            }
+        } else {
+            dataMap.set(sortKey, i);
+        }
+    }
+
+    return result;
+}
+
+function isObjectEmpty(obj) {
+    return Object.keys(obj).length === 0;
+}
+
+function showErr(error) {
+    layer.alert(error, {
+        icon: 2,
+        title: '错误'
+    });
+}
+
+function showWarning(error, callback) {
+    layer.alert(error, {
+        icon: 0,
+        title: '注意',
+        yes: function(index) {
+            if (typeof callback === 'function') {
+                callback();
+            }
+            layer.close(index);
+        }
+    });
+}
+function showSuccess(msg) {
+    layer.msg(msg, {
+        icon: 1,
+        time: 2000
+    });
+}
+
+function isNumeric(value) {
+    return /^-?\d+(\.\d+)?$/.test(value);
+}
+
+/*---------------------------------------------请求---------------------------------------*/
+class BaseService {
+    constructor(baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    request(endpoint, method, data) {
+        return new Promise((resolve, reject) => {
+            $.ajax({
+                url: this.baseUrl + endpoint,
+                type: method,
+                data: data,
+                dataType: 'json',
+                success: (response) => {
+                    try {
+                        const parsedResponse = JSON.parse(JSON.stringify(response));
+                        resolve(parsedResponse);
+                    } catch (e) {
+                        reject(new Error('请求解析失败: ' + response));
+                    }
+                },
+                error: (jqXHR, textStatus, errorThrown) => {
+                    reject(new Error('请求失败: ' + textStatus + ', ' + errorThrown));
+                },
+            });
+        });
+    }
+}
+
+/*------------------------------------------------------UI组件-------------------------------------------*/
+class ComponentBase {
+    constructor(options) {
+        this.el = document.querySelector(options.el);
+        this.components = options.components || {};
+        this.componentData = options.componentData || {};
+        this.events = {};
+        this.componentInstances = {};
+        this.render();
+    }
+
+    render() {
+        this.el.innerHTML = this.compile(this.el.innerHTML);
+    }
+
+    compile(template) {
+        const componentNames = Object.keys(this.components);
+        componentNames.forEach(name => {
+            const regex = new RegExp(`<${name}([^>]*)>(.*?)<\/${name}>`, 'g');
+            template = template.replace(regex, (match, attrs) => {
+                const props = {};
+
+                attrs.replace(/(\w+)="([^"]*)"/g, (match, key, value) => {
+                    if (CONSTANTS[value] !== undefined) {
+                        value = CONSTANTS[value];
+                    }
+
+                    props[key] = value;
+                });
+
+                const component = new this.components[name]({
+                    props: props,
+                    data: this.componentData[name] || {},
+                    parent: this
+                });
+
+                this.componentInstances[name] = component;
+
+                return component.render();
+            });
+        });
+
+        return template;
+    }
+
+    on(event, callback) {
+        if (!this.events[event]) {
+            this.events[event] = [];
+        }
+        this.events[event].push(callback);
+    }
+
+    emit(event, ...args) {
+        if (this.events[event]) {
+            this.events[event].forEach(callback => callback(...args));
+        }
+    }
+
+    getComponent(name) {
+        return this.componentInstances[name];
+    }
+}
+
+class Component {
+    constructor(options) {
+        this.data = options.data || {};
+        this.props = options.props || {};
+        this.template = options.template || '';
+        this.id = 'component-' + Math.random().toString(36).substr(2, 9);
+        this.props['comp-id'] = this.id;
+    }
+
+    render() {
+        let template = this.template;
+
+        for (const key in this.props) {
+            template = template.replace(new RegExp(`{{${key}}}`, 'g'), this.props[key]);
+        }
+
+        setTimeout(() => {
+            this.bindEvents();
+            this.mounted();
+        }, 0);
+
+        return template;
+    }
+
+    bindEvents() {
+        const rootElement = document.querySelector(`#${this.id}`);
+
+        if (!rootElement) {
+            return;
+        }
+
+        this.unbindEvents();
+
+        const supportedEvents = ['click', 'input', 'blur', 'change'];
+
+        supportedEvents.forEach(eventType => {
+            const elements = rootElement.querySelectorAll(`[v-on\\:${eventType}]`);
+
+            elements.forEach(element => {
+                const method = element.getAttribute(`v-on:${eventType}`);
+                if (method && typeof this[method] === 'function') {
+                    const listener = this[method].bind(this);
+                    element.addEventListener(eventType, listener);
+
+                    if (!this.eventListeners[eventType]) {
+                        this.eventListeners[eventType] = [];
+                    }
+                    this.eventListeners[eventType].push({ element, listener });
+                }
+            });
+        });
+    }
+
+    unbindEvents() {
+        for (const eventType in this.eventListeners) {
+            this.eventListeners[eventType].forEach(({ element, listener }) => {
+                element.removeEventListener(eventType, listener);
+            });
+        }
+        this.eventListeners = {};
+    }
+
+    mounted() {
+
+    }
+}
+
+class LayuiTableBase {
+    constructor(id) {
+        this.tableId = id;
+    }
+
+    /*
+     * 表格中可编辑列的处理
+     */
+    editEvent(editColumns = [], callback) {
+        const tableItem = $('#' + this.tableId);
+
+        const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
+
+        trs.each(function (i, tr) {
+            for (let col of editColumns) {
+                const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
+                if (td.length > 0) {//合并隐藏的不处理
+                    const valDiv = td.find('div');
+                    valDiv.attr('contenteditable', true).css('background-color', 'lightblue');
+
+                    valDiv.off('input', handleInputd4)
+                        .off('keypress')
+                        .off('blur')
+                        .on('input', handleInputd4)
+                        .on('keypress', function (e) {
+                            if (e.keyCode === 13) {
+                                e.preventDefault();
+                                $(this).blur();
+                            }
+                        })
+                        .on('blur', function () {
+                            const val = valDiv.text().trim();
+                            if (!isNumeric(val)) {
+                                valDiv.html('0');
+                            }
+
+                            if (callback !== null && typeof callback === 'function') {
+                                const index = $(this).parent().parent().data('index');
+                                callback(index);
+                            }
+                        });
+                }
+            }
+        });
+    }
+
+    switchEvent(switchColumns = [], callback) {
+        const tableItem = $('#' + this.tableId);
+        const trs = tableItem.next('.layui-table-view').find('.layui-table-body tbody tr');
+
+        trs.each(function (i, tr) {
+            for (let clo of switchColumns) {
+                const td = $(tr).find('td[data-field="' + clo + '"]').not('.layui-hide');
+                if (td.length > 0) {
+                    const buttons = td.find('.layui-btn');
+
+                    buttons.off('click').on('click', function (e) {
+                        e.preventDefault();
+                        e.stopPropagation();
+
+                        const currTr = $(this).closest('tr');
+                        const index = currTr.data('index');
+
+                        buttons.removeClass('selected').addClass('unselected');
+                        $(this).removeClass('unselected').addClass('selected');
+
+                        if (callback !== null && typeof callback === 'function') {
+                            callback(index);
+                        }
+                    });
+                }
+            }
+        });
+    }
+
+    /*
+     * 表格中金额字段样式,小于0的数值设置为红色
+     */
+    setValueClass(redColumns, boldColumns = []) {
+        const trs = $('#' + this.tableId).next('.layui-table-view').find('.layui-table-body tbody tr');
+        trs.each(function(i, tr) {
+            for(let col of redColumns) {
+                const td = $(tr).find('td[data-field="' + col + '"]').not('.layui-hide');
+                if (td.length > 0) {//合并隐藏的不处理
+                    const valDiv = td.find('div');
+                    const val = valDiv.text().trim();
+                    if (parseFloat(val) < 0) {
+                        valDiv.addClass('negative-value');
+                    } else {
+                        valDiv.removeClass('negative-value');
+                    }
+                }
+            }
+
+            for (let boldCol of boldColumns) {
+                const boldTd = $(tr).find('td[data-field="' + boldCol + '"]').not('.layui-hide');
+                if (boldTd.length > 0) {
+                    const boldDiv = boldTd.find('div');
+                    boldDiv.css('font-weight', 'bold');
+                }
+            }
+        });
+    }
+}
+
+/*------------------------------------------------------审批任务-------------------------------------------*/
+class TaskManager {
+    constructor() {
+        this.globalTaskList = [];
+        this.currentTaskList = [];
+    }
+
+    async initializeGlobalTaskList() {
+        this.globalTaskList = await this._fetchTasksFromBackend();
+        this._updateCurrentTaskList();
+    }
+
+    async _fetchTasksFromBackend() {
+        const response = await statsApi.getStatsSettings([CONSTANTS.KEY_APPROVAL_TASK]);
+        let data = [];
+        if (response.state === true) {
+            data = response[CONSTANTS.KEY_APPROVAL_TASK];
+        }
+        return data.map(task => this._normalizeTask(task));
+    }
+
+    async _syncTaskWithBackend(task) {
+        const response = await statsApi.setStatsSettings(CONSTANTS.KEY_APPROVAL_TASK, task);
+        return response.state === true;
+    }
+
+    async _syncTaskListWithBackend() {
+        try {
+            this.globalTaskList = await this._fetchTasksFromBackend();
+            this._updateCurrentTaskList();
+        } catch (error) {
+            console.error("Failed to sync tasks with backend:", error);
+        }
+    }
+
+    _mergeTasks(taskList, newTask) {
+        const taskId = newTask.id;
+        const taskIndex = taskList.findIndex(task => task.id === taskId);
+
+        if (taskIndex !== -1) {
+            taskList[taskIndex] = { ...taskList[taskIndex], ...newTask };
+        } else {
+            taskList.push(newTask);
+        }
+
+        return taskList;
+    }
+
+    _normalizeTask(task) {
+        return {
+            id: task.id || this._generateUniqueId(),
+            type: task.type || this.getTaskType(),
+            time: task.time || new Date().toISOString(),
+            data: task.data || {},
+            status: task.status || 'pending',
+            initiator: task.initiator || '',
+            approver: task.approver || ''
+        };
+    }
+
+    _generateUniqueId() {
+        return 'xxxx-xxxx-4xxx-yxxx-xxxxxx'.replace(/[xy]/g, function(c) {
+            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    }
+
+    getTaskType() {
+        throw new Error("Subclasses must implement getTaskType()");
+    }
+
+    _updateCurrentTaskList() {
+        const field = this.getTaskType();
+        this.currentTaskList = this.globalTaskList.filter(task => task.type === field);
+        this._sortCurrentTaskListByTime();
+    }
+
+    _sortCurrentTaskListByTime() {
+        this.currentTaskList.sort((a, b) => new Date(a.time) - new Date(b.time));
+    }
+
+    async addTask(data = {}, initiator = '', approver = '') {
+        const newTask = this._normalizeTask({ data, initiator, approver });
+        const taskTmp = deepCloneData(this.globalTaskList);
+        const updateTask = this._mergeTasks(taskTmp, newTask);
+        if (await this._syncTaskWithBackend(updateTask)) {
+            this.globalTaskList.push(newTask);
+            this._updateCurrentTaskList();
+            return true;
+        }
+
+        return false;
+    }
+
+    getPendingTasks() {
+        return this.currentTaskList;
+    }
+
+    async executeTask(taskId, approvalHandler) {
+        await this._syncTaskListWithBackend();
+
+        const taskIndex = this.currentTaskList.findIndex(task => task.id === taskId);
+        if (taskIndex === -1) {
+            console.warn("Task already processed or not found.");
+            return false;
+        }
+
+        const task = this.currentTaskList[taskIndex];
+
+        await approvalHandler(task);
+
+        this.currentTaskList.splice(taskIndex, 1);
+
+        const globalIndex = this.globalTaskList.findIndex(t => t.id === taskId);
+        if (globalIndex !== -1) {
+            this.globalTaskList.splice(globalIndex, 1);
+        }
+
+        await this._syncTaskWithBackend(this.globalTaskList);
+
+        return true;
+    }
+
+    async updateTask(taskId, updatedData) {
+        const taskIndex = this.globalTaskList.findIndex(task => task.id === taskId);
+        if (taskIndex === -1) {
+            console.warn(`Task with ID ${taskId} not found.`);
+            return false;
+        }
+
+        if (Object.keys(updatedData).length === 0) {
+            this.globalTaskList.splice(taskIndex, 1);
+        } else {
+            this.globalTaskList[taskIndex].data = updatedData;
+        }
+
+        const updateSuccess = await this._syncTaskWithBackend(this.globalTaskList);
+        if (updateSuccess) {
+            this._updateCurrentTaskList();
+            return true;
+        } else {
+            console.error(`Failed to update task ${taskId} in the backend.`);
+            return false;
+        }
+    }
+}

+ 2 - 16
admin/templates/default/refill_amount_stats.daily_statement.php

@@ -263,26 +263,12 @@
 <script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/layui/layui.js"></script>
 <link rel="stylesheet" type="text/css" href="<?php echo ADMIN_TEMPLATES_URL; ?>/layui/css/layui.css"/>
 <link rel="stylesheet" type="text/css" href="<?php echo RESOURCE_SITE_URL; ?>/js/jquery-ui/themes/ui-lightness/jquery.ui.css"/>
-<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-component.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-utils.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-amount.js?t=<?php echo time();?>"></script>
 
 <script type="text/javascript">
     const systemTag = '<?php echo ADMIN_NAME;?>';
 
-    function isValidDate(dateString) { //校验日期是否合法
-        const regex = /^\d{4}-\d{2}-\d{2}$/;
-        if (!regex.test(dateString)) {
-            return false;
-        }
-
-        const date = new Date(dateString);
-        if (Number.isNaN(date.getTime())) {
-            return false;
-        }
-
-        const [year, month, day] = dateString.split('-').map(Number);
-        return date.getFullYear() === year && date.getMonth() + 1 === month && date.getDate() === day;
-    }
-
     function getLastDay() {
         const date = new Date();
         date.setDate(date.getDate() - 1);

+ 2 - 1
admin/templates/default/refill_amount_stats.index.php

@@ -469,7 +469,8 @@
 <script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/layui/layui.js"></script>
 <link rel="stylesheet" type="text/css" href="<?php echo ADMIN_TEMPLATES_URL; ?>/layui/css/layui.css"/>
 <link rel="stylesheet" type="text/css" href="<?php echo RESOURCE_SITE_URL; ?>/js/jquery-ui/themes/ui-lightness/jquery.ui.css"/>
-<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-component.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-utils.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-amount.js?t=<?php echo time();?>"></script>
 <?php session_start(); ?>
 <script type="text/javascript">
     const systemTag = '<?php echo ADMIN_NAME;?>';

+ 2 - 1
admin/templates/default/refill_amount_stats.monthly_statement.php

@@ -334,7 +334,8 @@
 <script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/layui/layui.js"></script>
 <link rel="stylesheet" type="text/css" href="<?php echo ADMIN_TEMPLATES_URL; ?>/layui/css/layui.css"/>
 <link rel="stylesheet" type="text/css" href="<?php echo RESOURCE_SITE_URL; ?>/js/jquery-ui/themes/ui-lightness/jquery.ui.css"/>
-<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-component.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-utils.js?t=<?php echo time();?>"></script>
+<script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/stats-amount.js?t=<?php echo time();?>"></script>
 <script type="text/javascript" src="<?php echo ADMIN_TEMPLATES_URL;?>/js/xlsx.bundle.js"></script>
 <!-- https://github.com/gitbrent/xlsx-js-style -->
 

Plik diff jest za duży
+ 1737 - 0
admin/templates/default/refill_quota_stats.index.php


+ 31 - 0
test/TestlAmountStats.php

@@ -60,4 +60,35 @@ class TestlAmountStats extends TestCase
 
 
     }
+
+    public function testrefill_evidence()
+    {
+//        $condition_counts['mchid'] = ['in', [10283]];
+//        $condition_counts['status'] = 2;
+//        $counts = Model('')->table('refill_evidence')
+//            ->field('sum(amount) as amounts,mchid')
+//            ->where($condition_counts)
+//            ->group('mchid')
+//            ->select();
+
+
+        $provider_model = Model('refill_provider');
+        $condition_provider['opened'] = 1;
+        $condition_provider['refill_provider.store_id'] = ['in', [174]];
+        $provider_items = $provider_model->table('refill_provider,store')
+            ->field('refill_provider.name,refill_provider.qualitys,refill_provider.type,store.store_name,store.store_id,refill_provider.provider_id')
+            ->join('inner')
+            ->on('store.store_id=refill_provider.store_id')
+            ->where($condition_provider)
+            ->order('opened asc, name asc')
+            ->select();
+        $provider_ids = array_map(function($item) { return $item['provider_id']; }, $provider_items);
+        $condition_stat['provider_amount.provider_id'] = ['in', $provider_ids];
+        $counts = Model()->table('provider_amount,refill_provider')
+            ->where($condition_stat)
+            ->join('inner')->on('provider_amount.provider_id=refill_provider.provider_id')
+            ->field('sum(amount) as amounts, provider_amount.provider_id')
+            ->group('provider_amount.provider_id')
+            ->find();
+    }
 }