feat:材料添加“阶段”和“重要等级”两列,并且返回按createtime倒序

This commit is contained in:
shijing 2026-03-18 16:39:15 +08:00
parent 0e2239b59f
commit a6456966b6
8 changed files with 105 additions and 5 deletions

View File

@ -36,6 +36,8 @@ REPLACE_TYPE_MAP = {"平替": "alternative", "新研发": "new_development"}
ADVANTAGE_MAP = {"品质": "quality", "成本": "cost"}
# 星级:中文 -> 1/2/3
STAR_LEVEL_MAP = {"": 1, "": 2, "": 3, "1": 1, "2": 2, "3": 3}
STAGE_VALUES = {choice[0] for choice in Material.STAGE_CHOICES}
IMPORTANCE_LEVEL_VALUES = {choice[0] for choice in Material.IMPORTANCE_LEVEL_CHOICES}
def _cell(v: Any) -> str:
@ -88,6 +90,13 @@ def _parse_cost_compare(s: str) -> Optional[Decimal]:
return None
def _parse_choice(s: str, allowed_values: set) -> Optional[str]:
value = _cell(s)
if not value:
return None
return value if value in allowed_values else None
def _first_line(s: str) -> str:
if not s:
return ""
@ -248,6 +257,8 @@ class Command(BaseCommand):
"major_category": major_category,
"material_category": material_category,
"material_subcategory": material_subcategory,
"stage": _parse_choice(get(row, "阶段"), STAGE_VALUES),
"importance_level": _parse_choice(get(row, "重要等级"), IMPORTANCE_LEVEL_VALUES),
"spec": _single_line(get(row, "规格型号")) or None,
"standard": _single_line(get(row, "符合标准")) or None,
"application_scene": application_scene or None,

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.7 on 2026-03-18 07:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('material', '0003_materialcategory_materialsubcategory'),
]
operations = [
migrations.AddField(
model_name='material',
name='importance_level',
field=models.CharField(blank=True, choices=[('核心', '核心'), ('优先', '优先'), ('一般', '一般'), ('观察/受限', '观察/受限')], max_length=20, null=True, verbose_name='重要等级'),
),
migrations.AddField(
model_name='material',
name='stage',
field=models.CharField(blank=True, choices=[('初步交流阶段', '初步交流阶段'), ('完成上会审议', '完成上会审议'), ('选品上样阶段', '选品上样阶段'), ('招标阶段', '招标阶段'), ('项目落地阶段', '项目落地阶段')], max_length=20, null=True, verbose_name='阶段'),
),
]

View File

@ -41,10 +41,27 @@ class Material(models.Model):
('approved', '已审核'),
)
STAGE_CHOICES = (
('初步交流阶段', '初步交流阶段'),
('完成上会审议', '完成上会审议'),
('选品上样阶段', '选品上样阶段'),
('招标阶段', '招标阶段'),
('项目落地阶段', '项目落地阶段'),
)
IMPORTANCE_LEVEL_CHOICES = (
('核心', '核心'),
('优先', '优先'),
('一般', '一般'),
('观察/受限', '观察/受限'),
)
name = models.CharField(max_length=255, verbose_name='材料名称')
major_category = models.CharField(max_length=20, choices=MAJOR_CATEGORY_CHOICES, verbose_name='专业类别')
material_category = models.CharField(max_length=255, verbose_name='材料分类')
material_subcategory = models.CharField(max_length=255, verbose_name='材料子分类')
stage = models.CharField(max_length=20, choices=STAGE_CHOICES, blank=True, null=True, verbose_name='阶段')
importance_level = models.CharField(max_length=20, choices=IMPORTANCE_LEVEL_CHOICES, blank=True, null=True, verbose_name='重要等级')
spec = models.CharField(max_length=255, blank=True, null=True, verbose_name='规格型号')
standard = models.CharField(max_length=255, blank=True, null=True, verbose_name='符合标准')
application_scene = models.JSONField(default=list, blank=True, null=True, verbose_name='应用场景')

View File

