QuotationToolModel的实现,形成价格明细清单.md

Posted dy2903

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了QuotationToolModel的实现,形成价格明细清单.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

以上是关于QuotationToolModel的实现,形成价格明细清单.md的主要内容,如果未能解决你的问题,请参考以下文章

钙钛矿型催化材料的相关信息

回购价低于股价怎么办?

回购价低于股价怎么办?

单词demantoite翠榴石demantoite英语

早鸟价今晚结束:Linux内核tracers的实现原理与应用

ADAMoracle预言机可靠便捷低成本精准喂价实现生态发展