7 Commits 9b209be544 ... 32d0f77998

Autor SHA1 Mensagem Data
  Michael Buesch 32d0f77998 Implement tree drag&drop 2 anos atrás
  Michael Buesch bf5258e93b Add optional price factor 5 dias atrás
  Michael Buesch 763136e1ce Fix QInputDialog crash 10 meses atrás
  Michael Buesch daa48ca5f8 Fix clipboard copying 1 ano atrás
  Michael Buesch 7c1ede0097 Release script: Use xz compression 1 ano atrás
  Michael Buesch 441c633ba1 setup: Add install_requires 1 ano atrás
  Michael Buesch 9b209be544 Implement tree drag&drop 2 anos atrás

BIN
example.pmg


+ 1 - 1
maintenance/makerelease.sh

@@ -22,5 +22,5 @@ hook_get_version()
 }
 }
 
 
 project=partmgr
 project=partmgr
-default_archives=py-sdist-bz2
+default_archives=py-sdist-xz
 makerelease "$@"
 makerelease "$@"

+ 69 - 23
partmgr/core/database.py

@@ -2,7 +2,7 @@
 #
 #
 # PartMgr - Database
 # PartMgr - Database
 #
 #
-# Copyright 2014-2022 Michael Buesch <m@bues.ch>
+# Copyright 2014-2024 Michael Buesch <m@bues.ch>
 #
 #
 # This program is free software; you can redistribute it and/or modify
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # it under the terms of the GNU General Public License as published by
@@ -91,7 +91,7 @@ class Database:
 	"Part database interface."
 	"Part database interface."
 
 
 	# Database version number
 	# Database version number
