forked from erp-dev/erp
feat: add discount_amount to payment_order and receipt_order, and change total_amount logic
This commit is contained in:
@@ -11,15 +11,30 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
class PaymentOrderSerializer(serializers.ModelSerializer):
|
||||
supplier_name = serializers.CharField(source='supplier.name', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
bank_account_name = serializers.CharField(source='bank_account.name', read_only=True)
|
||||
settlement_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = business_models.PaymentOrder
|
||||
fields = [
|
||||
'id', 'supplier', 'supplier_name', 'payment_date',
|
||||
'amount', 'operator', 'operator_name', 'status',
|
||||
'remarks', 'created_at', 'updated_at',
|
||||
'id', 'supplier', 'supplier_name', 'bank_account', 'bank_account_name',
|
||||
'payment_date', 'amount', 'discount_amount', 'settlement_amount',
|
||||
'operator', 'operator_name', 'status',
|
||||
'remarks', 'markup', 'created_at', 'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'supplier_name', 'operator_name', 'status', 'created_at', 'updated_at']
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'supplier_name',
|
||||
'operator_name',
|
||||
'bank_account_name',
|
||||
'settlement_amount',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
def get_settlement_amount(self, obj):
|
||||
return str(obj.settlement_amount)
|
||||
|
||||
|
||||
class PaymentOrderPagination(pagination.LimitOffsetPagination):
|
||||
@@ -35,9 +50,11 @@ class PaymentOrderView(StockChangeViewMixin, views.APIView):
|
||||
if not self.check_employee_permission(request):
|
||||
return self.permission_error_response('无权限访问')
|
||||
merchant = request.user.employee.merchant
|
||||
queryset = business_models.PaymentOrder.objects.filter(merchant=merchant).select_related(
|
||||
'supplier', 'operator'
|
||||
).order_by('-created_at')
|
||||
queryset = (
|
||||
business_models.PaymentOrder.objects.filter(merchant=merchant)
|
||||
.select_related('supplier', 'operator', 'bank_account')
|
||||
.order_by('-created_at')
|
||||
)
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(queryset, request, view=self)
|
||||
serializer = PaymentOrderSerializer(page, many=True)
|
||||
@@ -51,6 +68,9 @@ class PaymentOrderView(StockChangeViewMixin, views.APIView):
|
||||
supplier_id = data.get('supplier')
|
||||
payment_date = data.get('payment_date')
|
||||
amount = data.get('amount')
|
||||
discount_amount = data.get('discount_amount')
|
||||
bank_account_id = data.get('bank_account')
|
||||
markup = data.get('markup')
|
||||
remarks = data.get('remarks', '')
|
||||
|
||||
if not supplier_id:
|
||||
@@ -66,6 +86,12 @@ class PaymentOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response({'error': f'供应商 {supplier_id} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
operator = request.user.employee
|
||||
bank_account = None
|
||||
if bank_account_id not in (None, '', 0):
|
||||
try:
|
||||
bank_account = basic_models.BankAccount.objects.get(id=bank_account_id, merchant=merchant)
|
||||
except basic_models.BankAccount.DoesNotExist:
|
||||
return Response({'error': '银行账户不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
payment_order = business_services.create_payment_order(
|
||||
@@ -73,8 +99,11 @@ class PaymentOrderView(StockChangeViewMixin, views.APIView):
|
||||
supplier=supplier,
|
||||
payment_date=payment_date,
|
||||
amount=amount,
|
||||
discount_amount=discount_amount,
|
||||
operator=operator,
|
||||
remarks=remarks,
|
||||
bank_account=bank_account,
|
||||
markup=markup,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -106,9 +135,10 @@ class PaymentOrderReviewView(StockChangeViewMixin, views.APIView):
|
||||
|
||||
merchant = request.user.employee.merchant
|
||||
try:
|
||||
payment_order = business_models.PaymentOrder.objects.select_related(
|
||||
'supplier', 'operator'
|
||||
).get(id=pk, merchant=merchant)
|
||||
payment_order = (
|
||||
business_models.PaymentOrder.objects.select_related('supplier', 'operator', 'bank_account')
|
||||
.get(id=pk, merchant=merchant)
|
||||
)
|
||||
except business_models.PaymentOrder.DoesNotExist:
|
||||
return self.not_found_response('付款单不存在')
|
||||
|
||||
@@ -126,8 +156,9 @@ class PaymentOrderReviewView(StockChangeViewMixin, views.APIView):
|
||||
except ValueError as exc:
|
||||
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
refreshed = business_models.PaymentOrder.objects.select_related(
|
||||
'supplier', 'operator'
|
||||
).get(id=payment_order.id)
|
||||
refreshed = (
|
||||
business_models.PaymentOrder.objects.select_related('supplier', 'operator', 'bank_account')
|
||||
.get(id=payment_order.id)
|
||||
)
|
||||
return Response(PaymentOrderSerializer(refreshed).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -11,15 +11,30 @@ from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
class ReceiptOrderSerializer(serializers.ModelSerializer):
|
||||
customer_name = serializers.CharField(source='customer.name', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
bank_account_name = serializers.CharField(source='bank_account.name', read_only=True)
|
||||
settlement_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = business_models.ReceiptOrder
|
||||
fields = [
|
||||
'id', 'customer', 'customer_name', 'receipt_date',
|
||||
'amount', 'operator', 'operator_name', 'status',
|
||||
'remarks', 'created_at', 'updated_at',
|
||||
'id', 'customer', 'customer_name', 'bank_account', 'bank_account_name',
|
||||
'receipt_date', 'amount', 'discount_amount', 'settlement_amount',
|
||||
'operator', 'operator_name', 'status',
|
||||
'remarks', 'markup', 'created_at', 'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'customer_name', 'operator_name', 'status', 'created_at', 'updated_at']
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'customer_name',
|
||||
'operator_name',
|
||||
'bank_account_name',
|
||||
'settlement_amount',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
def get_settlement_amount(self, obj):
|
||||
return str(obj.settlement_amount)
|
||||
|
||||
|
||||
class ReceiptOrderPagination(pagination.LimitOffsetPagination):
|
||||
@@ -35,9 +50,11 @@ class ReceiptOrderView(StockChangeViewMixin, views.APIView):
|
||||
if not self.check_employee_permission(request):
|
||||
return self.permission_error_response('无权限访问')
|
||||
merchant = request.user.employee.merchant
|
||||
queryset = business_models.ReceiptOrder.objects.filter(merchant=merchant).select_related(
|
||||
'customer', 'operator'
|
||||
).order_by('-created_at')
|
||||
queryset = (
|
||||
business_models.ReceiptOrder.objects.filter(merchant=merchant)
|
||||
.select_related('customer', 'operator', 'bank_account')
|
||||
.order_by('-created_at')
|
||||
)
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(queryset, request, view=self)
|
||||
serializer = ReceiptOrderSerializer(page, many=True)
|
||||
@@ -51,6 +68,9 @@ class ReceiptOrderView(StockChangeViewMixin, views.APIView):
|
||||
customer_id = data.get('customer')
|
||||
receipt_date = data.get('receipt_date')
|
||||
amount = data.get('amount')
|
||||
discount_amount = data.get('discount_amount')
|
||||
bank_account_id = data.get('bank_account')
|
||||
markup = data.get('markup')
|
||||
remarks = data.get('remarks', '')
|
||||
|
||||
if not customer_id:
|
||||
@@ -66,6 +86,12 @@ class ReceiptOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response({'error': f'客户 {customer_id} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
operator = request.user.employee
|
||||
bank_account = None
|
||||
if bank_account_id not in (None, '', 0):
|
||||
try:
|
||||
bank_account = basic_models.BankAccount.objects.get(id=bank_account_id, merchant=merchant)
|
||||
except basic_models.BankAccount.DoesNotExist:
|
||||
return Response({'error': '银行账户不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
receipt_order = business_services.create_receipt_order(
|
||||
@@ -73,8 +99,11 @@ class ReceiptOrderView(StockChangeViewMixin, views.APIView):
|
||||
customer=customer,
|
||||
receipt_date=receipt_date,
|
||||
amount=amount,
|
||||
discount_amount=discount_amount,
|
||||
operator=operator,
|
||||
remarks=remarks,
|
||||
bank_account=bank_account,
|
||||
markup=markup,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -106,9 +135,10 @@ class ReceiptOrderReviewView(StockChangeViewMixin, views.APIView):
|
||||
|
||||
merchant = request.user.employee.merchant
|
||||
try:
|
||||
receipt_order = business_models.ReceiptOrder.objects.select_related(
|
||||
'customer', 'operator'
|
||||
).get(id=pk, merchant=merchant)
|
||||
receipt_order = (
|
||||
business_models.ReceiptOrder.objects.select_related('customer', 'operator', 'bank_account')
|
||||
.get(id=pk, merchant=merchant)
|
||||
)
|
||||
except business_models.ReceiptOrder.DoesNotExist:
|
||||
return self.not_found_response('收款单不存在')
|
||||
|
||||
@@ -126,8 +156,9 @@ class ReceiptOrderReviewView(StockChangeViewMixin, views.APIView):
|
||||
except ValueError as exc:
|
||||
return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
refreshed = business_models.ReceiptOrder.objects.select_related(
|
||||
'customer', 'operator'
|
||||
).get(id=receipt_order.id)
|
||||
refreshed = (
|
||||
business_models.ReceiptOrder.objects.select_related('customer', 'operator', 'bank_account')
|
||||
.get(id=receipt_order.id)
|
||||
)
|
||||
return Response(ReceiptOrderSerializer(refreshed).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from business import services as business_services
|
||||
|
||||
|
||||
class StatementRecordSerializer(serializers.Serializer):
|
||||
source_type = serializers.CharField()
|
||||
@@ -39,3 +41,17 @@ class StatementResponseSerializer(serializers.Serializer):
|
||||
return data
|
||||
|
||||
|
||||
class StatementRecordQuerySerializer(serializers.Serializer):
|
||||
"""单条对账记录查询的参数序列化器"""
|
||||
|
||||
counterparty_type = serializers.ChoiceField(
|
||||
choices=business_services.STATEMENT_COUNTERPARTY_CHOICES,
|
||||
help_text='客户/供应商类型(customer/supplier)',
|
||||
)
|
||||
counterparty_id = serializers.IntegerField(min_value=1, help_text='客户或供应商 ID')
|
||||
order_type = serializers.ChoiceField(
|
||||
choices=business_services.STATEMENT_ORDER_TYPE_CHOICES,
|
||||
help_text='来源单据类型(如 sales_order)',
|
||||
)
|
||||
order_id = serializers.IntegerField(min_value=1, help_text='来源单据 ID')
|
||||
|
||||
|
||||
@@ -1,155 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
from typing import Iterable, List
|
||||
|
||||
from rest_framework import views
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from business import models as business_models
|
||||
from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
from business import services as business_services
|
||||
from . import serializers as statement_serializers
|
||||
from api_v1.views.stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
TWO_PLACES = Decimal('0.01')
|
||||
ZERO = Decimal('0')
|
||||
from . import serializers as statement_serializers
|
||||
|
||||
|
||||
class StatementViewBase(StockChangeViewMixin, views.APIView):
|
||||
"""
|
||||
提供对账单视图的公共实现:权限校验、序列化上下文及记录构建方法。
|
||||
提供对账单视图的公共实现:权限校验、序列化上下文及响应构造。
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = statement_serializers.StatementResponseSerializer
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._current_balance_display: str | None = None
|
||||
self._current_balance_value: Decimal | None = None
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {
|
||||
'request': self.request,
|
||||
'view': self,
|
||||
'summary_builder': self._build_summary,
|
||||
'summary_builder': business_services.build_statement_summary,
|
||||
}
|
||||
|
||||
def _build_response(self, counterparty, records: List[dict]):
|
||||
processed_records = self._attach_running_totals(records)
|
||||
def _build_response(self, payload: dict):
|
||||
serializer = self.serializer_class(
|
||||
instance={
|
||||
'counterparty': counterparty.id,
|
||||
'counterparty_name': counterparty.name,
|
||||
'records': processed_records,
|
||||
},
|
||||
instance=payload,
|
||||
context=self.get_serializer_context(),
|
||||
)
|
||||
return Response(serializer.data)
|
||||
|
||||
def _build_record(
|
||||
self,
|
||||
*,
|
||||
counterparty_id: int,
|
||||
counterparty_name: str,
|
||||
source_type: str,
|
||||
source_label: str,
|
||||
source_id: int,
|
||||
occurred_at,
|
||||
recorded_at,
|
||||
status: int,
|
||||
status_label: str,
|
||||
positive_amount,
|
||||
negative_amount,
|
||||
items: List[dict] | None = None,
|
||||
extra: dict | None = None,
|
||||
) -> dict:
|
||||
items = items or []
|
||||
record = {
|
||||
'counterparty': counterparty_id,
|
||||
'counterparty_name': counterparty_name,
|
||||
'source_type': source_type,
|
||||
'source_label': source_label,
|
||||
'source_id': source_id,
|
||||
'occurred_at': occurred_at,
|
||||
'recorded_at': recorded_at,
|
||||
'status': status,
|
||||
'status_label': status_label,
|
||||
'positive_amount': self._normalize_amount(positive_amount),
|
||||
'negative_amount': self._normalize_amount(negative_amount),
|
||||
'items': items,
|
||||
}
|
||||
if extra:
|
||||
record['extra'] = extra
|
||||
return record
|
||||
|
||||
def _aggregate_items(self, order_items) -> List[dict]:
|
||||
"""Merge duplicate order items without changing the response schema."""
|
||||
|
||||
aggregated = OrderedDict()
|
||||
for item in order_items:
|
||||
product_id = getattr(item, 'product_id', None)
|
||||
product_name = getattr(getattr(item, 'product', None), 'name', '')
|
||||
unit = getattr(item, 'unit', '')
|
||||
price = getattr(item, 'price', Decimal('0'))
|
||||
key = (product_id, product_name, unit, price)
|
||||
if key not in aggregated:
|
||||
aggregated[key] = {
|
||||
'product_id': product_id,
|
||||
'product_name': product_name,
|
||||
'quantity': Decimal('0'),
|
||||
'price': price,
|
||||
'unit': unit,
|
||||
}
|
||||
aggregated[key]['quantity'] += Decimal(getattr(item, 'quantity', 0))
|
||||
return list(aggregated.values())
|
||||
|
||||
def _sort_records(self, records: Iterable[dict]) -> List[dict]:
|
||||
return sorted(
|
||||
records,
|
||||
key=lambda item: (item['occurred_at'], item['recorded_at'], item['source_id']),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
def _normalize_amount(self, value) -> Decimal:
|
||||
if isinstance(value, Decimal):
|
||||
decimal_value = value
|
||||
else:
|
||||
decimal_value = Decimal(str(value))
|
||||
return decimal_value.quantize(TWO_PLACES, rounding=ROUND_HALF_UP)
|
||||
|
||||
def _decimal_to_string(self, value: Decimal) -> str:
|
||||
normalized = self._normalize_amount(value)
|
||||
return format(normalized, 'f')
|
||||
|
||||
def _build_summary(self, payload: dict):
|
||||
records = payload.get('records', [])
|
||||
total_positive = sum((record.get('positive_amount', ZERO) for record in records), ZERO)
|
||||
total_negative = sum((record.get('negative_amount', ZERO) for record in records), ZERO)
|
||||
return {
|
||||
'positive_total': self._decimal_to_string(total_positive),
|
||||
'negative_total': self._decimal_to_string(total_negative),
|
||||
}
|
||||
|
||||
def _attach_running_totals(self, records: List[dict]) -> List[dict]:
|
||||
running_total = ZERO
|
||||
current_balance_value = self._current_balance_value or ZERO
|
||||
current_balance_display = self._current_balance_display or self._decimal_to_string(current_balance_value)
|
||||
processed = []
|
||||
for record in records:
|
||||
record_copy = dict(record)
|
||||
record_copy['cumulative_amount'] = self._decimal_to_string(running_total)
|
||||
record_copy['current_balance'] = current_balance_display
|
||||
arrears_amount = current_balance_value - running_total
|
||||
record_copy['arrears_amount'] = self._decimal_to_string(arrears_amount)
|
||||
delta = record_copy['positive_amount'] - record_copy['negative_amount']
|
||||
running_total += delta
|
||||
processed.append(record_copy)
|
||||
return processed
|
||||
|
||||
|
||||
class CustomerStatementView(StatementViewBase):
|
||||
"""
|
||||
@@ -166,115 +49,8 @@ class CustomerStatementView(StatementViewBase):
|
||||
except basic_models.Customer.DoesNotExist:
|
||||
return self.not_found_response('客户不存在')
|
||||
|
||||
self._get_customer_balance(merchant, customer)
|
||||
records = self._collect_customer_records(merchant, customer)
|
||||
return self._build_response(customer, records)
|
||||
|
||||
def _collect_customer_records(self, merchant, customer) -> List[dict]:
|
||||
records: List[dict] = []
|
||||
records.extend(self._build_sales_records(merchant, customer))
|
||||
records.extend(self._build_sales_return_records(merchant, customer))
|
||||
records.extend(self._build_receipt_records(merchant, customer))
|
||||
return self._sort_records(records)
|
||||
|
||||
def _build_sales_records(self, merchant, customer) -> List[dict]:
|
||||
qs = (
|
||||
business_models.SalesOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
customer=customer,
|
||||
status=business_models.SalesOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('customer')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
items = self._aggregate_items(order.items.all())
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.customer_id,
|
||||
counterparty_name=order.customer.name,
|
||||
source_type='sales_order',
|
||||
source_label='销售单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.sales_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=order.get_total_amount(),
|
||||
negative_amount=ZERO,
|
||||
items=items,
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _build_sales_return_records(self, merchant, customer) -> List[dict]:
|
||||
qs = (
|
||||
business_models.SalesReturnOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
customer=customer,
|
||||
status=business_models.SalesReturnStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('customer')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
items = self._aggregate_items(order.items.all())
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.customer_id,
|
||||
counterparty_name=order.customer.name,
|
||||
source_type='sales_return_order',
|
||||
source_label='销售退货单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.return_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
items=items,
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _build_receipt_records(self, merchant, customer) -> List[dict]:
|
||||
qs = (
|
||||
business_models.ReceiptOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
customer=customer,
|
||||
status=business_models.ReceiptOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('customer')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.customer_id,
|
||||
counterparty_name=order.customer.name,
|
||||
source_type='receipt_order',
|
||||
source_label='收款单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.receipt_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _get_customer_balance(self, merchant, customer):
|
||||
balance = business_services.BalanceService.get_customer_balance(
|
||||
merchant=merchant,
|
||||
customer=customer,
|
||||
)
|
||||
self._current_balance_value = balance
|
||||
self._current_balance_display = self._decimal_to_string(balance)
|
||||
return self._current_balance_display
|
||||
payload = business_services.build_customer_statement(merchant=merchant, customer=customer)
|
||||
return self._build_response(payload)
|
||||
|
||||
|
||||
class SupplierStatementView(StatementViewBase):
|
||||
@@ -292,114 +68,56 @@ class SupplierStatementView(StatementViewBase):
|
||||
except basic_models.Supplier.DoesNotExist:
|
||||
return self.not_found_response('供应商不存在')
|
||||
|
||||
self._get_supplier_balance(merchant, supplier)
|
||||
records = self._collect_supplier_records(merchant, supplier)
|
||||
return self._build_response(supplier, records)
|
||||
|
||||
def _collect_supplier_records(self, merchant, supplier) -> List[dict]:
|
||||
records: List[dict] = []
|
||||
records.extend(self._build_purchase_records(merchant, supplier))
|
||||
records.extend(self._build_purchase_return_records(merchant, supplier))
|
||||
records.extend(self._build_payment_records(merchant, supplier))
|
||||
return self._sort_records(records)
|
||||
|
||||
def _build_purchase_records(self, merchant, supplier) -> List[dict]:
|
||||
qs = (
|
||||
business_models.PurchaseOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
status=business_models.PurchaseOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('supplier')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
items = self._aggregate_items(order.items.all())
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.supplier_id,
|
||||
counterparty_name=order.supplier.name,
|
||||
source_type='purchase_order',
|
||||
source_label='采购单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.purchase_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=order.get_total_amount(),
|
||||
negative_amount=ZERO,
|
||||
items=items,
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _build_purchase_return_records(self, merchant, supplier) -> List[dict]:
|
||||
qs = (
|
||||
business_models.PurchaseReturnOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
status=business_models.PurchaseReturnStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('supplier')
|
||||
.prefetch_related('items__product')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
items = self._aggregate_items(order.items.all())
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.supplier_id,
|
||||
counterparty_name=order.supplier.name,
|
||||
source_type='purchase_return_order',
|
||||
source_label='采购退货单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.return_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
items=items,
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _build_payment_records(self, merchant, supplier) -> List[dict]:
|
||||
qs = (
|
||||
business_models.PaymentOrder.objects.filter(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
status=business_models.PaymentOrderStatusEnum.APPROVED,
|
||||
)
|
||||
.select_related('supplier')
|
||||
)
|
||||
records = []
|
||||
for order in qs:
|
||||
records.append(
|
||||
self._build_record(
|
||||
counterparty_id=order.supplier_id,
|
||||
counterparty_name=order.supplier.name,
|
||||
source_type='payment_order',
|
||||
source_label='付款单',
|
||||
source_id=order.id,
|
||||
occurred_at=order.payment_date,
|
||||
recorded_at=order.created_at,
|
||||
status=order.status,
|
||||
status_label=order.get_status_display(),
|
||||
positive_amount=ZERO,
|
||||
negative_amount=order.get_total_amount(),
|
||||
)
|
||||
)
|
||||
return records
|
||||
|
||||
def _get_supplier_balance(self, merchant, supplier):
|
||||
balance = business_services.BalanceService.get_supplier_balance(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
)
|
||||
self._current_balance_value = balance
|
||||
self._current_balance_display = self._decimal_to_string(balance)
|
||||
return self._current_balance_display
|
||||
payload = business_services.build_supplier_statement(merchant=merchant, supplier=supplier)
|
||||
return self._build_response(payload)
|
||||
|
||||
|
||||
class StatementRecordView(StatementViewBase):
|
||||
"""
|
||||
根据业务主体与单据信息查询单条对账记录,返回与列表接口一致的 schema。
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
if not self.check_employee_permission(request):
|
||||
return self.permission_error_response('无权限访问')
|
||||
|
||||
serializer = statement_serializers.StatementRecordQuerySerializer(data=request.query_params)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
params = serializer.validated_data
|
||||
|
||||
merchant = request.user.employee.merchant
|
||||
counterparty_type = params['counterparty_type']
|
||||
counterparty_id = params['counterparty_id']
|
||||
order_type = params['order_type']
|
||||
order_id = params['order_id']
|
||||
|
||||
if counterparty_type == 'customer':
|
||||
try:
|
||||
counterparty = basic_models.Customer.objects.get(id=counterparty_id, merchant=merchant)
|
||||
except basic_models.Customer.DoesNotExist:
|
||||
return self.not_found_response('客户不存在')
|
||||
payload = business_services.build_customer_statement(merchant=merchant, customer=counterparty)
|
||||
else:
|
||||
try:
|
||||
counterparty = basic_models.Supplier.objects.get(id=counterparty_id, merchant=merchant)
|
||||
except basic_models.Supplier.DoesNotExist:
|
||||
return self.not_found_response('供应商不存在')
|
||||
payload = business_services.build_supplier_statement(merchant=merchant, supplier=counterparty)
|
||||
|
||||
matched_records = [
|
||||
record
|
||||
for record in payload.get('records', [])
|
||||
if record['source_type'] == order_type and record['source_id'] == order_id
|
||||
]
|
||||
|
||||
if not matched_records:
|
||||
return self.not_found_response('未找到匹配的对账记录')
|
||||
|
||||
single_payload = {
|
||||
'counterparty': payload['counterparty'],
|
||||
'counterparty_name': payload['counterparty_name'],
|
||||
'records': matched_records,
|
||||
}
|
||||
return self._build_response(single_payload)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user