@ -21,6 +21,8 @@ class MaterialSerializer(serializers.ModelSerializer):
brand = serializers.CharField(source='factory.brand', read_only=True)
major_category_display = serializers.CharField(source='get_major_category_display', read_only=True)
replace_type_display = serializers.CharField(source='get_replace_type_display', read_only=True)
stage_display = serializers.CharField(source='get_stage_display', read_only=True)
importance_level_display = serializers.CharField(source='get_importance_level_display', read_only=True)
application_scene = JSONListField(
child=serializers.ChoiceField(choices=Material.APPLICATION_SCENE_CHOICES),
required=False,
@ -41,7 +43,8 @@ class MaterialSerializer(serializers.ModelSerializer):
class Meta:
model = Material
fields = ['id', 'name', 'major_category', 'major_category_display',
'material_category', 'material_subcategory', 'spec', 'standard',
'material_category', 'material_subcategory', 'stage', 'stage_display',
'importance_level', 'importance_level_display', 'spec', 'standard',
'application_scene', 'application_scene_display', 'application_desc',
'replace_type', 'replace_type_display', 'advantage', 'advantage_display',
'advantage_desc', 'cost_compare', 'cost_desc', 'cases', 'brochure',
@ -81,11 +84,14 @@ class MaterialListSerializer(serializers.ModelSerializer):
brand = serializers.CharField(source='factory.brand', read_only=True)
major_category_display = serializers.CharField(source='get_major_category_display', read_only=True)
status_display = serializers.CharField(source='get_status_display', read_only=True)
stage_display = serializers.CharField(source='get_stage_display', read_only=True)
importance_level_display = serializers.CharField(source='get_importance_level_display', read_only=True)
class Meta:
model = Material
fields = ['id', 'name', 'major_category', 'major_category_display',
'material_category', 'material_subcategory', 'factory',
'material_category', 'material_subcategory', 'stage', 'stage_display',
'importance_level', 'importance_level_display', 'factory',
'factory_name', 'brand', 'status', 'status_display']

View File

@ -18,7 +18,7 @@ class MaterialViewSet(ModelViewSet):
"""
根据用户角色过滤材料
"""
queryset = Material.objects.all()
queryset = Material.objects.all().order_by('-created_at', '-id')
# 普通用户只能看到自己工厂的材料
if self.request.user.role != 'admin':
@ -190,6 +190,8 @@ class MaterialViewSet(ModelViewSet):
"""
return Response({
'major_category': Material.MAJOR_CATEGORY_CHOICES,
'stage': Material.STAGE_CHOICES,
'importance_level': Material.IMPORTANCE_LEVEL_CHOICES,
'replace_type': Material.REPLACE_TYPE_CHOICES,
'advantage': Material.ADVANTAGE_CHOICES,
'application_scene': Material.APPLICATION_SCENE_CHOICES,

View File

@ -6,6 +6,21 @@ from pathlib import Path
from decouple import config
import os
def _cast_debug(value):
if isinstance(value, bool):
return value
normalized = str(value).strip().lower()
truthy = {'1', 'true', 't', 'yes', 'y', 'on', 'debug', 'dev', 'development'}
falsy = {'0', 'false', 'f', 'no', 'n', 'off', 'release', 'prod', 'production'}
if normalized in truthy:
return True
if normalized in falsy:
return False
raise ValueError(f"Invalid DEBUG value: {value}")
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -16,9 +31,13 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = config('SECRET_KEY', default='django-insecure-change-in-production')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
DEBUG = config('DEBUG', default=True, cast=_cast_debug)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
ALLOWED_HOSTS = config(
'ALLOWED_HOSTS',
default='localhost,127.0.0.1,testserver',
cast=lambda v: [s.strip() for s in v.split(',')],
)
# Application definition

View File

@ -10,6 +10,8 @@
<el-descriptions-item label="专业类别">{{ displayText(material.major_category_display) }}</el-descriptions-item>
<el-descriptions-item label="材料分类">{{ displayText(material.material_category) }}</el-descriptions-item>
<el-descriptions-item label="材料子类">{{ displayText(material.material_subcategory) }}</el-descriptions-item>
<el-descriptions-item label="阶段">{{ displayText(material.stage_display) }}</el-descriptions-item>
<el-descriptions-item label="重要等级">{{ displayText(material.importance_level_display) }}</el-descriptions-item>
<el-descriptions-item label="规格型号">{{ displayText(material.spec) }}</el-descriptions-item>
<el-descriptions-item label="符合标准">{{ displayText(material.standard) }}</el-descriptions-item>
<el-descriptions-item label="应用场景">{{ displayList(material.application_scene_display) }}</el-descriptions-item>

View File

@ -18,6 +18,8 @@
<el-table-column prop="major_category_display" label="专业类别" />
<el-table-column prop="material_category" label="材料分类" />
<el-table-column prop="material_subcategory" label="材料子类" />
<el-table-column prop="stage_display" label="阶段" />
<el-table-column prop="importance_level_display" label="重要等级" />
<el-table-column prop="brand" label="所属工厂" />
<el-table-column prop="status_display" label="状态" width="120" />
<el-table-column label="操作" width="320">
@ -66,6 +68,16 @@
<el-option v-for="item in subcategoryOptions" :key="item.value" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="阶段">
<el-select v-model="form.stage" clearable>
<el-option v-for="item in stageOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="重要等级">
<el-select v-model="form.importance_level" clearable>
<el-option v-for="item in importanceLevelOptions" :key="item[0]" :label="item[1]" :value="item[0]" />
</el-select>
</el-form-item>
<el-form-item label="规格型号">
<el-input v-model="form.spec" />
</el-form-item>
@ -200,6 +212,8 @@ const form = reactive({
major_category: '',
material_category: '',
material_subcategory: '',
stage: '',
importance_level: '',
spec: '',
standard: '',
application_scene: [],
@ -224,6 +238,8 @@ const form = reactive({
})
const majorOptions = ref([])
const stageOptions = ref([])
const importanceLevelOptions = ref([])
const replaceOptions = ref([])
const advantageOptions = ref([])
const sceneOptions = ref([])
@ -252,6 +268,8 @@ const loadMaterials = async () => {
const loadChoices = async () => {
const data = await fetchMaterialChoices()
majorOptions.value = data.major_category
stageOptions.value = data.stage
importanceLevelOptions.value = data.importance_level
replaceOptions.value = data.replace_type
advantageOptions.value = data.advantage
sceneOptions.value = data.application_scene
@ -291,6 +309,8 @@ const resetForm = () => {
major_category: '',
material_category: '',
material_subcategory: '',
stage: '',
importance_level: '',
spec: '',
standard: '',
application_scene: [],