-	DB_VERSION	= 0
+	DB_VERSION	= 1
 
 
 	# User editable parameters
 	# User editable parameters
 	USER_PARAMS = {
 	USER_PARAMS = {
@@ -116,10 +116,12 @@ class Database:
 				ver = self.getGlobalParameter(
 				ver = self.getGlobalParameter(
 					"partmgr_db_version")
 					"partmgr_db_version")
 				ver = ver.getDataInt() if ver else None
 				ver = ver.getDataInt() if ver else None
-				if ver is None or ver != self.DB_VERSION:
+				if ver is None or ver < 0 or ver > self.DB_VERSION:
 					self.filename = None
 					self.filename = None
 					raise PartMgrError("Invalid database "
 					raise PartMgrError("Invalid database "
 						    "version")
 						    "version")
+				if ver == 0:
+					self.__upgrade_0to1() # Upgrade DB version to 1.
 			self.__setUserParameterDefaults()
 			self.__setUserParameterDefaults()
 		except (sql.Error, ValueError, TypeError) as e:
 		except (sql.Error, ValueError, TypeError) as e:
 			self.filename = None
 			self.filename = None
@@ -200,13 +202,15 @@ class Database:
 
 
 	def __initTables(self):
 	def __initTables(self):
 		entityColumns = "id INTEGER PRIMARY KEY AUTOINCREMENT, "\
 		entityColumns = "id INTEGER PRIMARY KEY AUTOINCREMENT, "\
-				"name TEXT, description TEXT, "\
+				"name TEXT, "\
+				"description TEXT, "\
 				"flags INTEGER, "\
 				"flags INTEGER, "\
 				"createTimeStamp INTEGER, "\
 				"createTimeStamp INTEGER, "\
 				"modifyTimeStamp INTEGER"
 				"modifyTimeStamp INTEGER"
 		tables = (
 		tables = (
 			"parameters(" + entityColumns + ", "
 			"parameters(" + entityColumns + ", "
-				   "parentType INTEGER, parent INTEGER, "
+				   "parentType INTEGER, "
+				   "parent INTEGER, "
 				   "data TEXT)",
 				   "data TEXT)",
 			"parts(" + entityColumns + ", "
 			"parts(" + entityColumns + ", "
 			      "category INTEGER)",
 			      "category INTEGER)",
@@ -218,17 +222,22 @@ class Database:
 			"footprints(" + entityColumns + ", "
 			"footprints(" + entityColumns + ", "
 				   "image TEXT)",
 				   "image TEXT)",
 			"stock(" + entityColumns + ", "
 			"stock(" + entityColumns + ", "
-			      "part INTEGER, category INTEGER, "
+			      "part INTEGER, "
+			      "category INTEGER, "
 			      "footprint INTEGER, "
 			      "footprint INTEGER, "
 			      "minQuantity INTEGER, "
 			      "minQuantity INTEGER, "
 			      "targetQuantity INTEGER, "
 			      "targetQuantity INTEGER, "
 			      "quantityUnits INTEGER)",
 			      "quantityUnits INTEGER)",
 			"origins(" + entityColumns + ", "
 			"origins(" + entityColumns + ", "
-				"stockItem INTEGER, supplier INTEGER, "
+				"stockItem INTEGER, "
+				"supplier INTEGER, "
 				"orderCode TEXT, "
 				"orderCode TEXT, "
-				"price FLOAT, priceTimeStamp INTEGER)",
+				"price FLOAT, "
+				"priceTimeStamp INTEGER, "
+				"priceFact FLOAT DEFAULT 1.0)",
 			"storages(" + entityColumns + ", "
 			"storages(" + entityColumns + ", "
-				 "stockItem INTEGER, location INTEGER, "
+				 "stockItem INTEGER, "
+				 "location INTEGER, "
 				 "quantity INTEGER)",
 				 "quantity INTEGER)",
 		)
 		)
 		c = self.db.cursor()
 		c = self.db.cursor()
@@ -236,6 +245,13 @@ class Database:
 			c.execute("CREATE TABLE IF NOT EXISTS %s;" % table)
 			c.execute("CREATE TABLE IF NOT EXISTS %s;" % table)
 		self.__commit()
 		self.__commit()
 
 
+	def __upgrade_0to1(self):
+		print("Updating database version 0 to version 1.")
+		c = self.db.cursor()
+		c.execute("ALTER TABLE origins ADD COLUMN priceFact FLOAT DEFAULT 1.0;")
+		self.getGlobalParameter("partmgr_db_version").setData(1)
+		self.__commit()
+
 	def __sqlIsEmpty(self):
 	def __sqlIsEmpty(self):
 		try:
 		try:
 			c = self.db.cursor()
 			c = self.db.cursor()
@@ -1290,11 +1306,18 @@ class Database:
 		id = Entity.toId(origin)
 		id = Entity.toId(origin)
 		try:
 		try:
 			c = self.db.cursor()
 			c = self.db.cursor()
-			c.execute("SELECT name, description, flags, "
+			c.execute("SELECT "
+				  "name, "
+				  "description, "
+				  "flags, "
 				  "createTimeStamp, "
 				  "createTimeStamp, "
 				  "modifyTimeStamp, "
 				  "modifyTimeStamp, "
-				  "stockItem, supplier, orderCode, "
-				  "price, priceTimeStamp "
+				  "stockItem, "
+				  "supplier, "
+				  "orderCode, "
+				  "price, "
+				  "priceTimeStamp, "
+				  "priceFact "
 				  "FROM origins "
 				  "FROM origins "
 				  "WHERE id=?;",
 				  "WHERE id=?;",
 				  (int(id),))
 				  (int(id),))
@@ -1311,6 +1334,7 @@ class Database:
 				      orderCode = fromBase64(data[7]),
 				      orderCode = fromBase64(data[7]),
 				      price = float(data[8]),
 				      price = float(data[8]),
 				      priceTimeStamp = int(data[9]),
 				      priceTimeStamp = int(data[9]),
+				      priceFact = float(data[10]),
 				      id = id,
 				      id = id,
 				      db = self)
 				      db = self)
 		except (sql.Error, ValueError, TypeError) as e:
 		except (sql.Error, ValueError, TypeError) as e:
@@ -1323,11 +1347,18 @@ class Database:
 		stockItemId = Entity.toId(stockItem)
 		stockItemId = Entity.toId(stockItem)
 		try:
 		try:
 			c = self.db.cursor()
 			c = self.db.cursor()
