【QuotationTool】Model的實(shí)現(xiàn)(二),形成價(jià)格明細(xì)清單.md

項(xiàng)目鏈接:https://gitee.com/duyang2903/quotationTools

【QuotationTool】Model的實(shí)現(xiàn)(一),獲得Excel路徑以及Excel輸出格式里面我們已經(jīng)獲得了Excel的路徑,已經(jīng)規(guī)定好了輸出和輸出有哪些列,下面就可以開始正式轉(zhuǎn)換了。

預(yù)處理

由Controller進(jìn)行調(diào)度

首先自然是讀取Excel,我們?cè)贑ontroller里面調(diào)用XlrdTool中的getAssociativeArray

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

我們知道Controller其實(shí)是數(shù)據(jù)的中轉(zhuǎn)站,所以其他Model處理以后的lists都要發(fā)到Controller中。

接下來就是調(diào)度rehandleModelClass.py進(jìn)行預(yù)處理了

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

那么我們來看一下rehandleModelClass.py是怎么實(shí)現(xiàn)的。

rehandleModelClass

這個(gè)Model主要是對(duì)讀入的數(shù)組進(jìn)行預(yù)處理,主要是

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

因?yàn)樽x入的Excel可能不規(guī)范,比如沒有總計(jì)行或者小計(jì)行等,所以我們需要把這些行加上。

然后加上ColorTag

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

removeRows

功能:刪除不需要的行

我們?cè)诜治鲂枨蟮臅r(shí)候就說過,官方的報(bào)價(jià)清單里面冗余太多,需要?jiǎng)h除

那怎么刪除呢?當(dāng)然是遍歷數(shù)組,對(duì)符合條件的刪除唄。

這就有一個(gè)問題,刪除以后iterator就改變了,所以最后的結(jié)果會(huì)亂七八糟

有什么辦法可以解決嗎?可以參考Python的list循環(huán)遍歷中,刪除數(shù)據(jù)的正確方法

    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行");
                    # 單獨(dú)刪除NHCT導(dǎo)出模板中的含截止日期行
                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'價(jià)格明細(xì)清單')  != -1:
                    self.lists.remove(aList);
                    info("刪除了含有價(jià)格明細(xì)清單的行");
                    # 刪除空行,取出所有的values,通過map全部變?yōu)閟tr類型,然后轉(zhuǎn)換為list,最后串接在一起。
                elif len("".join(list(map(str,aList.values())))) == 0 :
                    self.lists.remove(aList);
                    info("刪除空行");
                else:
                    continue;
            except Exception as data:
                error("刪除空行時(shí),超出表格范圍"+str(data));
            

加行

因?yàn)檩斎氲腅xcel可能不含有小計(jì)行等,我們需要再進(jìn)行一次遍歷,把該加上行的地方加上行。

    def getRow (self , key , value):
        # 先全部填上空白
        row = {};
        for k in self.lists[0].keys():
            row[k] = "";
        
        row[key] = value;
        return row;
    # **************加上小計(jì)行、總計(jì)行**************
    def addRow(self):
        # 遍歷lists,插入小計(jì)行、總計(jì)行
        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] != '小計(jì)':
                    self.lists.insert(i,self.getRow(colTag,'小計(jì)'));
                    info ('在第'+str(i)+'行增加了小計(jì)行')

            if self.lists[-1][colTag] != '總計(jì)':
                self.lists.append(self.getRow(colTag,'總計(jì)'));
                info ('在最后一行增加了總計(jì)行')

            if self.lists[-2][colTag] != '小計(jì)':
                self.lists.insert(len(self.lists)-1 , self.getRow(colTag,'小計(jì)'));
                info ('在倒數(shù)第二行增加了小計(jì)行')
        except Exception as data:
            error(data);
            error ("addRow函數(shù)中")

加列

我們把要加的列放到inputVariable.py中的diff數(shù)組中

比如

diff = {
    'totalNum':'0',
    "unit":"個(gè)",
    "billType":"增值稅",
    "taxRate":"17%"
    };

