1
0
forked from erp-dev/erp

feat: app verion && some field modify (printing & shipment)

This commit is contained in:
2026-07-08 20:36:51 +08:00
parent ddbf798665
commit 36e4bb6de6
32 changed files with 3465 additions and 26 deletions

247
api_v1/views/basic_info.py Normal file
View File

@@ -0,0 +1,247 @@
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework import filters
from rest_framework.response import Response
from flower.viewsets import LimitedModelViewSet
from basic_info import models as basic_models
from basic_info.services import CustomerVisibilityService
def _parse_bool(value: str | None) -> bool | None:
if value is None:
return None
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "y"}:
return True
if normalized in {"0", "false", "no", "n"}:
return False
return None
class CustomerAddressSerializer(serializers.ModelSerializer):
merchant_name = serializers.CharField(source="merchant.name", read_only=True)
customer_name = serializers.CharField(source="customer.name", read_only=True)
created_by_name = serializers.CharField(source="created_by.name", read_only=True, allow_null=True)
deleted_by_name = serializers.CharField(source="deleted_by.name", read_only=True, allow_null=True)
class Meta:
model = basic_models.CustomerAddress
fields = [
"id",
"merchant",
"merchant_name",
"customer",
"customer_name",
"address",
"contact_name",
"contact_phone",
"area",
"coordinates",
"is_default",
"remark",
"extra",
"created_by",
"created_by_name",
"deleted_at",
"deleted_by",
"deleted_by_name",
"created_at",
"updated_at",
]
read_only_fields = [
"id",
"merchant",
"merchant_name",
"customer_name",
"created_by",
"created_by_name",
"deleted_at",
"deleted_by",
"deleted_by_name",
"created_at",
"updated_at",
]
def validate_customer(self, customer):
request = self.context["request"]
view = self.context["view"]
if self.instance is not None and customer.id != self.instance.customer_id:
raise serializers.ValidationError("不允许修改客户")
if not view.can_access_customer(customer, request.user):
raise serializers.ValidationError("客户不存在或不可见")
return customer
class CustomerAddressViewSet(LimitedModelViewSet):
"""
客户地址 CRUD。
查询参数:
- customer: 客户ID
- customer_name: 客户名称模糊查询
- address: 地址模糊查询
- contact_name: 联系人模糊查询
- contact_phone: 联系电话模糊查询
- area: 地区模糊查询
- is_default: 是否默认地址 true/false
- include_deleted: 是否包含软删除记录 true/false
- only_deleted: 是否仅查询软删除记录 true/false
- search: 客户名/地址/联系人/电话/地区/备注模糊搜索
- ordering: id/-id/created_at/-created_at/updated_at/-updated_at/is_default/-is_default
"""
queryset = basic_models.CustomerAddress.objects.all()
serializer_class = CustomerAddressSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ["id", "created_at", "updated_at", "is_default"]
ordering = ["-is_default", "-created_at", "-id"]
def get_employee(self):
return getattr(self.request.user, "employee", None)
def can_view_all_customers(self, user) -> bool:
return user.is_superuser or user.has_perm("basic_info.view_all_customers")
def can_access_customer(self, customer, user) -> bool:
if user.is_superuser:
return True
emp = getattr(user, "employee", None)
if emp is None or emp.merchant_id != customer.merchant_id:
return False
if user.has_perm("basic_info.view_all_customers"):
return True
return CustomerVisibilityService.is_customer_visible_to_employee(customer, user)
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("merchant", "customer", "created_by", "deleted_by")
)
user = self.request.user
if not user.is_superuser:
emp = self.get_employee()
if emp is None or emp.merchant_id is None:
return queryset.none()
queryset = queryset.filter(merchant=emp.merchant)
if not user.has_perm("basic_info.view_all_customers"):
customer_queryset = CustomerVisibilityService.filter_customers_for_employee(
basic_models.Customer.objects.filter(merchant=emp.merchant),
user,
)
queryset = queryset.filter(customer__in=customer_queryset)
only_deleted = _parse_bool(self.request.query_params.get("only_deleted"))
include_deleted = _parse_bool(self.request.query_params.get("include_deleted"))
if only_deleted:
queryset = queryset.filter(deleted_at__isnull=False)
elif not include_deleted:
queryset = queryset.filter(deleted_at__isnull=True)
customer = self.request.query_params.get("customer")
if customer:
if not customer.strip().isdigit():
return queryset.none()
queryset = queryset.filter(customer_id=customer)
customer_name = (self.request.query_params.get("customer_name") or "").strip()
if customer_name:
queryset = queryset.filter(customer__name__icontains=customer_name)
address = (self.request.query_params.get("address") or "").strip()
if address:
queryset = queryset.filter(address__icontains=address)
contact_name = (self.request.query_params.get("contact_name") or "").strip()
if contact_name:
queryset = queryset.filter(contact_name__icontains=contact_name)
contact_phone = (self.request.query_params.get("contact_phone") or "").strip()
if contact_phone:
queryset = queryset.filter(contact_phone__icontains=contact_phone)
area = (self.request.query_params.get("area") or "").strip()
if area:
queryset = queryset.filter(area__icontains=area)
is_default = _parse_bool(self.request.query_params.get("is_default"))
if is_default is not None:
queryset = queryset.filter(is_default=is_default)
search = (self.request.query_params.get("search") or "").strip()
if search:
queryset = queryset.filter(
Q(customer__name__icontains=search)
| Q(address__icontains=search)
| Q(contact_name__icontains=search)
| Q(contact_phone__icontains=search)
| Q(area__icontains=search)
| Q(remark__icontains=search)
)
return queryset.distinct()
def _unset_other_default_addresses(self, address: basic_models.CustomerAddress):
if not address.is_default or address.deleted_at is not None:
return
basic_models.CustomerAddress.objects.filter(
merchant=address.merchant,
customer=address.customer,
deleted_at__isnull=True,
is_default=True,
).exclude(id=address.id).update(is_default=False)
@transaction.atomic
def perform_create(self, serializer):
customer = serializer.validated_data["customer"]
emp = self.get_employee()
if not self.request.user.is_superuser and emp is None:
raise PermissionDenied("当前用户未绑定员工")
address = serializer.save(
merchant=customer.merchant,
created_by=emp,
)
self._unset_other_default_addresses(address)
@transaction.atomic
def perform_update(self, serializer):
address = serializer.save()
self._unset_other_default_addresses(address)
@transaction.atomic
def perform_destroy(self, instance):
if instance.deleted_at is not None:
return
emp = self.get_employee()
if not self.request.user.is_superuser and emp is None:
raise PermissionDenied("当前用户未绑定员工")
instance.deleted_at = timezone.now()
instance.deleted_by = emp
instance.is_default = False
instance.save(update_fields=["deleted_at", "deleted_by", "is_default", "updated_at"])
@transaction.atomic
def set_default(self, request, pk: int):
address = self.get_object()
if address.deleted_at is not None:
raise ValidationError("已删除的客户地址不能设为默认地址")
address.is_default = True
address.save(update_fields=["is_default", "updated_at"])
self._unset_other_default_addresses(address)
serializer = self.get_serializer(address)
return Response(serializer.data)