-			c.execute("SELECT id, name, description, flags, "
+			c.execute("SELECT "
+				  "id, "
+				  "name, "
+				  "description, "
+				  "flags, "
 				  "createTimeStamp, "
 				  "createTimeStamp, "
 				  "modifyTimeStamp, "
 				  "modifyTimeStamp, "
-				  "supplier, orderCode, "
-				  "price, priceTimeStamp "
+				  "supplier, "
+				  "orderCode, "
+				  "price, "
+				  "priceTimeStamp, "
+				  "priceFact "
 				  "FROM origins "
 				  "FROM origins "
 				  "WHERE stockItem=?;",
 				  "WHERE stockItem=?;",
 				  (int(stockItemId),))
 				  (int(stockItemId),))
@@ -1344,6 +1375,7 @@ class Database:
 					orderCode = fromBase64(d[7]),
 					orderCode = fromBase64(d[7]),
 					price = float(d[8]),
 					price = float(d[8]),
 					priceTimeStamp = int(d[9]),
 					priceTimeStamp = int(d[9]),
+					priceFact = float(d[10]),
 					id = int(d[0]),
 					id = int(d[0]),
 					db = self)
 					db = self)
 				 for d in data ]
 				 for d in data ]
@@ -1359,12 +1391,18 @@ class Database:
 			c = self.db.cursor()
 			c = self.db.cursor()
 			if origin.inDatabase(self):
 			if origin.inDatabase(self):
 				c.execute("UPDATE origins "
 				c.execute("UPDATE origins "
-					  "SET name=?, description=?, flags=?, "
+					  "SET "
+					  "name=?, "
+					  "description=?, "
+					  "flags=?, "
 					  "createTimeStamp=?, "
 					  "createTimeStamp=?, "
 					  "modifyTimeStamp=?, "
 					  "modifyTimeStamp=?, "
-					  "stockItem=?, supplier=?, "
+					  "stockItem=?, "
+					  "supplier=?, "
 					  "orderCode=?, "
 					  "orderCode=?, "
-					  "price=?, priceTimeStamp=? "
+					  "price=?, "
+					  "priceTimeStamp=?, "
+					  "priceFact=? "
 					  "WHERE id=?;",
 					  "WHERE id=?;",
 					  (toBase64(origin.name),
 					  (toBase64(origin.name),
 					   toBase64(origin.description),
 					   toBase64(origin.description),
@@ -1376,16 +1414,23 @@ class Database:
 					   toBase64(origin.orderCode),
 					   toBase64(origin.orderCode),
 					   float(origin.price),
 					   float(origin.price),
 					   int(origin.getPriceTimeStampInt()),
 					   int(origin.getPriceTimeStampInt()),
+					   float(origin.priceFact),
 					   int(origin.id)))
 					   int(origin.id)))
 			else:
 			else:
 				c.execute("INSERT INTO "
 				c.execute("INSERT INTO "
-					  "origins(name, description, flags, "
+					  "origins("
+					  "name, "
+					  "description, "
+					  "flags, "
 					  "createTimeStamp, "
 					  "createTimeStamp, "
 					  "modifyTimeStamp, "
 					  "modifyTimeStamp, "
-					  "stockItem, supplier, "
+					  "stockItem, "
+					  "supplier, "
 					  "orderCode, "
 					  "orderCode, "
-					  "price, priceTimeStamp) "
-					  "VALUES(?,?,?,?,?,?,?,?,?,?);",
+					  "price, "
+					  "priceTimeStamp, "
+					  "priceFact) "
+					  "VALUES(?,?,?,?,?,?,?,?,?,?,?);",
 					  (toBase64(origin.name),
 					  (toBase64(origin.name),
 					   toBase64(origin.description),
 					   toBase64(origin.description),
 					   int(origin.flags),
 					   int(origin.flags),
@@ -1395,7 +1440,8 @@ class Database:
 					   int(origin.supplier),
 					   int(origin.supplier),
 					   toBase64(origin.orderCode),
 					   toBase64(origin.orderCode),
 					   float(origin.price),
 					   float(origin.price),
-					   int(origin.getPriceTimeStampInt())))
+					   int(origin.getPriceTimeStampInt()),
+					   float(origin.priceFact)))
 				origin.id = c.lastrowid
 				origin.id = c.lastrowid
 				origin.db = self
 				origin.db = self
 			self.__commit()
 			self.__commit()

