diff --git a/gui/main_gui.ui b/gui/main_gui.ui index defbf8a..64a48cb 100644 --- a/gui/main_gui.ui +++ b/gui/main_gui.ui @@ -35,7 +35,7 @@ 10 0 401 - 171 + 176 @@ -53,6 +53,11 @@ + + + 11 + + Pension value @@ -60,6 +65,11 @@ + + + 11 + + true @@ -82,6 +92,11 @@ + + + 10 + + Investment mix (funds 50% / shares 50%) @@ -108,6 +123,11 @@ + + + 11 + + Annual share trades @@ -115,6 +135,11 @@ + + + 11 + + Annual fund trades @@ -122,6 +147,11 @@ + + + 11 + + true @@ -132,6 +162,11 @@ false + + + 10 + + Calculate @@ -139,6 +174,11 @@ + + + 11 + + true @@ -151,6 +191,11 @@ true + + + 10 + + @@ -161,7 +206,17 @@ 33 + + + 10 + + + + + 10 + + File @@ -173,6 +228,11 @@ Edit Platforms + + + 10 + + diff --git a/gui/output_window.ui b/gui/output_window.ui index c028924..baa549c 100644 --- a/gui/output_window.ui +++ b/gui/output_window.ui @@ -34,6 +34,11 @@ 250 + + + 11 + + @@ -44,6 +49,11 @@ 24 + + + 10 + + OK @@ -57,6 +67,11 @@ 24 + + + 10 + + Save diff --git a/gui/platform_edit.ui b/gui/platform_edit.ui index ddf9a0e..fe3a57d 100644 --- a/gui/platform_edit.ui +++ b/gui/platform_edit.ui @@ -6,32 +6,20 @@ 0 0 - 498 - 303 + 630 + 567 - - - 498 - 303 - - - - - 498 - 303 - - Platform Editor - 20 + 10 20 - 461 - 243 + 611 + 241 @@ -75,6 +63,11 @@ false + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -92,7 +85,7 @@ - false + true true @@ -101,6 +94,11 @@ + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -117,6 +115,11 @@ + + + 11 + + Share dealing discount # of trades @@ -127,6 +130,11 @@ + + + 11 + + Share platform monthly fee cap @@ -134,6 +142,11 @@ + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -150,6 +163,11 @@ + + + 11 + + Share dealing fee* @@ -160,6 +178,11 @@ false + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -186,6 +209,11 @@ + + + 11 + + Fund dealing fee* @@ -195,10 +223,21 @@ - + + + + 11 + + + + + + 11 + + Share dealing discount amount @@ -206,6 +245,11 @@ + + + 11 + + Share platform fee* @@ -213,6 +257,11 @@ + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -229,6 +278,11 @@ + + + 11 + + Platform name @@ -239,6 +293,11 @@ false + + + 11 + + QAbstractSpinBox::ButtonSymbols::NoButtons @@ -261,12 +320,17 @@ - 350 - 270 + 482 + 534 141 24 + + + 10 + + Save @@ -274,8 +338,8 @@ - 10 - 280 + 8 + 262 191 21 @@ -287,7 +351,7 @@ - 437 + 577 10 61 16 @@ -300,6 +364,147 @@ Qt::AlignmentFlag::AlignCenter + + + + 11 + 309 + 611 + 31 + + + + + 0 + + + + + + 11 + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + % + + + 100.000000000000000 + + + + + + + + 11 + + + + on the first + + + + + + + + 11 + + + + the fee is + + + + + + + + 11 + + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + £ + + + 9999999.000000000000000 + + + + + + + + false + + + + 532 + 481 + 91 + 24 + + + + + 10 + + + + Add row + + + + + false + + + + 440 + 481 + 91 + 24 + + + + + 10 + + + + Remove row + + + + + + 6 + 479 + 421 + 21 + + + + + 10 + + + + on the value above £ there is no charge + + @@ -321,7 +526,6 @@ share_deal_fee_box share_deal_reduce_trades_box share_deal_reduce_amount_box - save_but plat_name_check fund_deal_fee_check share_plat_fee_check @@ -329,6 +533,11 @@ share_deal_fee_check share_deal_reduce_trades_check share_deal_reduce_amount_check + first_tier_box + first_tier_fee_box + del_row_but + add_row_but + save_but diff --git a/src/main_window.py b/src/main_window.py index 24a5eea..cd9b0f0 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -66,7 +66,6 @@ class SIPPCompare(QMainWindow): def init_variables(self): self.optional_boxes = self.platform_win.get_optional_boxes() self.fund_plat_fee = self.platform_win.get_fund_plat_fee() - self.fund_deal_fee = self.platform_win.get_fund_deal_fee() self.share_plat_fee = self.platform_win.get_share_plat_fee() self.share_deal_fee = self.platform_win.get_share_deal_fee() @@ -77,16 +76,21 @@ class SIPPCompare(QMainWindow): self.plat_name = None if self.optional_boxes[1]: + self.fund_deal_fee = self.platform_win.get_fund_deal_fee() + else: + self.fund_deal_fee = None + + if self.optional_boxes[2]: self.share_plat_max_fee = self.platform_win.get_share_plat_max_fee() else: self.share_plat_max_fee = None - if self.optional_boxes[2]: + if self.optional_boxes[3]: self.share_deal_reduce_trades = self.platform_win.get_share_deal_reduce_trades() else: self.share_deal_reduce_trades = None - if self.optional_boxes[3]: + if self.optional_boxes[4]: self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount() else: self.share_deal_reduce_amount = None @@ -101,7 +105,8 @@ class SIPPCompare(QMainWindow): slider_val: int = self.mix_slider.value() funds_value = (slider_val / 100) * value_num fund_trades_num = int(self.fund_trades_combo.currentText()) - self.fund_deal_fees = fund_trades_num * self.fund_deal_fee + if self.fund_deal_fee is not None: + self.fund_deal_fees = fund_trades_num * self.fund_deal_fee for i in range(1, len(self.fund_plat_fee[0])): band = self.fund_plat_fee[0][i] @@ -120,15 +125,15 @@ class SIPPCompare(QMainWindow): if self.share_plat_max_fee is not None: if (self.share_plat_fee * shares_value / 12) > self.share_plat_max_fee: self.share_plat_fees = self.share_plat_max_fee * 12 - else: - self.share_plat_fees = self.share_plat_fee * shares_value + else: + self.share_plat_fees = self.share_plat_fee * shares_value share_trades_num = int(self.share_trades_combo.currentText()) if self.share_deal_reduce_trades is not None: if (share_trades_num / 12) >= self.share_deal_reduce_trades: self.share_deal_fees = self.share_deal_reduce_amount * share_trades_num - else: - self.share_deal_fees = self.share_deal_fee * share_trades_num + else: + self.share_deal_fees = self.share_deal_fee * share_trades_num self.show_output_win() diff --git a/src/platform_edit.py b/src/platform_edit.py index 55f4e4a..06573cc 100644 --- a/src/platform_edit.py +++ b/src/platform_edit.py @@ -1,8 +1,9 @@ from PyQt6.QtCore import QRegularExpression -from PyQt6.QtGui import QRegularExpressionValidator -from PyQt6.QtWidgets import QWidget +from PyQt6.QtGui import QRegularExpressionValidator, QFont +from PyQt6.QtWidgets import QWidget, QLabel from PyQt6 import uic +from widgets.fastedit_spinbox import FastEditQDoubleSpinBox import main_window @@ -16,11 +17,7 @@ class PlatformEdit(QWidget): # Create main window object, passing this instance as param self.main_win = main_window.SIPPCompare(self) - # TODO: Make fund_plat_fee user-defined - self.fund_plat_fee = [ - [0, 250000, 1000000, 2000000], - [0, 0.25, 0.1, 0.05] - ] + self.fund_plat_fee = [] self.plat_name = "" self.fund_deal_fee = 0.0 self.share_plat_fee = 0.0 @@ -28,19 +25,22 @@ class PlatformEdit(QWidget): self.share_deal_fee = 0.0 self.share_deal_reduce_trades = 0.0 self.share_deal_reduce_amount = 0.0 + self.widgets_list_list = [] + + self.fund_fee_rows = 1 # Debugging feature: set with "--DEBUG_AUTOFILL" cmd argument self.autofill = autofill if autofill: self.save_but.setEnabled(True) self.required_fields = [ - self.fund_deal_fee_box, self.share_plat_fee_box, self.share_deal_fee_box ] self.optional_fields = [ self.plat_name_box, + self.fund_deal_fee_box, self.share_plat_max_fee_box, self.share_deal_reduce_trades_box, self.share_deal_reduce_amount_box @@ -48,12 +48,14 @@ class PlatformEdit(QWidget): self.optional_check_boxes = [ self.plat_name_check, + self.fund_deal_fee_check, self.share_plat_max_fee_check, self.share_deal_reduce_trades_check, self.share_deal_reduce_amount_check ] self.check_boxes_ticked = [ + True, True, False, False, @@ -74,8 +76,14 @@ class PlatformEdit(QWidget): for check_box in self.optional_check_boxes: check_box.checkStateChanged.connect(self.check_valid) + self.first_tier_box.valueChanged.connect(self.check_valid) + self.first_tier_fee_box.valueChanged.connect(self.check_valid) + self.first_tier_box.valueChanged.connect(self.update_tier_labels) + # NOTE: Signal defined in UI file to close window when save button clicked self.save_but.clicked.connect(self.init_variables) + self.add_row_but.clicked.connect(self.add_row) + self.del_row_but.clicked.connect(self.remove_row) # Set validators # Regex accepts any characters that match [a-Z], [0-9] or _ @@ -83,11 +91,28 @@ class PlatformEdit(QWidget): QRegularExpressionValidator(QRegularExpression("\\w*")) ) + def create_plat_fee_struct(self): + plat_fee_struct = [[0], [0]] + plat_fee_struct[0].append(self.first_tier_box.value()) + plat_fee_struct[1].append(self.first_tier_fee_box.value()) + + for i in range(len(self.widgets_list_list)): + band = self.widgets_list_list[i][1].value() + fee = self.widgets_list_list[i][3].value() + plat_fee_struct[0].append(band) + plat_fee_struct[1].append(fee) + + return plat_fee_struct + # Get fee structure variables from user input when "Save" clicked def init_variables(self): # If debugging, save time by hardcoding if self.autofill: self.plat_name = "AJBell" + self.fund_plat_fee = [ + [0, 250000, 1000000, 2000000], + [0, 0.25, 0.1, 0.05] + ] self.fund_deal_fee = 1.50 self.share_plat_fee = 0.0025 self.share_plat_max_fee = 3.50 @@ -96,6 +121,7 @@ class PlatformEdit(QWidget): self.share_deal_reduce_amount = 3.50 else: self.plat_name = self.plat_name_box.text() + self.fund_plat_fee = self.create_plat_fee_struct() self.fund_deal_fee = float(self.fund_deal_fee_box.value()) self.share_plat_fee = float(self.share_plat_fee_box.value()) / 100 self.share_plat_max_fee = float(self.share_plat_max_fee_box.value()) @@ -116,6 +142,7 @@ class PlatformEdit(QWidget): # It's also called when any field emits a textChanged() or valueChanged() signal def check_valid(self): valid = True + tiers_valid = True # Check all required fields have a non-zero value for field in self.required_fields: @@ -145,11 +172,118 @@ class PlatformEdit(QWidget): input_box_item.setEnabled(False) self.check_boxes_ticked[i] = False - if valid: + if self.first_tier_fee_box.value() == 0: + tiers_valid = False + + if self.fund_fee_rows > 1: + if self.widgets_list_list[0][1].value() <= self.first_tier_box.value(): + tiers_valid = False + if self.widgets_list_list[0][3].value() == 0: + tiers_valid = False + + for i in range(len(self.widgets_list_list) - 1, 0, -1): + if self.widgets_list_list[i][1].value() <= self.widgets_list_list[i-1][1].value(): + tiers_valid = False + if self.widgets_list_list[i][3].value() == 0: + tiers_valid = False + + if tiers_valid and self.fund_fee_rows < 6: + self.add_row_but.setEnabled(True) + else: + self.add_row_but.setEnabled(False) + + if valid and tiers_valid: self.save_but.setEnabled(True) else: self.save_but.setEnabled(False) + def update_tier_labels(self): + if self.fund_fee_rows > 1: + prev_value = self.first_tier_box.value() + self.widgets_list_list[0][0].setText(f"between £{int(prev_value)} and") + + for i in range(len(self.widgets_list_list) - 1, 0, -1): + prev_value = self.widgets_list_list[i-1][1].value() + self.widgets_list_list[i][0].setText(f"between £{int(prev_value)} and") + + if self.fund_fee_rows > 1: + max_band = self.widgets_list_list[self.fund_fee_rows - 2][1].value() + else: + max_band = self.first_tier_box.value() + self.val_above_lab.setText(f"on the value above £{int(max_band)} there is no charge") + + def add_row(self): + widgets = [] + font = QFont() + font.setPointSize(11) + + widgets.append(QLabel(self.gridLayoutWidget_2)) + widgets[0].setFont(font) + + widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2)) + widgets[1].setPrefix("£") + widgets[1].setMaximum(9999999) + widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) + widgets[1].setFont(font) + widgets[1].valueChanged.connect(self.check_valid) + widgets[1].valueChanged.connect(self.update_tier_labels) + + widgets.append(QLabel(self.gridLayoutWidget_2)) + widgets[2].setText(f"the fee is") + widgets[2].setFont(font) + + widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2)) + widgets[3].setSuffix("%") + widgets[3].setMaximum(100) + widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons) + widgets[3].setFont(font) + widgets[3].valueChanged.connect(self.check_valid) + + # TODO: why 28.5? + self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * (self.fund_fee_rows + 1), 0))) + for i in range(len(widgets)): + self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1) + + self.fund_fee_rows += 1 + + self.widgets_list_list.append(widgets) + cur_label_idx = self.gridLayout_2.indexOf(widgets[0]) + cur_box_idx = self.gridLayout_2.indexOf(widgets[1]) + cur_label_pos = list(self.gridLayout_2.getItemPosition(cur_label_idx))[:2] + cur_box_pos = list(self.gridLayout_2.getItemPosition(cur_box_idx))[:2] + + prev_box_row = cur_box_pos[0] - 1 + prev_box_item = self.gridLayout_2.itemAtPosition(prev_box_row, cur_box_pos[1]).widget() + cur_label_item = self.gridLayout_2.itemAtPosition(cur_label_pos[0], cur_label_pos[1]).widget() + cur_label_item.setText(f"between £{int(prev_box_item.value())} and") + + if self.fund_fee_rows > 1: + self.del_row_but.setEnabled(True) + + if self.fund_fee_rows > 5: + self.add_row_but.setEnabled(False) + + self.check_valid() + + # TODO: Tab order + + def remove_row(self): + for widget in self.widgets_list_list[self.fund_fee_rows - 2]: + self.gridLayout_2.removeWidget(widget) + widget.hide() + self.widgets_list_list.pop() + self.fund_fee_rows -= 1 + self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * self.fund_fee_rows, 0))) + + if self.fund_fee_rows < 2: + self.del_row_but.setEnabled(False) + + if self.fund_fee_rows < 6: + self.add_row_but.setEnabled(True) + + self.check_valid() + self.update_tier_labels() + # Getter functions (is this necessary? maybe directly reading class vars would be best...) def get_optional_boxes(self): return self.check_boxes_ticked