zoukankan      html  css  js  c++  java
  • 【QuotationTool】Model的实现(二),形成价格明细清单.md

    项目链接:https://gitee.com/xyjtysk/quotationTools

    【QuotationTool】Model的实现(一),获得Excel路径以及Excel输出格式里面我们已经获得了Excel的路径,已经规定好了输出和输出有哪些列,下面就可以开始正式转换了。

    预处理

    由Controller进行调度

    首先自然是读取Excel,我们在Controller里面调用XlrdTool中的getAssociativeArray

    lists = XlrdTool().getAssociativeArray(inputPath, sheetName, inputParam.keys())
    

    我们知道Controller其实是数据的中转站,所以其他Model处理以后的lists都要发到Controller中。

    接下来就是调度rehandleModelClass.py进行预处理了

            rehandleInstance = M("rehandle");
            rehandleInstance.assign(lists);
            lists = rehandleInstance.doRehandel(diffList);
    

    那么我们来看一下rehandleModelClass.py是怎么实现的。

    rehandleModelClass

    这个Model主要是对读入的数组进行预处理,主要是

            # 删除含有#的行
            self.removeRows();
            # 加行
            self.addRow();
            # 加colorTag
            self.addColorTag();
            # 加列
            self.addColumns(diffList);
    

    因为读入的Excel可能不规范,比如没有总计行或者小计行等,所以我们需要把这些行加上。

    然后加上ColorTag

    最后按照输出的keys把不存在的列加上。

    removeRows

    功能:删除不需要的行

    我们在分析需求的时候就说过,官方的报价清单里面冗余太多,需要删除

    那怎么删除呢?当然是遍历数组,对符合条件的删除呗。

    这就有一个问题,删除以后iterator就改变了,所以最后的结果会乱七八糟

    有什么办法可以解决吗?可以参考Python的list循环遍历中,删除数据的正确方法

        def removeRows(self):
            # 逆序遍历,否者一边删除一边iterator就改变了
            for aList in self.lists[::-1]:
                try:
                    if set(['BOM','typeID','ID']) < set(aList.keys()) and str (aList['BOM']).find("#") != -1 and aList['ID'] == "" and aList['typeID'] == "":
                        self.lists.remove(aList);
                        info("删除了含有#的行");
                    elif 'description' in aList.keys() and str(aList['description']).find('Factory integrated') != -1:
                        self.lists.remove(aList);
                        info("删除了含有Factory integrated行");
                        # 单独删除NHCT导出模板中的含截止日期行
                    elif 'unitsNetPrice' in aList.keys() and str(aList['unitsNetPrice']).find(u'截止日期') != -1:
                        self.lists.remove(aList);
                        info("删除了含有截止日期的行");
                    elif 'ID' in aList.keys() and str(aList['ID']).find(u'价格明细清单')  != -1:
                        self.lists.remove(aList);
                        info("删除了含有价格明细清单的行");
                        # 删除空行,取出所有的values,通过map全部变为str类型,然后转换为list,最后串接在一起。
                    elif len("".join(list(map(str,aList.values())))) == 0 :
                        self.lists.remove(aList);
                        info("删除空行");
                    else:
                        continue;
                except Exception as data:
                    error("删除空行时,超出表格范围"+str(data));
                
    

    加行

    因为输入的Excel可能不含有小计行等,我们需要再进行一次遍历,把该加上行的地方加上行。

        def getRow (self , key , value):
            # 先全部填上空白
            row = {};
            for k in self.lists[0].keys():
                row[k] = "";
            
            row[key] = value;
            return row;
        # **************加上小计行、总计行**************
        def addRow(self):
            # 遍历lists,插入小计行、总计行
            try:
                aDiff = [i for i in ['BOM','typeID','description']  if i in self.lists[0].keys()];
                colTag = aDiff[0];
                for i in range(len(self.lists) - 1 , 1 , -1):
                    list = self.lists[i];
                    if list['ID']  != "" and self.lists[i-1][colTag] != '小计':
                        self.lists.insert(i,self.getRow(colTag,'小计'));
                        info ('在第'+str(i)+'行增加了小计行')
    
                if self.lists[-1][colTag] != '总计':
                    self.lists.append(self.getRow(colTag,'总计'));
                    info ('在最后一行增加了总计行')
    
                if self.lists[-2][colTag] != '小计':
                    self.lists.insert(len(self.lists)-1 , self.getRow(colTag,'小计'));
                    info ('在倒数第二行增加了小计行')
            except Exception as data:
                error(data);
                error ("addRow函数中")
    
    

    加列

    我们把要加的列放到inputVariable.py中的diff数组中

    比如

    diff = {
        'totalNum':'0',
        "unit":"个",
        "billType":"增值税",
        "taxRate":"17%"
        };
    

    再动态的从inputVariable.py里面读取diff数组

    
        # **************加列    **************        
        def addColumns (self , diffList):
            var = __import__("libs.inputVariable");
            inputvar = getattr(var , "inputVariable");
            diff = getattr(inputvar , "diff");
            for arr in self.lists:
                if arr['colorTag'] == "general":
                    for d in diffList:
                        # 查找到相应的字段则直接复制,没查找到的则为空
                        arr[d] = diff.get(d) if diff.get(d) != None  else "";
                else:
                    for d in diffList:
                        arr[d] = "";
    

    这样就可以灵活的扩展输出的了。

    加颜色标签

    遍历数组加上colorTag,用于区别不同的行的角色,主要有

    • header:标题
    • site:设备的标题
    • subtotal:小计
    • total:总计
    • general:其他
        # **************加颜色标签**************        
        def addColorTag (self)        :
            try:
                aDiff = [i for i in ['BOM','typeID','description']  if i in self.lists[0].keys()];
                colTag = aDiff[0];
                for aList in self.lists:
                    if aList[colTag] == "小计":
                        aList['colorTag'] = "subtotal";
                    elif aList[colTag] == "总计":
                        aList['colorTag'] = "total";
                    elif aList['ID'] != "":
                        aList['colorTag'] = 'site';
                    else:
                        aList['colorTag'] = "general";
                        
                self.lists[0]['colorTag'] = "header"
            except Exception as data:
                error(data)
                error("缺少字段");
    
    

    image.png

    添加公式

    预处理完了就把相应的公式添加上就可以了,对应formulaModelClass.py

    处理数量列

    从NHCT导出来的文档有个特点,每套设备的配置的第一行一定是主机,也就是说它的数量代表着有多少套设备

    image.png

    这样其他行只要除以设备数就可以得到单套设备的配置了

    如何区分site

    那么就有个问题了,怎么区分不同的设备呢?

    我们可以使用

    • self.aSite:数组,存放site行的序号

    • self.aSubtotal:数组,存放小计行的序号

    • self.aTotal :存放标题的序号

    这样就知道每套设备从那里开始呢

    那怎么获得这些数组呢?

    遍历一下即可。

        def getSubtotalIndex (self):
            self.aSite = [];
            self.aSubtotal = [];
            self.aTotal = 0;
            # 遍历数组,根据colorTag来进行判断
            for i , arr in enumerate (self.lists):
                if arr['colorTag'] == 'site':
                    self.aSite.append(i);
                elif arr['colorTag'] == 'subtotal':
    
                    self.aSubtotal.append(i);
                elif arr['colorTag'] == 'total':
                    self.aTotal = i;
                else:
                    continue;
    
            self.aHeader = 0;
    

    添加“单套数量”列

    关键代码如下:

    # 从aSite数组里面取出site所在行的行号
    for i , s in enumerate(self.aSite):
        # 如果site标题所在行的quantity为空,同时在'BOM'那一列没有BTO的字样时
        if self.lists[s]['quantity'] == "" and self.lists[s][tag].find("BTO") == -1:
            # 获得到了套数
            Qty = int (self.lists[s+1]['quantity']);
            # 配置开始行均为主机,所以他的quantity实际就是套数
            self.lists[s]['quantity'] = Qty
            # 将剩下的都除以套数
            for j in range(s + 1 , self.aSubtotal[i]):
                self.lists[j]['quantity']= int(self.lists[j]['quantity'])/Qty;
     
    

    首先从Site序号数组中获得site在那里,它的下一行即为主机

    取出主机的数量,即为设备实际的套数

    剩下的行都除以套数即可得到单套配置

    添加总数量列

    总数量列需要添加公式

    关键代码如下:

    for i , s in enumerate(self.aSite):
        # siteInitial代表表格中显示的site起始行(表格是从1开始)
        siteInitial = str(s + 1); 
        for j in range(s + 1 , self.aSubtotal[i]-1 + 1):
            self.lists[j]['totalQuantity'] = '=$' + self.dCol['quantity'] + "$" + siteInitial + "*" + self.dCol['quantity'] + str (j + 1);
    
    

    i表示site在aSite数组里面的序号,s表示每个site的序号

    需要注意的是Excel是从1开始的,而数组一般是从0开始的,所以在Excel里面site的序号 = s + 1

    最后一行我们可以详细的说一下:

    '=$' + self.dCol['quantity'] + "$" + siteInitial + "*" + self.dCol['quantity'] + str (j + 1);

    • 在看self.dCol['quantity']表示什么意思之前,我们可以看一下assign函数里面有这样一段
      image.png
    colOrdinal = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
                          'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
            # 先组合成为dict
    self.dCol = dict(zip(self.outputKeys, colOrdinal));
    

    colOrdinal其实就是A~Z,它与outputKeys一起组成一个dict,这样的好处在于,我们可以通过self.dCol['quantity']获得数量列所在的下标。在上图中就是"E"

    • 那么"j"从那里来?

      for j in range(s + 1 , self.aSubtotal[i]-1 + 1):

      也就是说j表示每套设备的配置细节行。

      self.aSubtotal[i]-1表示小计行前一行,然后+1就可以得到在Excel里面的行号

      所以这段表示对每套的配置进行遍历,加上这个总数量行的公式即可。

    总结一下,主要过程是

    • 对设备site数组进行遍历,可以得到每套设备的起始行号,

    • 然后对每套设备的详细配置项进行遍历,在每一行加上总数量的公式

    • 注意Excel的行号与python 的数组的行号不同。

    把这一小节理解了,后面其他的函数基本上都是沿着这个思路来写的

    比如说重构单价列

    # # **************重构单价列**************
    def rehandleUnitPrice(self):
        try :
            for i , s in enumerate(self.aSite):
                siteInitial = str(s + 1 );
                for j in range(s + 1 , self.aSubtotal[i] - 1+ 1 ):
                    self.lists[j]['unitsNetPrice'] = '=' + self.dCol['unitsNetListPrice'] + str(j+1) + "*" + self.dCol['discount'] + str(j + 1 );                
                
        except Exception as data:
            error('缺少price字段' + str(data));
    
    

    添加总价列

    def addTotalPrice (self):
        try :
            for i , s in enumerate(self.aSite):
                siteInitial = str(s + 1 );
                for j in range(s + 1 , self.aSubtotal[i] - 1 + 1):
                    self.lists[j]['totalPrice'] = '=' + self.dCol['unitsNetPrice'] + str(j+1) + "*" + self.dCol['totalQuantity'] + str(j+1);
                
        except Exception as data:
            error('缺少price字段' + str(data));
    

    image.png

    重构折扣列

    rehandleDisc主要目的是方便我们统一修改折扣。

    如下图所示
    image.png

    在每个site里面加一个折扣,它等于总计栏里面的折扣。

    而配置细项里面的折扣又等于对应site里面的折扣。

    这样只需要修改总计栏里面的折扣,就可以把全局的折扣改变了。

    然后再修改每套设备里面的折扣就可以了。

    缺点就是没有办法针对某些单板、模块进行折扣的修改。

    具体代码如下:

    # **************重构折扣列#######################
    def rehandleDisc(self):
        # 若输出含有折扣
        if 'discount' in self.outputKeys:
            # 在总计行上填上100%
            self.lists[-1]['discount'] = 1;
            for i , s in enumerate(self.aSite):
            # 所有的site上的off与总计行的off相等
                self.lists[s]['discount'] = '=' + self.dCol['discount'] + str(self.aTotal + 1);
            # 详细配置的disc列与site行的相等
                siteInitial = str(s + 1 );
                for j in range(s + 1 , self.aSubtotal[i] - 1+ 1 ):
                    self.lists[j]['discount'] = '=' + self.dCol['discount'] + siteInitial;
    

    添加小计和总计行的公式

    小计行公式

    小计行要做的主要有三件事:

    • 添加上“小计”字样,有些输出的表格里面可能不含有BOM或者description,我们要做一下判断

    • 添加单套设备的小计,用SUMPRODUCT来实现,本质上就是数量行与价格行一一相乘并相加
      image.png

    • 添加设备总单价公式,直接使用SUM就可以了。

    核心代码为:

    # 看typeID或者description谁在输入的列中
    aDiff = [i for i in ['typeID', 'description'] if i in self.outputKeys];
    tag = aDiff[0];
    # 在小计行的typeID或者description位处加上配置主机的型号
    for i,sub  in enumerate(self.aSubtotal):
        siteInitial = self.aSite[i] + 1;
        siteEnd = sub - 1;
        self.lists[sub][tag] = '';
        if 'typeID' in self.outputKeys and 'BOM' not in self.outputKeys:
            self.lists[sub]['typeID'] = '小计';
            
        self.lists[sub]['totalPrice'] = '=SUM(' + self.dCol['totalPrice'] + str(siteInitial + 1) + ":" + self.dCol['totalPrice'] + str(siteEnd + 1) + ")";
        # 单套总价格
        if getParser('inOutmode','outputMode') in ["internal",'HPE']:
            self.lists[sub]['unitsNetPrice'] = '=SUMPRODUCT(' + self.dCol['unitsNetPrice'] + str(siteInitial + 1) + ":" + self.dCol['unitsNetPrice'] + str(siteEnd + 1) + "," + self.dCol['quantity'] + str(siteInitial + 1 ) + ":" + self.dCol['quantity'] + str(siteEnd + 1) + ")";
    
    

    image.png

    添加总计

    总计的公式等于所有的当前列加起来,除以二。这是因为每套设备的价格明细之和与小计相等,如果把所有的行加起来,说明算了两次,除以二即可。
    image.png

        self.lists[-1]['totalPrice'] = '=SUM(' + self.dCol['totalPrice'] + '2:' + self.dCol['totalPrice'] + str(self.aTotal) + ')/2';
    

    controller进行调度

    最后我们来看一下controller是如何调度上述的代码的

    # —————————————————————————参数准备—————————————————————————
            # 分别获取输入和输出文件的名称
            var = __import__("libs.inputVariable")
            inputvar = getattr(var,"inputVariable")        
            inputFile = M("file").getProjectName();
            outputFile = M("outputfile").getOutputFile(inputFile);
            info("打开的文件是" + inputFile);
            # 获得输入和输出的keys
            [inputParam , outputParam] = M("parameter").getParameter(inputFile);
            # 以quotationTools的根目录作为基准
            basepath = os.path.dirname(os.path.dirname(os.path.dirname(__file__)));
            inputPath = os.path.join(basepath,getParser('path','inputfilePath'),inputFile);
            outputPath = os.path.join(basepath,getParser('path','outputfilePath'),outputFile);
            # 主sheetName
            sheetName = '价格明细清单';
            # 添加公式
            iFormula = M("formula");
            iFormula.assign(lists, list(outputParam.keys()));
            lists = iFormula.addFormula();
            # 替换首行为想让他输出的模式
            for k in outputParam.keys():
                lists[0][k] = outputParam[k];
    

    image.png

  • 相关阅读:
    importlib
    js给kindeditor添加值
    在kindeditor 获取textarea 中 输入的值
    获取lable选中时触发事件
    Django之ModelForm组件
    KindEditor 和 xss过滤
    from 动态显示select数据
    CBV 验证装饰器的使用
    views获取数据 -- request包含的方法
    django -- 自定义simpletag 和 filter
  • 原文地址:https://www.cnblogs.com/dy2903/p/8466877.html
Copyright © 2011-2022 走看看