feat:材料添加“阶段”和“重要等级”两列,并且返回按createtime倒序
This commit is contained in:
parent
0e2239b59f
commit
a6456966b6
|
|
@ -36,6 +36,8 @@ REPLACE_TYPE_MAP = {"平替": "alternative", "新研发": "new_development"}
|
||||||
ADVANTAGE_MAP = {"品质": "quality", "成本": "cost"}
|
ADVANTAGE_MAP = {"品质": "quality", "成本": "cost"}
|
||||||
# 星级:中文 -> 1/2/3
|
# 星级:中文 -> 1/2/3
|
||||||
STAR_LEVEL_MAP = {"一": 1, "二": 2, "三": 3, "1": 1, "2": 2, "3": 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:
|
def _cell(v: Any) -> str:
|
||||||
|
|
@ -88,6 +90,13 @@ def _parse_cost_compare(s: str) -> Optional[Decimal]:
|
||||||
return None
|
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:
|
def _first_line(s: str) -> str:
|
||||||
if not s:
|
if not s:
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -248,6 +257,8 @@ class Command(BaseCommand):
|
||||||
"major_category": major_category,
|
"major_category": major_category,
|
||||||
"material_category": material_category,
|
"material_category": material_category,
|
||||||
"material_subcategory": material_subcategory,
|
"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,
|
"spec": _single_line(get(row, "规格型号")) or None,
|
||||||
"standard": _single_line(get(row, "符合标准")) or None,
|
"standard": _single_line(get(row, "符合标准")) or None,
|
||||||
"application_scene": application_scene or None,
|
"application_scene": application_scene or None,
|
||||||
|
|
|
||||||
|
|
@ -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='阶段'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -41,10 +41,27 @@ class Material(models.Model):
|
||||||
('approved', '已审核'),
|
('approved', '已审核'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
STAGE_CHOICES = (
|
||||||
|
('初步交流阶段', '初步交流阶段'),
|
||||||
|
('完成上会审议', '完成上会审议'),
|
||||||
|
('选品上样阶段', '选品上样阶段'),
|
||||||
|
('招标阶段', '招标阶段'),
|
||||||
|
('项目落地阶段', '项目落地阶段'),
|
||||||
|
)
|
||||||
|
|
||||||
|
IMPORTANCE_LEVEL_CHOICES = (
|
||||||
|
('核心', '核心'),
|
||||||
|
('优先', '优先'),
|
||||||
|
('一般', '一般'),
|
||||||
|
('观察/受限', '观察/受限'),
|
||||||
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=255, verbose_name='材料名称')
|
name = models.CharField(max_length=255, verbose_name='材料名称')
|
||||||
major_category = models.CharField(max_length=20, choices=MAJOR_CATEGORY_CHOICES, 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_category = models.CharField(max_length=255, verbose_name='材料分类')
|
||||||
material_subcategory = 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='规格型号')
|
spec = models.CharField(max_length=255, blank=True, null=True, verbose_name='规格型号')
|
||||||
standard = 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='应用场景')
|
application_scene = models.JSONField(default=list, blank=True, null=True, verbose_name='应用场景')
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ class MaterialSerializer(serializers.ModelSerializer):
|
||||||
brand = serializers.CharField(source='factory.brand', read_only=True)
|
brand = serializers.CharField(source='factory.brand', read_only=True)
|
||||||
major_category_display = serializers.CharField(source='get_major_category_display', 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)
|
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(
|
application_scene = JSONListField(
|
||||||
child=serializers.ChoiceField(choices=Material.APPLICATION_SCENE_CHOICES),
|
child=serializers.ChoiceField(choices=Material.APPLICATION_SCENE_CHOICES),
|
||||||
required=False,
|
required=False,
|
||||||
|
|
@ -41,7 +43,8 @@ class MaterialSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Material
|
model = Material
|
||||||
fields = ['id', 'name', 'major_category', 'major_category_display',
|
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',
|
'application_scene', 'application_scene_display', 'application_desc',
|
||||||
'replace_type', 'replace_type_display', 'advantage', 'advantage_display',
|
'replace_type', 'replace_type_display', 'advantage', 'advantage_display',
|
||||||
'advantage_desc', 'cost_compare', 'cost_desc', 'cases', 'brochure',
|
'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)
|
brand = serializers.CharField(source='factory.brand', read_only=True)
|
||||||
major_category_display = serializers.CharField(source='get_major_category_display', 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)
|
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:
|
class Meta:
|
||||||
model = Material
|
model = Material
|
||||||
fields = ['id', 'name', 'major_category', 'major_category_display',
|
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']
|
'factory_name', 'brand', 'status', 'status_display']
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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':
|
if self.request.user.role != 'admin':
|
||||||
|
|
@ -190,6 +190,8 @@ class MaterialViewSet(ModelViewSet):
|
||||||
"""
|
"""
|
||||||
return Response({
|
return Response({
|
||||||
'major_category': Material.MAJOR_CATEGORY_CHOICES,
|
'major_category': Material.MAJOR_CATEGORY_CHOICES,
|
||||||
|
'stage': Material.STAGE_CHOICES,
|
||||||
|
'importance_level': Material.IMPORTANCE_LEVEL_CHOICES,
|
||||||
'replace_type': Material.REPLACE_TYPE_CHOICES,
|
'replace_type': Material.REPLACE_TYPE_CHOICES,
|
||||||
'advantage': Material.ADVANTAGE_CHOICES,
|
'advantage': Material.ADVANTAGE_CHOICES,
|
||||||
'application_scene': Material.APPLICATION_SCENE_CHOICES,
|
'application_scene': Material.APPLICATION_SCENE_CHOICES,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,21 @@ from pathlib import Path
|
||||||
from decouple import config
|
from decouple import config
|
||||||
import os
|
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'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
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')
|
SECRET_KEY = config('SECRET_KEY', default='django-insecure-change-in-production')
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on 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
|
# Application definition
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
<el-descriptions-item label="专业类别">{{ displayText(material.major_category_display) }}</el-descriptions-item>
|
<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_category) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="材料子类">{{ displayText(material.material_subcategory) }}</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.spec) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="符合标准">{{ displayText(material.standard) }}</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>
|
<el-descriptions-item label="应用场景">{{ displayList(material.application_scene_display) }}</el-descriptions-item>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
<el-table-column prop="major_category_display" label="专业类别" />
|
<el-table-column prop="major_category_display" label="专业类别" />
|
||||||
<el-table-column prop="material_category" label="材料分类" />
|
<el-table-column prop="material_category" label="材料分类" />
|
||||||
<el-table-column prop="material_subcategory" 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="brand" label="所属工厂" />
|
||||||
<el-table-column prop="status_display" label="状态" width="120" />
|
<el-table-column prop="status_display" label="状态" width="120" />
|
||||||
<el-table-column label="操作" width="320">
|
<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-option v-for="item in subcategoryOptions" :key="item.value" :label="item.name" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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-form-item label="规格型号">
|
||||||
<el-input v-model="form.spec" />
|
<el-input v-model="form.spec" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -200,6 +212,8 @@ const form = reactive({
|
||||||
major_category: '',
|
major_category: '',
|
||||||
material_category: '',
|
material_category: '',
|
||||||
material_subcategory: '',
|
material_subcategory: '',
|
||||||
|
stage: '',
|
||||||
|
importance_level: '',
|
||||||
spec: '',
|
spec: '',
|
||||||
standard: '',
|
standard: '',
|
||||||
application_scene: [],
|
application_scene: [],
|
||||||
|
|
@ -224,6 +238,8 @@ const form = reactive({
|
||||||
})
|
})
|
||||||
|
|
||||||
const majorOptions = ref([])
|
const majorOptions = ref([])
|
||||||
|
const stageOptions = ref([])
|
||||||
|
const importanceLevelOptions = ref([])
|
||||||
const replaceOptions = ref([])
|
const replaceOptions = ref([])
|
||||||
const advantageOptions = ref([])
|
const advantageOptions = ref([])
|
||||||
const sceneOptions = ref([])
|
const sceneOptions = ref([])
|
||||||
|
|
@ -252,6 +268,8 @@ const loadMaterials = async () => {
|
||||||
const loadChoices = async () => {
|
const loadChoices = async () => {
|
||||||
const data = await fetchMaterialChoices()
|
const data = await fetchMaterialChoices()
|
||||||
majorOptions.value = data.major_category
|
majorOptions.value = data.major_category
|
||||||
|
stageOptions.value = data.stage
|
||||||
|
importanceLevelOptions.value = data.importance_level
|
||||||
replaceOptions.value = data.replace_type
|
replaceOptions.value = data.replace_type
|
||||||
advantageOptions.value = data.advantage
|
advantageOptions.value = data.advantage
|
||||||
sceneOptions.value = data.application_scene
|
sceneOptions.value = data.application_scene
|
||||||
|
|
@ -291,6 +309,8 @@ const resetForm = () => {
|
||||||
major_category: '',
|
major_category: '',
|
||||||
material_category: '',
|
material_category: '',
|
||||||
material_subcategory: '',
|
material_subcategory: '',
|
||||||
|
stage: '',
|
||||||
|
importance_level: '',
|
||||||
spec: '',
|
spec: '',
|
||||||
standard: '',
|
standard: '',
|
||||||
application_scene: [],
|
application_scene: [],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue