Files
django-vue3-admin/backend/apps/pisadmin/miscprocurement/models.py
T

799 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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):
"""杂采材料信息"""
factory = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True)
materialtype = models.CharField(max_length=50, verbose_name="材质", help_text="材质", null=True, blank=True)
density = models.CharField(max_length=20, verbose_name="比重", help_text="比重")
price = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="单价", help_text="单价")
status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)")
class Meta:
db_table = table_prefix + "misc_procurement_material_info"
verbose_name = "杂采材料信息"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
def __str__(self):
return self.materialtype or ""
class MiscProcurementStationInfo(CoreModel):
"""杂采工站信息"""
company_code = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True)
stationname = models.CharField(max_length=50, verbose_name="工站名称", help_text="工站名称")
stationcode = models.CharField(max_length=20, verbose_name="工站代码", help_text="工站代码", null=True, blank=True)
stationtype = models.IntegerField(verbose_name="工站类型归属", help_text="工站类型归属(1:模治具 2:石墨)")
unit = models.CharField(max_length=20, verbose_name="计量单位", help_text="计量单位", null=True, blank=True)
rate = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="费率", help_text="费率")
status = models.IntegerField(default=1, verbose_name="可用状态", help_text="可用状态(1可用0不可用)")
class Meta:
db_table = table_prefix + "misc_procurement_station_info"
verbose_name = "杂采加工工站信息"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
def __str__(self):
return self.stationname
class MiscProcMaterial(CoreModel):
"""杂采料号信息"""
company_code = models.CharField(max_length=10, verbose_name="交易厂区", help_text="交易厂区(公司)", null=True, blank=True)
partid = models.CharField(max_length=50, verbose_name="料号", help_text="料号(物料编码)", null=True, blank=True)
partid_name = models.CharField(max_length=50, verbose_name="物料说明", help_text="物料说明")
specification = models.CharField(max_length=50, verbose_name="品名规格", help_text="品名规格")
unit = models.CharField(max_length=50, verbose_name="单位", help_text="单位")
partid_category_id = models.IntegerField(verbose_name="物料分类", help_text="物料分类(1:模治具,2:石墨 3:)", null=True, blank=True)
status = models.IntegerField(default=1, verbose_name="启用否", help_text="启用否(1:启用 0 禁用)")
class Meta:
db_table = table_prefix + "misc_proc_materials"
verbose_name = "杂采料号信息"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
def __str__(self):
return self.partid or self.partid_name
class CostEstimateTemplateHead(models.Model):
"""成本估算模板主表;确认某一版本时,同 template_no 下更低版本的主表行由接口批量改为作废(2)。"""
STATUS_CHOICES = (
(0, "未确认"),
(1, "已确认"),
(2, "作废"),
)
template_no = models.CharField(max_length=20, db_index=True, db_column="TemplateNo", verbose_name="模板编号")
template_name = models.CharField(max_length=50, db_column="TemplateName", verbose_name="模板名称")
procurement_category = models.CharField(max_length=4, db_column="ProcurementCategory", verbose_name="采购类别:1-策采;2-杂采")
is_bom = models.CharField(max_length=4, db_column="IsBom", verbose_name="启用BOM否")
acti = models.CharField(max_length=4, db_column="Acti", null=True, blank=True, default="Y", verbose_name="有效否:Y/N")
# 允许为空:前端备注可不填
template_desc = models.CharField(max_length=500, db_column="TemplateDesc", null=True, blank=True, verbose_name="模板描述")
is_can_add_materials = models.IntegerField(db_column="IsCanAddMaterials", default=0, verbose_name="材料允许供应商增行")
is_can_add_process = models.IntegerField(db_column="IsCanAddprocess", default=0, verbose_name="加工允许供应商增行")
create_user = models.CharField(max_length=20, db_column="CreateUser", null=True, blank=True, verbose_name="创建人")
create_time = models.DateTimeField(db_column="CreateTime", 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="最后更新时间")
version = models.IntegerField(db_column="Version", default=1, verbose_name="版本号")
status = models.IntegerField(db_column="Status", default=0, verbose_name="状态", choices=STATUS_CHOICES)
class Meta:
db_table = "t_CostEstimate_Template_Head"
verbose_name = "成本估算模板主表"
verbose_name_plural = verbose_name
ordering = ("-update_time", "-id")
indexes = [
models.Index(fields=["template_no", "version"], name="idx_cost_template_head_no_ver"),
]
constraints = [
models.UniqueConstraint(
fields=("template_no", "version"),
name="uq_cost_template_head_no_ver",
),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return self.template_name
@property
def items(self):
"""明细表按 (template_no, version) 与当前主表行对齐(非 ORM 反向关联)。"""
return CostEstimateTemplateBody.objects.filter(
template_no=self.template_no,
version=self.version,
).order_by("item_order", "id")
class CostEstimateTemplateBody(models.Model):
COST_CATEGORY_CHOICES = (
("1", "材料成本"),
("2", "加工成本"),
("3", "其它成本"),
("4", "管销研成本"),
("5", "利润成本"),
("6", "税费成本"),
("7", "产品明细"),
)
SUPPLIER_REQUIRED_CHOICES = (
(0, " "),
(1, "带出不可修改"),
(2, "带出可修改"),
(3, "不带出必填"),
(4, "不带出可空"),
(5, "必填"),
(6, "可空"),
)
# 与主表通过 (template_no, version) 逻辑关联;主表上该组合唯一(见 CostEstimateTemplateHead.Meta.constraints)
template_no = models.CharField(max_length=20, db_column="TemplateNo", verbose_name="模板编号")
cost_category = models.CharField(max_length=4, choices=COST_CATEGORY_CHOICES, db_column="CostCategory", verbose_name="成本类别")
item_order = models.IntegerField(db_column="ItemOrder", verbose_name="排序序号")
item_no = models.CharField(max_length=100, db_column="ItemNo", verbose_name="项次编号")
item_name_cn = models.CharField(max_length=100, db_column="ItemName_cn", verbose_name="中文名称")
item_name_en = models.CharField(max_length=100, db_column="ItemName_en", null=True, blank=True, verbose_name="英文名称")
item_name_vn = models.CharField(max_length=100, db_column="ItemName_vn", null=True, blank=True, verbose_name="越南语名称")
is_fixed = models.IntegerField(db_column="is_fixed", verbose_name="是否固定字段:1是0否")
is_computed = models.IntegerField(db_column="is_computed", verbose_name="是否系统自动计算/带出")
purchaser_required = models.IntegerField(db_column="PurchaserRequired", verbose_name="采购必填否(1/0)")
supplier_required = models.IntegerField(db_column="SupplierRequired", choices=SUPPLIER_REQUIRED_CHOICES, verbose_name="供应商操作")
remark = models.CharField(max_length=200, db_column="Remark", null=True, blank=True, verbose_name="备注")
create_user = models.CharField(max_length=20, db_column="CreateUser", null=True, blank=True, verbose_name="创建人")
create_time = models.DateTimeField(db_column="CreateTime", 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="最后更新时间")
version = models.IntegerField(db_column="Version", default=1, verbose_name="版本号")
class Meta:
db_table = "t_CostEstimate_Template_Body"
verbose_name = "成本估算模板明细"
verbose_name_plural = verbose_name
ordering = ("item_order", "id")
indexes = [
models.Index(fields=["template_no", "version"], name="idx_cost_template_body_no_ver"),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return self.item_no
class Inquiry(CoreModel):
"""杂采询价单主表"""
# (1开立;2确认;3发布;4报价中;5报价结束;6比议价中;7价格审核;8核价通过(结束);9落标(结束);0作废)
STATUS_CHOICES = (
(1, "开立"),
(2, "确认"),
(3, "发布"),
(4, "报价中"),
(5, "报价结束"),
(6, "比议价中"),
(7, "价格审核"),
(8, "核价通过(结束)"),
(9, "落标(结束)"),
(0, "作废"),
)
PAYMENT_METHOD_CHOICES = (
(1, "月结30天"),
(2, "月结60天"),
(3, "不到付款"),
(4, "预付30%"),
(5, "预付50%"),
(6, "余款至生产"),
(7, "价格审核"),
(8, "价格结算(运费)"),
(9, "新建(结束)"),
)
PURCHASE_TYPE_CHOICES = (
(1, "策采"),
(2, "杂采"),
)
CATEGORY_CHOICES = (
(1, "中压"),
(2, "活塞"),
(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="询价单名称")
purchase_type = models.IntegerField(choices=PURCHASE_TYPE_CHOICES, verbose_name="采购类别")
material_type = models.CharField(max_length=20, null=True, blank=True, verbose_name="材料类型")
template = models.CharField(
max_length=20,
verbose_name="询价模版",
help_text="对应成本估算模板编号;发布生成供应商报价单时,子表字段是否从询价单带入由该模板明细 is_computed=1 或 supplier_required 为 1/2 决定。",
)
template_version = models.IntegerField(
db_column="TemplateVersion",
default=1,
verbose_name="模板版本号",
help_text="与 template 共同锁定成本估算模板主表版本(CostEstimateTemplateHead.version);发布与供应商报价结构均以此为准。",
)
is_bom = models.IntegerField(default=0, verbose_name="是否BOM否")
currency = models.CharField(max_length=20, default="CNY", verbose_name="交易币别")
company_code = models.CharField(max_length=20, null=True, blank=True, verbose_name="公司代码")
purchase_dept = models.CharField(max_length=20, null=True, blank=True, verbose_name="采购部门")
buyer = models.CharField(max_length=20, verbose_name="采购负责人")
quote_deadline = models.DateTimeField(null=True, blank=True, verbose_name="报价截止时间")
target_price = models.DecimalField(db_column="target_price", max_digits=12, decimal_places=4, default=0, verbose_name="目标价格")
lead_time_days = models.IntegerField(default=0, verbose_name="交货周期(天)")
payment_method = models.IntegerField(choices=PAYMENT_METHOD_CHOICES, verbose_name="付款方式")
status = models.IntegerField(choices=STATUS_CHOICES, default=1, db_index=True, verbose_name="状态")
confirm_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="确认人")
confirm_time = models.DateTimeField(null=True, blank=True, verbose_name="确认时间")
release_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="发布人")
release_time = models.DateTimeField(null=True, blank=True, verbose_name="发布时间")
comparison_user = models.CharField(max_length=20, null=True, blank=True, verbose_name="比价人")
comparison_time = models.DateTimeField(null=True, blank=True, verbose_name="比价时间")
win_supplier_code = models.CharField(max_length=20, null=True, blank=True, verbose_name="中标供应商代码")
win_price = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True, verbose_name="中标价格")
approval_number = models.CharField(max_length=20, null=True, blank=True, verbose_name="EIP审核单号")
approval_status = models.IntegerField(null=True, blank=True, verbose_name="EIP审核结果")
remark = models.CharField(max_length=100, null=True, blank=True, verbose_name="备注")
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
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"
verbose_name = "询价单"
verbose_name_plural = verbose_name
ordering = ("-create_time", "-id")
def __str__(self) -> str: # pragma: no cover - simple repr
return self.inquiry_no
class InquirySupplier(models.Model):
"""杂采询价单-供应商关联表"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="suppliers",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
supplier_code = models.CharField(max_length=20, db_column="supplier_code", verbose_name="供应商代码")
supplier_name = models.CharField(max_length=50, db_column="supplier_name", verbose_name="供应商名称")
contact_person = models.CharField(max_length=20, db_column="contact_person", null=True, blank=True, verbose_name="联系人")
contact_phone = models.CharField(max_length=20, db_column="contact_phone", null=True, blank=True, verbose_name="联系电话")
contact_email = models.CharField(max_length=50, db_column="contact_email", null=True, blank=True, verbose_name="联系邮箱")
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="添加时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="添加人")
class Meta:
db_table = table_prefix + "proc_inquiry_supplier"
verbose_name = "杂采询价单-供应商关联表"
verbose_name_plural = verbose_name
unique_together = ("inquiry_no", "part_id", "supplier_code")
ordering = ("id",)
def __str__(self) -> str: # pragma: no cover - simple repr
return self.supplier_name
class InquiryAttachment(models.Model):
"""杂采询价单-附件关联表(采购方上传)。发布询价生成报价单时不写入 `pissupplier.QuotationAttachment`,供应商通过详情中的询价附件只读展示。"""
FILE_TYPE_CHOICES = (
(1, "产品图纸"),
(2, "招标文件"),
(3, "其它文件"),
)
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="attachments",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
file_type = models.IntegerField(choices=FILE_TYPE_CHOICES, db_column="file_type", verbose_name="文件类型")
file_name = models.CharField(max_length=100, db_column="file_name", verbose_name="文件名称")
file_path = models.CharField(max_length=200, db_column="file_path", verbose_name="文件路径")
upload_time = models.DateTimeField(db_column="uploadtime", auto_now_add=True, verbose_name="上传时间")
upload_user = models.CharField(max_length=20, db_column="uploaduser", null=True, blank=True, verbose_name="上传人")
class Meta:
db_table = table_prefix + "proc_inquiry_attachment"
verbose_name = "杂采询价单-附件关联表"
verbose_name_plural = verbose_name
unique_together = ("inquiry_no", "part_id", "file_name")
ordering = ("-upload_time",)
def __str__(self) -> str: # pragma: no cover - simple repr
return self.file_name
class InquiryMaterialCost(models.Model):
"""杂采询价单-材料成本明细表"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="material_costs",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
material_spec = models.CharField(max_length=20, db_column="material_spec", null=True, blank=True, verbose_name="材料规格")
length = models.CharField(max_length=10, db_column="Length", null=True, blank=True, verbose_name="长")
width = models.CharField(max_length=10, db_column="Width", null=True, blank=True, verbose_name="宽")
height = models.CharField(max_length=10, db_column="Height", null=True, blank=True, verbose_name="高")
unit_price = models.DecimalField(max_digits=10, decimal_places=2, db_column="UnitPrice", null=True, blank=True, verbose_name="单价")
qty = models.IntegerField(db_column="qty", null=True, blank=True, verbose_name="数量")
specific_gravity = models.CharField(
max_length=10, db_column="SpecificGravity", null=True, blank=True, verbose_name="比重"
)
material_cost = models.DecimalField(
max_digits=12, decimal_places=4, db_column="material_cost", null=True, blank=True, verbose_name="材料费用"
)
weight = models.DecimalField(
max_digits=12,
decimal_places=4,
db_column="Weight",
null=True,
blank=True,
verbose_name="重量",
)
remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注")
option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息")
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
class Meta:
db_table = table_prefix + "proc_inquiry_material_cost"
verbose_name = "杂采询价单-材料成本明细表"
verbose_name_plural = verbose_name
ordering = ("id",)
indexes = [
models.Index(fields=["part_id"]),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return f"{self.inquiry_no_id}-{self.part_id}"
class InquiryProcessCost(models.Model):
"""杂采询价单-加工成本明细表"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="process_costs",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
process_station = models.CharField(
max_length=20, db_column="process_station", null=True, blank=True, verbose_name="加工工站"
)
# 单位为文本(如 PCS/小时/次),历史误建为 DecimalField 会导致前端传字符串时报校验错误
unit = models.CharField(max_length=20, db_column="Unit", null=True, blank=True, verbose_name="单位")
unit_rate = models.DecimalField(
max_digits=12, decimal_places=4, db_column="UnitRate", null=True, blank=True, verbose_name="费率"
)
process_qty = models.CharField(max_length=20, db_column="ProcessQty", null=True, blank=True, verbose_name="加工数量")
process_price = models.DecimalField(
max_digits=10, decimal_places=2, db_column="ProcessPrice", null=True, blank=True, verbose_name="加工费用"
)
remark = models.CharField(max_length=100, db_column="remark", null=True, blank=True, verbose_name="备注")
option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息")
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
class Meta:
db_table = table_prefix + "proc_inquiry_process_cost"
verbose_name = "杂采询价单-加工成本明细表"
verbose_name_plural = verbose_name
ordering = ("id",)
indexes = [
models.Index(fields=["part_id"]),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return f"{self.inquiry_no_id}-{self.part_id}"
class InquiryOtherCost(models.Model):
"""杂采询价单-其它成本明细表"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="other_costs",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
packaging_cost = models.DecimalField(
max_digits=12, decimal_places=4, db_column="packaging_cost", null=True, blank=True, verbose_name="包装费用"
)
transportation_cost = models.DecimalField(
max_digits=12, decimal_places=4, db_column="transportation_cost", null=True, blank=True, verbose_name="运输费用"
)
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
class Meta:
db_table = table_prefix + "proc_inquiry_other_cost"
verbose_name = "杂采询价单-其它成本明细表"
verbose_name_plural = verbose_name
ordering = ("id",)
indexes = [
models.Index(fields=["part_id"]),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return f"{self.inquiry_no_id}-{self.part_id}"
class InquiryProfitCost(models.Model):
"""杂采询价单-税费利润明细表"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="profit_costs",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="PartId", verbose_name="产品料号")
tax_rate = models.DecimalField(
max_digits=12, decimal_places=4, db_column="tax_rate", null=True, blank=True, verbose_name="税率"
)
profit_rate = models.DecimalField(
max_digits=12, decimal_places=4, db_column="profit_rate", null=True, blank=True, verbose_name="利润率"
)
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
class Meta:
db_table = table_prefix + "proc_inquiry_profit_cost"
verbose_name = "杂采询价单-税费利润明细表"
verbose_name_plural = verbose_name
ordering = ("id",)
indexes = [
models.Index(fields=["part_id"]),
]
def __str__(self) -> str: # pragma: no cover - simple repr
return f"{self.inquiry_no_id}-{self.part_id}"
class InquiryRfqItem(models.Model):
"""杂采询价单-上阶物料明细表(采购产品明细)"""
inquiry_no = models.ForeignKey(
Inquiry,
to_field="inquiry_no",
db_column="inquiry_no",
on_delete=models.CASCADE,
related_name="rfq_items",
verbose_name="询价单号",
)
part_id = models.CharField(max_length=50, db_column="Partid", verbose_name="产品料号")
product_name = models.CharField(max_length=20, db_column="product_name", null=True, blank=True, verbose_name="产品名称")
unit = models.CharField(max_length=10, db_column="unit", null=True, blank=True, verbose_name="计量单位")
qty = models.IntegerField(db_column="qty", null=True, blank=True, verbose_name="需求数量数量")
is_bom = models.IntegerField(db_column="IsBom", null=True, blank=True, verbose_name="是否成本结构")
unit_price = models.DecimalField(
max_digits=12, decimal_places=4, db_column="unit_price", null=True, blank=True, verbose_name="标准品单价"
)
total_material_cost = models.DecimalField(
max_digits=12, decimal_places=4, db_column="total_material_cost", null=True, blank=True, verbose_name="材料成本合计"
)
total_processing_cost = models.DecimalField(
max_digits=12,
decimal_places=4,
db_column="total_processing_cost",
null=True,
blank=True,
verbose_name="加工成本合计",
)
total_other_expense = models.DecimalField(
max_digits=12, decimal_places=4, db_column="total_other_expense", null=True, blank=True, verbose_name="其他费用合计"
)
total_opex_amt = models.DecimalField(
max_digits=12, decimal_places=4, db_column="total_opex_amt", null=True, blank=True, verbose_name="管销研费用"
)
profit_rate = models.DecimalField(
max_digits=12, decimal_places=4, db_column="profit_rate", null=True, blank=True, verbose_name="利润"
)
total_price_excl_tax = models.DecimalField(
max_digits=12, decimal_places=4, db_column="total_price_excl_tax", null=True, blank=True, verbose_name="不含税总价"
)
tax_rate = models.DecimalField(
max_digits=12, decimal_places=4, db_column="tax_rate", null=True, blank=True, verbose_name="税费"
)
total_price_incl_tax = models.DecimalField(
max_digits=12, decimal_places=4, db_column="total_price_incl_tax", null=True, blank=True, verbose_name="含税总价"
)
option_json = models.TextField(db_column="OptionJson", null=True, blank=True, verbose_name="可选扩展信息")
create_time = models.DateTimeField(db_column="createtime", auto_now_add=True, verbose_name="创建时间")
create_user = models.CharField(max_length=20, db_column="createuser", null=True, blank=True, verbose_name="创建人")
class Meta:
db_table = table_prefix + "misc_rfq_items"
verbose_name = "杂采询价单-上阶物料明细表"
verbose_name_plural = verbose_name
ordering = ("id",)
indexes = [
models.Index(fields=["part_id"]),
]
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}"