"""Долги: кто и сколько должен за взятые булки.""" from __future__ import annotations from decimal import Decimal from PySide6.QtCore import Qt from PySide6.QtGui import QColor from PySide6.QtWidgets import ( QDialog, QFormLayout, QHeaderView, QLineEdit, QMessageBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, ) from .. import journal from .. import money as m from ..models import SALE_KIND_LABELS from . import theme from . import widgets as w ROLE_SALE_ID = Qt.UserRole + 2 class PaymentDialog(QDialog): def __init__(self, suggested: Decimal, currency: str, parent=None): super().__init__(parent) self.setWindowTitle("Принять оплату") self.setMinimumWidth(340) layout = QVBoxLayout(self) layout.setContentsMargins(20, 16, 20, 16) layout.setSpacing(10) form = QFormLayout() form.setSpacing(8) self.date = w.DateInput() form.addRow("Дата", self.date) self.amount = w.MoneySpin(currency) self.amount.set_decimal(suggested) self.amount.selectAll() form.addRow("Сумма", self.amount) self.note = QLineEdit() form.addRow("Заметка", self.note) layout.addLayout(form) layout.addWidget(w.label(f"Долг по этой продаже: {m.fmt_money(suggested, currency)}", "dim")) layout.addWidget( w.row(None, w.button("Отмена", on_click=self.reject), w.button("Принять", "primary", self.accept)) ) self.amount.setFocus() def ask_payment(parent, suggested: Decimal, currency: str) -> Decimal | None: """Спросить сумму оплаты. Возвращает None, если отменили.""" dialog = PaymentDialog(suggested, currency, parent) if dialog.exec() != QDialog.Accepted: return None return dialog.amount.value_decimal() class DebtsPage(QWidget): """Дерево: человек → его неоплаченные продажи.""" def __init__(self, ctx, parent=None): super().__init__(parent) self.ctx = ctx layout = QVBoxLayout(self) layout.setContentsMargins(20, 18, 20, 18) layout.setSpacing(12) layout.addWidget(w.heading("Долги")) self.total_label = w.label("", "h2") layout.addWidget( w.row( w.button("Принять оплату", "primary", self.take_payment), w.button("Погасить целиком", on_click=self.settle_full), None, self.total_label, ) ) self.tree = QTreeWidget() self.tree.setHeaderLabels(["Кто / что", "Дата", "Сумма", "Оплачено", "Долг"]) self.tree.setAlternatingRowColors(True) self.tree.setRootIsDecorated(True) self.tree.header().setStretchLastSection(False) self.tree.header().setSectionResizeMode(0, QHeaderView.Stretch) self.tree.itemDoubleClicked.connect(self.take_payment) layout.addWidget(self.tree, 1) self.empty = w.label("Никто ничего не должен.", "dim") layout.addWidget(self.empty) # --- отрисовка --- def refresh(self) -> None: doc = self.ctx.vault.doc currency = self.ctx.currency debtors = self.ctx.report.debtors self.tree.clear() for debtor in debtors: parent = QTreeWidgetItem( [debtor.name, "", "", "", m.fmt_money(debtor.debt, currency)] ) font = parent.font(0) font.setBold(True) parent.setFont(0, font) parent.setFont(4, font) parent.setForeground(4, QColor(theme.WARN)) parent.setTextAlignment(4, Qt.AlignRight | Qt.AlignVCenter) for sale in sorted(debtor.sales, key=lambda s: s.date): what = ", ".join( f"{doc.product_name(line.product_id)} × {m.fmt_qty(line.qty)}" for line in sale.lines ) child = QTreeWidgetItem( [ f"{SALE_KIND_LABELS.get(sale.kind, sale.kind)}: {what}", sale.date.strftime("%d.%m.%Y"), m.fmt_money(sale.total, currency), m.fmt_money(sale.paid, currency), m.fmt_money(sale.debt, currency), ] ) child.setData(0, ROLE_SALE_ID, sale.id) for column in (2, 3, 4): child.setTextAlignment(column, Qt.AlignRight | Qt.AlignVCenter) parent.addChild(child) self.tree.addTopLevelItem(parent) parent.setExpanded(True) for column in range(1, 5): self.tree.resizeColumnToContents(column) total = self.ctx.report.summary.receivable self.total_label.setText(f"Мне должны: {m.fmt_money(total, currency)}") self.empty.setVisible(not debtors) self.tree.setVisible(bool(debtors)) # --- действия --- def _selected_sale(self): item = self.tree.currentItem() if item is None: QMessageBox.information(self, "Не выбрано", "Выбери конкретную продажу.") return None sale_id = item.data(0, ROLE_SALE_ID) if sale_id is None: QMessageBox.information( self, "Выбери продажу", "Раскрой человека и выбери, за что он платит." ) return None return self.ctx.vault.doc.sale(sale_id) def take_payment(self) -> None: sale = self._selected_sale() if sale is None: return amount = ask_payment(self, sale.debt, self.ctx.currency) if amount is None: return try: journal.add_sale_payment(self.ctx.vault, sale.id, amount) except journal.ValidationError as exc: QMessageBox.warning(self, "Не получилось", str(exc)) return self.ctx.changed() def settle_full(self) -> None: sale = self._selected_sale() if sale is None: return if sale.debt <= 0: return journal.add_sale_payment(self.ctx.vault, sale.id, sale.debt) self.ctx.changed()