diff --git a/gui_app.py b/gui_app.py index 354b349..8f33780 100644 --- a/gui_app.py +++ b/gui_app.py @@ -504,7 +504,7 @@ def accept(self): class ConflictDialog(QDialog): """Dialog for resolving data conflicts.""" - def __init__(self, month_key: str, translations: Translations, parent=None): + def __init__(self, month_key: str, translations: Translations, parent=None, conflicts_info: list = None): """ Initialize conflict resolution dialog. @@ -512,6 +512,7 @@ def __init__(self, month_key: str, translations: Translations, parent=None): month_key: Month identifier string (e.g., "2024-01") translations: Translations object for localized UI text parent: Parent widget (optional) + conflicts_info: Optional list of conflict dictionaries """ super().__init__(parent) self.month_key = month_key @@ -520,15 +521,48 @@ def __init__(self, month_key: str, translations: Translations, parent=None): self.setWindowTitle(self.translations.get('conflict_title')) self.setModal(True) - self.resize(400, 200) + self.resize(500, 300) layout = QVBoxLayout() + # Format month_key + try: + year, month = month_key.split('-') + formatted_month = f"{int(month):02d}/{year}" + except: + formatted_month = month_key + # Message - msg = QLabel(self.translations.get('conflict_message', month_key=month_key)) + msg = QLabel(self.translations.get('conflict_message', month_key=formatted_month)) msg.setFont(QFont('Arial', 11)) layout.addWidget(msg) + if conflicts_info: + conflicts_text = QTextEdit() + conflicts_text.setReadOnly(True) + + # Format conflicts for display + text_lines = [] + for c in conflicts_info: + text_lines.append(f"• {c['category']} ➔ {c['subcat']}:") + try: + text_lines.append(f" {self.translations.get('existing', 'Existing')}: ₪{float(c['existing']):,.2f}") + except (ValueError, TypeError): + text_lines.append(f" {self.translations.get('existing', 'Existing')}: {c['existing']}") + try: + text_lines.append(f" {self.translations.get('new_amount', 'New')}: ₪{float(c['new']):,.2f}") + except (ValueError, TypeError): + text_lines.append(f" {self.translations.get('new_amount', 'New')}: {c['new']}") + text_lines.append("") + + conflicts_text.setText("\n".join(text_lines)) + + # Adjust alignment based on RTL + if self.translations.is_rtl(): + conflicts_text.setAlignment(Qt.AlignmentFlag.AlignRight) + + layout.addWidget(conflicts_text) + # Buttons override_btn = QPushButton(self.translations.get('override_btn')) override_btn.clicked.connect(lambda: self.set_decision("override")) @@ -2250,7 +2284,7 @@ def update_progress(self, message: str): """ self.statusBar().showMessage(message) - def resolve_conflict(self, month_key: str) -> str: + def resolve_conflict(self, month_key: str, conflicts_info: list = None) -> str: """ Resolve data conflict via GUI dialog. @@ -2258,11 +2292,12 @@ def resolve_conflict(self, month_key: str) -> str: Args: month_key: Month identifier string (e.g., "2024-01") + conflicts_info: Optional list of conflict details Returns: User's decision: "override", "add", or "skip" """ - dialog = ConflictDialog(month_key, self.translations, self) + dialog = ConflictDialog(month_key, self.translations, self, conflicts_info) dialog.exec() return dialog.decision diff --git a/output/budget.log b/output/budget.log deleted file mode 100644 index e69de29..0000000 diff --git a/src/dashboard_writer.py b/src/dashboard_writer.py index 0396bfd..cb452fc 100644 --- a/src/dashboard_writer.py +++ b/src/dashboard_writer.py @@ -181,6 +181,31 @@ def _update_sheet(self, year: int, df: pd.DataFrame) -> None: category_ranges = self._get_category_row_ranges(ws) existing_map = self._build_subcat_location_map(ws, category_ranges) + # Pre-calculate conflicts for the whole year + conflicts_by_month = {} + for _, row in df.iterrows(): + cat = row['category'] + subcat = row['subcat'] + month = str(int(row['month'])) + amount = row['monthly_amount'] + col = month_columns.get(month) + if not col: + continue + + if (cat, subcat) in existing_map: + row_idx = existing_map[(cat, subcat)] + cell_value = ws.cell(row=row_idx, column=col + 1).value + if cell_value is not None and cell_value != 0: + month_key = f"{year}-{month}" + if month_key not in conflicts_by_month: + conflicts_by_month[month_key] = [] + conflicts_by_month[month_key].append({ + 'category': cat, + 'subcat': subcat, + 'existing': cell_value, + 'new': amount + }) + for _, row in df.iterrows(): cat = row['category'] subcat = row['subcat'] @@ -205,7 +230,8 @@ def _update_sheet(self, year: int, df: pd.DataFrame) -> None: if existing_value is not None and existing_value != 0: month_key = f"{year}-{month}" if month_key not in self.user_decisions: - decision = self._prompt_user_decision(month_key, self.conflict_resolver) + conflicts = conflicts_by_month.get(month_key, []) + decision = self._prompt_user_decision(month_key, self.conflict_resolver, conflicts) self.user_decisions[month_key] = decision else: decision = self.user_decisions[month_key] @@ -237,12 +263,23 @@ def _update_sheet(self, year: int, df: pd.DataFrame) -> None: logger.info(f"Dashboard sheet updated for year {year}") - def _prompt_user_decision(self, month_key: str, conflict_resolver: Optional[Callable[[str], str]] = None) -> str: + def _prompt_user_decision(self, month_key: str, conflict_resolver: Optional[Callable] = None, conflicts_info: list = None) -> str: # Use callback if provided (GUI mode), otherwise use input (CLI mode) if conflict_resolver: - return conflict_resolver(month_key) + import inspect + sig = inspect.signature(conflict_resolver) + if len(sig.parameters) >= 2: + return conflict_resolver(month_key, conflicts_info) + else: + return conflict_resolver(month_key) print(f"\nData already exists for {month_key}. Choose how to handle it:") + if conflicts_info: + print(f"Conflicts ({len(conflicts_info)} found):") + for c in conflicts_info[:3]: + print(f" {c['category']} -> {c['subcat']}: Existing {c['existing']}, New {c['new']}") + if len(conflicts_info) > 3: + print(f" ... and {len(conflicts_info) - 3} more") print("1. Override existing data") print("2. Add to existing data") print("3. Skip this month")