Download apps/contracts/admin/contractpayment_admin.py from jun2024/fc: direct link, hf CLI and curl.
- Browser
- Download file 5.62 kB
-
https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/contracts/admin/contractpayment_admin.py
- Command line
-
hf download hf://spaces/jun2024/fc/apps/contracts/admin/contractpayment_admin.py
-
curl -L -o contractpayment_admin.py https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/contracts/admin/contractpayment_admin.py
5.62 kB
| from __future__ import annotations | |
| from typing import TYPE_CHECKING, Any, ClassVar | |
| from django import forms | |
| from django.contrib import admin | |
| from django.db.models import QuerySet | |
| from django.http import HttpRequest | |
| from django.utils.html import format_html | |
| from django.utils.translation import gettext_lazy as _ | |
| from apps.contracts.models import ContractPayment, Invoice, InvoiceStatus | |
| if TYPE_CHECKING: | |
| BaseModelAdmin = admin.ModelAdmin | |
| BaseTabularInline = admin.TabularInline | |
| else: | |
| try: | |
| import nested_admin | |
| BaseModelAdmin = nested_admin.NestedModelAdmin | |
| BaseTabularInline = nested_admin.NestedTabularInline | |
| except ImportError: | |
| BaseModelAdmin = admin.ModelAdmin | |
| BaseTabularInline = admin.TabularInline | |
| class InvoiceAdminForm(forms.ModelForm[Invoice]): | |
| file = forms.FileField( | |
| required=False, | |
| label=_("上传发票"), | |
| help_text=_("支持 PDF、JPG、JPEG、PNG,最大 20MB"), | |
| ) | |
| class Meta: | |
| model = Invoice | |
| fields = ( | |
| "file", | |
| "original_filename", | |
| "invoice_code", | |
| "invoice_number", | |
| "invoice_date", | |
| "amount", | |
| "tax_amount", | |
| "total_amount", | |
| "remark", | |
| ) | |
| widgets = { | |
| "remark": forms.Textarea(attrs={"rows": 2, "cols": 20, "style": "width:160px;resize:vertical;"}), | |
| } | |
| def save(self, commit: bool = True) -> Invoice: | |
| instance = super().save(commit=False) | |
| uploaded_file = self.cleaned_data.get("file") | |
| if uploaded_file: | |
| from apps.contracts.admin.wiring_admin import get_invoice_upload_service | |
| svc = get_invoice_upload_service() | |
| payment_id: int = instance.payment_id or self.instance.payment_id | |
| saved = svc.save_invoice_file(uploaded_file, payment_id) | |
| instance.file_path = saved.file_path | |
| instance.original_filename = saved.original_filename | |
| # 文件已由 service 创建记录,直接返回 | |
| return saved | |
| if commit: | |
| instance.save() | |
| return instance | |
| class InvoiceInline(BaseTabularInline): # type: ignore[type-arg] | |
| model = Invoice | |
| form = InvoiceAdminForm | |
| extra = 1 | |
| fields: ClassVar = ( | |
| "file", | |
| "file_link", | |
| "total_amount", | |
| ) | |
| readonly_fields: ClassVar = ("file_link",) | |
| # 隐藏 verbose_name_plural,避免显示识别结果 | |
| verbose_name = _("发票") | |
| verbose_name_plural = _("发票") | |
| def file_link(self, obj: Invoice) -> str: | |
| if not obj.pk or not obj.file_path: | |
| return "-" | |
| from django.conf import settings | |
| # 确保 file_path 不为空字符串 | |
| if not obj.file_path.strip(): | |
| return "-" | |
| url = f"{settings.MEDIA_URL}{obj.file_path}" | |
| return format_html('<a href="{}" target="_blank">{}</a>', url, obj.original_filename or _("查看")) | |
| def delete_model(self, request: HttpRequest, obj: Invoice) -> None: | |
| from apps.contracts.admin.wiring_admin import get_invoice_upload_service | |
| svc = get_invoice_upload_service() | |
| try: | |
| svc.delete_invoice(obj.pk) | |
| except Exception: | |
| # 即使 service 抛出,也确保 Admin 不崩溃 | |
| obj.delete() | |
| class ContractPaymentInline(BaseTabularInline[ContractPayment, ContractPayment]): | |
| model = ContractPayment | |
| extra = 0 | |
| fields = ("amount", "received_at", "invoiced_amount", "invoice_status", "note") | |
| def get_formset(self, request: HttpRequest, obj: Any = None, **kwargs: Any) -> Any: | |
| FormSet = super().get_formset(request, obj, **kwargs) | |
| original_clean = FormSet.clean | |
| def clean_fs(self: Any) -> None: | |
| original_clean(self) | |
| for form in self.forms: | |
| if not hasattr(form, "cleaned_data") or form.cleaned_data.get("DELETE"): | |
| continue | |
| amt = form.cleaned_data.get("amount") | |
| inv = form.cleaned_data.get("invoiced_amount") or 0 | |
| if amt and inv is not None: | |
| if float(inv) - float(amt) > 1e-6: | |
| form.add_error("invoiced_amount", _("开票金额不能大于收款金额")) | |
| else: | |
| if float(inv) == 0: | |
| form.cleaned_data["invoice_status"] = InvoiceStatus.UNINVOICED | |
| elif 0 < float(inv) < float(amt): | |
| form.cleaned_data["invoice_status"] = InvoiceStatus.INVOICED_PARTIAL | |
| else: | |
| form.cleaned_data["invoice_status"] = InvoiceStatus.INVOICED_FULL | |
| FormSet.clean = clean_fs # type: ignore[method-assign] | |
| return FormSet | |
| class ContractPaymentAdmin(BaseModelAdmin[ContractPayment]): # type: ignore[type-arg] | |
| change_form_template = "admin/contracts/contractpayment/change_form.html" | |
| list_display = ("id", "contract", "amount", "received_at", "invoice_status", "invoiced_amount") | |
| list_filter = ("invoice_status", "received_at") | |
| search_fields = ("contract__name",) | |
| autocomplete_fields = ("contract",) | |
| inlines: ClassVar = [InvoiceInline] | |
| class Media: | |
| css = {"all": ("contracts/css/invoice_recognition.css",)} | |
| js = ("contracts/js/invoice_recognition.js",) | |
| def get_queryset(self, request: HttpRequest) -> QuerySet[ContractPayment, ContractPayment]: | |
| return super().get_queryset(request).select_related("contract") | |