編集画面の充実
This commit is contained in:
parent
be14fa7eb2
commit
25032d3b9b
3 changed files with 313 additions and 108 deletions
|
|
@ -110,6 +110,7 @@ def build_invoice_items_edit_table(
|
|||
on_delete_row: Callable[[int], None],
|
||||
products: List[Product],
|
||||
on_product_select: Callable[[int], None] | None = None,
|
||||
row_refs: Optional[dict] = None,
|
||||
) -> ft.Column:
|
||||
"""編集モードの明細テーブル。"""
|
||||
header_row = ft.Row(
|
||||
|
|
@ -173,19 +174,29 @@ def build_invoice_items_edit_table(
|
|||
on_click=lambda _, idx=i: on_delete_row(idx),
|
||||
)
|
||||
|
||||
subtotal_text = ft.Text(
|
||||
f"¥{item.subtotal:,}",
|
||||
size=12,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
width=70,
|
||||
text_align=ft.TextAlign.RIGHT,
|
||||
)
|
||||
|
||||
if row_refs is not None:
|
||||
row_refs[i] = {
|
||||
"product": product_field,
|
||||
"quantity": quantity_field,
|
||||
"unit_price": unit_price_field,
|
||||
"subtotal": subtotal_text,
|
||||
}
|
||||
|
||||
data_rows.append(
|
||||
ft.Row(
|
||||
[
|
||||
product_field,
|
||||
quantity_field,
|
||||
unit_price_field,
|
||||
ft.Text(
|
||||
f"¥{item.subtotal:,}",
|
||||
size=12,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
width=70,
|
||||
text_align=ft.TextAlign.RIGHT,
|
||||
),
|
||||
subtotal_text,
|
||||
delete_button,
|
||||
],
|
||||
key=f"row-{i}-{item.description}",
|
||||
|
|
|
|||
362
main.py
362
main.py
|
|
@ -252,6 +252,8 @@ class FlutterStyleDashboard:
|
|||
self.app_service = AppService()
|
||||
self.invoices = []
|
||||
self.customers = []
|
||||
self._item_row_refs: Dict[int, Dict[str, ft.Control]] = {}
|
||||
self._total_amount_text: Optional[ft.Text] = None
|
||||
|
||||
self.setup_page()
|
||||
self.setup_database()
|
||||
|
|
@ -313,7 +315,8 @@ class FlutterStyleDashboard:
|
|||
document_type=invoice.document_type,
|
||||
amount=invoice.total_amount,
|
||||
notes=invoice.notes,
|
||||
items=invoice.items
|
||||
items=invoice.items,
|
||||
is_draft=invoice.is_draft,
|
||||
)
|
||||
|
||||
logging.info(f"サンプルデータ作成完了: {len(sample_invoices)}件")
|
||||
|
|
@ -509,6 +512,14 @@ class FlutterStyleDashboard:
|
|||
visible=is_draft,
|
||||
)
|
||||
|
||||
delete_button = self._build_delete_draft_button(is_view_mode)
|
||||
|
||||
trailing_controls: List[ft.Control] = []
|
||||
if delete_button:
|
||||
trailing_controls.append(delete_button)
|
||||
if is_draft:
|
||||
trailing_controls.append(draft_badge)
|
||||
|
||||
app_bar = AppBar(
|
||||
title="伝票詳細",
|
||||
show_back=True,
|
||||
|
|
@ -518,7 +529,7 @@ class FlutterStyleDashboard:
|
|||
action_icon=ft.Icons.SAVE if getattr(self, 'is_detail_edit_mode', False) else ft.Icons.EDIT,
|
||||
action_tooltip="保存" if getattr(self, 'is_detail_edit_mode', False) else "編集",
|
||||
bottom=None,
|
||||
trailing_controls=([draft_badge] + ([doc_type_menu] if doc_type_menu else [])) if is_draft else ([doc_type_menu] if doc_type_menu else []),
|
||||
trailing_controls=trailing_controls,
|
||||
title_control=title_ctrl,
|
||||
)
|
||||
|
||||
|
|
@ -864,7 +875,7 @@ class FlutterStyleDashboard:
|
|||
logging.warning(f"差分判定失敗: {e}")
|
||||
return True
|
||||
|
||||
def create_slip_card(self, slip) -> ft.Container:
|
||||
def create_slip_card(self, slip, interactive: bool = True) -> ft.Control:
|
||||
"""伝票カード作成(コンパクト表示)"""
|
||||
theme = self.invoice_card_theme
|
||||
palette = self.doc_type_palette
|
||||
|
|
@ -928,7 +939,24 @@ class FlutterStyleDashboard:
|
|||
bgcolor=theme["tag_bg"],
|
||||
border_radius=10,
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(f"No: {invoice_number}", size=10, color=theme["subtitle_color"]),
|
||||
ft.Container(
|
||||
content=ft.Text(
|
||||
"下書き",
|
||||
size=9,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=theme["tag_text_color"],
|
||||
),
|
||||
padding=ft.Padding.symmetric(horizontal=8, vertical=2),
|
||||
bgcolor=theme["tag_bg"],
|
||||
border_radius=10,
|
||||
visible=is_draft_card,
|
||||
),
|
||||
],
|
||||
spacing=6,
|
||||
),
|
||||
],
|
||||
spacing=6,
|
||||
),
|
||||
|
|
@ -994,21 +1022,6 @@ class FlutterStyleDashboard:
|
|||
|
||||
status_chip = ft.Text("✓ LOCK", size=9, color=theme["tag_text_color"]) if final_locked else ft.Container()
|
||||
|
||||
if is_draft_card:
|
||||
status_chip = ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content=ft.Text("DRAFT", size=9, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
|
||||
padding=ft.Padding.symmetric(horizontal=6, vertical=2),
|
||||
bgcolor=theme["draft_badge_bg"],
|
||||
border_radius=999,
|
||||
),
|
||||
status_chip or ft.Container(),
|
||||
],
|
||||
spacing=4,
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
)
|
||||
|
||||
card_bg = theme["draft_card_bg"] if is_draft_card else theme["card_bg"]
|
||||
card_border = ft.Border.all(1, theme["draft_border"]) if is_draft_card else None
|
||||
card_shadow = [
|
||||
|
|
@ -1029,6 +1042,9 @@ class FlutterStyleDashboard:
|
|||
shadow=card_shadow,
|
||||
)
|
||||
|
||||
if not interactive:
|
||||
return ft.Container(content=card_body)
|
||||
|
||||
return ft.GestureDetector(
|
||||
content=card_body,
|
||||
on_tap=on_single_tap,
|
||||
|
|
@ -1242,6 +1258,7 @@ class FlutterStyleDashboard:
|
|||
# 編集不可チェック(新規作成時はFalse)
|
||||
is_new_invoice = self.editing_invoice.invoice_number.startswith("NEW-")
|
||||
edit_bg = ft.Colors.BROWN_50
|
||||
is_draft = bool(getattr(self.editing_invoice, "is_draft", False))
|
||||
|
||||
# LOCK条件:明示的に確定された場合のみLOCK
|
||||
# PDF生成だけではLOCKしない(お試しPDFを許可)
|
||||
|
|
@ -1273,39 +1290,36 @@ class FlutterStyleDashboard:
|
|||
"""顧客選択画面を開く"""
|
||||
self.open_customer_picker()
|
||||
|
||||
customer_field = None
|
||||
if (not is_view_mode and not is_locked) or is_new_invoice:
|
||||
customer_field = ft.TextField(
|
||||
label="顧客名",
|
||||
value=self.editing_invoice.customer.name if self.editing_invoice.customer.name != "選択してください" else "",
|
||||
disabled=is_locked,
|
||||
width=260,
|
||||
bgcolor=edit_bg,
|
||||
def set_doc_type(dt: DocumentType):
|
||||
if self.is_detail_edit_mode and not is_locked:
|
||||
self.select_document_type(dt)
|
||||
|
||||
doc_type_items = [
|
||||
ft.PopupMenuItem(
|
||||
content=ft.Text(dt.value),
|
||||
on_click=lambda _, d=dt: set_doc_type(d)
|
||||
)
|
||||
for dt in DocumentType
|
||||
if dt != DocumentType.DRAFT
|
||||
]
|
||||
|
||||
def update_customer_name(e):
|
||||
"""顧客名を更新"""
|
||||
if self.editing_invoice:
|
||||
customer_name = e.control.value or ""
|
||||
found_customer = None
|
||||
for customer in self.app_service.customer.get_all_customers():
|
||||
if customer.name == customer_name or customer.formal_name == customer_name:
|
||||
found_customer = customer
|
||||
break
|
||||
def toggle_draft(e):
|
||||
self.editing_invoice.is_draft = bool(e.control.value)
|
||||
self.update_main_content()
|
||||
|
||||
if found_customer:
|
||||
self.editing_invoice.customer = found_customer
|
||||
if self.is_detail_edit_mode and not is_locked:
|
||||
doc_type_menu = ft.PopupMenuButton(
|
||||
content=ft.Text(
|
||||
self.selected_document_type.value,
|
||||
size=12,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=ft.Colors.WHITE,
|
||||
),
|
||||
items=doc_type_items,
|
||||
tooltip="帳票タイプ変更",
|
||||
)
|
||||
else:
|
||||
from models.invoice_models import Customer
|
||||
self.editing_invoice.customer = Customer(
|
||||
id=0,
|
||||
name=customer_name,
|
||||
formal_name=customer_name,
|
||||
address="",
|
||||
phone=""
|
||||
)
|
||||
|
||||
customer_field.on_change = update_customer_name
|
||||
doc_type_menu = None
|
||||
|
||||
# 日付・時間ピッカー(テキスト入力NGなのでボタン+ポップアップ)
|
||||
date_button = None
|
||||
|
|
@ -1425,6 +1439,8 @@ class FlutterStyleDashboard:
|
|||
if not is_new_invoice and self._invoice_snapshot:
|
||||
if not self._is_invoice_changed(self.editing_invoice, self._invoice_snapshot):
|
||||
self._show_snack("変更はありませんでした", ft.Colors.BLUE_GREY_600)
|
||||
self.is_detail_edit_mode = False
|
||||
self.update_main_content()
|
||||
return
|
||||
|
||||
# UIで更新された明細を保存前に正規化して確定
|
||||
|
|
@ -1477,7 +1493,8 @@ class FlutterStyleDashboard:
|
|||
document_type=self.editing_invoice.document_type,
|
||||
amount=amount,
|
||||
notes=getattr(self.editing_invoice, 'notes', ''),
|
||||
items=self.editing_invoice.items # UIの明細を渡す
|
||||
items=self.editing_invoice.items,
|
||||
is_draft=bool(getattr(self.editing_invoice, 'is_draft', False)),
|
||||
)
|
||||
logging.info(f"create_invoice戻り値: {success}")
|
||||
if success:
|
||||
|
|
@ -1581,62 +1598,126 @@ class FlutterStyleDashboard:
|
|||
run_spacing=4,
|
||||
) if summary_tags else ft.Container(height=0)
|
||||
|
||||
customer_label = self.editing_invoice.customer.formal_name
|
||||
if not customer_label or customer_label == "選択してください":
|
||||
customer_label = "顧客を選択"
|
||||
|
||||
if (not is_view_mode and not is_locked) or is_new_invoice:
|
||||
customer_control = ft.Button(
|
||||
content=ft.Text(customer_label, no_wrap=True),
|
||||
width=220,
|
||||
height=36,
|
||||
on_click=lambda _: select_customer(),
|
||||
style=ft.ButtonStyle(
|
||||
padding=ft.Padding.symmetric(horizontal=10, vertical=6),
|
||||
bgcolor=ft.Colors.BLUE_GREY_100,
|
||||
shape=ft.RoundedRectangleBorder(radius=6),
|
||||
),
|
||||
)
|
||||
else:
|
||||
customer_control = ft.Text(
|
||||
customer_label,
|
||||
size=13,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
)
|
||||
|
||||
if self.is_detail_edit_mode and not is_locked:
|
||||
doc_type_control = ft.PopupMenuButton(
|
||||
content=ft.Text(
|
||||
self.selected_document_type.value,
|
||||
size=12,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=ft.Colors.WHITE,
|
||||
),
|
||||
items=doc_type_items,
|
||||
tooltip="帳票タイプ変更",
|
||||
)
|
||||
else:
|
||||
doc_type_control = ft.Text(
|
||||
self.selected_document_type.value,
|
||||
size=12,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=ft.Colors.WHITE,
|
||||
)
|
||||
|
||||
draft_control: ft.Control
|
||||
if self.is_detail_edit_mode and not is_locked:
|
||||
draft_control = ft.Switch(
|
||||
label="下書き",
|
||||
value=is_draft,
|
||||
on_change=toggle_draft,
|
||||
)
|
||||
elif is_draft:
|
||||
draft_control = ft.Container(
|
||||
content=ft.Text("下書き", size=11, color=ft.Colors.BROWN_800),
|
||||
padding=ft.Padding.symmetric(horizontal=8, vertical=4),
|
||||
bgcolor=ft.Colors.BROWN_100,
|
||||
border_radius=12,
|
||||
)
|
||||
else:
|
||||
draft_control = ft.Container()
|
||||
|
||||
summary_card = ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
customer_field if customer_field else ft.Text(
|
||||
self.editing_invoice.customer.formal_name,
|
||||
size=13,
|
||||
ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content=doc_type_control if self.is_detail_edit_mode and not is_locked else ft.Text(
|
||||
self.selected_document_type.value,
|
||||
size=9,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=self.invoice_card_theme["tag_text_color"],
|
||||
),
|
||||
padding=ft.Padding.symmetric(horizontal=8, vertical=2),
|
||||
bgcolor=self.invoice_card_theme["tag_bg"],
|
||||
border_radius=10,
|
||||
),
|
||||
ft.Row([
|
||||
ft.Button(
|
||||
content=ft.Text("顧客選択", size=12),
|
||||
on_click=lambda _: select_customer(),
|
||||
disabled=is_locked or is_view_mode,
|
||||
) if not is_view_mode and not is_locked else ft.Container(),
|
||||
ft.Text(
|
||||
f"¥{self.editing_invoice.total_amount:,} (税込)",
|
||||
size=15,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=ft.Colors.BLUE_700,
|
||||
f"No: {self.editing_invoice.invoice_number}",
|
||||
size=10,
|
||||
color=self.invoice_card_theme["subtitle_color"],
|
||||
),
|
||||
], spacing=8, alignment=ft.MainAxisAlignment.END),
|
||||
],
|
||||
spacing=6,
|
||||
),
|
||||
draft_control,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
customer_control,
|
||||
self._build_total_amount_text(),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text(
|
||||
self.editing_invoice.invoice_number,
|
||||
size=12,
|
||||
color=ft.Colors.BLUE_GREY_500,
|
||||
),
|
||||
ft.Row([
|
||||
date_button if date_button else ft.Text(
|
||||
self.editing_invoice.date.strftime("%Y/%m/%d"),
|
||||
size=12,
|
||||
color=ft.Colors.BLUE_GREY_600,
|
||||
),
|
||||
ft.Text(" "),
|
||||
time_button if time_button else ft.Text(
|
||||
self.editing_invoice.date.strftime("%H:%M"),
|
||||
size=12,
|
||||
color=ft.Colors.BLUE_GREY_600,
|
||||
),
|
||||
], spacing=4, alignment=ft.MainAxisAlignment.END),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
spacing=12,
|
||||
),
|
||||
summary_badges,
|
||||
],
|
||||
spacing=6,
|
||||
spacing=8,
|
||||
),
|
||||
padding=ft.Padding.all(14),
|
||||
bgcolor=ft.Colors.BLUE_GREY_50,
|
||||
border_radius=10,
|
||||
padding=ft.Padding.symmetric(horizontal=12, vertical=10),
|
||||
bgcolor=ft.Colors.BROWN_50 if is_draft else self.invoice_card_theme["card_bg"],
|
||||
border_radius=self.invoice_card_theme["card_radius"],
|
||||
shadow=[self.invoice_card_theme["shadow"]],
|
||||
)
|
||||
|
||||
items_section = ft.Container(
|
||||
|
|
@ -1666,7 +1747,7 @@ class FlutterStyleDashboard:
|
|||
spacing=6,
|
||||
),
|
||||
padding=ft.Padding.all(12),
|
||||
bgcolor=ft.Colors.WHITE,
|
||||
bgcolor=ft.Colors.BROWN_50 if is_draft else ft.Colors.WHITE,
|
||||
border_radius=10,
|
||||
)
|
||||
|
||||
|
|
@ -1703,7 +1784,6 @@ class FlutterStyleDashboard:
|
|||
lower_scroll = ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
summary_card,
|
||||
notes_section,
|
||||
],
|
||||
spacing=12,
|
||||
|
|
@ -1715,6 +1795,7 @@ class FlutterStyleDashboard:
|
|||
|
||||
top_stack = ft.Column(
|
||||
[
|
||||
summary_card,
|
||||
items_section,
|
||||
],
|
||||
spacing=12,
|
||||
|
|
@ -1778,16 +1859,16 @@ class FlutterStyleDashboard:
|
|||
del self.editing_invoice.items[index]
|
||||
self.update_main_content()
|
||||
|
||||
def _update_item_field(self, item_index: int, field_name: str, value: str):
|
||||
def _update_item_field(self, index: int, field_name: str, value: str):
|
||||
"""明細フィールドを更新"""
|
||||
if not self.editing_invoice or item_index >= len(self.editing_invoice.items):
|
||||
if not self.editing_invoice or index >= len(self.editing_invoice.items):
|
||||
return
|
||||
|
||||
item = self.editing_invoice.items[item_index]
|
||||
item = self.editing_invoice.items[index]
|
||||
|
||||
# デバッグ用:更新前の値をログ出力
|
||||
old_value = getattr(item, field_name)
|
||||
logging.debug(f"Updating item {item_index} {field_name}: '{old_value}' -> '{value}'")
|
||||
logging.debug(f"Updating item {index} {field_name}: '{old_value}' -> '{value}'")
|
||||
|
||||
if field_name == 'description':
|
||||
item.description = value
|
||||
|
|
@ -1814,8 +1895,9 @@ class FlutterStyleDashboard:
|
|||
item.unit_price = 0
|
||||
logging.error(f"Unit price update error: {e}")
|
||||
|
||||
# 入力途中で画面全体を再描画すると編集値が飛びやすいため、
|
||||
# ここではモデル更新のみに留める(再描画は保存/行追加/行削除時に実施)。
|
||||
self._refresh_item_row(index)
|
||||
self._refresh_total_amount_display()
|
||||
self.page.update()
|
||||
|
||||
def _select_product_for_row(self, item_index: int, product_id: Optional[int]):
|
||||
"""商品選択ドロップダウンから呼ばれ、商品情報を行に反映"""
|
||||
|
|
@ -1832,8 +1914,11 @@ class FlutterStyleDashboard:
|
|||
item.product_id = product.id
|
||||
item.description = product.name
|
||||
item.unit_price = product.unit_price
|
||||
item.quantity = 1
|
||||
self._refresh_item_row(item_index, full_refresh=True)
|
||||
self._refresh_total_amount_display()
|
||||
# 行だけ更新し、再描画は即時に行う
|
||||
self.update_main_content()
|
||||
self.page.update()
|
||||
except Exception as e:
|
||||
logging.warning(f"商品選択反映失敗: {e}")
|
||||
|
||||
|
|
@ -1882,6 +1967,7 @@ class FlutterStyleDashboard:
|
|||
on_delete_row=self._delete_item_row,
|
||||
products=self.app_service.product.get_all_products(),
|
||||
on_product_select=self._open_product_picker_for_row,
|
||||
row_refs=self._ensure_item_row_refs(),
|
||||
)
|
||||
|
||||
def _open_product_picker_for_row(self, item_index: int):
|
||||
|
|
@ -1913,9 +1999,12 @@ class FlutterStyleDashboard:
|
|||
item.product_id = product.id
|
||||
item.description = product.name
|
||||
item.unit_price = product.unit_price
|
||||
item.quantity = 1
|
||||
# 先にフラグを戻してから画面更新(詳細に即戻る)
|
||||
self.is_product_picker_open = False
|
||||
self.is_new_product_form_open = False
|
||||
self._refresh_item_row(row, full_refresh=True)
|
||||
self._refresh_total_amount_display()
|
||||
self.update_main_content()
|
||||
except Exception as e:
|
||||
logging.warning(f"商品選択適用失敗: {e}")
|
||||
|
|
@ -1938,6 +2027,108 @@ class FlutterStyleDashboard:
|
|||
except Exception as e:
|
||||
logging.warning(f"TimePicker open error: {e}")
|
||||
|
||||
def _build_delete_draft_button(self, is_view_mode: bool) -> Optional[ft.Control]:
|
||||
if not is_view_mode:
|
||||
return None
|
||||
invoice = getattr(self, "editing_invoice", None)
|
||||
if not invoice or not getattr(invoice, "is_draft", False):
|
||||
return None
|
||||
return ft.IconButton(
|
||||
icon=ft.Icons.DELETE_FOREVER,
|
||||
icon_color=ft.Colors.RED_400,
|
||||
tooltip="下書きを削除",
|
||||
on_click=self._confirm_delete_current_invoice,
|
||||
)
|
||||
|
||||
def _confirm_delete_current_invoice(self, _=None):
|
||||
invoice = getattr(self, "editing_invoice", None)
|
||||
if not invoice or not getattr(invoice, "is_draft", False):
|
||||
return
|
||||
dialog = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("下書きを削除"),
|
||||
content=ft.Text("この下書きを削除しますか? この操作は元に戻せません。"),
|
||||
actions=[
|
||||
ft.TextButton("キャンセル", on_click=self._close_dialog),
|
||||
ft.TextButton("削除", style=ft.ButtonStyle(color=ft.Colors.RED_600), on_click=lambda _: self._delete_current_draft()),
|
||||
],
|
||||
actions_alignment=ft.MainAxisAlignment.END,
|
||||
)
|
||||
self.page.dialog = dialog
|
||||
dialog.open = True
|
||||
self.page.update()
|
||||
|
||||
def _close_dialog(self, _=None):
|
||||
dialog = getattr(self.page, "dialog", None)
|
||||
if dialog:
|
||||
dialog.open = False
|
||||
self.page.update()
|
||||
|
||||
def _delete_current_draft(self):
|
||||
invoice = getattr(self, "editing_invoice", None)
|
||||
if not invoice or not getattr(invoice, "is_draft", False):
|
||||
self._close_dialog()
|
||||
return
|
||||
try:
|
||||
success = self.app_service.invoice.delete_invoice_by_uuid(invoice.uuid)
|
||||
if success:
|
||||
self._show_snack("下書きを削除しました", ft.Colors.GREEN_600)
|
||||
self.editing_invoice = None
|
||||
self.invoices = self.app_service.invoice.get_recent_invoices(20)
|
||||
self.current_tab = 0
|
||||
else:
|
||||
self._show_snack("削除に失敗しました", ft.Colors.RED_600)
|
||||
except Exception as e:
|
||||
logging.error(f"ドラフト削除エラー: {e}")
|
||||
self._show_snack("削除中にエラーが発生しました", ft.Colors.RED_600)
|
||||
finally:
|
||||
self._close_dialog()
|
||||
self.update_main_content()
|
||||
|
||||
def _build_total_amount_text(self) -> ft.Text:
|
||||
total = self.editing_invoice.total_amount if self.editing_invoice else 0
|
||||
self._total_amount_text = ft.Text(
|
||||
f"¥{total:,} (税込)",
|
||||
size=15,
|
||||
weight=ft.FontWeight.BOLD,
|
||||
color=ft.Colors.BLUE_700,
|
||||
)
|
||||
return self._total_amount_text
|
||||
|
||||
def _refresh_total_amount_display(self):
|
||||
if not self._total_amount_text:
|
||||
return
|
||||
total = self.editing_invoice.total_amount if self.editing_invoice else 0
|
||||
self._total_amount_text.value = f"¥{total:,} (税込)"
|
||||
|
||||
def _ensure_item_row_refs(self) -> Dict[int, Dict[str, ft.Control]]:
|
||||
self._item_row_refs = {}
|
||||
return self._item_row_refs
|
||||
|
||||
def _refresh_item_row(self, index: int, full_refresh: bool = False):
|
||||
if not self.editing_invoice:
|
||||
return
|
||||
row_refs = getattr(self, "_item_row_refs", {})
|
||||
row_controls = row_refs.get(index)
|
||||
if not row_controls or index >= len(self.editing_invoice.items):
|
||||
return
|
||||
item = self.editing_invoice.items[index]
|
||||
|
||||
if full_refresh:
|
||||
product_button = row_controls.get("product")
|
||||
if product_button and isinstance(product_button.content, ft.Text):
|
||||
product_button.content.value = item.description or "商品選択"
|
||||
quantity_field = row_controls.get("quantity")
|
||||
if quantity_field:
|
||||
quantity_field.value = str(item.quantity)
|
||||
unit_price_field = row_controls.get("unit_price")
|
||||
if unit_price_field:
|
||||
unit_price_field.value = f"{item.unit_price:,}"
|
||||
|
||||
subtotal_text = row_controls.get("subtotal")
|
||||
if subtotal_text:
|
||||
subtotal_text.value = f"¥{item.subtotal:,}"
|
||||
|
||||
def _show_snack(self, message: str, color=ft.Colors.BLUE_GREY_800):
|
||||
try:
|
||||
logging.info(f"show_snack: {message}")
|
||||
|
|
@ -2399,7 +2590,8 @@ class FlutterStyleDashboard:
|
|||
customer=self.selected_customer,
|
||||
document_type=self.selected_document_type,
|
||||
amount=amount,
|
||||
notes=""
|
||||
notes="",
|
||||
is_draft=bool(getattr(self, "editing_invoice", None) and getattr(self.editing_invoice, "is_draft", False)),
|
||||
)
|
||||
|
||||
if invoice:
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ class InvoiceService:
|
|||
document_type: DocumentType,
|
||||
amount: int,
|
||||
notes: str = "",
|
||||
items: List[InvoiceItem] = None) -> Optional[Invoice]:
|
||||
items: List[InvoiceItem] = None,
|
||||
is_draft: bool = False) -> Optional[Invoice]:
|
||||
"""新規伝票作成
|
||||
|
||||
Args:
|
||||
|
|
@ -171,7 +172,8 @@ class InvoiceService:
|
|||
date=datetime.now(),
|
||||
items=items,
|
||||
document_type=document_type,
|
||||
notes=notes
|
||||
notes=notes,
|
||||
is_draft=is_draft
|
||||
)
|
||||
|
||||
# --- 長期保管向け: canonical payload + hash chain ---
|
||||
|
|
|
|||
Loading…
Reference in a new issue