├── .gitignore ├── 1. 财务明细数据.csv ├── 2. shops.csv ├── 3. result.csv ├── README.md ├── images.qrc ├── images_pyqt.py ├── logo.ico └── pyqt5_process_csv_data.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | venv3/ 4 | build/ 5 | dist/ 6 | Madman.spec 7 | -------------------------------------------------------------------------------- /1. 财务明细数据.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangy8961/python3-pyqt5-process-csv-data/1535d9326b78e83e997fc06f88627d353179cd9c/1. 财务明细数据.csv -------------------------------------------------------------------------------- /2. shops.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangy8961/python3-pyqt5-process-csv-data/1535d9326b78e83e997fc06f88627d353179cd9c/2. shops.csv -------------------------------------------------------------------------------- /3. result.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangy8961/python3-pyqt5-process-csv-data/1535d9326b78e83e997fc06f88627d353179cd9c/3. result.csv -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [python3-pyqt5-process-csv-data](http://www.madmalls.com/blog/post/process-csv-data-and-pyqt5/) 2 | 3 | [![Python](https://img.shields.io/badge/Python-v3.6.1-brightgreen.svg)](https://www.python.org/) 4 | [![PyQt5](https://img.shields.io/badge/PyQt5-v5.11.2-orange.svg)](https://www.riverbankcomputing.com/software/pyqt/intro) 5 | [![PyInstaller](https://img.shields.io/badge/pyinstaller-v3.4-lightgrey.svg)](https://www.pyinstaller.org/) 6 | 7 | 8 | 9 | ![](http://www.madmalls.com/api/medias/uploaded/process-csv-01-54579793.png) 10 | 11 | ![](http://www.madmalls.com/api/medias/uploaded/process-csv-02-96422444.png) 12 | 13 | ![](http://www.madmalls.com/api/medias/uploaded/process-csv-03-1e3614f0.png) 14 | 15 | ![](http://www.madmalls.com/api/medias/uploaded/process-csv-04-0075221d.png) 16 | 17 | 18 | 19 | # 1. 搭建环境 20 | 21 | 打开cmd命令行,切换到 D:\python-code\python3-pyqt5-process-csv-data 目录下 22 | 23 | ``` 24 | D:\python-code\python3-pyqt5-process-csv-data> python -m venv venv3 25 | ``` 26 | 27 | # 2. 激活 28 | 29 | ``` 30 | D:\python-code\python3-pyqt5-process-csv-data> venv3\Scripts\activate 31 | (venv3) D:\python-code\python3-pyqt5-process-csv-data> 32 | ``` 33 | 34 | # 3. 安装包 35 | 36 | ``` 37 | (venv3) D:\python-code\python3-pyqt5-process-csv-data> pip install pyqt5 38 | (venv3) D:\python-code\python3-pyqt5-process-csv-data> pip install pyinstaller 39 | ``` 40 | 41 | # 4. 图标 42 | 43 | 创建`images.qrc`,注意ico图标放在当前目录下的子目录img中: 44 | 45 | ``` 46 | 47 | 48 | img/logo.ico 49 | 50 | 51 | ``` 52 | 53 | 生成`images_pyqt.py`,去文件目录下执行: 54 | 55 | ``` 56 | (venv3) D:\python-code\python3-pyqt5-process-csv-data> pyrcc5 -o images_pyqt.py images.qrc 57 | ``` 58 | 59 | 最后在代码中`import images_pyqt`,并且修改下图片路径,一定要在路径前面加上`冒号`: 60 | 61 | ```python 62 | import images_pyqt 63 | 64 | def init_ui(self): 65 | self.setWindowIcon(QIcon(':/img/logo.ico')) # 图标 66 | ``` 67 | 68 | # 5. 打包成exe 69 | 70 | ``` 71 | (venv3) D:\python-code\python3-pyqt5-process-csv-data> pyinstaller --name Madman --onefile --windowed --icon=D:\python-code\python3-pyqt5-process-csv-data\logo.ico -w --paths=D:\python-code\python3-pyqt5-process-csv-data\venv3\Lib\site-packages --paths=D:\python-code\python3-pyqt5-process-csv-data pyqt5_process_csv_data.py 72 | ``` -------------------------------------------------------------------------------- /images.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | img/logo.ico 4 | 5 | -------------------------------------------------------------------------------- /images_pyqt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Resource object code 4 | # 5 | # Created by: The Resource Compiler for PyQt5 (Qt v5.11.1) 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore 10 | 11 | qt_resource_data = b"\ 12 | \x00\x00\x03\x83\ 13 | \x00\ 14 | \x00\x10\xbe\x78\x9c\xbd\xd6\x69\x4c\xd3\x60\x1c\xc7\xf1\xa7\x1b\ 15 | \x93\xc8\x36\x36\xd8\x66\xd4\x98\x6d\xc1\x18\x0c\x5e\x08\x98\x18\ 16 | \xaf\xf8\xce\x04\x4d\x8c\xef\x8c\x6f\x4c\x7c\xc1\x26\x1d\x72\x79\ 17 | \x06\x15\x14\xaf\xa8\x18\x8f\x77\xbe\x30\xd1\x37\x18\xb9\xd1\xf8\ 18 | \xd2\x23\x1a\x04\x15\x13\x54\x50\xa2\x99\xe3\x12\xd4\xa1\xf3\xdc\ 19 | \x10\xdc\xe3\xef\xd9\x5a\x32\xd9\x26\xa3\xab\x96\x7c\x28\x29\xa5\ 20 | \xdf\xf6\xbf\xd0\x96\x10\x0e\x5f\x56\x2b\x61\xdf\xc9\x55\x3d\x21\ 21 | \x33\x08\x21\xf3\x01\x9b\xc8\x5a\x12\xdc\x1e\x58\xf0\x3b\x83\x3a\ 22 | \x28\xda\xe2\xa8\x19\x67\x85\x32\x78\x03\x8d\xa0\x61\xdb\xe5\x5a\ 23 | \xaa\xc6\xb2\xc8\x19\xf8\xa3\x7b\x2d\xb0\x36\x43\x29\x3c\x85\x31\ 24 | \xa0\xe0\x12\xb6\xcb\xd4\xcd\x61\x6b\x33\xec\x14\xae\x95\x83\x74\ 25 | \xd8\x0f\x5d\xf0\x4b\xe8\x8a\x7a\xe2\xed\x57\x8d\x2d\x85\x4c\x0e\ 26 | \xcd\x74\xd8\x0f\x5d\xc0\x3a\x19\x50\x09\xce\x09\x4d\x59\xfa\x55\ 27 | \xa3\x59\x8c\x02\xad\x05\x70\x04\x9c\x40\x45\x38\x66\xef\x5f\xba\ 28 | \x92\xfb\x6c\xce\xa0\x84\x25\x70\x0a\x7a\x43\xbb\x21\xfd\x58\xc4\ 29 | \xdc\xcf\x7b\x60\x24\xdb\x6e\x19\x54\x05\x9d\xc6\xe5\x27\xbc\x0b\ 30 | \xcf\xa2\x31\x10\xa9\x2b\x77\xdf\x86\x2e\xa8\x60\xa5\xad\xd5\x78\ 31 | \xc9\xf1\x42\xe7\x3e\xf8\xc9\x1c\xb5\x2b\x57\xdf\xd6\x6a\x20\xf6\ 32 | \x47\xa9\x49\x58\xaf\x43\xfb\x32\x0c\x6f\x7f\x92\x4a\x0b\x7b\xd4\ 33 | \xb4\x64\x20\x99\x1e\xfb\x31\x1f\x9d\x6c\xd9\xfb\xb6\x16\x13\x6b\ 34 | \xab\xd1\xcb\xc5\xf5\xd6\x61\xfd\x0d\xa8\xbd\xcd\x40\x77\xbc\xd6\ 35 | \xd2\xa2\x3e\x75\xc0\xde\xf7\x26\x7a\xea\xe7\x12\xd9\xfa\x6c\xce\ 36 | \x68\x68\xd1\xdc\x84\x9f\xaf\xc3\x77\xd6\x15\xf1\xcf\xf5\xe3\x6d\ 37 | \x51\x85\xc7\x2a\x4b\xdf\xd6\x6e\xd2\xa3\xbb\x19\x9d\x9b\xe0\x0d\ 38 | \xed\x32\xdb\xdb\x31\x77\x97\x26\xac\x5f\xfa\x56\x47\x8f\x7b\x33\ 39 | \xa4\xf7\x6b\xb9\x41\xfb\x1d\xdd\x3e\xb4\xef\xa2\x33\x32\xb1\x1b\ 40 | \xd0\x6a\xa4\x05\xaf\x92\xc3\xda\x41\x49\x74\xdf\x87\x99\xf4\xf4\ 41 | \x68\xa6\xa4\x3e\x5f\xaf\xf0\xdb\xef\xa7\x8e\x45\xec\x8a\x73\x7f\ 42 | \x16\x3e\xf7\x50\xc5\xfd\x1a\x7a\xe8\x4b\x9a\xd4\x3e\x45\x3f\x6a\ 43 | \xdb\xfe\xd8\x40\x0b\xdf\x84\xcf\x7d\xa2\x9d\x83\x29\xf4\x84\x6f\ 44 | \xa1\xbc\x7d\x36\xf7\xee\x68\x73\x0f\x57\xe6\x9e\x8d\xcf\x61\xa9\ 45 | \x6c\xfd\xfc\x8e\x14\x5a\xd4\x1b\x5b\x3b\xf8\x39\x68\xe9\x91\x6f\ 46 | \xf3\xc6\xef\x09\xf1\xf4\xed\x0f\xf1\xbf\xee\xd4\xc6\xdc\x16\xed\ 47 | \x1e\x32\xd0\x93\x23\x8b\xe3\xee\xe3\x1e\x3b\xe5\xb6\xe8\xc0\xc7\ 48 | \x39\xf1\xf4\xfd\xb8\xc7\x7a\x0b\x7b\x34\x7e\xa9\xfd\x92\x01\x2d\ 49 | \x3d\xfa\x3d\x5d\x6a\xbf\x9f\xef\xd4\xef\xc2\x71\xde\x4a\xed\xb3\ 50 | \x7b\xc2\x9e\x77\x26\x29\x7d\x3f\x1c\xe6\x5f\x26\x2b\x70\x9c\x4a\ 51 | \xe9\xfd\x20\x09\xfd\x16\x98\xc5\x77\xea\x08\xfe\x7e\x36\xb4\xfe\ 52 | \xc7\xfe\x67\xd8\xc0\x9e\x41\xf9\x1d\x81\x3e\xb3\x11\x3c\xff\xa9\ 53 | \x7f\x4e\x78\xc7\x08\x3c\x83\x59\xbf\xb0\x4f\x9d\x80\xf5\xf9\x7f\ 54 | \xda\x6f\x50\x52\xfb\xbd\x94\x0e\x5b\x9b\x29\x4d\x6c\x8b\x8b\x30\ 55 | \x83\xb9\xf0\xfc\x5f\xf5\x1d\xf5\x8a\xcf\xf6\xdb\xc9\x5b\xf8\x66\ 56 | \x15\xc9\x6b\x89\xd8\x67\xb6\x82\x57\xe6\xfe\x57\x68\x72\xd4\x92\ 57 | \xf5\x7c\xf3\x34\x15\xdf\x98\x10\xf1\xdd\x4f\xe8\x27\x41\xb5\x4c\ 58 | \xfd\x61\xa8\xc6\x73\x3f\x17\xb3\x9f\xce\xd7\x2a\x26\x7d\xef\x15\ 59 | \xce\x61\x11\x38\xa7\xd0\x1f\x9e\xd0\x75\xc3\x15\x74\x57\xe3\x9a\ 60 | \x55\x10\xf3\x82\xfb\x09\x29\xea\xd7\xb0\x73\x28\x80\xd1\x49\xba\ 61 | \x6e\xb8\x02\x6b\x84\xee\x10\x5c\x44\x6f\x85\xa3\x8e\x43\x97\x8b\ 62 | \x3d\x1c\x3e\x03\x3d\xdc\x88\xd2\x1d\x82\x8b\xb0\x02\x54\x6c\x7f\ 63 | \x74\x2f\xa0\xb9\x8c\x6f\x4a\x50\xf2\x0d\x93\xcf\x39\xc6\x73\x58\ 64 | \x05\x83\x21\xdd\x1e\xb8\x80\xf9\xe4\xe0\xf9\xab\x64\x73\x12\x17\ 65 | \x47\x0d\xa7\x80\xb8\xbb\xa1\xfd\xe2\x5e\x35\xbb\x37\x97\x42\x37\ 66 | \x5c\x82\x6c\x60\xdb\xc2\xf6\xf7\x59\xa8\xdf\x65\xa1\xbe\x0a\x0b\ 67 | \x75\x11\x0b\xad\x20\x16\x3f\x21\x16\x1f\xb8\xa0\x82\x10\x33\x76\ 68 | \x9a\x39\xc5\x93\x60\x63\x4c\x14\xe8\x70\x0c\xcb\x9d\xa0\x72\x17\ 69 | \xe1\xa8\x87\x24\x52\x0f\x87\x64\x62\x39\xf5\x59\x7e\x03\xaa\x6c\ 70 | \x22\xaf\ 71 | " 72 | 73 | qt_resource_name = b"\ 74 | \x00\x03\ 75 | \x00\x00\x70\x37\ 76 | \x00\x69\ 77 | \x00\x6d\x00\x67\ 78 | \x00\x08\ 79 | \x05\xe2\x41\xff\ 80 | \x00\x6c\ 81 | \x00\x6f\x00\x67\x00\x6f\x00\x2e\x00\x69\x00\x63\x00\x6f\ 82 | " 83 | 84 | qt_resource_struct_v1 = b"\ 85 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ 86 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ 87 | \x00\x00\x00\x0c\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ 88 | " 89 | 90 | qt_resource_struct_v2 = b"\ 91 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ 92 | \x00\x00\x00\x00\x00\x00\x00\x00\ 93 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ 94 | \x00\x00\x00\x00\x00\x00\x00\x00\ 95 | \x00\x00\x00\x0c\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ 96 | \x00\x00\x01\x62\x4b\x49\xea\xf7\ 97 | " 98 | 99 | qt_version = [int(v) for v in QtCore.qVersion().split('.')] 100 | if qt_version < [5, 8, 0]: 101 | rcc_version = 1 102 | qt_resource_struct = qt_resource_struct_v1 103 | else: 104 | rcc_version = 2 105 | qt_resource_struct = qt_resource_struct_v2 106 | 107 | def qInitResources(): 108 | QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) 109 | 110 | def qCleanupResources(): 111 | QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) 112 | 113 | qInitResources() 114 | -------------------------------------------------------------------------------- /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangy8961/python3-pyqt5-process-csv-data/1535d9326b78e83e997fc06f88627d353179cd9c/logo.ico -------------------------------------------------------------------------------- /pyqt5_process_csv_data.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from decimal import Decimal 3 | import os 4 | import sys 5 | from PyQt5.QtWidgets import QApplication, QMainWindow, QGridLayout, QWidget, QPushButton, QLabel, QInputDialog, QFileDialog, QMessageBox, QCheckBox 6 | from PyQt5.QtGui import QIcon, QFont 7 | import images_pyqt 8 | 9 | basepath = os.path.abspath(os.path.dirname(__file__)) # 当前模块文件的根目录 10 | 11 | 12 | def get_reduced_money(s): 13 | '''获取每笔交易的使用了优惠券的金额''' 14 | int_part = s.split('.')[0] 15 | return int(int_part.replace(int_part[-1], '0')) 16 | 17 | 18 | def get_combination(reduced_money, c_par_1, c_par_2, c_par_3, c_max_1, c_max_2, c_max_3): 19 | '''算出各优惠券的数量组合''' 20 | combination = [] 21 | for i in range(c_max_1+1): 22 | for j in range(c_max_2+1): 23 | for k in range(c_max_3+1): 24 | if c_par_1 * i + c_par_2 * j + c_par_3 * k == reduced_money: 25 | combination.append((i, j, k)) 26 | return combination 27 | 28 | 29 | class Ui_MainWindow(QMainWindow): 30 | def __init__(self): 31 | super().__init__() 32 | self.c_par_1 = 0 # 优惠券1的面额 33 | self.c_max_1 = 0 # 优惠券1最大叠加的张数 34 | self.c_par_2 = 0 # 优惠券2的面额 35 | self.c_max_2 = 0 # 优惠券2最大叠加的张数 36 | self.c_par_3 = 0 # 优惠券3的面额 37 | self.c_max_3 = 0 # 优惠券3最大叠加的张数 38 | self.data_csv = None # 财务明细数据 csv 文件 39 | self.data = {} # 操作员账号是key, value是需要的列组成的字典 40 | self.faild_rows = [] # 程序无法计算出使用了多少张优惠券时,将财务明细数据 csv 文件中的该条记录保存到这里 41 | self.shops_csv = None # 店铺名称 csv 文件 42 | self.shops = {} # 操作员账号是key, 店铺名称是value 43 | self.rate = 0.6 # 实收金额的手续费率 % 44 | self.init_ui() 45 | 46 | def init_ui(self): 47 | self.setFixedSize(960, 640) # 窗口大小, 固定值,窗口不能放大 48 | self.setWindowIcon(QIcon(':/img/logo.ico')) # 图标 49 | self.setWindowTitle('王颜公子 - Python3 Process CSV Data') # 窗口标题 50 | self.statusBar().showMessage('Madman 2018. © All Rights Reserved Blog: http://www.madmalls.com') # 状态栏消息 51 | 52 | # 网格布局, 假设分成 12 列 53 | self.main_widget = QWidget() # 创建窗口主部件 54 | self.main_widget.setObjectName('main_widget') 55 | self.main_layout = QGridLayout() # 创建主部件的网格布局 56 | self.main_widget.setLayout(self.main_layout) # 设置窗口主部件布局为网格布局 57 | self.setCentralWidget(self.main_widget) # 设置窗口主部件 58 | 59 | # 第0行 60 | self.label_1 = QLabel('1. 请指定优惠券面额及最大叠加张数', self) 61 | self.label_1.setFont(QFont('微软雅黑', 10)) 62 | self.label_1.setStyleSheet('color: #ff9999') 63 | self.main_layout.addWidget(self.label_1, 0, 0, 1, 12) 64 | # 第1行 65 | self.btn_1 = QPushButton('修改', self) 66 | self.btn_1.clicked.connect(self.modify_coupon) 67 | self.main_layout.addWidget(self.btn_1, 1, 0, 1, 2) 68 | self.label_2 = QLabel('优惠券1面额(元): ', self) 69 | self.main_layout.addWidget(self.label_2, 1, 3, 1, 3) 70 | self.label_3 = QLabel('0', self) 71 | self.label_3.setFont(QFont('微软雅黑', 12)) 72 | self.label_3.setStyleSheet('color: #ff9999') 73 | self.main_layout.addWidget(self.label_3, 1, 6, 1, 1) 74 | # 第2行 75 | self.btn_2 = QPushButton('修改', self) 76 | self.btn_2.clicked.connect(self.modify_coupon) 77 | self.main_layout.addWidget(self.btn_2, 2, 0, 1, 2) 78 | self.label_4 = QLabel('优惠券1最大叠加数(张): ', self) 79 | self.main_layout.addWidget(self.label_4, 2, 3, 1, 3) 80 | self.label_5 = QLabel('0', self) 81 | self.label_5.setFont(QFont('微软雅黑', 12)) 82 | self.label_5.setStyleSheet('color: #ff9999') 83 | self.main_layout.addWidget(self.label_5, 2, 6, 1, 1) 84 | # 第3行 85 | self.btn_3 = QPushButton('修改', self) 86 | self.btn_3.clicked.connect(self.modify_coupon) 87 | self.main_layout.addWidget(self.btn_3, 3, 0, 1, 2) 88 | self.label_6 = QLabel('优惠券2面额(元): ', self) 89 | self.main_layout.addWidget(self.label_6, 3, 3, 1, 3) 90 | self.label_7 = QLabel('0', self) 91 | self.label_7.setFont(QFont('微软雅黑', 12)) 92 | self.label_7.setStyleSheet('color: #ff9999') 93 | self.main_layout.addWidget(self.label_7, 3, 6, 1, 1) 94 | # 第4行 95 | self.btn_4 = QPushButton('修改', self) 96 | self.btn_4.clicked.connect(self.modify_coupon) 97 | self.main_layout.addWidget(self.btn_4, 4, 0, 1, 2) 98 | self.label_8 = QLabel('优惠券2最大叠加数(张): ', self) 99 | self.main_layout.addWidget(self.label_8, 4, 3, 1, 3) 100 | self.label_9 = QLabel('0', self) 101 | self.label_9.setFont(QFont('微软雅黑', 12)) 102 | self.label_9.setStyleSheet('color: #ff9999') 103 | self.main_layout.addWidget(self.label_9, 4, 6, 1, 1) 104 | # 第5行 105 | self.btn_5 = QPushButton('修改', self) 106 | self.btn_5.clicked.connect(self.modify_coupon) 107 | self.main_layout.addWidget(self.btn_5, 5, 0, 1, 2) 108 | self.label_10 = QLabel('优惠券3面额(元): ', self) 109 | self.main_layout.addWidget(self.label_10, 5, 3, 1, 3) 110 | self.label_11 = QLabel('0', self) 111 | self.label_11.setFont(QFont('微软雅黑', 12)) 112 | self.label_11.setStyleSheet('color: #ff9999') 113 | self.main_layout.addWidget(self.label_11, 5, 6, 1, 1) 114 | # 第6行 115 | self.btn_6 = QPushButton('修改', self) 116 | self.btn_6.clicked.connect(self.modify_coupon) 117 | self.main_layout.addWidget(self.btn_6, 6, 0, 1, 2) 118 | self.label_12 = QLabel('优惠券3最大叠加数(张): ', self) 119 | self.main_layout.addWidget(self.label_12, 6, 3, 1, 3) 120 | self.label_13 = QLabel('0', self) 121 | self.label_13.setFont(QFont('微软雅黑', 12)) 122 | self.label_13.setStyleSheet('color: #ff9999') 123 | self.main_layout.addWidget(self.label_13, 6, 6, 1, 1) 124 | 125 | # 第7行 126 | self.label_14 = QLabel('2. 请选择快收后台导出的财务明细数据 csv 文件', self) 127 | self.label_14.setFont(QFont('微软雅黑', 10)) 128 | self.label_14.setStyleSheet('color: #ff9999') 129 | self.main_layout.addWidget(self.label_14, 7, 0, 1, 12) 130 | # 第8行 131 | self.btn_7 = QPushButton('选择', self) 132 | self.btn_7.clicked.connect(self.chose_data_csv) 133 | self.main_layout.addWidget(self.btn_7, 8, 0, 1, 2) 134 | self.label_15 = QLabel('', self) # 用来显示选择的文件名 135 | self.main_layout.addWidget(self.label_15, 8, 3, 1, 11) 136 | 137 | # 第9行 138 | self.label_16 = QLabel('3. (可选) 请选择包含店铺名称与操作员账号对应关系的 csv 文件', self) 139 | self.label_16.setFont(QFont('微软雅黑', 10)) 140 | self.label_16.setStyleSheet('color: #ff9999') 141 | self.main_layout.addWidget(self.label_16, 9, 0, 1, 12) 142 | # 第10行 143 | self.btn_8 = QPushButton('选择', self) 144 | self.btn_8.clicked.connect(self.chose_shops_csv) 145 | self.main_layout.addWidget(self.btn_8, 10, 0, 1, 2) 146 | self.label_17 = QLabel('', self) # 用来显示选择的文件名 147 | self.main_layout.addWidget(self.label_17, 10, 3, 1, 11) 148 | 149 | # 第11行 150 | self.label_18 = QLabel('4. 请勾选统计报表的列', self) 151 | self.label_18.setFont(QFont('微软雅黑', 10)) 152 | self.label_18.setStyleSheet('color: #ff9999') 153 | self.main_layout.addWidget(self.label_18, 11, 0, 1, 12) 154 | # 第12行 155 | self.cb_1 = QCheckBox('操作员账号', self) 156 | self.cb_1.toggle() 157 | self.main_layout.addWidget(self.cb_1, 12, 0, 1, 3) 158 | self.cb_2 = QCheckBox('店铺名称', self) 159 | self.main_layout.addWidget(self.cb_2, 12, 3, 1, 3) 160 | self.cb_3 = QCheckBox('交易笔数', self) 161 | self.cb_3.toggle() 162 | self.main_layout.addWidget(self.cb_3, 12, 6, 1, 3) 163 | self.cb_4 = QCheckBox('优惠笔数', self) 164 | self.cb_4.toggle() 165 | self.main_layout.addWidget(self.cb_4, 12, 9, 1, 3) 166 | # 第13行 167 | self.cb_5 = QCheckBox('优惠金额(元)', self) 168 | self.cb_5.toggle() 169 | self.main_layout.addWidget(self.cb_5, 13, 0, 1, 3) 170 | self.cb_6 = QCheckBox('优惠券1的笔数(0元)', self) 171 | self.main_layout.addWidget(self.cb_6, 13, 3, 1, 3) 172 | self.cb_7 = QCheckBox('优惠券2的笔数(0元)', self) 173 | self.main_layout.addWidget(self.cb_7, 13, 6, 1, 3) 174 | self.cb_8 = QCheckBox('优惠券3的笔数(0元)', self) 175 | self.main_layout.addWidget(self.cb_8, 13, 9, 1, 3) 176 | # 第14行 177 | self.cb_9 = QCheckBox('实收金额(元)', self) 178 | self.cb_9.toggle() 179 | self.main_layout.addWidget(self.cb_9, 14, 0, 1, 3) 180 | self.cb_10 = QCheckBox('退款笔数', self) 181 | self.cb_10.toggle() 182 | self.main_layout.addWidget(self.cb_10, 14, 3, 1, 3) 183 | self.cb_11 = QCheckBox('退款总额(元)', self) 184 | self.cb_11.toggle() 185 | self.main_layout.addWidget(self.cb_11, 14, 6, 1, 3) 186 | 187 | # 第15行 188 | self.label_19 = QLabel('5. 请指定实收金额手续费率', self) 189 | self.label_19.setFont(QFont('微软雅黑', 10)) 190 | self.label_19.setStyleSheet('color: #ff9999') 191 | self.main_layout.addWidget(self.label_19, 15, 0, 1, 12) 192 | # 第16行 193 | self.btn_9 = QPushButton('修改', self) 194 | self.btn_9.clicked.connect(self.modify_rate) 195 | self.main_layout.addWidget(self.btn_9, 16, 0, 1, 2) 196 | self.label_20 = QLabel('实收金额手续费率(%): ', self) 197 | self.main_layout.addWidget(self.label_20, 16, 3, 1, 3) 198 | self.label_21 = QLabel('0.6', self) 199 | self.label_21.setFont(QFont('微软雅黑', 12)) 200 | self.label_21.setStyleSheet('color: #ff9999') 201 | self.main_layout.addWidget(self.label_21, 16, 6, 1, 1) 202 | 203 | # 第17行 204 | self.label_22 = QLabel('6. 导出最终统计报表', self) 205 | self.label_22.setFont(QFont('微软雅黑', 10)) 206 | self.label_22.setStyleSheet('color: #ff9999') 207 | self.main_layout.addWidget(self.label_22, 17, 0, 1, 12) 208 | # 第18行 209 | self.btn_10 = QPushButton('导出', self) 210 | self.btn_10.clicked.connect(self.output_reslut) 211 | self.main_layout.addWidget(self.btn_10, 18, 0, 1, 2) 212 | 213 | self.show() 214 | 215 | def modify_coupon(self): 216 | sender = self.sender() 217 | if sender == self.btn_1: 218 | text, ok = QInputDialog.getInt(self, '修改优惠券1面额', '请输入面额:', min=10, step=10) 219 | if ok: 220 | self.c_par_1 = text # 保存面额 221 | self.label_3.setText(str(text)) 222 | self.c_max_1 = 1 223 | self.label_5.setText('1') # 自动显示初始张数为1 224 | self.cb_6.setChecked(True) # 勾选导出的对应列 225 | self.cb_6.setText('优惠券1的笔数({}元)'.format(text)) # 修改列的名称 226 | elif sender == self.btn_2: 227 | text, ok = QInputDialog.getInt(self, '修改优惠券1最大叠加张数', '请输入最大叠加张数:', min=1) 228 | if ok: 229 | self.c_max_1 = text # 保存最大叠加张数 230 | self.label_5.setText(str(text)) 231 | elif sender == self.btn_3: 232 | text, ok = QInputDialog.getInt(self, '修改优惠券2面额', '请输入面额:', min=10, step=10) 233 | if ok: 234 | self.c_par_2 = text # 保存面额 235 | self.label_7.setText(str(text)) 236 | self.c_max_2 = 1 237 | self.label_9.setText('1') # 自动显示初始张数为1 238 | self.cb_7.setChecked(True) # 勾选导出的对应列 239 | self.cb_7.setText('优惠券2的笔数({}元)'.format(text)) # 修改列的名称 240 | elif sender == self.btn_4: 241 | text, ok = QInputDialog.getInt(self, '修改优惠券2最大叠加张数', '请输入最大叠加张数:', min=1) 242 | if ok: 243 | self.c_max_2 = text # 保存最大叠加张数 244 | self.label_9.setText(str(text)) 245 | elif sender == self.btn_5: 246 | text, ok = QInputDialog.getInt(self, '修改优惠券3面额', '请输入面额:', min=10, step=10) 247 | if ok: 248 | self.c_par_3 = text # 保存面额 249 | self.label_11.setText(str(text)) 250 | self.c_max_3 = 1 251 | self.label_13.setText('1') # 自动显示初始张数为1 252 | self.cb_8.setChecked(True) # 勾选导出的对应列 253 | self.cb_8.setText('优惠券3的笔数({}元)'.format(text)) # 修改列的名称 254 | elif sender == self.btn_6: 255 | text, ok = QInputDialog.getInt(self, '修改优惠券3最大叠加张数', '请输入最大叠加张数:', min=1) 256 | if ok: 257 | self.c_max_3 = text # 保存最大叠加张数 258 | self.label_13.setText(str(text)) 259 | 260 | # 如果是导出几次后,重新修改优惠券信息,需要重新触发 get_data() 方法 261 | if self.data_csv: # 如果没选择 财务明细数据 csv,则条件为False 262 | self.get_data(self.data_csv) 263 | 264 | def chose_data_csv(self): 265 | filename, filetype = QFileDialog.getOpenFileName(self, '选择文件', basepath) 266 | if filename: 267 | self.data_csv = filename 268 | self.get_data(self.data_csv) 269 | 270 | def get_data(self, filename): 271 | self.data = {} # 每次调用该函数时,都需要先清空之前的数据 272 | self.faild_rows = [] # 每次调用该函数时,都需要先清空之前的数据 273 | try: 274 | with open(filename) as f: 275 | f_csv = csv.DictReader(f) 276 | for row in f_csv: # row表示每一行数据,每一列都是str类型 277 | if not row['操作员账号']: # csv文件最后一行,是汇总数据,没有操作员账号 278 | continue 279 | 280 | operator_id = row['操作员账号'].strip() 281 | if operator_id not in self.data: # 第一次碰到该商户 282 | self.data[operator_id] = {} 283 | self.data[operator_id]['shop_name'] = '' # 该商户的店铺名称 284 | self.data[operator_id]['trade_count'] = 0 # 该商户的交易笔数 285 | self.data[operator_id]['reduced_count'] = 0 # 该商户的优惠笔数 286 | self.data[operator_id]['reduced_succeed_count'] = 0 # 成功计算成优惠券组合的笔数 287 | self.data[operator_id]['reduced_faild_count'] = 0 # 未能成功计算成优惠券组合的笔数 288 | self.data[operator_id]['reduced_total'] = 0 # 该商户的优惠总额 289 | self.data[operator_id]['coupon_1'] = 0 # 优惠券1的张数 290 | self.data[operator_id]['coupon_2'] = 0 # 优惠券2的张数 291 | self.data[operator_id]['coupon_3'] = 0 # 优惠券3的张数 292 | self.data[operator_id]['received_total'] = Decimal('0.0') # 该商户的实收总额 293 | self.data[operator_id]['refund_count'] = 0 # 该商户的退款笔数 294 | self.data[operator_id]['refund_total'] = Decimal('0.0') # 该商户的退款总额 295 | 296 | self.data[operator_id]['trade_count'] += 1 # 该商户的交易笔数加1 297 | 298 | reduced_price = row['优惠金额(元)'] 299 | if Decimal(reduced_price) >= min(self.c_par_1, self.c_par_2, self.c_par_3): # 只有大于3张优惠券最小值的才是使用了优惠券,才计算为优惠笔数 300 | reduced_money = get_reduced_money(reduced_price) # 没使用优惠券时返回None 301 | if reduced_money: 302 | self.data[operator_id]['reduced_count'] += 1 # 该商户的优惠笔数加1 303 | self.data[operator_id]['reduced_total'] += reduced_money # 该商户的优惠总额累加 304 | 305 | combination = get_combination(reduced_money, self.c_par_1, self.c_par_2, self.c_par_3, self.c_max_1, self.c_max_2, self.c_max_3) 306 | if len(combination) == 1: 307 | self.data[operator_id]['coupon_1'] += combination[0][0] # 优惠券1的张数累加 308 | self.data[operator_id]['coupon_2'] += combination[0][1] # 优惠券2的张数累加 309 | self.data[operator_id]['coupon_3'] += combination[0][2] # 优惠券3的张数累加 310 | self.data[operator_id]['reduced_succeed_count'] += 1 311 | else: # 如果组合没有或者多于一种,请求人工核对,将这一条 row 保存到 faild.csv 中 312 | self.faild_rows.append(dict(row)) # 先将OrderedDict转换成Dict 313 | self.data[operator_id]['reduced_faild_count'] += 1 314 | 315 | received_price = Decimal(row['实收金额(元)']) 316 | self.data[operator_id]['received_total'] += received_price # 该商户的实收总额累加 317 | 318 | if row['状态'].strip() == '退款': # 状态为 '退款' 时,退款金额看 实收金额(元) 的值 319 | self.data[operator_id]['refund_count'] += 1 # 该商户的退款笔数加1 320 | self.data[operator_id]['refund_total'] += received_price # 该商户的退款总额累加 321 | 322 | self.label_15.setText(filename) # 显示选择的文件名 323 | except (UnicodeDecodeError, KeyError) as e: 324 | QMessageBox.warning(self, '警告', '请选择正确的 [快收后台的账务明细数据] 文件!') 325 | # print(self.data) 326 | # print(self.faild_rows) 327 | 328 | def chose_shops_csv(self): 329 | filename, filetype = QFileDialog.getOpenFileName(self, '选择文件', basepath) 330 | if filename: 331 | self.shops_csv = filename 332 | self.get_shops(self.shops_csv) 333 | 334 | def get_shops(self, filename): 335 | self.shops = {} # 每次调用该函数时,都需要先清空之前的数据 336 | try: 337 | with open(filename) as f: 338 | f_csv = csv.DictReader(f) 339 | for row in f_csv: 340 | if not row['操作员账号']: # csv文件最后一行为空 341 | continue 342 | operator_id = row['操作员账号'].strip() 343 | shop = row['店铺名称'] 344 | if operator_id not in self.shops: 345 | self.shops[operator_id] = shop 346 | self.label_17.setText(filename) # 显示选择的文件名 347 | self.cb_2.setChecked(True) # 勾选列 348 | except (UnicodeDecodeError, KeyError) as e: 349 | QMessageBox.warning(self, '警告', '请选择正确的 [店铺名称与操作员账号对应关系] 文件!') 350 | # print(self.shops) 351 | 352 | def modify_rate(self): 353 | # 后面四个数字的作用依次是 初始值 最小值 最大值 小数点后位数 354 | text, ok = QInputDialog.getDouble(self, '修改费率', '请输入费率(单位 %):', 0.60, -10000, 10000, 2) 355 | if ok: 356 | self.rate = text 357 | self.label_21.setText(str(text)) 358 | 359 | def output_reslut(self): 360 | if not self.data: 361 | QMessageBox.warning(self, '警告', '请选择 [快收后台的账务明细数据] 文件!') 362 | return 363 | if self.cb_2.isChecked() and not self.shops: 364 | QMessageBox.warning(self, '警告', '由于您勾选了[店铺名称],所以请选择 [操作员账号与店铺名称对应表] 文件!') 365 | return 366 | 367 | filename, filetype = QFileDialog.getSaveFileName(self, '导出报表', basepath, 'CSV Files (*.csv)') 368 | if filename: 369 | self.rows = [] # 最终要插入 result.csv 文件的记录,每个元素也是一个列表 370 | 371 | # 合计总数 372 | total_trade_count = 0 373 | total_reduced_count = 0 374 | total_reduced_succeed_count = 0 375 | total_reduced_faild_count = 0 376 | total_reduced = Decimal('0') 377 | total_coupon_1 = 0 378 | total_coupon_2 = 0 379 | total_coupon_3 = 0 380 | total_received = Decimal('0') 381 | total_refund_count = 0 382 | total_refund = Decimal('0') 383 | 384 | for key, value in self.data.items(): 385 | row = [] # 统计结果csv文件的每一行数据 386 | 387 | # 如果未选择复选框,则统计报表中不输出该列 388 | if self.cb_1.isChecked(): 389 | row.append(key) 390 | 391 | if self.cb_2.isChecked(): 392 | if key in self.shops: 393 | row.append(self.shops[key]) 394 | elif key.lstrip('0') in self.shops: 395 | row.append(self.shops[key.lstrip('0')]) 396 | else: 397 | row.append('') 398 | 399 | if self.cb_3.isChecked(): 400 | row.append(value['trade_count']) 401 | 402 | if self.cb_4.isChecked(): 403 | row.append(value['reduced_count']) 404 | 405 | if self.cb_5.isChecked(): 406 | row.append(value['reduced_total']) 407 | 408 | if self.cb_6.isChecked(): 409 | row.append(value['coupon_1']) 410 | 411 | if self.cb_7.isChecked(): 412 | row.append(value['coupon_2']) 413 | 414 | if self.cb_8.isChecked(): 415 | row.append(value['coupon_3']) 416 | 417 | if self.cb_9.isChecked(): 418 | row.append(value['received_total']) 419 | 420 | if self.cb_10.isChecked(): 421 | row.append(value['refund_count']) 422 | 423 | if self.cb_11.isChecked(): 424 | row.append(value['refund_total']) 425 | 426 | self.rows.append(row) 427 | 428 | # 合计统计 429 | total_trade_count += value['trade_count'] 430 | total_reduced_count += value['reduced_count'] 431 | total_reduced_succeed_count += value['reduced_succeed_count'] 432 | total_reduced_faild_count += value['reduced_faild_count'] 433 | total_reduced += value['reduced_total'] 434 | total_coupon_1 += value['coupon_1'] 435 | total_coupon_2 += value['coupon_2'] 436 | total_coupon_3 += value['coupon_3'] 437 | total_received += value['received_total'] 438 | total_refund_count += value['refund_count'] 439 | total_refund += value['refund_total'] 440 | 441 | headers = [] # csv表头 442 | totals = [] # csv合计 443 | 444 | if self.cb_1.isChecked(): 445 | headers.append('操作员账号') 446 | totals.append('合计') 447 | if self.cb_2.isChecked(): 448 | headers.append('店铺名称') 449 | totals.append('(Total):') 450 | if self.cb_3.isChecked(): 451 | headers.append('交易笔数') 452 | totals.append(total_trade_count) 453 | if self.cb_4.isChecked(): 454 | headers.append('优惠笔数') 455 | totals.append(total_reduced_count) 456 | if self.cb_5.isChecked(): 457 | headers.append('优惠金额(元)') 458 | totals.append(total_reduced) 459 | if self.cb_6.isChecked(): 460 | headers.append(self.cb_6.text()) 461 | totals.append(total_coupon_1) 462 | if self.cb_7.isChecked(): 463 | headers.append(self.cb_7.text()) 464 | totals.append(total_coupon_2) 465 | if self.cb_8.isChecked(): 466 | headers.append(self.cb_8.text()) 467 | totals.append(total_coupon_3) 468 | if self.cb_9.isChecked(): 469 | headers.append('实收金额(元)') 470 | totals.append(total_received) 471 | if self.cb_10.isChecked(): 472 | headers.append('退款笔数') 473 | totals.append(total_refund_count) 474 | if self.cb_11.isChecked(): 475 | headers.append('退款总额(元)') 476 | totals.append(total_refund) 477 | 478 | try: 479 | with open(filename, 'w', newline='') as f: 480 | f_csv = csv.writer(f) 481 | f_csv.writerow(headers) 482 | f_csv.writerows(self.rows) 483 | f_csv.writerow(totals) 484 | 485 | f_csv.writerow([]) # 空一行 486 | f_csv.writerow(['商场活动数据', '实收金额', '实收金额手续费', '实际结算金额', '商场到账金额', '差额']) 487 | rate_price = total_received * Decimal(str(self.rate / 100)) 488 | f_csv.writerow(['', total_received, rate_price, total_received-rate_price, '/', '/']) 489 | 490 | f_csv.writerow([]) # 空一行 491 | f_csv.writerow(['结算公式:']) 492 | f_csv.writerow(['实收金额=快收后台实收金额']) 493 | f_csv.writerow(['实收金额手续费=实收金额*{}%'.format(self.rate)]) 494 | f_csv.writerow(['实际结算金额=实收金额-实收金额手续费']) 495 | QMessageBox.information(self, '导出成功', '统计报表为 [{}],请用WPS等程序查看!'.format(filename)) 496 | except PermissionError as e: 497 | QMessageBox.warning(self, '导出失败', '指定的导出文件 [{}] 可能正在被WPS等程序打开,请先关闭它!'.format(filename)) 498 | return 499 | 500 | # 导出优惠券无法计算的行 501 | if self.faild_rows: 502 | QMessageBox.warning(self, '警告', '有 [{}] 笔优惠金额无法计算出使用的优惠券组合( 成功计算出 [{}] 笔 ),请人工核对。请单击 [OK] 按钮后,选择要保存这些记录到哪?'.format(total_reduced_faild_count, total_reduced_succeed_count)) 503 | filename, filetype = QFileDialog.getSaveFileName(self, '导出异常的优惠券信息', basepath, 'CSV Files (*.csv)') 504 | if filename: 505 | try: 506 | headers = [key for key in self.faild_rows[0].keys()] # 随便拿一条,拿Dict的keys,生成csv表头 507 | with open(filename, 'w', newline='') as f: 508 | f_csv = csv.DictWriter(f, headers) 509 | f_csv.writeheader() 510 | f_csv.writerows(self.faild_rows) 511 | QMessageBox.warning(self, '警告', '优惠券数量无法正确计算的交易记录导出为 [{}],请用WPS等程序查看!'.format(filename)) 512 | except PermissionError as e: 513 | QMessageBox.warning(self, '警告', '用来保存 [优惠券数量无法正确计算的交易记录] 的csv文件 [{}] 可能正在被WPS等程序打开,请先关闭它!'.format(filename)) 514 | return 515 | else: 516 | QMessageBox.warning(self, '警告', '有部分交易中的优惠券数量无法计算,需要人工核对,但是您没有选择要导出的文件!') 517 | else: 518 | QMessageBox.warning(self, '警告', '统计报表未导出,请指定导出文件!') 519 | 520 | 521 | if __name__ == "__main__": 522 | app = QApplication(sys.argv) 523 | ex = Ui_MainWindow() 524 | ex.show() 525 | sys.exit(app.exec_()) 526 | --------------------------------------------------------------------------------