+ 23 - 5
partmgr/core/origin.py

@@ -2,7 +2,7 @@
 #
 #
 # PartMgr - Origin descriptor
 # PartMgr - Origin descriptor
 #
 #
-# Copyright 2014 Michael Buesch <m@bues.ch>
+# Copyright 2014-2024 Michael Buesch <m@bues.ch>
 #
 #
 # This program is free software; you can redistribute it and/or modify
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # it under the terms of the GNU General Public License as published by
@@ -29,10 +29,17 @@ class Origin(Entity):
 
 
 	NO_PRICE = -0.1
 	NO_PRICE = -0.1
 
 
-	def __init__(self, name,
-		     stockItem=None, supplier=None, orderCode="",
-		     price=NO_PRICE, priceTimeStamp=None,
-		     **kwds):
+	def __init__(
+		self,
+		name,
+		stockItem=None,
+		supplier=None,
+		orderCode="",
+		price=NO_PRICE,
+		priceTimeStamp=None,
+		priceFact=1.0,
+		**kwds,
+	):
 		Entity.__init__(self,
 		Entity.__init__(self,
 				name = name,
 				name = name,
 				entityType = "Origin",
 				entityType = "Origin",
@@ -42,6 +49,7 @@ class Origin(Entity):
 		self.orderCode = orderCode
 		self.orderCode = orderCode
 		self.price = float(price)
 		self.price = float(price)
 		self.priceStamp = Timestamp(priceTimeStamp)
 		self.priceStamp = Timestamp(priceTimeStamp)
+		self.priceFact = float(priceFact)
 
 
 	def syncDatabase(self):
 	def syncDatabase(self):
 		if self.db:
 		if self.db:
@@ -82,6 +90,16 @@ class Origin(Entity):
 			self.setPriceTimeStampNow()
 			self.setPriceTimeStampNow()
 		self.syncDatabase()
 		self.syncDatabase()
 
 
+	def getPriceFact(self):
+		return self.priceFact
+
+	def setPriceFact(self, fact):
+		self.priceFact = fact
+		self.syncDatabase()
+
+	def getEffectivePrice(self):
+		return self.getPrice() * self.getPriceFact()
+
 	def setPriceTimeStampNow(self):
 	def setPriceTimeStampNow(self):
 		self.priceStamp.setNow()
 		self.priceStamp.setNow()
 		self.syncDatabase()
 		self.syncDatabase()

+ 29 - 2
partmgr/gui/originselect.py

@@ -2,7 +2,7 @@
 #
 #
 # PartMgr GUI - Origin widgets
 # PartMgr GUI - Origin widgets
 #
 #
-# Copyright 2014 Michael Buesch <m@bues.ch>
+# Copyright 2014-2024 Michael Buesch <m@bues.ch>
 #
 #
 # This program is free software; you can redistribute it and/or modify
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # it under the terms of the GNU General Public License as published by
@@ -51,9 +51,18 @@ class PriceSpinBox(ProtectedDoubleSpinBox):
 			return Origin.NO_PRICE
 			return Origin.NO_PRICE
 		return ProtectedDoubleSpinBox.valueFromText(self, text)
 		return ProtectedDoubleSpinBox.valueFromText(self, text)
 
 
+class PriceFactorSpinBox(ProtectedDoubleSpinBox):
+	def __init__(self, parent=None):
+		ProtectedDoubleSpinBox.__init__(self, parent)
+		self.setMinimum(0.0)
+		self.setMaximum(16777215.0)
+		self.setDecimals(9)
+		self.setSingleStep(0.01)
+
 class OriginWidget(QWidget):
 class OriginWidget(QWidget):
 	codeChanged = Signal(str)
 	codeChanged = Signal(str)
 	priceChanged = Signal(float)
 	priceChanged = Signal(float)
+	priceFactChanged = Signal(float)
 
 
 	def __init__(self, origin, parent=None):
 	def __init__(self, origin, parent=None):
 		QWidget.__init__(self, parent)
 		QWidget.__init__(self, parent)
@@ -70,11 +79,19 @@ class OriginWidget(QWidget):
 		self.price.setValue(Origin.NO_PRICE if price is None else price)
 		self.price.setValue(Origin.NO_PRICE if price is None else price)
 		priceLayout.addWidget(self.price, 0, 1)
 		priceLayout.addWidget(self.price, 0, 1)
 
 
+		label = QLabel("Factor:", self)
+		priceLayout.addWidget(label, 1, 0)
+
+		fact = origin.getPriceFact()
+		self.priceFact = PriceFactorSpinBox(self)
+		self.priceFact.setValue(fact)
+		priceLayout.addWidget(self.priceFact, 1, 1)
+
 		self.priceStamp = QLabel(self)
 		self.priceStamp = QLabel(self)
 		font = self.priceStamp.font()
 		font = self.priceStamp.font()
 		font.setPointSize(8)
 		font.setPointSize(8)
 		self.priceStamp.setFont(font)
 		self.priceStamp.setFont(font)
-		priceLayout.addWidget(self.priceStamp, 1, 0, 1, 2)
+		priceLayout.addWidget(self.priceStamp, 2, 0, 1, 2)
 
 
 		self.layout().addLayout(priceLayout)
 		self.layout().addLayout(priceLayout)
 
 
@@ -88,6 +105,7 @@ class OriginWidget(QWidget):
 
 
 		self.code.textChanged.connect(self.codeChanged)
 		self.code.textChanged.connect(self.codeChanged)
 		self.price.valueChanged.connect(self.priceChanged)
 		self.price.valueChanged.connect(self.priceChanged)
+		self.priceFact.valueChanged.connect(self.priceFactChanged)
 
 
 		self.updatePriceStamp(origin)
 		self.updatePriceStamp(origin)
 		self.setProtected()
 		self.setProtected()
@@ -95,6 +113,7 @@ class OriginWidget(QWidget):
 	def setProtected(self, prot=True):
 	def setProtected(self, prot=True):
 		self.code.setProtected(prot)
 		self.code.setProtected(prot)
 		self.price.setProtected(prot)
 		self.price.setProtected(prot)
+		self.priceFact.setProtected(prot)
 
 
 	def updatePriceStamp(self, origin):
 	def updatePriceStamp(self, origin):
 		priceStamp = origin.getPriceTimeStamp()
 		priceStamp = origin.getPriceTimeStamp()
@@ -105,6 +124,7 @@ class OriginWidget(QWidget):
 class OneOriginSelectWidget(ItemSelectWidget):
 class OneOriginSelectWidget(ItemSelectWidget):
 	codeChanged = Signal(str)
 	codeChanged = Signal(str)
 	priceChanged = Signal(float)
 	priceChanged = Signal(float)
+	priceFactChanged = Signal(float)
 
 
 	def __init__(self, origin, parent=None):
 	def __init__(self, origin, parent=None):
 		self.originWidget = OriginWidget(origin)
 		self.originWidget = OriginWidget(origin)
@@ -122,6 +142,8 @@ class OneOriginSelectWidget(ItemSelectWidget):
 		self.originWidget.codeChanged.connect(self.codeChanged)
 		self.originWidget.codeChanged.connect(self.codeChanged)
 		self.originWidget.priceChanged.connect(self.__priceChanged)
 		self.originWidget.priceChanged.connect(self.__priceChanged)
 		self.originWidget.priceChanged.connect(self.priceChanged)
 		self.originWidget.priceChanged.connect(self.priceChanged)
+		self.originWidget.priceFactChanged.connect(self.__priceFactChanged)
+		self.originWidget.priceFactChanged.connect(self.priceFactChanged)
 
 
 	def setProtected(self, prot=True):
 	def setProtected(self, prot=True):
 		self.originWidget.setProtected(prot)
 		self.originWidget.setProtected(prot)
@@ -156,6 +178,10 @@ class OneOriginSelectWidget(ItemSelectWidget):
 		self.origin.setPrice(newPrice)
 		self.origin.setPrice(newPrice)
 		self.originWidget.updatePriceStamp(self.origin)
 		self.originWidget.updatePriceStamp(self.origin)
 
 
+	def __priceFactChanged(self, newPriceFact):
+		self.origin.setPriceFact(newPriceFact)
+		self.originWidget.updatePriceStamp(self.origin)
+
 class OriginsSelectWidget(GroupSelectWidget):
 class OriginsSelectWidget(GroupSelectWidget):
 	selectionChanged = Signal()
 	selectionChanged = Signal()
 	contentChanged = Signal()
 	contentChanged = Signal()
@@ -178,6 +204,7 @@ class OriginsSelectWidget(GroupSelectWidget):
 			widget.selectionChanged.connect(self.contentChanged)
 			widget.selectionChanged.connect(self.contentChanged)
 			widget.codeChanged.connect(self.contentChanged)
 			widget.codeChanged.connect(self.contentChanged)
 			widget.priceChanged.connect(self.contentChanged)
 			widget.priceChanged.connect(self.contentChanged)
+			widget.priceFactChanged.connect(self.contentChanged)
 			self.addItemSelectWidget(widget)
 			self.addItemSelectWidget(widget)
 		self.finishUpdate()
 		self.finishUpdate()
 
 

+ 2 - 2
partmgr/gui/tree.py

@@ -482,7 +482,7 @@ class Tree(QTreeView):
 		newName, ok = QInputDialog.getText(
 		newName, ok = QInputDialog.getText(
 				self, "Rename category",
 				self, "Rename category",
 				"Rename category",
 				"Rename category",
-				QLineEdit.Normal,
+				QLineEdit.EchoMode.Normal,
 				category.getName())
 				category.getName())
 		if not ok:
 		if not ok:
 			return
 			return
@@ -494,7 +494,7 @@ class Tree(QTreeView):
 		newName, ok = QInputDialog.getText(
 		newName, ok = QInputDialog.getText(
 				self, "Rename item",
 				self, "Rename item",
 				"Rename item",
 				"Rename item",
-				QLineEdit.Normal,
+				QLineEdit.EchoMode.Normal,
 				stockItem.getName())
 				stockItem.getName())
 		if not ok:
 		if not ok:
 			return
 			return

+ 2 - 2
partmgr/gui/util.py

@@ -2,7 +2,7 @@
 #
 #
 # PartMgr GUI - Utils
 # PartMgr GUI - Utils
 #
 #
-# Copyright 2014 Michael Buesch <m@bues.ch>
+# Copyright 2014-2023 Michael Buesch <m@bues.ch>
 #
 #
 # This program is free software; you can redistribute it and/or modify
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@ from partmgr.core.util import *
 
 
 def copyStrToClipboard(string):
 def copyStrToClipboard(string):
 	clipboard = QApplication.clipboard()
 	clipboard = QApplication.clipboard()
-	for clip in (QClipboard.Clipboard, QClipboard.Selection):
+	for clip in (QClipboard.Mode.Clipboard, QClipboard.Mode.Selection):
 		if string:
 		if string:
 			clipboard.setText(string, clip)
 			clipboard.setText(string, clip)
 		else:
 		else:

+ 1 - 0
setup.py

@@ -43,6 +43,7 @@ setup(
 			    "partmgr/gui", ],
 			    "partmgr/gui", ],
 	scripts		= [ "partmgr-gui", ],
 	scripts		= [ "partmgr-gui", ],
 	keywords	= [ ],
 	keywords	= [ ],
+	install_requires = [ "PySide6", ],
 	classifiers	= [
 	classifiers	= [
 	],
 	],
 	long_description=readmeText,
 	long_description=readmeText,