Skip to content

Commit af088e7

Browse files
committed
Add invoice printing, settings
1 parent 2051d7a commit af088e7

17 files changed

+728
-55
lines changed

PySimpleSale.pyproject

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"files": ["data/resources.qrc","src/entity/supplier.py","src/entity/unit.py","src/unitwidget.py","src/dao/countrydao.py","src/customerwidget.ui","src/citieswidget.py","src/dao/citydao.py","src/mainwindow.ui","src/countrywidget.ui","src/dao/dao.py","src/sqlconnection.py","src/entity/purchase.py","src/countrieswidget.py","src/entity/customer.py","src/entity/country.py","src/dao/unitdao.py","src/countrieswidget.ui","src/entity/sale.py","src/productwidget.ui","src/citieswidget.ui","src/statewidget.ui","src/supplierwidget.ui","src/entity/saleproduct.py","src/stateswidget.py","src/dao/customerdao.py","src/dao/statedao.py","src/dao/purchasedao.py","src/stateswidget.ui","src/salewidget.ui","src/dao/supplierdao.py","src/productwidget.py","src/dao/productdao.py","src/entity/product.py","src/statewidget.py","src/entity/city.py","src/reportswidget.py","src/entity/state.py","src/citywidget.py","src/noteditabledelegate.py","src/dao/saledao.py","src/countrywidget.py","src/mainwindow.py","src/customerwidget.py","main.py","src/dao/saleproductdao.py","src/salewidget.py","src/supplierwidget.py","src/reportswidget.ui","src/unitwidget.ui","src/citywidget.ui"]
2+
"files": ["src/invoiceprinter.py","src/entity/purchase.py","src/entity/customer.py","src/dao/customerdao.py","src/dao/statedao.py","src/entity/country.py","src/unitswidget.py","src/unitwidget.ui","src/salewidget.ui","src/dao/dao.py","src/productwidget.ui","src/citywidget.ui","src/dao/supplierdao.py","src/dao/citydao.py","src/dao/productdao.py","src/mainwindow.ui","src/entity/sale.py","src/countrywidget.py","src/mainwindow.py","src/customerwidget.ui","src/stateswidget.py","src/salewidget.py","src/settingswidget.py","src/dao/purchasedao.py","src/entity/city.py","src/dao/saleproductdao.py","src/dao/unitdao.py","src/supplierwidget.py","src/citieswidget.py","src/dao/countrydao.py","src/dao/saledao.py","src/countrieswidget.ui","src/statewidget.ui","src/customerwidget.py","src/noteditabledelegate.py","src/productwidget.py","src/reportswidget.ui","src/supplierwidget.ui","src/unitswidget.ui","src/countrywidget.ui","src/citieswidget.ui","src/entity/state.py","src/entity/saleproduct.py","src/stateswidget.ui","src/entity/unit.py","src/sqlconnection.py","src/citywidget.py","src/statewidget.py","src/reportswidget.py","data/resources.qrc","main.py","src/unitwidget.py","src/entity/product.py","src/entity/supplier.py","src/settingswidget.ui","src/countrieswidget.py"]
33
}

README.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ A simple sale management software made with PyQt5 and SQLite.
44