再動(dòng)態(tài)的從inputVariable.py里面讀取diff數(shù)組


    # **************加列    **************        
    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:
                    # 查找到相應(yīng)的字段則直接復(fù)制,沒查找到的則為空
                    arr[d] = diff.get(d) if diff.get(d) != None  else "";
            else:
                for d in diffList:
                    arr[d] = "";

這樣就可以靈活的擴(kuò)展輸出的了。

加顏色標(biāo)簽

遍歷數(shù)組加上colorTag,用于區(qū)別不同的行的角色,主要有

  • header:標(biāo)題
  • site:設(shè)備的標(biāo)題
  • subtotal:小計(jì)
  • total:總計(jì)
  • general:其他
    # **************加顏色標(biāo)簽**************        
    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] == "小計(jì)":
                    aList['colorTag'] = "subtotal";
                elif aList[colTag] == "總計(jì)":
                    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

添加公式

預(yù)處理完了就把相應(yīng)的公式添加上就可以了,對(duì)應(yīng)formulaModelClass.py

處理數(shù)量列

從NHCT導(dǎo)出來的文檔有個(gè)特點(diǎn),每套設(shè)備的配置的第一行一定是主機(jī),也就是說它的數(shù)量代表著有多少套設(shè)備

image.png

這樣其他行只要除以設(shè)備數(shù)就可以得到單套設(shè)備的配置了

如何區(qū)分site

那么就有個(gè)問題了,怎么區(qū)分不同的設(shè)備呢?

我們可以使用

  • self.aSite:數(shù)組,存放site行的序號(hào)

  • self.aSubtotal:數(shù)組,存放小計(jì)行的序號(hào)

  • self.aTotal :存放標(biāo)題的序號(hào)

這樣就知道每套設(shè)備從那里開始呢

那怎么獲得這些數(shù)組呢?

遍歷一下即可。

    def getSubtotalIndex (self):
        self.aSite = [];
        self.aSubtotal = [];
        self.aTotal = 0;
        # 遍歷數(shù)組,根據(jù)colorTag來進(jìn)行判斷
        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;

添加“單套數(shù)量”列

關(guān)鍵代碼如下:

# 從aSite數(shù)組里面取出site所在行的行號(hào)
for i , s in enumerate(self.aSite):
    # 如果site標(biāo)題所在行的quantity為空,同時(shí)在'BOM'那一列沒有BTO的字樣時(shí)
    if self.lists[s]['quantity'] == "" and self.lists[s][tag].find("BTO") == -1:
        # 獲得到了套數(shù)
        Qty = int (self.lists[s+1]['quantity']);
        # 配置開始行均為主機(jī),所以他的quantity實(shí)際就是套數(shù)
        self.lists[s]['quantity'] = Qty
        # 將剩下的都除以套數(shù)
        for j in range(s + 1 , self.aSubtotal[i]):
            self.lists[j]['quantity']= int(self.lists[j]['quantity'])/Qty;
 

首先從Site序號(hào)數(shù)組中獲得site在那里,它的下一行即為主機(jī)

取出主機(jī)的數(shù)量,即為設(shè)備實(shí)際的套數(shù)

剩下的行都除以套數(shù)即可得到單套配置

添加總數(shù)量列

總數(shù)量列需要添加公式

關(guān)鍵代碼如下:

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數(shù)組里面的序號(hào),s表示每個(gè)site的序號(hào)

需要注意的是Excel是從1開始的,而數(shù)組一般是從0開始的,所以在Excel里面site的序號(hào) = s + 1

最后一行我們可以詳細(xì)的說一下:

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

  • 在看self.dCol['quantity']表示什么意思之前,我們可以看一下assign函數(shù)里面有這樣一段


    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其實(shí)就是A~Z,它與outputKeys一起組成一個(gè)dict,這樣的好處在于,我們可以通過self.dCol['quantity']獲得數(shù)量列所在的下標(biāo)。在上圖中就是"E"

  • 那么"j"從那里來?

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

    也就是說j表示每套設(shè)備的配置細(xì)節(jié)行。

    self.aSubtotal[i]-1表示小計(jì)行前一行,然后+1就可以得到在Excel里面的行號(hào)

    所以這段表示對(duì)每套的配置進(jìn)行遍歷,加上這個(gè)總數(shù)量行的公式即可。

