├── .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 | [](https://www.python.org/)
4 | [](https://www.riverbankcomputing.com/software/pyqt/intro)
5 | [](https://www.pyinstaller.org/)
6 |
7 |
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | 
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 |
--------------------------------------------------------------------------------