mirror of
https://github.com/huge-dream/django-vue3-admin.git
synced 2026-09-30 07:46:11 +00:00
新增 杂采比议价、询价履历记录 实现;调整部分ui效果
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from dvadmin.utils.models import CoreModel, table_prefix
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MiscProcurementMaterialInfo(CoreModel):
|
||||
"""杂采材料信息"""
|
||||
@@ -205,6 +211,10 @@ class Inquiry(CoreModel):
|
||||
(3, "模具/夹具"),
|
||||
(4, "管"),
|
||||
)
|
||||
BUYING_METHOD_CHOICES = (
|
||||
(1, "询价"),
|
||||
(2, "招标"),
|
||||
)
|
||||
|
||||
inquiry_no = models.CharField(max_length=20, unique=True, db_index=True, verbose_name="询价单号")
|
||||
title = models.CharField(max_length=20, verbose_name="询价单名称")
|
||||
@@ -246,6 +256,9 @@ class Inquiry(CoreModel):
|
||||
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
|
||||
update_user = models.CharField(max_length=20, db_column="UpdateUser", null=True, blank=True, verbose_name="最后更新人")
|
||||
update_time = models.DateTimeField(db_column="UpdateTime", null=True, blank=True, verbose_name="最后更新时间")
|
||||
buying_method = models.IntegerField(choices=BUYING_METHOD_CHOICES, null=True, blank=True, verbose_name="采购方式(寻源方式)")
|
||||
bid_start_time = models.DateTimeField(null=True, blank=True, verbose_name="投标开始时间")
|
||||
bid_end_time = models.DateTimeField(null=True, blank=True, verbose_name="投标截止时间")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "proc_inquiry_master"
|
||||
@@ -546,3 +559,240 @@ class InquiryRfqItem(models.Model):
|
||||
|
||||
def __str__(self) -> str: # pragma: no cover - simple repr
|
||||
return f"{self.inquiry_no_id}-{self.part_id}"
|
||||
|
||||
|
||||
class MiscLowPriceHeader(CoreModel):
|
||||
"""比价-制程最低价记录主表。"""
|
||||
|
||||
# 成本类别: 1-材料; 2-加工; 3-包装费; 4-运输费; 5-管销研费用; 6-利润率(与库表 CostType varchar 一致)
|
||||
COST_TYPE_CHOICES = (
|
||||
("1", "材料"),
|
||||
("2", "加工"),
|
||||
("3", "包装费"),
|
||||
("4", "运输费"),
|
||||
("5", "管销研费用"),
|
||||
("6", "利润率"),
|
||||
)
|
||||
|
||||
inquiry_no = models.CharField(
|
||||
max_length=20,
|
||||
db_index=True,
|
||||
db_column="inquiry_no",
|
||||
verbose_name="询价单号",
|
||||
help_text="业务主键列之一;逻辑主键与 id 并存",
|
||||
)
|
||||
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
|
||||
# 来源单号(报价单&询价单);设计库字段名为 SouceNo
|
||||
souce_no = models.CharField(
|
||||
max_length=20,
|
||||
null=True,
|
||||
blank=True,
|
||||
db_column="SouceNo",
|
||||
verbose_name="来源单号(报价单&询价单)",
|
||||
)
|
||||
cost_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=COST_TYPE_CHOICES,
|
||||
db_column="CostType",
|
||||
verbose_name="成本类别",
|
||||
)
|
||||
item_no = models.CharField(
|
||||
max_length=100,
|
||||
db_column="ItemNo",
|
||||
verbose_name="项次名",
|
||||
help_text="如:铝等项次名称",
|
||||
)
|
||||
min_price = models.CharField(
|
||||
max_length=10,
|
||||
db_column="MinPrice",
|
||||
verbose_name="最低价格",
|
||||
help_text="表结构为 varchar,若需参与运算可在业务层转换",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "misc_low_price_header"
|
||||
verbose_name = "比价-制程最低价记录主表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime", "id")
|
||||
indexes = [
|
||||
models.Index(fields=["inquiry_no", "part_id"]),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.inquiry_no}-{self.part_id}-{self.cost_type}"
|
||||
|
||||
|
||||
class MiscLowPriceDetail(CoreModel):
|
||||
"""比价-制程最低价记录次表"""
|
||||
|
||||
ITEM_NO_CHOICES = (
|
||||
(1, "重量"),
|
||||
(2, "单价"),
|
||||
)
|
||||
inquiry_no = models.CharField(max_length=20, db_column="inquiry_no", db_index=True, verbose_name="询价单号")
|
||||
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
|
||||
cost_type = models.CharField(
|
||||
max_length=50,
|
||||
db_column="CostType",
|
||||
verbose_name="成本类别",
|
||||
help_text="仅支持1-材料",
|
||||
)
|
||||
material_spec = models.CharField(max_length=50, db_column="materialspec", verbose_name="材料规格")
|
||||
item_no = models.CharField(
|
||||
max_length=100,
|
||||
db_column="ItemNo",
|
||||
verbose_name="项次名",
|
||||
help_text="策采:重量、损耗、单价;杂采:重量、单价",
|
||||
choices=ITEM_NO_CHOICES,
|
||||
)
|
||||
value = models.CharField(max_length=10, db_column="Value", verbose_name="最小值")
|
||||
souce_no = models.CharField(
|
||||
max_length=20,
|
||||
db_column="SouceNo",
|
||||
verbose_name="来源单号(报价单&询价单)",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "misc_low_price_detail"
|
||||
verbose_name = "比价-制程最低价记录次表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime", "id")
|
||||
indexes = [
|
||||
models.Index(fields=["inquiry_no", "part_id"]),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.inquiry_no}-{self.part_id}-{self.item_no}"
|
||||
|
||||
|
||||
class MiscNegotiationRecords(CoreModel):
|
||||
"""杂采议价记录表"""
|
||||
|
||||
inquiry_no = models.CharField(max_length=20, db_column="inquiry_no", db_index=True, verbose_name="询价单号")
|
||||
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
|
||||
supplier_code = models.CharField(max_length=50, null=True, blank=True, db_column="SupplierCode", verbose_name="供应商代码")
|
||||
bargaining_price = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价后价格")
|
||||
bargaining_time = models.DateTimeField(null=True, blank=True, verbose_name="议价时间")
|
||||
bargaining_user = models.CharField(max_length=20, null=True, blank=True, db_column="BargainingUser", verbose_name="议价人")
|
||||
is_awarded = models.IntegerField(default=0, null=True, blank=True, verbose_name="是否中标", help_text="是否中标(1:是 0:否)")
|
||||
quotation_no = models.CharField(max_length=20, null=True, blank=True, db_column="QuotationNo", verbose_name="报价单号")
|
||||
total_price_excl_tax = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价前不含税总价")
|
||||
total_price_incl_tax = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, default=0, verbose_name="议价前含税总价")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "misc_negotiation_records"
|
||||
verbose_name = "杂采议价记录表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime", "id")
|
||||
indexes = [
|
||||
models.Index(fields=["inquiry_no", "part_id"]),
|
||||
models.Index(
|
||||
fields=["inquiry_no", "part_id", "quotation_no"],
|
||||
name="pis_misc_ne_inq_part_qtn_idx",
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.inquiry_no}-{self.part_id}-{self.quotation_no or self.supplier_code}"
|
||||
|
||||
|
||||
class RFQOperationLogs(CoreModel):
|
||||
"""询价单操作日志表"""
|
||||
|
||||
# (01询价单创;02询价单确认;03询价单发布;04询价单还原;05询价发送通知;06报价;07比议价;08议价审核提交;09议价审核完成;10议价审核驳回)
|
||||
OPERATION_TYPE_CHOICES = (
|
||||
(1, "询价单创建"),
|
||||
(2, "询价单确认"),
|
||||
(3, "询价单发布"),
|
||||
(4, "询价单还原"),
|
||||
(5, "询价发送通知"),
|
||||
(6, "报价"),
|
||||
(7, "比议价"),
|
||||
(8, "议价审核提交"),
|
||||
(9, "议价审核完成"),
|
||||
(10, "议价审核驳回"),
|
||||
)
|
||||
PURCHASE_TYPE_CHOICES = (
|
||||
(1, "策采"),
|
||||
(2, "杂采"),
|
||||
)
|
||||
|
||||
operation_type = models.CharField(max_length=20, db_column="OperationType", verbose_name="操作类型", choices=OPERATION_TYPE_CHOICES)
|
||||
operation_user = models.CharField(max_length=20, null=True, blank=True, db_column="OperationUser", verbose_name="操作人")
|
||||
operation_time = models.DateTimeField(null=True, blank=True, db_column="OperationTime", verbose_name="操作时间")
|
||||
operation_desc = models.TextField(max_length=200, null=True, blank=True, db_column="OperationDesc", verbose_name="操作描述")
|
||||
inquiry_no = models.CharField(max_length=20, db_column="InquiryNo", db_index=True, verbose_name="询价单号")
|
||||
quotation_no = models.CharField(max_length=20, null=False, blank=False, db_column="QuotationNo", db_index=True, verbose_name="报价单号")
|
||||
per_status = models.CharField(max_length=50, null=True, blank=True, db_column="PerStatus", verbose_name="作业前状态")
|
||||
cur_status = models.TextField(max_length=100, null=True, blank=True, db_column="CurStatus", verbose_name="作业后状态")
|
||||
is_show_user = models.IntegerField(null=True, blank=True, db_column="IsShowUser", verbose_name="履历显示否", help_text="履历显示否(1:是 0:否)")
|
||||
purchase_type = models.IntegerField(choices=PURCHASE_TYPE_CHOICES, null=False, blank=False, db_column="PurchaseType", verbose_name="采购类别", help_text="采购类别(1:策采 2:杂采)")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "rfq_operation_logs"
|
||||
verbose_name = "询价单操作日志"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime", "id")
|
||||
indexes = [
|
||||
models.Index(fields=["inquiry_no", "quotation_no"]),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def inquiry_status_display(cls, code: Optional[int]) -> str:
|
||||
"""询价单状态码 → 与 Inquiry.STATUS_CHOICES 一致的可读文案。"""
|
||||
if code is None:
|
||||
return ""
|
||||
try:
|
||||
c = int(code)
|
||||
except (TypeError, ValueError):
|
||||
return str(code)
|
||||
return dict(Inquiry.STATUS_CHOICES).get(c, str(c))
|
||||
|
||||
@classmethod
|
||||
def append(
|
||||
cls,
|
||||
*,
|
||||
inquiry_no: str,
|
||||
purchase_type: int,
|
||||
operation_type: int,
|
||||
operation_user: Optional[str] = None,
|
||||
quotation_no: Optional[str] = None,
|
||||
per_status: Optional[int] = None,
|
||||
cur_status: Optional[int] = None,
|
||||
operation_desc: Optional[str] = None,
|
||||
is_show_user: int = 1,
|
||||
) -> None:
|
||||
"""
|
||||
写入一条询价操作日志。quotation_no 无关联报价单时使用 \"-\"。
|
||||
operation_type 与 OPERATION_TYPE_CHOICES 取值 1–10 一致。
|
||||
"""
|
||||
qn = (quotation_no or "-").strip()[:20] or "-"
|
||||
op_user = (operation_user or "").strip()[:20] if operation_user else None
|
||||
now = timezone.now()
|
||||
per_str = cls.inquiry_status_display(per_status) if per_status is not None else None
|
||||
cur_str = cls.inquiry_status_display(cur_status) if cur_status is not None else None
|
||||
desc = (operation_desc or "").strip()[:200] if operation_desc else None
|
||||
|
||||
cls.objects.create(
|
||||
operation_type=str(int(operation_type)),
|
||||
operation_user=op_user,
|
||||
operation_time=now,
|
||||
operation_desc=desc,
|
||||
inquiry_no=(inquiry_no or "")[:20],
|
||||
quotation_no=qn,
|
||||
per_status=(per_str[:50] if per_str else None),
|
||||
cur_status=(cur_str[:100] if cur_str else None),
|
||||
is_show_user=is_show_user,
|
||||
purchase_type=int(purchase_type),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def try_append(cls, **kwargs) -> None:
|
||||
"""写入失败不影响主流程,仅记录异常日志。"""
|
||||
try:
|
||||
cls.append(**kwargs)
|
||||
except Exception:
|
||||
logger.exception("写入询价操作日志失败")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.inquiry_no}-{self.quotation_no}"
|
||||
|
||||
@@ -9,6 +9,10 @@ from .models import (
|
||||
MiscProcurementMaterialInfo,
|
||||
MiscProcurementStationInfo,
|
||||
MiscProcMaterial,
|
||||
MiscLowPriceHeader,
|
||||
MiscLowPriceDetail,
|
||||
MiscNegotiationRecords,
|
||||
RFQOperationLogs,
|
||||
Inquiry,
|
||||
InquirySupplier,
|
||||
InquiryAttachment,
|
||||
@@ -262,6 +266,141 @@ class MiscPartCreateUpdateSerializer(CustomModelSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class MiscLowPriceHeaderSerializer(CustomModelSerializer):
|
||||
"""比价-制程最低价记录主表"""
|
||||
|
||||
class Meta:
|
||||
model = MiscLowPriceHeader
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"create_datetime",
|
||||
"update_datetime",
|
||||
"creator",
|
||||
"modifier",
|
||||
"dept_belong_id",
|
||||
]
|
||||
|
||||
|
||||
class MiscLowPriceDetailSerializer(CustomModelSerializer):
|
||||
"""比价-制程最低价记录次表"""
|
||||
|
||||
class Meta:
|
||||
model = MiscLowPriceDetail
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"create_datetime",
|
||||
"update_datetime",
|
||||
"creator",
|
||||
"modifier",
|
||||
"dept_belong_id",
|
||||
]
|
||||
|
||||
|
||||
class MiscNegotiationRecordsSerializer(CustomModelSerializer):
|
||||
"""杂采议价记录表"""
|
||||
|
||||
class Meta:
|
||||
model = MiscNegotiationRecords
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"create_datetime",
|
||||
"update_datetime",
|
||||
"creator",
|
||||
"modifier",
|
||||
"dept_belong_id",
|
||||
]
|
||||
|
||||
|
||||
class RFQOperationLogsSerializer(CustomModelSerializer):
|
||||
"""询价单操作日志(rfq_operation_logs)"""
|
||||
|
||||
operation_type = serializers.IntegerField(required=True)
|
||||
operation_time = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RFQOperationLogs
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"create_datetime",
|
||||
"update_datetime",
|
||||
"creator",
|
||||
"modifier",
|
||||
"dept_belong_id",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"inquiry_no": {"required": True, "allow_blank": False},
|
||||
"quotation_no": {"required": True, "allow_blank": False},
|
||||
"purchase_type": {"required": True},
|
||||
}
|
||||
|
||||
def validate_operation_type(self, value):
|
||||
valid_values = {choice[0] for choice in RFQOperationLogs.OPERATION_TYPE_CHOICES}
|
||||
if value not in valid_values:
|
||||
raise serializers.ValidationError("操作类型取值不合法")
|
||||
return value
|
||||
|
||||
def validate_purchase_type(self, value):
|
||||
valid_values = {choice[0] for choice in RFQOperationLogs.PURCHASE_TYPE_CHOICES}
|
||||
if value not in valid_values:
|
||||
raise serializers.ValidationError("采购类别取值不合法")
|
||||
return value
|
||||
|
||||
def _normalize_operation_type(self, validated_data: dict) -> None:
|
||||
ot = validated_data.get("operation_type")
|
||||
if ot is not None and not isinstance(ot, str):
|
||||
validated_data["operation_type"] = str(int(ot))
|
||||
|
||||
def create(self, validated_data):
|
||||
self._normalize_operation_type(validated_data)
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
self._normalize_operation_type(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
ot = data.get("operation_type")
|
||||
if ot is not None and ot != "":
|
||||
try:
|
||||
data["operation_type"] = int(ot)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
class MiscNegotiationRecordBatchItemSerializer(serializers.Serializer):
|
||||
"""比价保存时按报价单维度的议价行"""
|
||||
|
||||
quotation_no = serializers.CharField(max_length=20)
|
||||
supplier_code = serializers.CharField(max_length=50, required=False, allow_blank=True)
|
||||
is_awarded = serializers.IntegerField(required=False, default=0)
|
||||
bargaining_price = serializers.DecimalField(
|
||||
max_digits=12, decimal_places=4, required=False, allow_null=True
|
||||
)
|
||||
total_price_excl_tax = serializers.DecimalField(
|
||||
max_digits=12, decimal_places=4, required=False, allow_null=True
|
||||
)
|
||||
total_price_incl_tax = serializers.DecimalField(
|
||||
max_digits=12, decimal_places=4, required=False, allow_null=True
|
||||
)
|
||||
|
||||
|
||||
class MiscNegotiationSaveSerializer(serializers.Serializer):
|
||||
"""比价/议价结果批量写入杂采议价记录表"""
|
||||
|
||||
part_id = serializers.CharField(max_length=50)
|
||||
records = MiscNegotiationRecordBatchItemSerializer(many=True)
|
||||
|
||||
|
||||
class CostEstimateTemplateBodySerializer(serializers.ModelSerializer):
|
||||
supplier_behavior = serializers.SerializerMethodField()
|
||||
item_name_en = serializers.CharField(required=False, allow_blank=True, allow_null=True)
|
||||
@@ -890,6 +1029,16 @@ class InquirySerializer(CustomModelSerializer):
|
||||
rfq_items = InquiryRfqItemSerializer(many=True, required=False)
|
||||
# 列表/详情展示:company_code → 公司信息简称(同请求内按代码缓存)
|
||||
company_short_name = serializers.SerializerMethodField(read_only=True)
|
||||
bid_start_time = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
bid_end_time = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
def get_company_short_name(self, obj):
|
||||
code = (getattr(obj, "company_code", None) or "").strip()
|
||||
@@ -931,8 +1080,32 @@ class InquirySerializer(CustomModelSerializer):
|
||||
"inquiry_no": {"required": False, "allow_blank": True, "allow_null": True},
|
||||
# 由 resolve_inquiry_template_version 根据 template 从 CostEstimateTemplateHead 写入
|
||||
"template_version": {"required": False, "allow_null": True},
|
||||
"buying_method": {"required": False, "allow_null": True},
|
||||
}
|
||||
|
||||
def validate_buying_method(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
valid_values = {choice[0] for choice in Inquiry.BUYING_METHOD_CHOICES}
|
||||
if value not in valid_values:
|
||||
raise serializers.ValidationError("采购方式(寻源方式)取值不合法")
|
||||
return value
|
||||
|
||||
def _apply_inquiry_bid_times_for_buying_method(self, attrs, instance):
|
||||
"""采购方式为询价(1)时,投标开始/截止时间不入库。"""
|
||||
bm = attrs.get("buying_method")
|
||||
if bm is None and instance is not None:
|
||||
bm = getattr(instance, "buying_method", None)
|
||||
if bm is None:
|
||||
bm = 1
|
||||
try:
|
||||
bm = int(bm)
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
if bm == 1:
|
||||
attrs["bid_start_time"] = None
|
||||
attrs["bid_end_time"] = None
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = getattr(self, "instance", None)
|
||||
|
||||
@@ -941,6 +1114,7 @@ class InquirySerializer(CustomModelSerializer):
|
||||
|
||||
# 局部更新且未改模板/版本:保持库中原值
|
||||
if instance and not template_in_attrs and not tv_in_attrs:
|
||||
self._apply_inquiry_bid_times_for_buying_method(attrs, instance)
|
||||
return attrs
|
||||
|
||||
template_no = attrs.get("template")
|
||||
@@ -963,6 +1137,7 @@ class InquirySerializer(CustomModelSerializer):
|
||||
}
|
||||
)
|
||||
attrs["template_version"] = resolved
|
||||
self._apply_inquiry_bid_times_for_buying_method(attrs, instance)
|
||||
return attrs
|
||||
|
||||
def _generate_code(self, validated_data: dict) -> str:
|
||||
|
||||
@@ -4,6 +4,8 @@ from .views import (
|
||||
MiscMaterialViewSet,
|
||||
MiscStationViewSet,
|
||||
MiscPartViewSet,
|
||||
MiscLowPriceHeaderViewSet,
|
||||
MiscLowPriceDetailViewSet,
|
||||
InquiryViewSet,
|
||||
InquirySupplierViewSet,
|
||||
InquiryAttachmentViewSet,
|
||||
@@ -13,6 +15,7 @@ from .views import (
|
||||
InquiryProfitCostViewSet,
|
||||
InquiryRfqItemViewSet,
|
||||
CostEstimateTemplateViewSet,
|
||||
RFQOperationLogsViewSet,
|
||||
)
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
@@ -27,7 +30,10 @@ router.register(r'inquiry_process_cost', InquiryProcessCostViewSet)
|
||||
router.register(r'inquiry_other_cost', InquiryOtherCostViewSet)
|
||||
router.register(r'inquiry_profit_cost', InquiryProfitCostViewSet)
|
||||
router.register(r'inquiry_rfq_item', InquiryRfqItemViewSet)
|
||||
router.register(r'proc_low_price_header', MiscLowPriceHeaderViewSet)
|
||||
router.register(r'proc_low_price_detail', MiscLowPriceDetailViewSet)
|
||||
router.register(r'cost_template', CostEstimateTemplateViewSet)
|
||||
router.register(r'rfq_operation_logs', RFQOperationLogsViewSet)
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += router.urls
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
@@ -27,6 +29,10 @@ from .models import (
|
||||
MiscProcurementMaterialInfo,
|
||||
MiscProcurementStationInfo,
|
||||
MiscProcMaterial,
|
||||
MiscLowPriceHeader,
|
||||
MiscLowPriceDetail,
|
||||
MiscNegotiationRecords,
|
||||
RFQOperationLogs,
|
||||
Inquiry,
|
||||
InquirySupplier,
|
||||
InquiryAttachment,
|
||||
@@ -45,6 +51,11 @@ from .serializers import (
|
||||
MiscStationCreateUpdateSerializer,
|
||||
MiscPartSerializer,
|
||||
MiscPartCreateUpdateSerializer,
|
||||
MiscLowPriceHeaderSerializer,
|
||||
MiscLowPriceDetailSerializer,
|
||||
MiscNegotiationRecordsSerializer,
|
||||
MiscNegotiationSaveSerializer,
|
||||
RFQOperationLogsSerializer,
|
||||
InquirySerializer,
|
||||
InquirySupplierSerializer,
|
||||
InquiryAttachmentSerializer,
|
||||
@@ -59,6 +70,20 @@ from .serializers import (
|
||||
)
|
||||
|
||||
|
||||
def _negotiation_totals_from_quotation_item(quotation_no: str, part_id: str):
|
||||
"""从杂采报价单上阶物料明细取议价前含税/不含税总价(与 part_id 匹配行)。"""
|
||||
if not quotation_no or not part_id:
|
||||
return None, None
|
||||
item = (
|
||||
QuotationItem.objects.filter(quotation_no=quotation_no, part_id=part_id)
|
||||
.order_by("autoid")
|
||||
.first()
|
||||
)
|
||||
if not item:
|
||||
return None, None
|
||||
return item.total_price_excl_tax, item.total_price_incl_tax
|
||||
|
||||
|
||||
class MiscMaterialViewSet(CustomModelViewSet):
|
||||
queryset = MiscProcurementMaterialInfo.objects.all()
|
||||
serializer_class = MiscMaterialSerializer
|
||||
@@ -232,7 +257,16 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
"rfq_items",
|
||||
)
|
||||
serializer_class = InquirySerializer
|
||||
filter_fields = ("inquiry_no", "title", "purchase_type", "template", "template_version", "status", "buyer")
|
||||
filter_fields = (
|
||||
"inquiry_no",
|
||||
"title",
|
||||
"purchase_type",
|
||||
"template",
|
||||
"template_version",
|
||||
"status",
|
||||
"buyer",
|
||||
"buying_method",
|
||||
)
|
||||
search_fields = ("inquiry_no", "title", "material_type", "buyer", "remark")
|
||||
ordering = ("-update_datetime",)
|
||||
|
||||
@@ -326,7 +360,21 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
return ErrorResponse(msg="当前询价单状态仅允许查看,不允许编辑或删除;如需修改请先还原为“开立”")
|
||||
return None
|
||||
|
||||
def _save_status(self, instance, *, status, confirm_user=None, confirm_time=None, release_user=None, release_time=None, comparison_user=None, comparison_time=None):
|
||||
def _save_status(
|
||||
self,
|
||||
instance,
|
||||
*,
|
||||
status,
|
||||
confirm_user=None,
|
||||
confirm_time=None,
|
||||
release_user=None,
|
||||
release_time=None,
|
||||
comparison_user=None,
|
||||
comparison_time=None,
|
||||
operation_type=None,
|
||||
operation_desc=None,
|
||||
):
|
||||
old_status = int(instance.status if instance.status is not None else 0)
|
||||
current_time = timezone.now()
|
||||
current_user = self._get_request_username() or getattr(instance, "update_user", None)
|
||||
instance.status = status
|
||||
@@ -353,6 +401,18 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
"update_time",
|
||||
]
|
||||
)
|
||||
new_status = int(status)
|
||||
if operation_type is not None and old_status != new_status:
|
||||
RFQOperationLogs.try_append(
|
||||
inquiry_no=instance.inquiry_no,
|
||||
purchase_type=int(instance.purchase_type),
|
||||
operation_type=operation_type,
|
||||
operation_user=current_user or None,
|
||||
quotation_no="-",
|
||||
per_status=old_status,
|
||||
cur_status=new_status,
|
||||
operation_desc=operation_desc,
|
||||
)
|
||||
serializer = self.get_serializer(instance)
|
||||
return DetailResponse(data=serializer.data, msg="状态更新成功")
|
||||
|
||||
@@ -537,6 +597,9 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
contact_phone=supplier["contact_phone"] or None,
|
||||
contact_email=supplier["contact_email"] or None,
|
||||
quote_deadline=quote_deadline,
|
||||
buying_method=getattr(inquiry, "buying_method", None),
|
||||
bid_start_time=getattr(inquiry, "bid_start_time", None),
|
||||
bid_end_time=getattr(inquiry, "bid_end_time", None),
|
||||
delivery_days=getattr(inquiry, "lead_time_days", None),
|
||||
payment_method=getattr(inquiry, "payment_method", None),
|
||||
status=1,
|
||||
@@ -655,7 +718,17 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
QuotationItem.objects.bulk_create(item_bulk)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save()
|
||||
inquiry = serializer.save()
|
||||
RFQOperationLogs.try_append(
|
||||
inquiry_no=inquiry.inquiry_no,
|
||||
purchase_type=int(inquiry.purchase_type),
|
||||
operation_type=1,
|
||||
operation_user=self._get_request_username() or None,
|
||||
quotation_no="-",
|
||||
per_status=None,
|
||||
cur_status=int(inquiry.status) if inquiry.status is not None else 1,
|
||||
operation_desc="询价单创建",
|
||||
)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save()
|
||||
@@ -717,6 +790,8 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
confirm_time=current_time,
|
||||
release_user=getattr(instance, "release_user", None),
|
||||
release_time=getattr(instance, "release_time", None),
|
||||
operation_type=2,
|
||||
operation_desc="询价单确认",
|
||||
)
|
||||
|
||||
@action(methods=["put"], detail=True)
|
||||
@@ -731,6 +806,8 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
confirm_time=None,
|
||||
release_user=None,
|
||||
release_time=None,
|
||||
operation_type=4,
|
||||
operation_desc="询价单还原为开立",
|
||||
)
|
||||
|
||||
@action(methods=["put"], detail=True)
|
||||
@@ -751,6 +828,8 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
confirm_time=getattr(instance, "confirm_time", None),
|
||||
release_user=current_user,
|
||||
release_time=current_time,
|
||||
operation_type=3,
|
||||
operation_desc="询价单发布",
|
||||
)
|
||||
except serializers.ValidationError as exc:
|
||||
detail = getattr(exc, "detail", None)
|
||||
@@ -760,6 +839,160 @@ class InquiryViewSet(CustomModelViewSet):
|
||||
msg = str(detail or exc)
|
||||
return ErrorResponse(msg=msg)
|
||||
|
||||
@action(methods=["put"], detail=True)
|
||||
def start_bargaining(self, request, pk=None):
|
||||
"""开启比价议价:从报价结束状态进入比议价中"""
|
||||
instance = self.get_object()
|
||||
if int(instance.status or self.STATUS_PUBLISHED) not in (self.STATUS_PUBLISHED, self.STATUS_QUOTING, self.STATUS_QUOTE_ENDED):
|
||||
return ErrorResponse(msg='只有“发布”或“报价结束”状态的询价单才能开启比价')
|
||||
current_user = self._get_request_username() or None
|
||||
current_time = timezone.now()
|
||||
return self._save_status(
|
||||
instance,
|
||||
status=self.STATUS_BARGaining,
|
||||
confirm_user=getattr(instance, "confirm_user", None),
|
||||
confirm_time=getattr(instance, "confirm_time", None),
|
||||
release_user=getattr(instance, "release_user", None),
|
||||
release_time=getattr(instance, "release_time", None),
|
||||
comparison_user=current_user,
|
||||
comparison_time=current_time,
|
||||
operation_type=7,
|
||||
operation_desc="开启比议价",
|
||||
)
|
||||
|
||||
@action(methods=["put"], detail=True)
|
||||
def confirm_negotiation(self, request, pk=None):
|
||||
"""确认议价:从比议价中进入议价确认/价格审核"""
|
||||
instance = self.get_object()
|
||||
if int(instance.status or self.STATUS_BARGaining) != self.STATUS_BARGaining:
|
||||
return ErrorResponse(msg='只有“比议价中”状态的询价单才能确认议价')
|
||||
current_user = self._get_request_username() or None
|
||||
current_time = timezone.now()
|
||||
return self._save_status(
|
||||
instance,
|
||||
status=self.STATUS_NEGOTIATED,
|
||||
confirm_user=getattr(instance, "confirm_user", None),
|
||||
confirm_time=getattr(instance, "confirm_time", None),
|
||||
release_user=getattr(instance, "release_user", None),
|
||||
release_time=getattr(instance, "release_time", None),
|
||||
comparison_user=getattr(instance, "comparison_user", None),
|
||||
comparison_time=getattr(instance, "comparison_time", None),
|
||||
operation_type=8,
|
||||
operation_desc="议价审核提交(进入价格审核)",
|
||||
)
|
||||
|
||||
@action(methods=["put"], detail=True)
|
||||
def submit_price_audit(self, request, pk=None):
|
||||
"""价格审核提交:由「价格审核」(7) 进入「核价通过」(8)。"""
|
||||
instance = self.get_object()
|
||||
if int(instance.status or 0) != self.STATUS_NEGOTIATED:
|
||||
return ErrorResponse(msg='只有「价格审核」状态的询价单才能提交核价')
|
||||
return self._save_status(
|
||||
instance,
|
||||
status=self.STATUS_APPROVED,
|
||||
confirm_user=getattr(instance, "confirm_user", None),
|
||||
confirm_time=getattr(instance, "confirm_time", None),
|
||||
release_user=getattr(instance, "release_user", None),
|
||||
release_time=getattr(instance, "release_time", None),
|
||||
comparison_user=getattr(instance, "comparison_user", None),
|
||||
comparison_time=getattr(instance, "comparison_time", None),
|
||||
operation_type=9,
|
||||
operation_desc="议价审核完成(核价通过)",
|
||||
)
|
||||
|
||||
@action(methods=["get"], detail=True, url_path="negotiation_records")
|
||||
def negotiation_records(self, request, pk=None):
|
||||
"""按询价单号查询杂采议价记录(可选 part_id)。"""
|
||||
instance = self.get_object()
|
||||
part_id = (request.query_params.get("part_id") or "").strip()
|
||||
qs = MiscNegotiationRecords.objects.filter(inquiry_no=instance.inquiry_no)
|
||||
if part_id:
|
||||
qs = qs.filter(part_id=part_id)
|
||||
data = MiscNegotiationRecordsSerializer(qs.order_by("id"), many=True).data
|
||||
return DetailResponse(data=data, msg="success")
|
||||
|
||||
@action(methods=["put"], detail=True, url_path="save_negotiation_records")
|
||||
def save_negotiation_records(self, request, pk=None):
|
||||
"""按报价单写入杂采议价记录:议价结果 + 该报价单议价前含税/不含税总价快照(来自上阶物料明细)。"""
|
||||
instance = self.get_object()
|
||||
serializer = MiscNegotiationSaveSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
part_id = (serializer.validated_data.get("part_id") or "").strip()
|
||||
if not part_id:
|
||||
return ErrorResponse(msg="part_id 不能为空")
|
||||
records = serializer.validated_data.get("records") or []
|
||||
username = self._get_request_username() or None
|
||||
now = timezone.now()
|
||||
with transaction.atomic():
|
||||
for row in records:
|
||||
qn = (row.get("quotation_no") or "").strip()
|
||||
if not qn:
|
||||
return ErrorResponse(msg="records 中每条须包含 quotation_no(报价单号)")
|
||||
code = (row.get("supplier_code") or "").strip()
|
||||
if not code:
|
||||
qm = QuotationMaster.objects.filter(quotation_no=qn).only("supplier_code").first()
|
||||
if qm:
|
||||
code = (qm.supplier_code or "").strip()
|
||||
is_awarded = int(row.get("is_awarded") or 0)
|
||||
# 议价价格独立于是否中标:用户填写即落库(清空时前端传 null)
|
||||
bp = row.get("bargaining_price")
|
||||
snap_ex, snap_in = _negotiation_totals_from_quotation_item(qn, part_id)
|
||||
tex = row.get("total_price_excl_tax")
|
||||
tin = row.get("total_price_incl_tax")
|
||||
if tex is None:
|
||||
tex = snap_ex
|
||||
if tin is None:
|
||||
tin = snap_in
|
||||
if tex is None:
|
||||
tex = Decimal("0")
|
||||
if tin is None:
|
||||
tin = Decimal("0")
|
||||
defaults = {
|
||||
"supplier_code": code or None,
|
||||
"is_awarded": is_awarded,
|
||||
"bargaining_user": username,
|
||||
"bargaining_time": now,
|
||||
"bargaining_price": bp,
|
||||
"quotation_no": qn,
|
||||
"total_price_excl_tax": tex,
|
||||
"total_price_incl_tax": tin,
|
||||
}
|
||||
qs = MiscNegotiationRecords.objects.filter(
|
||||
inquiry_no=instance.inquiry_no,
|
||||
part_id=part_id,
|
||||
quotation_no=qn,
|
||||
)
|
||||
if qs.count() > 1:
|
||||
keep_id = qs.order_by("id").first().id
|
||||
qs.exclude(id=keep_id).delete()
|
||||
obj = (
|
||||
MiscNegotiationRecords.objects.filter(
|
||||
inquiry_no=instance.inquiry_no,
|
||||
part_id=part_id,
|
||||
quotation_no=qn,
|
||||
)
|
||||
.order_by("id")
|
||||
.first()
|
||||
)
|
||||
if obj:
|
||||
for k, v in defaults.items():
|
||||
setattr(obj, k, v)
|
||||
obj.save()
|
||||
else:
|
||||
MiscNegotiationRecords.objects.create(
|
||||
inquiry_no=instance.inquiry_no,
|
||||
part_id=part_id,
|
||||
**defaults,
|
||||
)
|
||||
out_qs = MiscNegotiationRecords.objects.filter(
|
||||
inquiry_no=instance.inquiry_no,
|
||||
part_id=part_id,
|
||||
).order_by("id")
|
||||
return DetailResponse(
|
||||
data=MiscNegotiationRecordsSerializer(out_qs, many=True).data,
|
||||
msg="议价记录已保存",
|
||||
)
|
||||
|
||||
def _notify_vendors_on_publish(self, inquiry: Inquiry):
|
||||
"""
|
||||
按询价单供应商子表汇总(与 `_create_supplier_quotations` 相同的 `_group_inquiry_suppliers`),
|
||||
@@ -924,3 +1157,51 @@ class InquiryRfqItemViewSet(CustomModelViewSet):
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
|
||||
class MiscLowPriceHeaderViewSet(CustomModelViewSet):
|
||||
"""比价-制程最低价记录主表"""
|
||||
|
||||
queryset = MiscLowPriceHeader.objects.all()
|
||||
serializer_class = MiscLowPriceHeaderSerializer
|
||||
filter_fields = ("inquiry_no", "part_id", "souce_no", "cost_type", "item_no")
|
||||
search_fields = ("inquiry_no", "part_id", "souce_no", "item_no")
|
||||
ordering = ("-create_datetime", "id")
|
||||
|
||||
|
||||
class MiscLowPriceDetailViewSet(CustomModelViewSet):
|
||||
"""比价-制程最低价记录次表"""
|
||||
|
||||
queryset = MiscLowPriceDetail.objects.all()
|
||||
serializer_class = MiscLowPriceDetailSerializer
|
||||
filter_fields = ("inquiry_no", "part_id", "cost_type", "material_spec", "item_no", "souce_no")
|
||||
search_fields = ("inquiry_no", "part_id", "material_spec", "item_no", "souce_no")
|
||||
ordering = ("-create_datetime", "id")
|
||||
|
||||
|
||||
class MiscNegotiationRecordsViewSet(CustomModelViewSet):
|
||||
"""杂采议价记录表"""
|
||||
|
||||
queryset = MiscNegotiationRecords.objects.all()
|
||||
serializer_class = MiscNegotiationRecordsSerializer
|
||||
filter_fields = ("inquiry_no", "part_id", "supplier_code")
|
||||
search_fields = ("inquiry_no", "part_id", "supplier_code")
|
||||
ordering = ("-create_datetime", "id")
|
||||
|
||||
|
||||
class RFQOperationLogsViewSet(CustomModelViewSet):
|
||||
"""询价单操作日志(rfq_operation_logs)"""
|
||||
|
||||
queryset = RFQOperationLogs.objects.all()
|
||||
serializer_class = RFQOperationLogsSerializer
|
||||
create_serializer_class = RFQOperationLogsSerializer
|
||||
update_serializer_class = RFQOperationLogsSerializer
|
||||
filter_fields = (
|
||||
"inquiry_no",
|
||||
"quotation_no",
|
||||
"operation_type",
|
||||
"operation_user",
|
||||
"purchase_type",
|
||||
)
|
||||
search_fields = ("inquiry_no", "quotation_no", "operation_user", "operation_desc")
|
||||
ordering = ("-create_datetime", "-id")
|
||||
@@ -25,6 +25,11 @@ class QuotationMaster(models.Model):
|
||||
(1, "已中标"),
|
||||
)
|
||||
|
||||
BUYING_METHOD_CHOICES = (
|
||||
(1, "询价"),
|
||||
(2, "招标"),
|
||||
)
|
||||
|
||||
autoid = models.BigAutoField( primary_key=True, db_column="autoId", verbose_name="自增ID" )
|
||||
quotation_no = models.CharField( max_length=20, unique=True, db_index=True, verbose_name="报价单单号" )
|
||||
inquiry_no = models.CharField( max_length=20, db_index=True, verbose_name="询价单单号", help_text="报价单关联的询价单单号" )
|
||||
@@ -53,6 +58,9 @@ class QuotationMaster(models.Model):
|
||||
null=True, blank=True, verbose_name="报价时间", help_text="正式提交报价时由 submit 接口写入当前时间"
|
||||
)
|
||||
remark = models.TextField( null=True, blank=True, verbose_name="报价说明及备注" )
|
||||
buying_method = models.IntegerField(choices=BUYING_METHOD_CHOICES, null=True, blank=True, verbose_name="采购方式(寻源方式)")
|
||||
bid_start_time = models.DateTimeField(null=True, blank=True, verbose_name="投标开始时间")
|
||||
bid_end_time = models.DateTimeField(null=True, blank=True, verbose_name="投标截止时间")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "sup_quotation_master"
|
||||
|
||||
@@ -383,6 +383,16 @@ class QuotationMasterSerializer(BusinessAuditSerializer):
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
bid_start_time = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
bid_end_time = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S",
|
||||
required=False,
|
||||
allow_null=True,
|
||||
)
|
||||
# 列表/详情:询价主表 company_code + 公司信息简称(同请求内按 inquiry_no 缓存,减轻重复查询)
|
||||
inquiry_company_code = serializers.SerializerMethodField(read_only=True)
|
||||
inquiry_company_short_name = serializers.SerializerMethodField(read_only=True)
|
||||
@@ -504,6 +514,9 @@ class QuotationMasterCreateUpdateSerializer(BusinessAuditSerializer):
|
||||
quoteuser = serializers.CharField(max_length=20, required=False, allow_blank=True, allow_null=True)
|
||||
quotetime = serializers.DateTimeField(required=False, allow_null=True)
|
||||
remark = serializers.CharField(required=False, allow_blank=True, allow_null=True)
|
||||
buying_method = serializers.IntegerField(required=False, allow_null=True)
|
||||
bid_start_time = serializers.DateTimeField(required=False, allow_null=True)
|
||||
bid_end_time = serializers.DateTimeField(required=False, allow_null=True)
|
||||
|
||||
audit_create_user_field = "createuser"
|
||||
audit_create_time_field = "creattime"
|
||||
@@ -548,6 +561,14 @@ class QuotationMasterCreateUpdateSerializer(BusinessAuditSerializer):
|
||||
raise serializers.ValidationError("中标状态值不合法")
|
||||
return value
|
||||
|
||||
def validate_buying_method(self, value):
|
||||
if value is None:
|
||||
return value
|
||||
valid_values = {choice[0] for choice in QuotationMaster.BUYING_METHOD_CHOICES}
|
||||
if value not in valid_values:
|
||||
raise serializers.ValidationError("采购方式(寻源方式)取值不合法")
|
||||
return value
|
||||
|
||||
def _upsert_attachments(self, quotation, attachments):
|
||||
QuotationAttachment.objects.filter(quotation_no=quotation).delete()
|
||||
if not attachments:
|
||||
|
||||
@@ -7,7 +7,7 @@ from rest_framework.decorators import action
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
from apps.pisadmin.miscprocurement.models import Inquiry
|
||||
from apps.pisadmin.miscprocurement.models import Inquiry, RFQOperationLogs
|
||||
from apps.pissupplier.models import (
|
||||
QuotationMaster,
|
||||
QuotationAttachment,
|
||||
@@ -84,7 +84,12 @@ class QuotationMasterViewSet(CustomModelViewSet):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _sync_inquiry_when_quotation_quoting(inquiry_no: str, *, actor_username: Optional[str] = None):
|
||||
def _sync_inquiry_when_quotation_quoting(
|
||||
inquiry_no: str,
|
||||
*,
|
||||
actor_username: Optional[str] = None,
|
||||
quotation_no: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
任意报价单进入「报价中」(status=2) 时,若询价单仍为「发布」(3),同步为「报价中」(4)。
|
||||
已进入报价中(4) 的询价单无需再改;不回退报价结束及之后状态。
|
||||
@@ -95,6 +100,7 @@ class QuotationMasterViewSet(CustomModelViewSet):
|
||||
inq = Inquiry.objects.filter(inquiry_no=inq_no, status=3).first()
|
||||
if not inq:
|
||||
return
|
||||
old_status = int(inq.status if inq.status is not None else 0)
|
||||
now = timezone.now()
|
||||
update_user = (str(actor_username).strip()[:20] if actor_username else None) or None
|
||||
inq.status = 4
|
||||
@@ -103,6 +109,16 @@ class QuotationMasterViewSet(CustomModelViewSet):
|
||||
if update_user:
|
||||
inq.update_user = update_user
|
||||
inq.save(update_fields=["status", "update_time", "update_user", "update_datetime"])
|
||||
RFQOperationLogs.try_append(
|
||||
inquiry_no=inq.inquiry_no,
|
||||
purchase_type=int(inq.purchase_type),
|
||||
operation_type=6,
|
||||
operation_user=update_user,
|
||||
quotation_no=(quotation_no or "-")[:20],
|
||||
per_status=old_status,
|
||||
cur_status=4,
|
||||
operation_desc="供应商进入报价中,询价单同步为报价中",
|
||||
)
|
||||
|
||||
@action(methods=["post"], detail=True, url_path="quote")
|
||||
def quote(self, request, pk=None):
|
||||
@@ -117,7 +133,11 @@ class QuotationMasterViewSet(CustomModelViewSet):
|
||||
instance.quoteuser = username
|
||||
with transaction.atomic():
|
||||
instance.save(update_fields=["status", "quotetime", "quoteuser"])
|
||||
self._sync_inquiry_when_quotation_quoting(instance.inquiry_no, actor_username=username)
|
||||
self._sync_inquiry_when_quotation_quoting(
|
||||
instance.inquiry_no,
|
||||
actor_username=username,
|
||||
quotation_no=getattr(instance, "quotation_no", None),
|
||||
)
|
||||
serializer = self.get_serializer(instance)
|
||||
return DetailResponse(data=serializer.data, msg="报价中状态更新成功")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user