55
![](https://i.imgur.com/4kdlkBV.png)
66

7+
### Invoice printing
8+
9+
![](https://i.imgur.com/dLbveYT.png)
10+
11+
712
### Database Design
813

914
![](https://i.imgur.com/IXEuZYU.png)
@@ -12,5 +17,14 @@ A simple sale management software made with PyQt5 and SQLite.
1217

1318
1. Install PyQt5
1419
2. Clone this repository
15-
3. Run ```python3 main.py```
16-
4. The database file will be created (database.db), open it in an external application in order to insert the the countries, states and cities (e.g. from CSVs)
20+
3. Install PyQt5: `pip install PyQt5`
21+
4. Run `python3 main.py`
22+
5. The database file will be created (database.db)
23+
24+
### Making sales
25+
26+
1. Now, open Tools > Countries to add some countries, or open the database file in an external application to import a CSV file.
27+
2. Add states and cities to the database (Tools > States, Tools > Cities).
28+
3. Add an unit of measurement (Tools > Units of Measurement)
29+
4. Add a new customer: go to the "Customers" tab, click on "New" button
30+
5. Go to "Sales" tab, open a new sale, select the customer and start including products

main.py

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
if __name__ == "__main__":
88
app = QApplication([])
9+
app.setOrganizationName("PySimpleSale")
10+
app.setApplicationName("PySimpleSale")
11+
app.setApplicationDisplayName("PySimpleSale")
912
widget = MainWindow()
1013
widget.setWindowIcon(QIcon("data/icons/logo.svg"))
1114
sys.exit(app.exec())

src/countrywidget.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ def edit(self, country):
2222
def save(self):
2323
dao = CountryDAO()
2424
if self.mode == "new":
25-
dao.insert(Country(self.nameLineEdit.text(),
25+
dao.insert(Country(None, self.nameLineEdit.text(),
2626
self.codeLineEdit.text()))
2727
else:
28-
dao.update(Country(self.nameLineEdit.text()),
29-
self.codeLineEdit.text())
28+
dao.update(Country(self.countryId,
29+
self.nameLineEdit.text()),
30+
self.codeLineEdit.text())
3031

3132
self.countryUpserted.emit()

src/dao/saleproductdao.py

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ class SaleProductDAO:
77
def __init__(self):
88
pass
99

10+
def select(self, saleId):
11+
query = QSqlQuery()
12+
query.prepare("SELECT sale_product_id, price, quantity,"
13+
"product_id FROM sale_product "
14+
"WHERE sale_id = :saleId")
15+
query.bindValue(":saleId", saleId)
16+
query.exec()
17+
saleProducts = []
18+
19+
while query.next():
20+
saleProduct = SaleProduct(query.value("sale_product_id"),
21+
query.value("price"),
22+
query.value("quantity"),
23+
query.value("product_id"),
24+
saleId)
25+
saleProducts.append(saleProduct)
26+
return saleProducts
27+
1028
def update(self, saleProductId):
1129
query = QSqlQuery()
1230
query.prepare("UPDATE sale_product SET quantity = :quantity, "

src/entity/country.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
class Country:
5-
def __init__(self, name, code):
5+
def __init__(self, id, name, code):
6+
self.id = id
67
self.name = name
78
self.code = code

src/invoiceprinter.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# This Python file uses the following encoding: utf-8
2+
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
3+
from PyQt5.QtGui import QPainter, QPixmap
4+
from PyQt5.QtWidgets import QDialog
5+
from PyQt5.QtCore import QPoint, QSettings
6+
from src.dao.customerdao import CustomerDAO
7+
from src.dao.citydao import CityDAO
8+
from src.dao.statedao import StateDAO
9+
from src.dao.countrydao import CountryDAO
10+
from src.dao.productdao import ProductDAO
11+
from src.dao.saleproductdao import SaleProductDAO
12+
13+
14+
class InvoicePrinter:
15+
def __init__(self):
16+
self.painter = QPainter()
17+
self.printer = QPrinter()
18+
self.settings = QSettings()
19+
self.margin = int(self.settings.value("printing/margin"))
20+
self.lineHeight = int(self.settings.value("printing/lineHeight"))
21+
22+
def printInvoice(self, sale):
23+
self.printDialog = QPrintDialog(self.printer)
24+
if self.printDialog.exec() == QDialog.Accepted:
25+
customerDao = CustomerDAO()
26+
customer = customerDao.select(sale.customerId)
27+
28+
self.painter.begin(self.printer)
29+
y = self.paintLogo()
30+
y += 2 * self.lineHeight
31+
y = self.paintBusiness(self.margin, y)
32+
y += 2 * self.lineHeight
33+
y = self.paintCustomer(customer, self.margin, y)
34+
y += 2 * self.lineHeight
35+
y = self.paintSaleProducts(sale, self.margin, y)
36+
y += 2 * self.lineHeight
37+
self.paintTotal(sale, self.margin, y)
38+
self.painter.end()
39+
40+
def paintLogo(self):
41+
logoPos = int(self.settings.value("printing/logoPosition"))
42+
logo = QPixmap(self.settings.value("printing/logoPath"))
43+
logoWidth = int(self.settings.value("printing/logoWidth"))
44+
logoHeight = int(self.settings.value("printing/logoHeight"))
45+
logoPoint = QPoint(self.margin, logoPos)
46+
self.painter.drawPixmap(logoPoint, logo.scaled(logoWidth, logoHeight))
47+
return logoHeight + logoPos
48+
49+
def paintBusiness(self, x, y):
50+
businessName = self.settings.value("business/name")
51+
businessPhone = self.settings.value("business/phone")
52+
businessAddressLine1 = self.settings.value("business/addressLine1")
53+
businessAddressLine2 = self.settings.value("business/addressLine2")
54+
businessEmail = self.settings.value("business/email")
55+
56+
fields = [businessName,
57+
businessPhone,
58+
businessAddressLine1,
59+
businessAddressLine2,
60+
businessEmail]
61+
62+
for field in fields:
63+
self.painter.drawText(QPoint(x, y), field)
64+
y += self.lineHeight
65+
66+
return y
67+
68+
def paintCustomer(self, customer, x, y):
69+
countryDao = CountryDAO()
70+
stateDao = StateDAO()
71+
cityDao = CityDAO()
72+
73+
customerCity = cityDao.select(customer.cityId)
74+
customerState = stateDao.select(customerCity.stateId)
75+
customerCountry = countryDao.select(customerState.countryId)
76+
77+
fields = [customer.name,
78+
customer.addressLine1,
79+
customer.addressLine2,
80+
customer.zipcode,
81+
customer.email,
82+
customer.phoneNumber,
83+
customerCity.name,
84+
customerState.name,
85+
customerCountry.name]
86+
87+
for field in fields:
88+
self.painter.drawText(QPoint(x, y), field)
89+
y += self.lineHeight
90+
return y
91+
92+
def paintSaleProducts(self, sale, x, y):
93+
productDao = ProductDAO()
94+
saleProductDao = SaleProductDAO()
95+
saleProducts = saleProductDao.select(sale.id)
96+
97+
for saleProduct in saleProducts:
98+
productName = productDao.select(saleProduct.productId)
99+
productTotal = saleProduct.quantity * saleProduct.price
100+
self.painter.drawText(QPoint(x, y), productName.name)
101+
y += self.lineHeight
102+
self.painter.drawText(QPoint(x, y), str(saleProduct.quantity)
103+
+ " x " +
104+
str(saleProduct.price)
105+
+ " = " +
106+
str(productTotal))
107+
y += self.lineHeight
108+
return y
109+
110+
def paintTotal(self, sale, x, y):
111+
self.painter.drawText(QPoint(x, y), "Total: $" + "%.2f" % sale.amount)
112+
y += self.lineHeight
113+
self.painter.drawText(QPoint(x, y), "Shipping: $" + "%.2f" % sale.shipping)
114+
y += self.lineHeight
115+
self.painter.drawText(QPoint(x, y), "Discount: $" + "%.2f" % sale.discount)

src/mainwindow.py

+24-18
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from src.countrieswidget import CountriesWidget
2020
from src.stateswidget import StatesWidget
2121
from src.citieswidget import CitiesWidget
22+
from src.unitswidget import UnitsWidget
23+
from src.settingswidget import SettingsWidget
2224

2325

2426
class MainWindow(QMainWindow):
@@ -140,9 +142,11 @@ def setUpTableView(self):
140142
columnsToHide = []
141143
columnsToShow = []
142144
headers = []
143-
145+
filter = ""
144146
if(currentIndex == 0):
145147
self.tableModel.setTable("customer")
148+
filter = "UPPER(name) LIKE UPPER ('{0}'||'%')"
149+
filter = filter.format(self.searchCustomerLineEdit.text())
146150
columnsToHide += [2, 3, 4, 7]
147151
columnsToShow += [0, 1, 8]
148152
headers += ["ID",
@@ -155,9 +159,8 @@ def setUpTableView(self):
155159

156160
elif(currentIndex == 1):
157161
self.tableModel.setTable("supplier")
158-
self.tableModel.setFilter("UPPER(name) LIKE UPPER('" +
159-
self.searchSupplierLineEdit.text() +
160-
"'||'%')")
162+
filter = "UPPER(name) LIKE UPPER('{0}'||'%')"
163+
filter = filter.format(self.searchSupplierLineEdit.text())
161164
columnsToHide += [2, 3, 4]
162165
columnsToShow += [0, 1, 5, 6]
163166
headers += ["ID",
@@ -170,9 +173,8 @@ def setUpTableView(self):
170173

171174
elif(currentIndex == 2):
172175
self.tableModel.setTable("product")
173-
self.tableModel.setFilter("UPPER(name) LIKE UPPER('" +
174-
self.searchProductLineEdit.text() +
175-
"'||'%')")
176+
filter = "UPPER(name) LIKE UPPER('{0}'||'%')"
177+
filter = filter.format(self.searchProductLineEdit.text())
176178
columnsToHide += [2, 6, 7]
177179
columnsToShow += [0, 1, 3, 4, 5]
178180
headers += ["ID",
@@ -186,16 +188,13 @@ def setUpTableView(self):
186188
self.tableModel.setTable("sale_view")
187189

188190
if self.searchSaleCheckBox.isChecked():
189-
self.tableModel.setFilter("UPPER(name) LIKE UPPER('" +
190-
self.searchSaleLineEdit.text() +
191-
"'||'%')")
191+
filter = "UPPER(name) LIKE UPPER('{0}'||'%')"
192+
filter = filter.format(self.searchSaleLineEdit.text())
192193
else:
193194
dt = self.searchSaleDateEdit.dateTime().toString('yyyy-MM-dd')
194-
self.tableModel.setFilter("UPPER(name) LIKE UPPER('" +
195-
self.searchSaleLineEdit.text() +
196-
"'||'%') AND datetime LIKE ('" +
197-
dt + "'||'%')")
198-
195+
filter = ("UPPER(name) LIKE UPPER('{0}'||'%') "
196+
"AND datetime LIKE ('{1}'||'%')")
197+
filter = filter.format(self.searchSaleLineEdit.text(), dt)
199198
columnsToShow += [0, 1, 2, 3]
200199
headers += ["ID",
201200
"Customer",
@@ -204,16 +203,16 @@ def setUpTableView(self):
204203

205204
elif(currentIndex == 4):
206205
self.tableModel.setTable("purchase_view")
207-
self.tableModel.setFilter("UPPER(name) LIKE UPPER(" +
208-
self.searchPurchaseLineEdit.text() +
209-
"'||'%')")
206+
filter = "UPPER(name) LIKE UPPER('{0}'||'%')"
207+
filter.format(self.searchPurchaseLineEdit.text())
210208

211209
columnsToShow += [0, 1, 2, 3]
212210
headers += ["ID",
213211
"Supplier",
214212
"Amount",
215213
"Date"]
216214

215+
self.tableModel.setFilter(filter)
217216
self.tableView.setModel(self.tableModel)
218217
self.tableModel.select()
219218

@@ -267,3 +266,10 @@ def openCities(self):
267266
self.citiesWidget = CitiesWidget()
268267
self.citiesWidget.show()
269268

269+
def openUnits(self):
270+
self.unitsWidget = UnitsWidget()
271+
self.unitsWidget.show()
272+
273+
def openSettings(self):
274+
self.settingsWidget = SettingsWidget()
275+
self.settingsWidget.show()

0 commit comments

Comments
 (0)