AX用产品型号分栏做统计

AX系统里,X++语言按月份+产品型号统计产量是常规操作,如果要把某个日期范围里生产过的产品型号做为栏(相当于字段)来统计生产数量,需要一些折中的代码来实现。

下面代码是实例,目的:输入从日期 和 到期期 两个参数,统计生产数量,要求每个月轮询,产品型号作为列头,结果输出到Excel里。

void ProdSum2Excel(TransDate _fromDate, TransDate _toDate)
{
    // Excel 對象
    SysExcelApplication   _excel;
    SysExcelWorkbooks     _books;
    SysExcelWorkbook      _book;
    SysExcelWorksheets    _sheets;
    SysExcelWorksheet     _sheet;
    SysExcelCells         _cells;

    // 統計用 Map: Key = "26年5月|SP|風筒", Value = 數量
    Map                   _dataMap;
    str                   _mapKey;
    int                   _mapValue;

    // 列表(保持輸出順序)
    List                  _monthList;
    ListEnumerator        _monthEnum;
    List                  _prodTypeList;
    ListEnumerator        _typeEnum;
    List                  _companyList;
    ListEnumerator        _compEnum;

    // 當前處理變量
    str                   _currentMonth;
    str                   _currentCompany;
    str                   _currentType;
    int                   _cellValue;
    int                   _rowTotal;
    int                   _colTotal;
    int                   _row;
    int                   _col;
    int                   _startRow;
    int                   _i;

    // 數據源變量
    SPL_DailyProd         _dailyProd;
    SPL_OrderList         _orderList;
    InventTable           _inventTable;
    ProdLine              _prodLine;
    TransDate             _prodDate;
    str                   _company;
    str                   _prodType;
    int                   _qty;
    str                   _monthKey;

    // 生成月份列表用變量
    TransDate             _tmpDate;
    int                   _tmpYear;
    int                   _tmpMonth;
    str                   _tmpMonthKey;
    int                   _loopCount;    // 防止死循環
    str                   _tmpStr;
    ;

    // 1. 初始化分公司列表(固定順序)
    _companyList = new List(Types::String);
    _companyList.addEnd("SP");
    _companyList.addEnd("JM");
    _companyList.addEnd("SR");
    //_companyList.addEnd("JA");    //不生產成品

    // 2. 根據 _fromDate 和 _toDate 生成月份列表
    _monthList = new List(Types::String);
    if (_fromDate > _toDate)
    {
        info("開始日期不能大於結束日期");
        return;
    }

    _tmpDate = _fromDate;
    _loopCount = 0;

    while (_tmpDate <= _toDate)
    {
        _loopCount++;
        if (_loopCount > 120)
        {
            info("月份循環超過安全限制,請檢查日期範圍");
            break;
        }

        _tmpYear = year(_tmpDate);
        _tmpMonth = MthOfYr(_tmpDate);
        _tmpMonthKey = strFmt("%1年%2月",
                        num2str(_tmpYear - 2000, 0, 0, 0, 0),
                        num2str(_tmpMonth, 0, 0, 0, 0));

        if (!element.isInList(_monthList, _tmpMonthKey))
        {
            _monthList.addEnd(_tmpMonthKey);
        }

        // 移到下個月1號(使用 dateMthFwd 更安全)
        _tmpDate = dateMthFwd(_tmpDate, 1);
        // 確保是下個月1號
        _tmpDate = mkDate(1, MthOfYr(_tmpDate), year(_tmpDate));
    }

    // 3. 初始化數據結構
    _dataMap = new Map(Types::String, Types::Integer);
    _prodTypeList = new List(Types::String);

    // 4. 從數據源讀取並統計數據(同時提取產品類型)
    while select SPL_ProdDate, SPL_DailyQty from _dailyProd
                    where   _dailyProd.SPL_ProdDate >= _fromDate
                    &&      _dailyProd.SPL_ProdDate <= _toDate
            join ItemId from _orderList
                    where   _orderList.SalesLineRecId   == _dailyProd.SalesLineRecId
            join SPL_NameAlias from _inventTable
                    where   _inventTable.ItemId == _orderList.ItemId
            join SPL_Factory from _prodLine
                    where   _prodLine.ProdLineId    == _dailyProd.ProdLineId
    {
        _prodDate = _dailyProd.SPL_ProdDate;
        _company = _prodLine.SPL_Factory;           // 分公司
        _prodType = _inventTable.SPL_NameAlias;     // 簡稱/產品類型
        _qty = _dailyProd.SPL_DailyQty;             // 完成數量

        // 生成月份Key: "26年5月"
        _monthKey = strFmt("%1年%2月",
                    num2str(year(_prodDate) - 2000, 0, 0, 0, 0),
                    num2str(MthOfYr(_prodDate), 0, 0, 0, 0));

        // 動態收集產品類型(只收集過濾後數據中出現的)
        if (_prodType != "" && !element.isInList(_prodTypeList, _prodType))
        {
            _prodTypeList.addEnd(_prodType);
        }

        // Map Key: "26年5月|SP|風筒"
        _mapKey = strFmt("%1|%2|%3", _monthKey, _company, _prodType);

        if (_dataMap.exists(_mapKey))
        {
            _mapValue = _dataMap.lookup(_mapKey) + _qty;
            _dataMap.remove(_mapKey);
            _dataMap.insert(_mapKey, _mapValue);
        }
        else
        {
            _dataMap.insert(_mapKey, _qty);
        }
    }

    // 5. 創建 Excel
    _excel = SysExcelApplication::construct();
    _excel.visible(false);

    _books = _excel.workbooks();
    _book = _books.add();
    _sheets = _book.worksheets();
    _sheet = _sheets.itemFromNum(1);
    _tmpStr= strFmt("%1至%2生產統計",date2str(_fromDate,321,2,3,2,3,2),date2str(_toDate,321,2,3,2,3,2));
    _sheet.name(_tmpStr);
    _cells = _sheet.cells();

    _row = 1;

    // 6. 按月份循環輸出
    _monthEnum = _monthList.getEnumerator();
    while (_monthEnum.moveNext())
    {
        _currentMonth = _monthEnum.current();

        // ---- 輸出月份標題 ----
        _cells.item(_row, 1).value(_currentMonth);
        _row++;

        // ---- 輸出表頭 ----
        _col = 1;
        _cells.item(_row, _col).value("分公司");
        _col++;

        _typeEnum = _prodTypeList.getEnumerator();
        while (_typeEnum.moveNext())
        {
            _cells.item(_row, _col).value(_typeEnum.current());
            _col++;
        }
        _cells.item(_row, _col).value("小計");
        _row++;

        _startRow = _row;

        // ---- 輸出各分公司數據 ----
        _compEnum = _companyList.getEnumerator();
        while (_compEnum.moveNext())
        {
            _currentCompany = _compEnum.current();
            _col = 1;
            _cells.item(_row, _col).value(_currentCompany);
            _col++;

            _rowTotal = 0;

            _typeEnum = _prodTypeList.getEnumerator();
            while (_typeEnum.moveNext())
            {
                _currentType = _typeEnum.current();
                _mapKey = strFmt("%1|%2|%3", _currentMonth, _currentCompany, _currentType);

                if (_dataMap.exists(_mapKey))
                {
                    _cellValue = _dataMap.lookup(_mapKey);
                    _cells.item(_row, _col).value(_cellValue);
                    _rowTotal += _cellValue;
                }
                else
                {
                    _cells.item(_row, _col).value(0);
                }
                _col++;
            }

            // 小計
            _cells.item(_row, _col).value(_rowTotal);
            _row++;
        }

        // ---- 輸出累計行 ----
        _col = 1;
        _cells.item(_row, _col).value("累計");
        _col++;

        _typeEnum = _prodTypeList.getEnumerator();
        while (_typeEnum.moveNext())
        {
            _currentType = _typeEnum.current();
            _colTotal = 0;

            // 遍歷各分公司,累加該產品類型的值
            _compEnum = _companyList.getEnumerator();
            while (_compEnum.moveNext())
            {
                _currentCompany = _compEnum.current();
                _mapKey = strFmt("%1|%2|%3", _currentMonth, _currentCompany, _currentType);

                if (_dataMap.exists(_mapKey))
                {
                    _colTotal += _dataMap.lookup(_mapKey);
                }
            }

            _cells.item(_row, _col).value(_colTotal);
            _col++;
        }

        // 累計小計 = 各分公司小計之和
        _colTotal = 0;
        _compEnum = _companyList.getEnumerator();
        while (_compEnum.moveNext())
        {
            _currentCompany = _compEnum.current();
            _rowTotal = 0;
            _typeEnum = _prodTypeList.getEnumerator();
            while (_typeEnum.moveNext())
            {
                _currentType = _typeEnum.current();
                _mapKey = strFmt("%1|%2|%3", _currentMonth, _currentCompany, _currentType);

                if (_dataMap.exists(_mapKey))
                {
                    _rowTotal += _dataMap.lookup(_mapKey);
                }
            }
            _colTotal += _rowTotal;
        }
        _cells.item(_row, _col).value(_colTotal);

        _row++;
        _row++;  // 空一行分隔
    }

    // 7. 自動調整列寬
    _col = 2 + _prodTypeList.elements();
    for (_i = 1; _i <= _col; _i++)
    {
        //_sheet.columns().item(_i).autoFit();
        _sheet.columns().item(_i).comObject().columnWidth(8.8);
        _sheet.columns().item(_i).numberFormat("#,##0");
        _sheet.columns().item(_i).comObject().horizontalAlignment(-4108);
    }

    // 8. 直接顯示 Excel,讓用戶手工保存
    _excel.visible(true);
    info("Excel 報表已生成,請切換到Excel程式");
}

上述代码调用了本FORM中中外两个Method,如下:

//列號轉字母(1->A, 2->B...)
str colNumToLetter(int _col)
{
    str _result = "";
    int _dividend = _col;
    int _modulo;
    ;
    while (_dividend > 0)
    {
        _modulo = (_dividend - 1) mod 26;
        _result = num2char(65 + _modulo) + _result;
        _dividend = (_dividend - 1) / 26;
    }

    return _result;
}


//檢查字符串是否在List中
boolean isInList(List _list, str _value)
{
    ListEnumerator _enum;
    ;
    _enum = _list.getEnumerator();
    while (_enum.moveNext())
    {
        if (_enum.current() == _value)
            return true;
    }
    return false;
}

发表评论