總結(jié)一下,主要過程是

  • 對(duì)設(shè)備site數(shù)組進(jìn)行遍歷,可以得到每套設(shè)備的起始行號(hào),

  • 然后對(duì)每套設(shè)備的詳細(xì)配置項(xiàng)進(jìn)行遍歷,在每一行加上總數(shù)量的公式

  • 注意Excel的行號(hào)與python 的數(shù)組的行號(hào)不同。

把這一小節(jié)理解了,后面其他的函數(shù)基本上都是沿著這個(gè)思路來寫的

比如說重構(gòu)單價(jià)列

# # **************重構(gòu)單價(jià)列**************
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));

添加總價(jià)列

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

重構(gòu)折扣列

rehandleDisc主要目的是方便我們統(tǒng)一修改折扣。

如下圖所示


image.png

在每個(gè)site里面加一個(gè)折扣,它等于總計(jì)欄里面的折扣。

而配置細(xì)項(xiàng)里面的折扣又等于對(duì)應(yīng)site里面的折扣。

這樣只需要修改總計(jì)欄里面的折扣,就可以把全局的折扣改變了。

然后再修改每套設(shè)備里面的折扣就可以了。

缺點(diǎn)就是沒有辦法針對(duì)某些單板、模塊進(jìn)行折扣的修改。

具體代碼如下:

# **************重構(gòu)折扣列#######################
def rehandleDisc(self):
    # 若輸出含有折扣
    if 'discount' in self.outputKeys:
        # 在總計(jì)行上填上100%
        self.lists[-1]['discount'] = 1;
        for i , s in enumerate(self.aSite):
        # 所有的site上的off與總計(jì)行的off相等
            self.lists[s]['discount'] = '=' + self.dCol['discount'] + str(self.aTotal + 1);
        # 詳細(xì)配置的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;

添加小計(jì)和總計(jì)行的公式

小計(jì)行公式

小計(jì)行要做的主要有三件事:

  • 添加上“小計(jì)”字樣,有些輸出的表格里面可能不含有BOM或者description,我們要做一下判斷

  • 添加單套設(shè)備的小計(jì),用SUMPRODUCT來實(shí)現(xiàn),本質(zhì)上就是數(shù)量行與價(jià)格行一一相乘并相加


    image.png
  • 添加設(shè)備總單價(jià)公式,直接使用SUM就可以了。

核心代碼為:

# 看typeID或者description誰在輸入的列中
aDiff = [i for i in ['typeID', 'description'] if i in self.outputKeys];
tag = aDiff[0];
# 在小計(jì)行的typeID或者description位處加上配置主機(jī)的型號(hào)
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'] = '小計(jì)';
        
    self.lists[sub]['totalPrice'] = '=SUM(' + self.dCol['totalPrice'] + str(siteInitial + 1) + ":" + self.dCol['totalPrice'] + str(siteEnd + 1) + ")";
    # 單套總價(jià)格
    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

添加總計(jì)

總計(jì)的公式等于所有的當(dāng)前列加起來,除以二。這是因?yàn)槊刻自O(shè)備的價(jià)格明細(xì)之和與小計(jì)相等,如果把所有的行加起來,說明算了兩次,除以二即可。


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

controller進(jìn)行調(diào)度

最后我們來看一下controller是如何調(diào)度上述的代碼的

# —————————————————————————參數(shù)準(zhǔn)備—————————————————————————
        # 分別獲取輸入和輸出文件的名稱
        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的根目錄作為基準(zhǔn)
        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 = '價(jià)格明細(xì)清單';
        # 添加公式
        iFormula = M("formula");
        iFormula.assign(lists, list(outputParam.keys()));
        lists = iFormula.addFormula();
        # 替換首行為想讓他輸出的模式
        for k in outputParam.keys():
            lists[0][k] = outputParam[k];
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容