Compare commits

..

No commits in common. "master" and "3.0.2025110710" have entirely different histories.

153 changed files with 1744 additions and 3845 deletions

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class AsmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.asm'

View File

@ -1,86 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-18 08:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('pum', '0009_supplieraudit'),
('wf', '0006_auto_20251215_1645'),
]
operations = [
migrations.CreateModel(
name='AssetLog',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('type', models.CharField(help_text='入库/出库', max_length=50, verbose_name='流水类型')),
('start_date', models.DateField(blank=True, null=True, verbose_name='启用日期')),
('items', models.JSONField(blank=True, default=list, null=True, verbose_name='资产明细')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetlog_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('keep_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dept', verbose_name='保管部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assetlog_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetlog_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='AssetCate',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, unique=True, verbose_name='类别名称')),
('code', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='类别编码')),
('default_unit', models.CharField(default='', max_length=20, verbose_name='默认单位')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetcate_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetcate_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Asset',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('card_number', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='卡片编号')),
('name', models.CharField(max_length=100, verbose_name='固定资产名称')),
('specification', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格型号')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='数量')),
('start_date', models.DateField(verbose_name='启用日期')),
('canuse_year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='可用年限')),
('original_value', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='资产原值')),
('storage_location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放地点')),
('state', models.CharField(help_text='在用/闲置', max_length=50, verbose_name='使用状态')),
('unit', models.CharField(default='', max_length=50, verbose_name='计量单位')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('cate', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='asm.assetcate', verbose_name='资产类别')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('keep_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dept', verbose_name='保管部门')),
('keeper', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='保管人')),
('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pum.supplier', verbose_name='供应商')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-24 06:53
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('asm', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='assetlog',
name='keeper',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='保管人'),
),
]

View File

@ -1,39 +0,0 @@
from apps.utils.models import CommonADModel, CommonBDModel, CommonAModel
from django.db import models
class AssetCate(CommonAModel):
name = models.CharField('类别名称',max_length=50, unique=True)
code = models.CharField('类别编码',max_length=50, unique=True, null=True, blank=True)
default_unit = models.CharField('默认单位', max_length=20, default='')
class Asset(CommonAModel):
"""
TN:固定资产台账
"""
card_number = models.CharField('卡片编号',max_length=50, unique=True, blank=True, null=True)
name = models.CharField('固定资产名称',max_length=100)
specification = models.CharField("规格型号", max_length=100, blank=True, null=True)
cate = models.ForeignKey(AssetCate, verbose_name='资产类别', on_delete=models.PROTECT)
quantity = models.PositiveIntegerField("数量", default=1)
start_date = models.DateField("启用日期")
canuse_year = models.PositiveSmallIntegerField("可用年限", null=True, blank=True)
original_value = models.DecimalField('资产原值', max_digits=15, decimal_places=2)
storage_location = models.CharField("存放地点", max_length=100, blank=True, null=True)
keeper = models.ForeignKey('system.user', verbose_name='保管人', on_delete=models.SET_NULL, null=True, blank=True)
keep_dept = models.ForeignKey('system.dept', verbose_name='保管部门', on_delete=models.SET_NULL, null=True, blank=True)
state = models.CharField("使用状态", max_length=50, help_text="在用/闲置")
supplier = models.ForeignKey('pum.supplier', verbose_name='供应商', on_delete=models.SET_NULL, null=True, blank=True)
unit = models.CharField("计量单位", max_length=50, default='')
note = models.TextField("备注", blank=True, null=True)
class AssetLog(CommonADModel):
"""
TN:资产操作日志
"""
type = models.CharField("流水类型", max_length=50, help_text="入库/出库")
keep_dept = models.ForeignKey('system.dept', verbose_name='保管部门', on_delete=models.SET_NULL, null=True, blank=True)
keeper = models.ForeignKey('system.user', verbose_name='保管人', on_delete=models.SET_NULL, null=True, blank=True)
start_date = models.DateField("启用日期", null=True, blank=True)
items = models.JSONField(verbose_name='资产明细', default=list, null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.PROTECT, related_name='assetlog_ticket', null=True, blank=True)

View File

@ -1,58 +0,0 @@
from apps.asm.models import Asset, AssetLog, AssetCate
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS
from rest_framework import serializers
from apps.system.models import User
from apps.wf.serializers import TicketSimpleSerializer
class AssetCateSerializer(CustomModelSerializer):
class Meta:
model = AssetCate
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetSerializer(CustomModelSerializer):
keep_dept_name = serializers.CharField(source="keep_dept.name", read_only=True)
keeper_name = serializers.CharField(source="keeper.name", read_only=True)
cate_name = serializers.CharField(source="cate.name", read_only=True)
class Meta:
model = Asset
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetCreateSerializer(CustomModelSerializer):
class Meta:
model = Asset
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetLogSerializer(CustomModelSerializer):
keeper_name = serializers.CharField(source="keeper.name", read_only=True)
keep_dept_name = serializers.CharField(source="keep_dept.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = AssetLog
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS_DEPT
class AssetLogCreateSerializer(CustomModelSerializer):
class Meta:
model = AssetLog
fields = ["keep_dept", "start_date", "items", "keeper", "type"]
extra_kwargs = {"start_date": {"required": True}, "keep_dept": {"required": True}}
def to_internal_value(self, data):
for item in data.get("items", []):
if item.get("keeper", None) is None:
item["keeper"] = data.get("keeper", None)
if item.get("keep_dept", None) is None:
item["keep_dept"] = data["keep_dept"]
if item.get("start_date", None) is None:
item["start_date"] = data["start_date"]
item["state"] = "在用"
return super().to_internal_value(data)
def validate_items(self, value):
AssetCreateSerializer(data=value, many=True).is_valid(raise_exception=True)
return value

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,13 +0,0 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.asm.views import AssetViewSet, AssetLogViewSet, AssetCateViewSet
API_BASE_URL = 'api/asm/'
router = DefaultRouter()
router.register('assetcate', AssetCateViewSet, basename='assetcate')
router.register('asset', AssetViewSet, basename='asset')
router.register('assetlog', AssetLogViewSet, basename='assetlog')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,71 +0,0 @@
from apps.wf.mixins import TicketMixin
from apps.utils.viewsets import CustomModelViewSet
from apps.asm.models import Asset, AssetLog, AssetCate
from rest_framework.exceptions import ParseError
from apps.asm.serializers import AssetSerializer, AssetLogSerializer, AssetCateSerializer, AssetLogCreateSerializer
from apps.wf.models import Ticket
from apps.pum.models import Supplier
class AssetCateViewSet(CustomModelViewSet):
"""
list: 固定资产分类
固定资产分类
"""
queryset = AssetCate.objects.all()
serializer_class = AssetCateSerializer
search_fields = ['name', 'code']
class AssetViewSet(CustomModelViewSet):
"""
list: 固定资产台账
固定资产台账
"""
queryset = Asset.objects.all()
serializer_class = AssetSerializer
search_fields = ['name', 'card_number', 'specification']
filterset_fields = ['keep_dept', 'state']
class AssetLogViewSet(TicketMixin, CustomModelViewSet):
"""
list: 固定资产操作
固定资产操作
"""
queryset = AssetLog.objects.all()
serializer_class = AssetLogSerializer
create_serializer_class = AssetLogCreateSerializer
def add_info_for_list(self, data):
supplierIds = []
cateIds = []
for dataitem in data:
supplierIds.extend([item["supplier"] for item in dataitem["items"]])
cateIds.extend([item["cate"] for item in dataitem["items"]])
supplier_dict = dict(Supplier.objects.filter(id__in=supplierIds).values_list("id", "name"))
assetcate_dict = dict(AssetCate.objects.filter(id__in=cateIds).values_list("id", "name"))
for dataitem in data:
for item in dataitem["items"]:
item["supplier_name"] = supplier_dict.get(item["supplier"], None)
item["cate_name"] = assetcate_dict.get(item["cate"], None)
return data
def get_workflow_key(self, instance:AssetLog):
if instance.type == "入库":
return "wf_assetlogin"
raise ParseError("不支持的流程类型")
def gen_other_ticket_data(self, instance:AssetLog):
return {"keep_dept_name": instance.keep_dept.name}
@staticmethod
def apply(ticket: Ticket, transition, new_ticket_data: dict):
assetlog:AssetLog = ticket.assetlog_ticket
items = assetlog.items
sr = AssetSerializer(data=items, many=True)
sr.is_valid(raise_exception=True)
sr.save(create_by=ticket.create_by)

View File

@ -4,7 +4,6 @@ from jinja2 import Template
from apps.bi.models import Dataset
import concurrent
from apps.utils.sql import execute_raw_sql, format_sqldata
from apps.utils.tools import MyJSONEncoder
forbidden_keywords = ["UPDATE", "DELETE", "DROP", "TRUNCATE", "INSERT", "CREATE", "ALTER", "GRANT", "REVOKE", "EXEC", "EXECUTE"]
@ -26,7 +25,7 @@ def format_json_with_placeholders(json_str, **kwargs):
# 遍历关键字参数,将占位符替换为对应的值
for key, value in kwargs.items():
formatted_json = formatted_json.replace("{" + key + "}", json.dumps(value, cls=MyJSONEncoder))
formatted_json = formatted_json.replace("{" + key + "}", json.dumps(value))
# 格式化后的字符串依然是 JSON 字符串,没有使用 json.loads()
return formatted_json

View File

@ -146,9 +146,7 @@ class DatasetRecordViewSet(ListModelMixin, CustomGenericViewSet):
queryset = DatasetRecord.objects.all()
serializer_class = DatasetRecordSerializer
filterset_fields = {
"timex": ["year", "month", "day"],
"dataset": ["exact"],
"dataset__code": ["exact"]
"timex": ["year", "month", "day"]
}

View File

@ -670,6 +670,7 @@ class TestViewSet(CustomGenericViewSet):
Handover.objects.all().delete()
Ftest.objects.all().delete()
FtestWork.objects.all().delete()
Material.objects.get_queryset(all=True).update(count=0, count_mb=0, count_wm=0)
BatchSt.objects.all().delete()
Ticket.objects.get_queryset(all=True).delete()
return Response()

View File

@ -11,7 +11,7 @@ router.register('question', QuestionViewSet, basename='question')
router.register('paper', PaperViewSet, basename='paper')
router.register('exam', ExamViewSet, basename='exam')
router.register('examrecord', ExamRecordViewSet, basename='examrecord')
router.register('training', TrainRecordViewSet, basename='training')
router.register('training', TrainRecordViewSet, basename='examrecord')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,40 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-15 07:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0005_workflow_cate'),
('em', '0022_equipment_cd_req_addr'),
]
operations = [
migrations.CreateModel(
name='Repair',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('fault_description', models.TextField(verbose_name='故障描述')),
('fault_cate', models.TextField(blank=True, null=True, verbose_name='故障类别')),
('repair_start_time', models.DateTimeField(blank=True, null=True, verbose_name='维修开始时间')),
('repair_duration', models.DecimalField(decimal_places=1, default=0.0, max_digits=5, verbose_name='维修工时(小时)')),
('repair_description', models.TextField(blank=True, null=True, verbose_name='维修描述')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='repair_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='repaire_equip', to='em.equipment', verbose_name='关联设备')),
('repair_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='维修人')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='repair_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='repair_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,5 +1,5 @@
from django.db import models
from apps.utils.models import CommonBModel, CommonADModel, CommonBDModel
from apps.utils.models import CommonBModel, CommonADModel
from apps.system.models import User
# Create your models here.
@ -156,20 +156,3 @@ class EInspect(CommonADModel):
inspect_time = models.DateTimeField("巡检时间")
result = models.CharField(max_length=20, choices=INSPECT_RESULTS, verbose_name="巡检结果", help_text=str(INSPECT_RESULTS))
note = models.TextField("备注", null=True, blank=True)
class Repair(CommonADModel):
"""
TN:维修申请
"""
equipment = models.ForeignKey(Equipment, verbose_name="关联设备", on_delete=models.CASCADE, related_name="repaire_equip")
fault_description = models.TextField("故障描述")
fault_cate = models.TextField("故障类别", null=True, blank=True)
repair_user = models.ForeignKey("system.user", verbose_name="维修人", on_delete=models.CASCADE, null=True, blank=True)
repair_start_time = models.DateTimeField("维修开始时间", null=True, blank=True)
repair_duration = models.DecimalField("维修工时(小时)", max_digits=5, decimal_places=1, default=0.0)
repair_description = models.TextField("维修描述", null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.PROTECT, related_name='repair_ticket', null=True, blank=True)

View File

@ -1,11 +1,10 @@
from apps.utils.serializers import CustomModelSerializer
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate, Repair
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate
from apps.system.models import Dept
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import ParseError
from apps.wf.serializers import TicketSimpleSerializer
class EcateSerializer(CustomModelSerializer):
@ -76,20 +75,4 @@ class EInspectSerializer(CustomModelSerializer):
class CdSerializer(serializers.Serializer):
method = serializers.CharField(label="方法名")
class RepairCreateSerializer(CustomModelSerializer):
class Meta:
model = Repair
fields = ["id", "equipment", "fault_description", "fault_cate"]
class RepairListSerializer(CustomModelSerializer):
equipment_fullname = serializers.StringRelatedField(source="equipment", read_only=True)
repair_user_name = serializers.CharField(source="repair_user.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = Repair
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS
method = serializers.CharField(label="方法名")

View File

@ -1,6 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView, RepairViewSet
from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView
API_BASE_URL = 'api/em/'
HTML_BASE_URL = 'dhtml/em/'
@ -10,7 +10,6 @@ router.register('equipment', EquipmentViewSet, basename='equipment')
router.register('echeckrecord', EcheckRecordViewSet, basename='echeckrecord')
router.register('einspect', EInspectViewSet, basename='einspect')
router.register('ecate', EcateViewSet, basename='ecate')
router.register('repair', RepairViewSet, basename='repair')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
path(API_BASE_URL + 'cd/', CdView.as_view()),

View File

@ -2,10 +2,9 @@ from django.shortcuts import render
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.utils import timezone
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate, Repair
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from apps.em.serializers import (EquipmentSerializer, EcheckRecordSerializer,
EInspectSerializer, EcateSerializer, CdSerializer, RepairCreateSerializer, RepairListSerializer)
from apps.em.serializers import EquipmentSerializer, EcheckRecordSerializer, EInspectSerializer, EcateSerializer, CdSerializer
from apps.em.filters import EquipFilterSet
from rest_framework.exceptions import ParseError
from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin
@ -21,9 +20,6 @@ from apps.enp.services import get_last_envdata
from rest_framework.views import APIView
from apps.utils.mixins import MyLoggingMixin
import importlib
from apps.wf.mixins import TicketMixin
from apps.wf.models import Ticket
from apps.system.models import User
# Create your views here.
@ -192,49 +188,4 @@ class CdView(MyLoggingMixin, APIView):
module, func = m.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
return Response(f(*args))
class RepairViewSet(TicketMixin, CustomModelViewSet):
"""
list:维修申请
维修申请
"""
queryset = Repair.objects.all()
serializer_class = RepairCreateSerializer
list_serializer_class = RepairListSerializer
retrieve_serializer_class = RepairListSerializer
select_related_fields = ["equipment", "repair_user", "create_by"]
filterset_fields = ["equipment", "repair_user", "ticket__state__type"]
search_fields = ["equipment__name", "equipment__number", "fault_description", "fault_cate", "repair_description"]
workflow_key = "wf_repair"
def gen_other_ticket_data(self, instance):
return {"equipment_fullname": str(instance.equipment)}
@staticmethod
def assgin(ticket: Ticket, transition, new_ticket_data: dict):
repair_user = new_ticket_data.get("repair_user", None)
fault_cate = new_ticket_data.get("fault_cate", None)
if repair_user and fault_cate:
repair = Repair.objects.get(ticket=ticket)
repair.repair_user = User.objects.get(id=repair_user)
repair.fault_cate = fault_cate
repair.save()
else:
raise ParseError("请分派维修人")
@staticmethod
def repair_completed(ticket: Ticket, transition, new_ticket_data: dict):
repair_start_time = new_ticket_data.get("repair_start_time", None)
repair_duration = new_ticket_data.get("repair_duration", None)
repair_description = new_ticket_data.get("repair_description", None)
if repair_start_time and repair_duration:
repair = Repair.objects.get(ticket=ticket)
repair.repair_start_time = repair_start_time
repair.repair_duration = repair_duration
repair.repair_description = repair_description
repair.save()
else:
raise ParseError("请填写维修开始时间和维修工时")
return Response(f(*args))

View File

@ -35,11 +35,7 @@ def translate_eval_formula(exp_str: str, year: int, month: int, day: int, hour:
for match in matches:
mpst = MpointStat.objects.filter(mpoint__code=match, type="hour", year=year, month=month, day=day, hour=hour).first()
if mpst is None:
try:
mpoint = Mpoint.objects.get(code=match)
except Exception as e:
myLogger.error(f"找不到该测点: exp_str: {exp_str} code: {match} {e}")
raise
mpoint = Mpoint.objects.get(code=match)
mpst, _ = MpointStat.objects.get_or_create(mpoint=mpoint, type="hour", year=year, month=month, day=day, hour=hour, defaults={"val": 0})
myLogger.error(f"找不到该测点的时间线数据: {match}, {year}, {month}, {day}, {hour}, 赋予0值")
if mpst:

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-24 08:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0005_workflow_cate'),
('hrm', '0020_auto_20251106_0942'),
]
operations = [
migrations.AlterField(
model_name='resignation',
name='ticket',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resignation_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-12 08:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('wf', '0005_workflow_cate'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0021_alter_resignation_ticket'),
]
operations = [
migrations.CreateModel(
name='EmpNeed',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('post_need', models.TextField(verbose_name='需求岗位')),
('count_need', models.PositiveIntegerField(verbose_name='需求人数')),
('salary', models.PositiveIntegerField(verbose_name='工资报酬')),
('arrival_date', models.DateField(verbose_name='到岗日期')),
('reason', models.TextField(help_text='新增人员/该岗原人员离职或辞职或辞退需补充/其他原因', verbose_name='申请理由')),
('duty', models.TextField(verbose_name='岗位人员职责描述')),
('gender', models.CharField(help_text='男/女/不限', max_length=10, verbose_name='性别要求')),
('education', models.CharField(help_text='高中/大专/不限', max_length=50, verbose_name='学历要求')),
('professional_requirement', models.TextField(blank=True, null=True, verbose_name='相关专业及技能要求')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empneed_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('dept_need', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dept', verbose_name='需求部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='empneed_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empneed_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,57 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-22 05:40
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('wf', '0006_auto_20251215_1645'),
('hrm', '0022_empneed'),
]
operations = [
migrations.CreateModel(
name='EmpPersonInfo',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=20, verbose_name='姓名')),
('gender', models.CharField(default='', max_length=10, verbose_name='性别')),
('IDcard', models.CharField(max_length=20, verbose_name='身份证号')),
('phone', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='手机号')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='emppersoninfo_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='emppersoninfo_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='EmpJoin',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('person', models.JSONField(default=list, verbose_name='人员信息')),
('join_date', models.DateField(verbose_name='入职日期')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empjoin_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('dept_need', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dept', verbose_name='入职部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='empjoin_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empjoin_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-23 03:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0023_empjoin_emppersoninfo'),
]
operations = [
migrations.AddField(
model_name='emppersoninfo',
name='post',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='岗位'),
),
]

View File

@ -1,40 +0,0 @@
# Generated by Django 3.2.12 on 2026-01-07 01:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0006_auto_20251215_1645'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0024_emppersoninfo_post'),
]
operations = [
migrations.CreateModel(
name='Leave',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('start_date', models.DateTimeField(verbose_name='开始日期')),
('end_date', models.DateTimeField(verbose_name='结束日期')),
('leave_type', models.PositiveSmallIntegerField(blank=True, choices=[(10, '事假'), (20, '病假'), (30, '婚假'), (40, '丧假'), (50, '公假'), (60, '工伤'), (70, '产假'), (80, '护理假'), (90, '其他')], null=True, verbose_name='请假类型')),
('reason', models.TextField(verbose_name='请假事由')),
('hour', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='请假时长')),
('file', models.TextField(blank=True, null=True, verbose_name='证明')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='员工')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -4,9 +4,8 @@ from apps.system.models import Post, User
from apps.utils.models import BaseModel, CommonADModel, CommonAModel, CommonBModel
from django.utils import timezone
from datetime import timedelta
from django.core.validators import RegexValidator
PHONE_VALIDATOR = RegexValidator(r'^1[3456789]\d{9}$', '手机号码格式不正确')
class Employee(CommonBModel):
"""
TN:员工信息
@ -203,71 +202,4 @@ class Resignation(CommonADModel):
end_date = models.DateField('离职日期')
reason = models.TextField('离职原因')
handle_date = models.DateField('办理离职交接日期', null=True, blank=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True, related_name='resignation_ticket')
class EmpNeed(CommonADModel):
"""
TN:员工需求
"""
dept_need = models.ForeignKey('system.Dept', verbose_name='需求部门', on_delete=models.CASCADE)
post_need = models.TextField("需求岗位")
count_need = models.PositiveIntegerField('需求人数')
salary = models.PositiveIntegerField("工资报酬")
arrival_date = models.DateField('到岗日期')
reason = models.TextField('申请理由', help_text="新增人员/该岗原人员离职或辞职或辞退需补充/其他原因")
duty = models.TextField("岗位人员职责描述")
gender = models.CharField('性别要求', max_length=10, help_text="男/女/不限")
education = models.CharField('学历要求', max_length=50, help_text='高中/大专/不限')
professional_requirement = models.TextField(verbose_name="相关专业及技能要求", null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='empneed_ticket', null=True, blank=True)
class EmpJoin(CommonADModel):
"""
TN:员工入职
"""
dept_need = models.ForeignKey('system.Dept', verbose_name='入职部门', on_delete=models.CASCADE)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='empjoin_ticket', null=True, blank=True)
person = models.JSONField('人员信息', default=list)
join_date = models.DateField('入职日期')
class EmpPersonInfo(CommonADModel):
"""
TN:入职人员信息
"""
name = models.CharField('姓名', max_length=20)
gender = models.CharField('性别', max_length=10, default='')
IDcard = models.CharField('身份证号', max_length=20)
phone = models.CharField('手机号', max_length=20,validators=[PHONE_VALIDATOR], null=True, blank=True)
post = models.CharField('岗位', max_length=20, null=True, blank=True)
note = models.TextField('备注', null=True, blank=True)
class Leave(CommonADModel):
"""
TN:员工请假
"""
E_TYPE_CHOISE = (
(10, '事假'),
(20, '病假'),
(30, '婚假'),
(40, '丧假'),
(50, '公假'),
(60, '工伤'),
(70, '产假'),
(80, '护理假'),
(90, '其他'),
)
employee = models.ForeignKey(Employee, verbose_name='员工', on_delete=models.CASCADE)
start_date = models.DateTimeField('开始日期')
end_date = models.DateTimeField('结束日期')
leave_type = models.PositiveSmallIntegerField('请假类型', choices=E_TYPE_CHOISE, null=True, blank=True)
reason = models.TextField('请假事由')
hour = models.PositiveSmallIntegerField('请假时长', null=True, blank=True)
file = models.TextField('证明', null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='leave_ticket', null=True, blank=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -9,7 +9,7 @@ from django.utils import timezone
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS
from apps.hrm.models import (Certificate, ClockRecord, Employee,
NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, Leave)
NotWorkRemark, Attendance, Resignation)
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
from django.db import transaction
from django.core.cache import cache
@ -17,7 +17,6 @@ from apps.utils.tools import check_id_number_strict, get_info_from_id
from rest_framework.exceptions import ParseError
from django.conf import settings
import datetime
from apps.wf.serializers import TicketSimpleSerializer
class EmployeeShortSerializer(CustomModelSerializer):
@ -320,60 +319,6 @@ class AttendanceSerializer(CustomModelSerializer):
class ResignationSerializer(CustomModelSerializer):
belong_dept_name = serializers.CharField(source="employee.belong_dept.name", read_only=True)
post_name = serializers.CharField(source="employee.post.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
employee_name = serializers.CharField(source="employee.name", read_only=True)
employee_id_number = serializers.CharField(source="employee.id_number", read_only=True)
class Meta:
model = Resignation
fields = '__all__'
def update(self, instance, validated_data):
validated_data.pop('employee')
return super().update(instance, validated_data)
def create(self, validated_data):
employee:Employee = validated_data['employee']
if employee.job_state == Employee.JOB_ON:
pass
else:
raise ParseError('员工不在职,不可创建申请')
if Resignation.objects.filter(employee=employee).exists():
raise ParseError('该员工已存在离职申请')
return super().create(validated_data)
class EmpNeedSerializer(CustomModelSerializer):
dept_need_name = serializers.CharField(source='dept_need.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = EmpNeed
fields = '__all__'
class EmpJoinSerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
dept_name = serializers.CharField(source='dept_need.name', read_only=True)
class Meta:
model = EmpJoin
fields = '__all__'
class EmpPersonInfoSerializer(CustomModelSerializer):
class Meta:
model = EmpPersonInfo
fields = (
'name',
'gender',
'IDcard',
'phone',
'post',
'note',
)
class LeaveSerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
employee_name = serializers.CharField(source='employee.name', read_only=True)
post_name = serializers.CharField(source="employee.post.name", read_only=True)
belong_dept_name = serializers.CharField(source='employee.belong_dept.name', read_only=True)
class Meta:
model = Leave
fields = '__all__'
fields = '__all__'

View File

@ -498,3 +498,9 @@ class HrmService:
def bind_resignation(ticket: Ticket, transition: Transition, new_ticket_data: dict):
ins = Resignation.objects.get(id=new_ticket_data['rpj'])
if ins.ticket and ins.ticket.id != ticket.id:
raise ParseError('重复创建工单')
ticket.create_by = ins.create_by
ticket.save()

View File

@ -1,5 +1,5 @@
from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet, EmpNeedViewSet,
AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, LeaveViewSet)
from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet,
AttendanceViewSet, ResignationViewSet)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@ -14,9 +14,6 @@ router.register('not_work_remark', NotWorkRemarkViewSet,
router.register('certificate', CertificateViewSet, basename='certificate')
router.register('attendance', AttendanceViewSet, basename='attendance')
router.register('resignation', ResignationViewSet, basename='resignation')
router.register('empneed', EmpNeedViewSet, basename='empneed')
router.register('empjoin', EmpJoinViewSet, basename='empjoin')
router.register('leave', LeaveViewSet, basename='leave')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -12,24 +12,23 @@ from rest_framework.response import Response
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
from apps.hrm.filters import (CertificateFilterSet, ClockRecordFilterSet, EmployeeFilterSet,
NotWorkRemarkFilterSet)
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, Leave
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer,
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer,
ClockRecordListSerializer,
EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer,
EmployeeNotWorkRemarkSerializer,EmpPersonInfoSerializer,
EmployeeNotWorkRemarkSerializer,
EmployeeSerializer,
ClockRecordSimpleSerializer, ClockRecordCreateSerializer,
NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer,
ResignationSerializer, EmpNeedSerializer, LeaveSerializer)
ResignationSerializer)
from apps.hrm.services import HrmService
from apps.third.dahua import dhClient
from apps.third.tapis import dhapis
from apps.utils.export import export_excel
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, RetrieveModelMixin
from apps.wf.models import Ticket
from apps.wf.mixins import TicketMixin
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin
epTypeOptions = {'employee': '正式员工', 'remployee': '相关方',
'visitor': '访客', 'driver': '货车司机'}
epStateOptions = {10: '在职', 20: '离职', 30: '退休'}
@ -394,78 +393,9 @@ class CertificateViewSet(CustomModelViewSet):
ins.get_state(need_update=True)
class ResignationViewSet(TicketMixin, EuModelViewSet):
class ResignationViewSet(CustomListModelMixin, BulkCreateModelMixin, CustomGenericViewSet):
perms_map = {"get": "*", "post": "resignation.create"}
select_related_fields = ['employee', 'employee__belong_dept', 'employee__post']
queryset = Resignation.objects.all()
serializer_class = ResignationSerializer
search_fields = ["employee__name"]
workflow_key = "wf_resignation"
def gen_other_ticket_data(self, instance):
return {"employee_name": instance.employee.name}
@staticmethod
def update_handle_date(ticket: Ticket, transition, new_ticket_data: dict):
handle_date = new_ticket_data.get("handle_date", None)
if handle_date:
resignation = Resignation.objects.get(ticket=ticket)
resignation.handle_date = handle_date
resignation.save()
else:
raise ParseError("请填写办理离职的交接日期")
@staticmethod
def make_job_off(ticket: Ticket, transition, new_ticket_data: dict):
resignation = Resignation.objects.get(ticket=ticket)
emp = resignation.employee
emp.job_state = Employee.JOB_OFF
emp.save(update_fields=['job_state'])
user = emp.user
user.is_deleted = True
user.save(update_fields=['is_deleted'])
class EmpNeedViewSet(TicketMixin, EuModelViewSet):
queryset = EmpNeed.objects.all()
serializer_class = EmpNeedSerializer
filterset_fields = ['dept_need']
search_fields = ["dept_need__name", "post_need"]
workflow_key = "wf_empneed"
def gen_other_ticket_data(self, instance):
return {"post_need": instance.post_need}
class EmpJoinViewSet(TicketMixin, EuModelViewSet):
queryset = EmpJoin.objects.all()
serializer_class = EmpJoinSerializer
workflow_key = "wf_empjoin"
def gen_other_ticket_data(self, instance):
return {"dept_name": instance.dept_need.name if instance.dept_need else None}
@staticmethod
def approve(ticket: Ticket, transition, new_ticket_data: dict):
person = new_ticket_data.get("person", None)
EmpJoin.objects.filter(ticket=ticket).update(person=person)
if not person:
raise ParseError("请选择人员")
serializer = EmpPersonInfoSerializer(data=person, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
class LeaveViewSet(TicketMixin, EuModelViewSet):
select_related_fields = [
'employee',
'employee__belong_dept',
'ticket',
]
queryset = Leave.objects.all()
serializer_class = LeaveSerializer
filterset_fields = ['leave_type', 'employee__belong_dept']
search_fields = ["employee__name", "leave_type"]
workflow_key = "wf_leave"
def gen_other_ticket_data(self, instance):
return {"hour": instance.hour if instance.hour else None}
search_fields = ["employee__name"]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-17 06:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inm', '0037_alter_materialbatch_material'),
]
operations = [
migrations.AddField(
model_name='mioitem',
name='count_send',
field=models.DecimalField(blank=True, decimal_places=3, max_digits=12, null=True, verbose_name='发出数量'),
),
]

View File

@ -161,7 +161,6 @@ class MIOItem(BaseModel):
batch = models.TextField('批次号', db_index=True)
unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
count = models.DecimalField('出入数量', max_digits=12, decimal_places=3)
count_send = models.DecimalField('发出数量', max_digits=12, decimal_places=3, null=True, blank=True)
count_tested = models.PositiveIntegerField('已检数', null=True, blank=True)
test_date = models.DateField('检验日期', null=True, blank=True)
test_user = models.ForeignKey(

View File

@ -126,7 +126,7 @@ class MIOItemCreateSerializer(CustomModelSerializer):
class Meta:
model = MIOItem
fields = ['mio', 'warehouse', 'material',
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index", "count_send"]
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index"]
extra_kwargs = {
'mio': {'required': True}, 'warehouse': {'required': False},
'material': {'required': False}, 'batch': {'required': False, "allow_null": True, "allow_blank": True}}
@ -156,8 +156,6 @@ class MIOItemCreateSerializer(CustomModelSerializer):
batch = validated_data.get("batch", None)
if not batch:
batch = ""
if batch != '' and len(batch) < 5:
raise ParseError('批次号格式错误')
if material.is_hidden:
raise ParseError('隐式物料不可出入库')
if mio.type in [MIO.MIO_TYPE_RETURN_IN, MIO.MIO_TYPE_BORROW_OUT]:
@ -214,18 +212,11 @@ class MIOItemCreateSerializer(CustomModelSerializer):
mio_type = mio.type
if mio_type != "pur_in" and mio_type != "other_in":
wprIds = [i["wpr"].id for i in mioitemw]
if instance.mb:
mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct())
if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次')
elif instance.wm:
wm_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("wm__id", flat=True).distinct())
if len(wm_ids) == 1 and wm_ids[0] == instance.wm.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次')
mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct())
if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次')
for item in mioitemw:
if item.get("wpr", None) is None and mio_type != "pur_in" and mio_type != "other_in":
raise ParseError(f'{item["number"]}_请提供产品明细ID')
@ -247,15 +238,6 @@ class MIOItemAListSerializer(CustomModelSerializer):
read_only_fields = EXCLUDE_FIELDS_BASE
class MIOItemListSimpleSerializer(CustomModelSerializer):
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
material_name = serializers.StringRelatedField(
source='material', read_only=True)
class Meta:
model = MIOItem
fields = ["id", "mio", "material", "warehouse", "material_name", "warehouse_name", "batch", "count", "test_date", "count_notok"]
class MIOItemSerializer(CustomModelSerializer):
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
material_ = MaterialSerializer(source='material', read_only=True)

View File

@ -37,12 +37,11 @@ def do_out(item: MIOItem, is_reverse: bool = False):
is_zhj = False # 是否组合件领料
if mias.exists():
is_zhj = True
for itema in mias:
material = itema.material
batch = itema.batch
rate = itema.rate
new_count = rate * item.count
action_list.append([material, batch, new_count, None, None])
mias_list = list(mias.values_list('material', 'batch', 'rate'))
for i in range(len(mias_list)):
material, batch, rate = mias_list[i]
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count, None])
else:
action_list = [[item.material, item.batch, item.count, defect]]
@ -89,14 +88,14 @@ def do_out(item: MIOItem, is_reverse: bool = False):
defect=defect
)
except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e:
raise ParseError(f"{str(xmaterial)}批次{xbatch}错误!{e}")
raise ParseError(f"批次错误!{e}")
mb.count = mb.count - xcount
if mb.count < 0:
raise ParseError(f"{mb.batch}-{str(mb.material)}-批次库存不足,操作失败")
else:
mb.save()
if xmaterial.into_wm:
if material.into_wm:
# 领到车间库存(或工段)
wm, new_create = WMaterial.objects.get_or_create(
batch=xbatch, material=xmaterial,
@ -157,14 +156,13 @@ def do_in(item: MIOItem):
if mias.exists():
is_zhj = True
for itema in mias:
material = itema.material
batch = itema.batch
rate = itema.rate
new_count = rate * item.count
action_list.append([material, batch, new_count, None, None])
mias_list = mias.values_list('material', 'batch', 'rate')
for i in mias_list:
material, batch, rate = i
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count, None])
else:
action_list = [[item.material, item.batch, item.count, defect, item.wm]]
action_list = [[item.material, item.batch, item.count, defect]]
production_dept = None
@ -172,31 +170,28 @@ def do_in(item: MIOItem):
if is_zhj:
xbatchs = [item.batch]
for al in action_list:
xmaterial, xbatch, xcount, defect, xwm = al
xmaterial, xbatch, xcount, defect = al
if xcount <= 0:
raise ParseError("存在非正数!")
xbatchs.append(xbatch)
if xmaterial.into_wm:
if xwm:
wm = xwm
if material.into_wm:
wm_qs = WMaterial.objects.filter(
batch=xbatch,
material=xmaterial,
belong_dept=belong_dept,
mgroup=mgroup,
defect=defect,
state=WMaterial.WM_OK)
count_x = wm_qs.count()
if count_x == 1:
wm = wm_qs.first()
elif count_x == 0:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
else:
wm_qs = WMaterial.objects.filter(
batch=xbatch,
material=xmaterial,
belong_dept=belong_dept,
mgroup=mgroup,
defect=defect,
state=WMaterial.WM_OK)
count_x = wm_qs.count()
if count_x == 1:
wm = wm_qs.first()
elif count_x == 0:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
else:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
# 扣减车间库存
new_count = wm.count - xcount
@ -277,7 +272,7 @@ class InmService:
"""
更新库存, 支持反向操作
"""
if is_reverse is False and not MIOItem.objects.filter(mio=instance).exists():
if not MIOItem.objects.filter(mio=instance).exists():
raise ParseError("出入库记录缺失明细,无法操作")
if instance.type == MIO.MIO_TYPE_PUR_IN: # 需要更新订单
@ -352,6 +347,8 @@ class InmService:
out = -1
"""
mioitems = MIOItem.objects.filter(mio=instance)
if not mioitems.exists():
raise ParseError("未填写物料明细")
for i in mioitems:
cls.update_mb_item(i, in_or_out, 'count')
@ -373,10 +370,7 @@ class InmService:
ddict["material_ofrom"] = i.material
if field == "count":
if i.test_date is not None:
count_change = i.count - i.count_notok
else:
count_change = i.count
count_change = i.count - i.count_notok
elif field == "count_notok":
count_change = getattr(i, field)
else:

View File

@ -122,13 +122,7 @@ def daoru_mioitem_test(path:str, mioitem:MIOItem):
FtestItem.objects.bulk_create(ftestitems)
else:
break
mioitem.test_date = test_date
mioitem.test_user = test_user
mioitem.count = MIOItemw.objects.filter(mioitem=mioitem).count()
mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
mioitem.save()
def daoru_mioitems(path:str, mio:MIO):
from apps.utils.snowflake import idWorker
@ -142,30 +136,19 @@ def daoru_mioitems(path:str, mio:MIO):
mioitems = []
ind = 2
while sheet[f"a{ind}"].value or sheet[f"b{ind}"].value:
batch = sheet[f"e{ind}"].value
while sheet[f"a{ind}"].value:
batch = sheet[f"b{ind}"].value
material_number = sheet[f"a{ind}"].value
if material_number:
try:
material = Material.objects.get(number=material_number)
except Exception as e:
raise ParseError(f"未找到物料:{material_number} {e}")
else:
material_name = sheet[f"b{ind}"].value
material_model = sheet[f"d{ind}"].value
material_specification = sheet[f"c{ind}"].value
try:
material = Material.objects.get(name=material_name, model=material_model, specification=material_specification)
except Exception as e:
raise ParseError(f"未找到物料:{material_name} {material_model} {material_specification} {e}")
try:
material = Material.objects.get(number=material_number)
except Exception as e:
raise ParseError(f"未找到物料:{material_number} {e}")
if batch:
pass
else:
batch = ""
if batch != '' and len(batch) < 5:
raise ParseError(f'{ind}行批次号{batch}:格式错误')
count = sheet[f"f{ind}"].value
warehouse_name = sheet[f"g{ind}"].value
count = sheet[f"c{ind}"].value
warehouse_name = sheet[f"d{ind}"].value
try:
warehouse = WareHouse.objects.get(name=warehouse_name)
except Exception as e:

View File

@ -13,10 +13,10 @@ router.register('warehouse', WarehouseVIewSet, basename='warehouse')
router.register('materialbatch', MaterialBatchViewSet,
basename='materialbatch')
router.register('mio', MIOViewSet, basename='mio')
router.register('mio/do', MioDoViewSet, basename='mio_do')
router.register('mio/sale', MioSaleViewSet, basename='mio_sale')
router.register('mio/pur', MioPurViewSet, basename='mio_pur')
router.register('mio/other', MioOtherViewSet, basename='mio_other')
router.register('mio/do', MioDoViewSet)
router.register('mio/sale', MioSaleViewSet)
router.register('mio/pur', MioPurViewSet)
router.register('mio/other', MioOtherViewSet)
router.register('mioitem', MIOItemViewSet, basename='mioitem')
router.register('mioitemw', MIOItemwViewSet, basename='mioitemw')
# router.register('pack', PackViewSet, basename='pack')

View File

@ -14,7 +14,7 @@ from apps.inm.serializers import (
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer, MIOItemListSimpleSerializer)
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer)
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.inm.services import InmService
@ -28,7 +28,6 @@ from apps.mtm.models import Material
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.db import connection
from datetime import datetime
# Create your views here.
@ -163,39 +162,20 @@ class MIOViewSet(CustomModelViewSet):
return mio
def add_info_for_list(self, data):
# 1. 收集所有mio的ID
mio_ids = [item['id'] for item in data]
# 2. 预初始化mio字典和items列表
mio_dict = {item['id']: {
**item,
'is_tested': False, # 默认值设为False
'mioitems': []
} for item in data}
# 3. 批量查询MIOItem数据
if mio_ids: # 避免空查询
mioitems = MIOItemListSimpleSerializer(
instance=MIOItem.objects.filter(
mio__id__in=mio_ids
).select_related("warehouse", "material"),
many=True
).data
# 4. 单次循环处理所有item
for item in mioitems:
mio_id = item['mio']
if mio_id in mio_dict:
mio_dict[mio_id]['mioitems'].append(item)
# 更新is_tested状态只要有一个item有test_date就为True
if item.get('test_date'):
mio_dict[mio_id]['is_tested'] = True
# 5. 直接返回原始data列表避免额外转换
# 获取检验状态
mio_dict = {}
for item in data:
item.update(mio_dict[item['id']])
return data
item['is_tested'] = None
mio_dict[item['id']] = item
mioitems = list(MIOItem.objects.filter(mio__id__in=mio_dict.keys()).values_list("mio__id", "test_date"))
for item in mioitems:
mioId, test_date = item
is_tested = False
if test_date:
is_tested = True
mio_dict[mioId]['is_tested'] = is_tested
datax = [mio_dict[key] for key in mio_dict.keys()]
return datax
def get_serializer_class(self):
if self.action in ['create', 'update', 'partial_update']:
@ -502,25 +482,13 @@ class MIOItemwViewSet(CustomModelViewSet):
perms_map = {'get': '*', 'post': 'mio.update', 'put': 'mio.update', 'delete': 'mio.update'}
queryset = MIOItemw.objects.all()
serializer_class = MIOItemwCreateUpdateSerializer
filterset_fields = {
'mioitem': ['exact'],
'mioitem__material__type': ['exact'],
"wpr": ['exact'],
"number": ["exact"],
"ftest": ["isnull"],
"mioitem__mio__state": ["exact"]
}
select_related_fields = ["ftest"]
filterset_fields = ['mioitem', 'wpr']
ordering = ["number", "create_time"]
ordering_fields = ["number", "create_time"]
def filter_queryset(self, queryset):
if not self.detail:
if not self.request.query_params.get('mioitem', None):
if "ftest__isnull" in self.request.query_params:
pass
else:
raise ParseError('请指定所属出入库记录明细')
if not self.detail and not self.request.query_params.get('mioitem', None):
raise ParseError('请指定所属出入库记录明细')
return super().filter_queryset(queryset)
def cal_mioitem_count(self, mioitem:MIOItem):
@ -528,10 +496,6 @@ class MIOItemwViewSet(CustomModelViewSet):
mioitem.count = count
mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
if mioitem.test_date is None:
mioitem.test_date = datetime.now()
if mioitem.test_user is None:
mioitem.test_user = self.request.user
mioitem.save()
def perform_create(self, serializer):

View File

@ -2,8 +2,7 @@ from django_filters import rest_framework as filters
from apps.mtm.models import Goal, Material, Route, RoutePack
from django.db.models.expressions import F
from rest_framework.exceptions import ParseError
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
from django.db.models.functions import Coalesce
class MaterialFilter(filters.FilterSet):
tag = filters.CharFilter(method='filter_tag', label="low_inm:库存不足")
@ -28,7 +27,7 @@ class MaterialFilter(filters.FilterSet):
def filter_tag(self, queryset, name, value):
if value == 'low_inm':
queryset = Material.annotate_count(queryset.exclude(count_safe=None).exclude(count_safe__lte=0)).filter(
queryset = queryset.exclude(count_safe=None).exclude(count_safe__lte=0).filter(
count__lte=F('count_safe'))
return queryset

View File

@ -6,8 +6,6 @@ from collections import defaultdict, deque
from django.db.models import Q
from datetime import datetime, timedelta
from django.utils import timezone
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
from django.db.models.functions import Coalesce
class Process(CommonBModel):
"""
@ -121,30 +119,7 @@ class Material(CommonAModel):
def __str__(self):
return f'{self.name}|{self.specification if self.specification else ""}|{self.model if self.model else ""}|{self.process.name if self.process else ""}'
@property
def fname(self):
return f'{self.name}|{self.specification if self.specification else ""}|{self.model if self.model else ""}|{self.process.name if self.process else ""}'
@staticmethod
def annotate_count(qs):
return qs.annotate(
count_mb=Coalesce(
Sum('mb_m__count', filter=Q(mb_m__state=10, mb_m__count__gt=0)),
Value(0),
output_field=DecimalField()
),
count_wm=Coalesce(
Sum('wm_m__count', filter=Q(wm_m__state=10, wm_m__count__gt=0)),
Value(0),
output_field=DecimalField()
)
).annotate(
count=ExpressionWrapper(
F('count_wm') + F('count_mb'),
output_field=DecimalField()
)
)
class Shift(CommonBModel):
"""TN:班次
"""
@ -229,7 +204,7 @@ class Mgroup(CommonBModel):
# 如果当前时间在结束时间之前,属于前一天
else:
return (w_s_time - timedelta(days=1)).date(), shift
raise ParseError(f"工段{self.name}的班次规则未覆盖时间点{w_s_time.strftime('%H:%M:%S')}")
# return w_s_time.date(), None
class TeamMember(BaseModel):
@ -476,7 +451,7 @@ class Route(CommonADModel):
return rqs
@classmethod
def validate_dag(cls, final_material_out:Material, rqs, check_final=True):
def validate_dag(cls, final_material_out:Material, rqs):
"""
TN:校验工艺路线是否正确
- 所有 Route 必须有 material_in material_out
@ -507,7 +482,7 @@ class Route(CommonADModel):
# 3. 检查final_material_out是否是终点
final_id = final_material_out.id
if check_final and final_id in reverse_graph:
if final_id in reverse_graph:
# raise ParseError(
# f"最终物料 {final_material_out.name}(ID:{final_id}) 不能作为任何Route的输入"
# )
@ -527,8 +502,6 @@ class Route(CommonADModel):
# 5. 检查未到达的物料
unreachable_ids = all_material_ids - visited
if check_final is False:
unreachable_ids.discard(final_id)
if unreachable_ids:
# unreachable_materials = Material.objects.filter(id__in=unreachable_ids).values_list('name', flat=True)
# raise ParseError(

View File

@ -38,15 +38,13 @@ class MaterialSerializer(CustomModelSerializer):
count = serializers.SerializerMethodField()
def get_count_mb(self, obj):
return getattr(obj, 'count_mb', None)
return getattr(obj, 'count_mb', 0)
def get_count_wm(self, obj):
return getattr(obj, 'count_wm', None)
return getattr(obj, 'count_wm', 0)
def get_count(self, obj):
if hasattr(obj, 'count_mb') and hasattr(obj, 'count_wm'):
return getattr(obj, 'count_mb', 0) + getattr(obj, 'count_wm', 0)
return None
return getattr(obj, 'count_mb', 0) + getattr(obj, 'count_wm', 0)
class Meta:
model = Material
@ -211,10 +209,7 @@ class RouteSerializer(CustomModelSerializer):
"""
material = instance.material
process = instance.process
material_out: Material = Material.objects.get_queryset(all=True).filter(
type__in=[Material.MA_TYPE_HALFGOOD, Material.MA_TYPE_GOOD],
parent=material, process=process).order_by("create_time").first()
material_out: Material = Material.objects.get_queryset(all=True).filter(type=Material.MA_TYPE_HALFGOOD, parent=material, process=process).first()
if material_out:
material_out.is_deleted = False
if material_out.parent == material:
@ -225,10 +220,7 @@ class RouteSerializer(CustomModelSerializer):
material_out.tracking = material_out_tracking
material_out.save()
return material_out
material_out = Material.objects.get_queryset(all=True).filter(name=material.name,
model=material.model, process=process,
specification=material.specification).order_by("create_time", "-is_hidden").first()
material_out = Material.objects.get_queryset(all=True).filter(name=material.name, model=material.model, process=process, specification=material.specification).first()
if material_out:
material_out.is_deleted = False
if material_out.parent is None:
@ -238,7 +230,6 @@ class RouteSerializer(CustomModelSerializer):
material_out.tracking = material_out_tracking
material_out.save()
return material_out
material_out = Material.objects.create(**{'parent': instance.material, 'process': instance.process,
'is_hidden': True, 'name': material.name,
'number': material.number,

View File

@ -44,10 +44,26 @@ class MaterialViewSet(CustomModelViewSet):
def get_queryset(self):
qs = super().get_queryset()
if self.action in ["list", "retrieve"] and self.request.query_params.get('wiith_count', None) == 'yes':
return Material.annotate_count(qs)
return qs
if self.action in ["list", "retrieve"]:
return qs.annotate(
count_wm=Coalesce(
Sum('mb_m__count', filter=Q(mb_m__state=10)),
Value(0),
output_field=DecimalField()
),
count_mb=Coalesce(
Sum('wm_m__count', filter=Q(wm_m__state=10)),
Value(0),
output_field=DecimalField()
)
).annotate(
count=ExpressionWrapper(
F('count_wm') + F('count_mb'),
output_field=DecimalField()
)
)
def perform_destroy(self, instance):
from apps.inm.models import MaterialBatch
if MaterialBatch.objects.filter(material=instance).exists():
@ -249,12 +265,9 @@ class RoutePackViewSet(CustomModelViewSet):
@transaction.atomic
def destroy(self, request, *args, **kwargs):
from apps.wpm.models import Mlog
obj: RoutePack = self.get_object()
if obj.state != RoutePack.RP_S_CREATE:
raise ParseError('该状态下不可删除')
if Mlog.objects.filter(route__routepack=obj).exists():
raise ParseError('该工艺路线包有生产记录,不可删除')
obj.delete()
Ticket.objects.filter(ticket_data__t_id=obj.id, ticket_data__t_model='routepack').delete()
return Response(status=204)
@ -382,16 +395,6 @@ class RouteViewSet(CustomModelViewSet):
raise ParseError('该工艺步骤被其他步骤引用,无法删除')
return super().perform_destroy(instance)
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer)
def dag(self, request, *args, **kwargs):
"""获取总图
获取总图
"""
materialId = self.request.data.get('product', None)
if materialId is None:
raise ParseError('缺少参数product')
return Response(Route.get_dag(rqs=Route.objects.filter(material__id=materialId)))
class SruleViewSet(CustomModelViewSet):
"""

View File

@ -1,5 +1,5 @@
from django_filters import rest_framework as filters
from apps.ofm.models import MroomBooking, BorrowRecord, VehicleUse
from apps.ofm.models import MroomBooking, BorrowRecord
from .models import LendingSeal
from apps.utils.filters import MyJsonListFilter
@ -15,16 +15,6 @@ class MroomBookingFilterset(filters.FilterSet):
"id": ["exact"]
}
class VehicleFilterset(filters.FilterSet):
class Meta:
model = VehicleUse
fields = {
'slot_vehicle__vehreg': ['exact', 'in'],
'slot_vehicle__vdate': ['exact', 'gte', 'lte'],
'create_by': ['exact'],
"id": ["exact"]
}
class SealFilter(filters.FilterSet):
seal = MyJsonListFilter(label='按印章名称查询', field_name="seal")

View File

@ -1,7 +1,6 @@
# Generated by Django 3.2.12 on 2025-11-18 03:08
# Generated by Django 3.2.12 on 2025-06-25 09:29
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
@ -12,9 +11,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0005_workflow_cate'),
]
operations = [
@ -43,114 +40,13 @@ class Migration(migrations.Migration):
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('title', models.CharField(max_length=100, verbose_name='会议主题')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('participant_count', models.PositiveIntegerField(default=0, verbose_name='参会人数')),
('key_participants', models.TextField(blank=True, null=True, verbose_name='主要参会领导')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleReg',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, verbose_name='车辆名称')),
('brand', models.CharField(blank=True, max_length=50, null=True, verbose_name='品牌')),
('plate', models.CharField(max_length=50, verbose_name='车牌号')),
('km', models.PositiveIntegerField(blank=True, null=True, verbose_name='行驶里程')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehiclereg_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehiclereg_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleUse',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='出发地点')),
('via', models.CharField(blank=True, max_length=100, null=True, verbose_name='途经地点')),
('destination', models.CharField(blank=True, max_length=100, null=True, verbose_name='到达地点')),
('start_km', models.PositiveIntegerField(verbose_name='出发公里数')),
('end_km', models.PositiveIntegerField(blank=True, null=True, verbose_name='归还公里数')),
('actual_km', models.PositiveIntegerField(editable=False, verbose_name='实际行驶公里数')),
('is_city', models.BooleanField(default=True, verbose_name='是否市内用车')),
('reason', models.CharField(max_length=100, verbose_name='用车事由')),
('reception', models.CharField(blank=True, max_length=50, null=True, verbose_name='接待人')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('vehiclereg', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicle_record', to='ofm.vehiclereg')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleSlot',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('vdate', models.DateField(db_index=True, verbose_name='使用日期')),
('slot', models.PositiveIntegerField(help_text='0-47', verbose_name='时段')),
('is_inuse', models.BooleanField(default=True, verbose_name='是否占用')),
('vehicle_use', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_vehicle', to='ofm.vehicleuse')),
('vehreg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_record', to='ofm.vehiclereg')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Publicity',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='记录编号')),
('title', models.CharField(max_length=100, verbose_name='送审稿件标题')),
('participants', models.CharField(max_length=50, verbose_name='所有撰稿人')),
('pub_dept', models.CharField(blank=True, max_length=50, null=True, verbose_name='部室/研究院')),
('pfile', models.CharField(blank=True, max_length=100, null=True, verbose_name='稿件路径')),
('level', models.JSONField(default=list, help_text="['重要', '一般', '非涉密']", verbose_name='涉密等级')),
('content', models.JSONField(default=list, help_text="['武器装备科研生产综合事项', '其它']", verbose_name='稿件内容涉及')),
('other_content', models.CharField(blank=True, max_length=100, null=True, verbose_name='其它内容')),
('report_purpose', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道目的')),
('channel', models.JSONField(default=list, help_text="['互联网', '信息平台', '官微', '公开发行物', '其它']", verbose_name='发布渠道')),
('other_channel', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('report_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='报道名称')),
('review', models.JSONField(blank=True, default=list, help_text="['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布']", null=True, verbose_name='第一撰稿人自审')),
('dept_opinion', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门负责人意见')),
('disposal_method', models.CharField(blank=True, max_length=50, null=True, verbose_name='处理方式')),
('secret_level', models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密等级')),
('secret_period', models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密期限')),
('dept_opinion_review', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门审查意见')),
('publicity_opinion', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道意见')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='MroomSlot',
fields=[
@ -160,83 +56,11 @@ class Migration(migrations.Migration):
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('mdate', models.DateField(db_index=True, verbose_name='会议日期')),
('slot', models.PositiveIntegerField(help_text='0-47', verbose_name='时段')),
('is_inuse', models.BooleanField(default=True, verbose_name='是否占用')),
('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_b', to='ofm.mroombooking')),
('mroom', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_m', to='ofm.mroom')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LendingSeal',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('seal', models.JSONField(default=list, help_text='[公章,法人章,财务章,合同章,业务章,其他章]', verbose_name='印章信息')),
('seal_other', models.CharField(blank=True, max_length=50, null=True, verbose_name='其他印章')),
('filename', models.TextField(verbose_name='文件名称')),
('file', models.TextField(verbose_name='文件内容')),
('file_count', models.PositiveIntegerField(verbose_name='用印份数')),
('is_lending', models.BooleanField(default=False, verbose_name='是否借出')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='联系方式')),
('lending_date', models.DateField(blank=True, null=True, verbose_name='借出日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='拟归还日期')),
('actual_return_date', models.DateField(blank=True, null=True, verbose_name='实际归还日期')),
('reason', models.CharField(blank=True, max_length=100, null=True, verbose_name='借用理由')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='seal_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='FileRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='资料名称')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='档案编号')),
('counts', models.CharField(blank=True, max_length=10, null=True, verbose_name='文件份数')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放位置')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='存档人电话')),
('reciver', models.CharField(blank=True, max_length=50, null=True, verbose_name='接收人(综合办)')),
('remark', models.TextField(blank=True, max_length=200, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BorrowRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('borrow_date', models.DateField(blank=True, null=True, verbose_name='借阅日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='归还日期')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='借阅人电话')),
('remark', models.JSONField(default=list, help_text="['借阅', '复印', '查阅']", verbose_name='用途')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('borrow_file', models.ManyToManyField(related_name='borrow_records', to='ofm.FileRecord')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrow_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
'unique_together': {('mroom', 'mdate', 'slot')},
},
),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 3.2.12 on 2025-09-05 03:07
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0002_alter_state_filter_dept'),
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ofm', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='LendingSeal',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('seal', models.JSONField(default=list, help_text='{"seal_name": "印章名称"}', verbose_name='印章信息')),
('filename', models.TextField(verbose_name='文件名称')),
('file', models.TextField(verbose_name='文件内容')),
('file_count', models.PositiveIntegerField(verbose_name='用印份数')),
('is_lending', models.BooleanField(default=False, verbose_name='是否借出')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='联系方式')),
('lending_date', models.DateField(blank=True, null=True, verbose_name='借出日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='拟归还日期')),
('actual_return_date', models.DateField(blank=True, null=True, verbose_name='实际归还日期')),
('reason', models.CharField(blank=True, max_length=100, null=True, verbose_name='借用理由')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('submit_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seal_submit_user', to=settings.AUTH_USER_MODEL, verbose_name='提交人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='seal_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-19 02:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0002_remove_vehicleuse_vehiclereg'),
]
operations = [
migrations.AddField(
model_name='borrowrecord',
name='count',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='借阅份数'),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2025-11-18 07:39
# Generated by Django 3.2.12 on 2025-09-08 03:11
from django.db import migrations
@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0001_initial'),
('ofm', '0002_lendingseal'),
]
operations = [
migrations.RemoveField(
model_name='vehicleuse',
name='vehiclereg',
model_name='lendingseal',
name='submit_user',
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-18 08:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0003_borrowrecord_count'),
]
operations = [
migrations.AlterField(
model_name='vehiclereg',
name='km',
field=models.PositiveIntegerField(default=0, verbose_name='行驶里程'),
),
migrations.AlterField(
model_name='vehicleuse',
name='start_km',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='出发公里数'),
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.2.12 on 2025-09-10 06:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0002_alter_state_filter_dept'),
('ofm', '0003_remove_lendingseal_submit_user'),
]
operations = [
migrations.CreateModel(
name='Vehicle',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('start_time', models.DateField(blank=True, null=True, verbose_name='出车时间')),
('end_time', models.DateField(blank=True, null=True, verbose_name='还车时间')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='出发地点')),
('destination', models.CharField(blank=True, max_length=100, null=True, verbose_name='到达地点')),
('start_km', models.PositiveIntegerField(verbose_name='出发公里数')),
('end_km', models.PositiveIntegerField(verbose_name='归还公里数')),
('actual_km', models.PositiveIntegerField(editable=False, verbose_name='实际行驶公里数')),
('is_city', models.BooleanField(default=True, verbose_name='是否市内用车')),
('reason', models.CharField(max_length=100, verbose_name='用车事由')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2026-01-04 06:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0004_auto_20251218_1636'),
]
operations = [
migrations.AlterField(
model_name='vehicleuse',
name='end_km',
field=models.PositiveIntegerField(default=0, verbose_name='归还公里数'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-09-10 06:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0004_vehicle'),
]
operations = [
migrations.AddField(
model_name='vehicle',
name='via',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='途经地点'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-09-11 01:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0005_vehicle_via'),
]
operations = [
migrations.AddField(
model_name='vehicle',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_belong_dept', to='system.dept', verbose_name='所属部门'),
),
]

View File

@ -0,0 +1,67 @@
# Generated by Django 3.2.12 on 2025-09-11 06:41
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ofm', '0006_vehicle_belong_dept'),
]
operations = [
migrations.AlterField(
model_name='lendingseal',
name='seal',
field=models.JSONField(default=list, help_text='[公章,法人章,财务章,合同章,业务章,其他章]', verbose_name='印章信息'),
),
migrations.CreateModel(
name='FileRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='资料名称')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='档案编号')),
('counts', models.CharField(blank=True, max_length=10, null=True, verbose_name='文件份数')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放位置')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='存档人电话')),
('reciver', models.CharField(blank=True, max_length=50, null=True, verbose_name='接收人(综合办)')),
('remark', models.TextField(blank=True, max_length=200, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BorrowRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('borrow_date', models.DateField(blank=True, null=True, verbose_name='借阅日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='归还日期')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='借阅人电话')),
('remark', models.JSONField(default=list, help_text=['借阅', '复印', '查阅'], verbose_name='用途')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('borrow_file', models.ManyToManyField(related_name='borrow_records', to='ofm.FileRecord')),
('borrow_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='borrow_user', to=settings.AUTH_USER_MODEL)),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.12 on 2025-09-12 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0007_auto_20250911_1441'),
]
operations = [
migrations.RemoveField(
model_name='borrowrecord',
name='borrow_user',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-09-12 07:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0002_alter_state_filter_dept'),
('ofm', '0008_remove_borrowrecord_borrow_user'),
]
operations = [
migrations.AddField(
model_name='borrowrecord',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrow_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.12 on 2025-09-19 01:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('ofm', '0009_borrowrecord_ticket'),
]
operations = [
migrations.AddField(
model_name='lendingseal',
name='seal_other',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='其他印章'),
),
migrations.CreateModel(
name='Publicity',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(max_length=50, verbose_name='记录编号')),
('title', models.CharField(max_length=100, verbose_name='送审稿件标题')),
('participants', models.CharField(max_length=50, verbose_name='所有撰稿人')),
('level', models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='用途')),
('content', models.JSONField(default=list, help_text=['武器装备科研生产综合事项', '其它'], verbose_name='稿件内容涉及')),
('other_content', models.CharField(blank=True, max_length=100, null=True, verbose_name='其它内容')),
('report_purpose', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道目的')),
('channel', models.JSONField(default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'], verbose_name='发布渠道')),
('channel_other', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('other_channel', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('report_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='报道名称')),
('review', models.JSONField(default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审')),
('dept_opinion', models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见')),
('dept_opinion_review', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门审查意见')),
('publicity_opinion', models.JSONField(default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.12 on 2025-09-24 05:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0010_auto_20250919_0921'),
]
operations = [
migrations.RemoveField(
model_name='publicity',
name='channel_other',
),
migrations.AddField(
model_name='publicity',
name='pfile',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='稿件路径'),
),
migrations.AddField(
model_name='publicity',
name='pub_dept',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='部室/研究院'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-09-24 06:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0011_auto_20250924_1359'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-09-25 07:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0012_publicity_ticket'),
]
operations = [
migrations.AddField(
model_name='mroomslot',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.12 on 2025-09-28 02:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0013_mroomslot_ticket'),
]
operations = [
migrations.AddField(
model_name='mroombooking',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
),
migrations.AddField(
model_name='mroomslot',
name='is_inuse',
field=models.BooleanField(default=True, verbose_name='是否占用'),
),
migrations.AlterUniqueTogether(
name='mroomslot',
unique_together={('mroom', 'mdate', 'slot', 'is_inuse')},
),
migrations.RemoveField(
model_name='mroomslot',
name='ticket',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-09-28 06:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0014_auto_20250928_1023'),
]
operations = [
migrations.AlterField(
model_name='vehicle',
name='end_km',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='归还公里数'),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.12 on 2025-09-29 07:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0015_alter_vehicle_end_km'),
]
operations = [
migrations.AddField(
model_name='mroombooking',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_belong_dept', to='system.dept', verbose_name='所属部门'),
),
migrations.AddField(
model_name='mroombooking',
name='key_participants',
field=models.TextField(blank=True, null=True, verbose_name='主要参会领导'),
),
migrations.AddField(
model_name='mroombooking',
name='note',
field=models.TextField(blank=True, null=True, verbose_name='备注'),
),
migrations.AddField(
model_name='mroombooking',
name='participant_count',
field=models.PositiveIntegerField(default=0, verbose_name='参会人数'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2025-10-10 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0016_auto_20250929_1551'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='secret_period',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密期限'),
),
migrations.AlterField(
model_name='publicity',
name='level',
field=models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='涉密等级'),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.12 on 2025-10-11 01:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0017_auto_20251010_1631'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='number',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='记录编号'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.12 on 2025-10-11 03:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0018_auto_20251011_0922'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.12 on 2025-10-11 06:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0019_auto_20251011_1128'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2025-11-25 08:26
# Generated by Django 3.2.12 on 2025-10-13 01:01
from django.db import migrations, models
@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('srm', '0009_auto_20251125_1616'),
('ofm', '0020_auto_20251011_1427'),
]
operations = [
migrations.AlterField(
model_name='paperrecord',
name='cor_author',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='通讯作者'),
model_name='publicity',
name='publicity_opinion',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道意见'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.12 on 2025-10-17 06:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0021_alter_publicity_publicity_opinion'),
]
operations = [
migrations.AlterUniqueTogether(
name='mroomslot',
unique_together=set(),
),
]

View File

@ -0,0 +1,49 @@
# Generated by Django 3.2.12 on 2025-10-21 06:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0004_workflow_view_path2'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('ofm', '0022_alter_mroomslot_unique_together'),
]
operations = [
migrations.CreateModel(
name='PatentInfo',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='拟申请专利名称')),
('author', models.CharField(max_length=100, verbose_name='发明人(设计人)')),
('type', models.CharField(choices=[('invention', '发明专利'), ('utility', '实用新型专利'), ('design', '外观设计专利')], default='invention', max_length=50, verbose_name='专利类型')),
('is_public', models.BooleanField(default=False, verbose_name='是否公开')),
('area', models.CharField(choices=[('Domestic', '国内申请'), ('Foreign', '国外申请'), (' PCT', 'PCT申请')], default='Domestic', max_length=50, verbose_name='拟申请地域')),
('identified', models.BooleanField(default=False, verbose_name='是否进行过科技成果鉴定')),
('published_article', models.BooleanField(default=False, verbose_name='是否发表过文章')),
('exhibited', models.BooleanField(default=False, verbose_name='是否参与过展会展出')),
('applied_to_production', models.BooleanField(default=False, verbose_name='是否参与应用于生产/销售')),
('participated_in_exchange', models.BooleanField(default=False, verbose_name='是否参与过技术交流')),
('tech_background_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术背景材料页数')),
('tech_disclosure_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术交底材料页数')),
('novelty_report_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='查新检索报告页数')),
('diagrams_or_photos_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='图/照片页数或张数')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentInfo_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,95 @@
# Generated by Django 3.2.12 on 2025-10-22 02:05
import apps.ofm.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0023_patentinfo'),
]
operations = [
migrations.RemoveField(
model_name='patentinfo',
name='applied_to_production',
),
migrations.RemoveField(
model_name='patentinfo',
name='diagrams_or_photos_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='exhibited',
),
migrations.RemoveField(
model_name='patentinfo',
name='identified',
),
migrations.RemoveField(
model_name='patentinfo',
name='novelty_report_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='participated_in_exchange',
),
migrations.RemoveField(
model_name='patentinfo',
name='published_article',
),
migrations.RemoveField(
model_name='patentinfo',
name='tech_background_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='tech_disclosure_pages',
),
migrations.AddField(
model_name='patentinfo',
name='other_area',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='其它申请地域'),
),
migrations.AddField(
model_name='patentinfo',
name='tech_file',
field=models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件'),
),
migrations.AddField(
model_name='patentinfo',
name='tech_status',
field=models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态'),
),
migrations.AlterField(
model_name='borrowrecord',
name='remark',
field=models.JSONField(default=list, help_text="['借阅', '复印', '查阅']", verbose_name='用途'),
),
migrations.AlterField(
model_name='publicity',
name='channel',
field=models.JSONField(default=list, help_text="['互联网', '信息平台', '官微', '公开发行物', '其它']", verbose_name='发布渠道'),
),
migrations.AlterField(
model_name='publicity',
name='content',
field=models.JSONField(default=list, help_text="['武器装备科研生产综合事项', '其它']", verbose_name='稿件内容涉及'),
),
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text="['同意', '不同意']", null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='level',
field=models.JSONField(default=list, help_text="['重要', '一般', '非涉密']", verbose_name='涉密等级'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text="['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布']", null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-10-24 05:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0024_auto_20251022_1005'),
]
operations = [
migrations.AlterField(
model_name='patentinfo',
name='area',
field=models.CharField(choices=[('Domestic', '国内申请'), ('Foreign', '国外申请'), ('PCT', 'PCT申请')], default='Domestic', max_length=50, verbose_name='拟申请地域'),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 3.2.12 on 2025-10-29 03:13
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0004_workflow_view_path2'),
('ofm', '0025_alter_patentinfo_area'),
]
operations = [
migrations.CreateModel(
name='PaperSe',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('paper_name', models.CharField(max_length=100, verbose_name='拟发表论文名称')),
('publication_name', models.CharField(max_length=100, verbose_name='拟投期刊名称')),
('author', models.CharField(max_length=100, verbose_name='作者')),
('paper_type', models.CharField(max_length=100, verbose_name='拟发表文章类型')),
('is_chinese_core', models.BooleanField(default=False, verbose_name='是否为中文核心')),
('is_sci', models.BooleanField(default=False, verbose_name='是否被SCI/EI收录')),
('tech_status', models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态')),
('tech_file', models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.12 on 2025-10-29 06:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0004_workflow_view_path2'),
('ofm', '0026_paperse'),
]
operations = [
migrations.CreateModel(
name='Papersecret',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('paper_name', models.CharField(max_length=100, verbose_name='拟发表论文名称')),
('publication_name', models.CharField(max_length=100, verbose_name='拟投期刊名称')),
('author', models.CharField(max_length=100, verbose_name='作者')),
('paper_type', models.CharField(max_length=100, verbose_name='拟发表文章类型')),
('is_chinese_core', models.BooleanField(default=False, verbose_name='是否为中文核心')),
('is_sci', models.BooleanField(default=False, verbose_name='是否被SCI/EI收录')),
('tech_status', models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态')),
('tech_file', models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.DeleteModel(
name='PaperSe',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-10-30 05:55
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0027_auto_20251029_1426'),
]
operations = [
migrations.AddField(
model_name='papersecret',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_belong_dept', to='system.dept', verbose_name='所属部门'),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.12 on 2025-11-03 01:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0028_papersecret_belong_dept'),
]
operations = [
migrations.RemoveField(
model_name='patentinfo',
name='belong_dept',
),
migrations.RemoveField(
model_name='patentinfo',
name='create_by',
),
migrations.RemoveField(
model_name='patentinfo',
name='ticket',
),
migrations.RemoveField(
model_name='patentinfo',
name='update_by',
),
migrations.DeleteModel(
name='Papersecret',
),
migrations.DeleteModel(
name='PatentInfo',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2025-11-05 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0029_auto_20251103_0939'),
]
operations = [
migrations.AlterField(
model_name='vehicle',
name='end_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='还车时间'),
),
migrations.AlterField(
model_name='vehicle',
name='start_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='出车时间'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.12 on 2025-11-06 08:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0030_auto_20251105_1715'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='disposal_method',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='处理方式'),
),
migrations.AddField(
model_name='publicity',
name='secret_level',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密等级'),
),
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='部门负责人意见'),
),
]

View File

@ -26,7 +26,6 @@ class Mroom(CommonADModel):
location = models.CharField('位置', max_length=100)
capacity = models.PositiveIntegerField('容纳人数')
class MroomBooking(CommonBDModel):
"""TN: 会议室预定信息"""
# belong_dept 是预定部门
@ -47,45 +46,10 @@ class MroomSlot(BaseModel):
is_inuse = models.BooleanField('是否占用', default=True)
class VehicleReg(CommonADModel):
"""TN: 车辆台账"""
name = models.CharField('车辆名称', max_length=50)
brand = models.CharField('品牌', max_length=50, null=True, blank=True)
plate = models.CharField('车牌号', max_length=50)
km = models.PositiveIntegerField('行驶里程', default=0)
# class Seal(BaseModel):
# """TN: 印章类型"""
# name = models.CharField('印章名称', max_length=50, unique=True)
class VehicleUse(CommonBDModel):
"""TN: 用车记录"""
location = models.CharField('出发地点', null=True, blank=True, max_length=100)
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
start_km = models.PositiveIntegerField('出发公里数', null=True, blank=True)
end_km = models.PositiveIntegerField('归还公里数', default=0)
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
is_city = models.BooleanField('是否市内用车', default=True)
reason = models.CharField('用车事由', max_length=100)
reception = models.CharField('接待人', max_length=50, blank=True, null=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='vehicle_ticket', null=True, blank=True, db_constraint=False)
def save(self, *args, **kwargs):
if self.start_km is not None:
self.start_km = int(self.start_km)
if self.end_km is not None:
self.end_km = int(self.end_km)
if self.start_km <= self.end_km:
self.actual_km = self.end_km - self.start_km
else:
raise ParseError(f'归还公里数不能小于出发公里数, 出发公里数组为 {self.start_km} km')
else:
self.actual_km = 0
return super().save(*args, **kwargs)
class VehicleSlot(BaseModel):
vehreg = models.ForeignKey(VehicleReg, on_delete=models.CASCADE, related_name="slot_record")
vehicle_use = models.ForeignKey(VehicleUse, on_delete=models.CASCADE, related_name="slot_vehicle")
vdate = models.DateField('使用日期', db_index=True)
slot = models.PositiveIntegerField('时段', help_text='0-47')
is_inuse = models.BooleanField('是否占用', default=True)
class LendingSeal(CommonBDModel):
"""TN: 印章外出用印信息"""
@ -105,6 +69,32 @@ class LendingSeal(CommonBDModel):
on_delete=models.SET_NULL, related_name='seal_ticket', null=True, blank=True, db_constraint=False)
note = models.TextField('备注', null=True, blank=True)
class Vehicle(CommonBDModel):
"""TN: 用车申请"""
start_time = models.DateTimeField('出车时间', blank=True, null=True)
end_time = models.DateTimeField('还车时间', blank=True, null=True)
location = models.CharField('出发地点', null=True, blank=True, max_length=100)
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
start_km = models.PositiveIntegerField('出发公里数')
end_km = models.PositiveIntegerField('归还公里数', null=True, blank=True)
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
is_city = models.BooleanField('是否市内用车', default=True)
reason = models.CharField('用车事由', max_length=100)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='vehicle_ticket', null=True, blank=True, db_constraint=False)
def save(self, *args, **kwargs):
if self.end_km:
if self.start_km <= self.end_km:
self.actual_km = self.end_km - self.start_km
else:
raise ParseError('归还公里数不能小于出发公里数')
else:
self.actual_km = 0
return super().save(*args, **kwargs)
class FileRecord(CommonBDModel):
"""TN: 档案台账"""
name = models.CharField('资料名称', max_length=100)
@ -115,12 +105,12 @@ class FileRecord(CommonBDModel):
reciver = models.CharField('接收人(综合办)', max_length=50, null=True, blank=True)
remark = models.TextField('备注', max_length=200, null=True, blank=True)
class BorrowRecord(CommonBDModel):
"""TN: 借阅、复印、查阅记录"""
borrow_file = models.ManyToManyField(FileRecord, related_name="borrow_records")
borrow_date = models.DateField('借阅日期', null=True, blank=True)
return_date = models.DateField('归还日期', null=True, blank=True)
count = models.PositiveIntegerField('借阅份数', null=True, blank=True)
contacts = models.CharField('借阅人电话', max_length=50, validators=[phone_validator], null=True, blank=True)
remark = models.JSONField('用途', default=list, help_text=str(['借阅', '复印', '查阅']))
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',

View File

@ -1,4 +1,4 @@
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, VehicleUse, FileRecord, BorrowRecord, Publicity, VehicleReg, VehicleSlot)
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity)
from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers
from django.db import transaction
@ -33,17 +33,14 @@ class MroomBookingSerializer(CustomModelSerializer):
mdate = validated_data.pop('mdate')
booking = super().create(validated_data)
MroomSlot.objects.filter(booking=booking).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
else:
raise ParseError("请选择时段")
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
def update(self, instance, validated_data):
mroom = validated_data.pop('mroom')
@ -51,17 +48,16 @@ class MroomBookingSerializer(CustomModelSerializer):
mdate = validated_data.pop('mdate')
booking = super().update(instance, validated_data)
MroomSlot.objects.filter(booking=instance).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
else:
raise ParseError("请选择时段")
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
class MroomSlotSerializer(CustomModelSerializer):
booking_title = serializers.CharField(source='booking.title', read_only=True)
class Meta:
@ -69,72 +65,6 @@ class MroomSlotSerializer(CustomModelSerializer):
fields = '__all__'
class VehicleRecordSerializer(CustomModelSerializer):
class Meta:
model = VehicleReg
fields = '__all__'
class VehicleUseSerializer(CustomModelSerializer):
vehreg = serializers.PrimaryKeyRelatedField(queryset=VehicleReg.objects.all(), write_only=True, label="车辆信息")
vdate = serializers.DateField(write_only=True, label="预订日期")
slots = serializers.ListField(child=serializers.IntegerField(), write_only=True, label="时段索引")
create_by_name = serializers.CharField(source='create_by.username', read_only=True)
create_by_phone = serializers.CharField(source='create_by.phone', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = VehicleUse
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['actual_km']
extra_kwargs = {'belong_dept': {'required': True}}
def create(self, validated_data):
vehreg = validated_data.pop('vehreg')
slots = validated_data.pop('slots')
vdate = validated_data.pop('vdate')
validated_data['start_km'] = vehreg.km
if validated_data.get('end_km', None):
vehreg.km= validated_data['end_km']
vehicle_use = super().create(validated_data)
VehicleSlot.objects.filter(vehicle_use=vehicle_use).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = VehicleSlot.objects.filter(vehreg=vehreg, vdate=vdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
VehicleSlot.objects.create(vehicle_use=vehicle_use, slot=slot, vdate=vdate, vehreg=vehreg, is_inuse=True)
return vehicle_use
else:
raise ParseError("请选择时段")
def update(self, instance, validated_data):
vehreg = validated_data.pop('vehreg')
slots = validated_data.pop('slots')
vdate = validated_data.pop('vdate')
vehicle_use = super().update(instance, validated_data)
VehicleSlot.objects.filter(vehicle_use=vehicle_use).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = VehicleSlot.objects.filter(vehreg=vehreg, vdate=vdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
VehicleSlot.objects.create(vehicle_use=vehicle_use, slot=slot, vdate=vdate, vehreg=vehreg, is_inuse=True)
return vehicle_use
else:
raise ParseError("请选择时段")
class VehSlotSerializer(CustomModelSerializer):
veh_name = serializers.CharField(source='vehreg.name', read_only=True)
class Meta:
model = VehicleSlot
fields = '__all__'
class LendingSealSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
@ -144,6 +74,17 @@ class LendingSealSerializer(CustomModelSerializer):
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class VehicleSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = Vehicle
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['actual_km']
class FileRecordSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)

View File

@ -1,7 +1,7 @@
from apps.wf.models import Ticket
# TicketFlow, Transition, Workflow, CustomField, State,
from apps.ofm.models import LendingSeal, VehicleUse, BorrowRecord, Publicity, MroomBooking, MroomSlot
from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord, Publicity, MroomBooking, MroomSlot
from rest_framework.exceptions import ParseError
@ -29,11 +29,10 @@ def mroombooking_reject(ticket: Ticket, transition, new_ticket_data: dict):
ins = MroomBooking.objects.get(id=new_ticket_data['t_id'])
MroomSlot.objects.filter(booking=ins).update(is_inuse=False)
def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
ticket_data = ticket.ticket_data
ins = LendingSeal.objects.get(id=new_ticket_data['t_id'])
ins.actual_return_date = None
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'LendingSeal',
't_id': ins.id,
@ -60,7 +59,7 @@ def lending_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
ins = VehicleUse.objects.get(id=new_ticket_data['t_id'])
ins = Vehicle.objects.get(id=new_ticket_data['t_id'])
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'Vehicle',
@ -68,6 +67,7 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
'is_city': ins.is_city
})
ins.actual_km = None
ins.end_time = None
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
@ -77,8 +77,8 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
def vehicle_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
obj = VehicleUse.objects.get(id=new_ticket_data['t_id'])
except VehicleUse.DoesNotExist:
obj = Vehicle.objects.get(id=new_ticket_data['t_id'])
except Vehicle.DoesNotExist:
raise ParseError("Publicity t_id 不存在")
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}

View File

@ -1,7 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleUseViewSet,
VehicleRegViewSet, VehicleSlotViewSet, FilerecordViewSet, FileborrowViewSet, PublicityViewSet)
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet, FileborrowViewSet, PublicityViewSet)
API_BASE_URL = 'api/ofm/'
HTML_BASE_URL = 'dhtml/ofm/'
@ -12,9 +11,7 @@ router.register('mroombooking', MroomBookingViewSet, basename='mroombooking')
router.register('mroomslot', MroomSlotViewSet, basename='mroomslot')
# router.register('sealmanage', SealManageViewSet, basename='sealmanage')
router.register('lendingseal', LendingSealViewSet, basename='lendingseal')
router.register('vehicle-use', VehicleUseViewSet, basename='vehicle_use')
router.register('vsolt', VehicleSlotViewSet, basename='vslot')
router.register('vehicle', VehicleRegViewSet, basename='vehicle')
router.register('vehicle', VehicleViewSet, basename='vehicle')
router.register('filerecord', FilerecordViewSet, basename='filerecord')
router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
router.register('publicity', PublicityViewSet, basename='publicity')

View File

@ -1,12 +1,12 @@
from django.shortcuts import render
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, VehicleUse, VehicleSlot, VehicleReg, FileRecord, BorrowRecord, Publicity
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer,
VehicleUseSerializer, VehicleRecordSerializer, VehSlotSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer)
VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer)
from apps.utils.mixins import CustomListModelMixin
from rest_framework.exceptions import ParseError
from apps.ofm.filters import MroomBookingFilterset, SealFilter, BorrowRecordFilter, VehicleFilterset
from apps.ofm.filters import MroomBookingFilterset, SealFilter, BorrowRecordFilter
from rest_framework.response import Response
class MroomViewSet(CustomModelViewSet):
@ -115,6 +115,8 @@ class LendingSealViewSet(CustomModelViewSet):
印章外出
"""
perms_map = {'get': '*', 'post': 'seal.update',
'put': 'seal.update', 'delete': 'seal.delete'}
queryset = LendingSeal.objects.all()
serializer_class = LendingSealSerializer
filterset_class = SealFilter
@ -122,106 +124,15 @@ class LendingSealViewSet(CustomModelViewSet):
data_filter = True
class VehicleRegViewSet(CustomModelViewSet):
class VehicleViewSet(CustomModelViewSet):
"""list: 车辆
车辆
"""
queryset = VehicleReg.objects.all()
serializer_class = VehicleRecordSerializer
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
ordering = ["-create_time"]
class VehicleUseViewSet(CustomModelViewSet):
"""list: 车辆使用
车辆使用
"""
queryset = VehicleUse.objects.all()
serializer_class = VehicleUseSerializer
select_related_fields = ["create_by", "ticket", "belong_dept"]
filterset_class = VehicleFilterset
def add_info_for_list(self, data):
vehicle_ids = [d["id"] for d in data]
slots = VehicleSlot.objects.filter(vehicle_use__in=vehicle_ids).order_by("vehreg", "vehicle_use", "vdate", "slot")
vehicle_info = {}
for slot in slots:
vehicle_id = slot.vehicle_use.id
if vehicle_id not in vehicle_info:
vehicle_info[vehicle_id] = {
"vdate": slot.vdate.strftime("%Y-%m-%d"), # 格式化日期
"vehreg": slot.vehreg.id,
"vehreg_name": slot.vehreg.name, # 会议室名称
"time_ranges": [], # 存储时间段(如 ["8:00-9:00", "10:00-11:30"]
"current_slots": [], # 临时存储连续的slot用于合并
}
# 检查是否连续当前slot是否紧接上一个slot
current_slots = vehicle_info[vehicle_id]["current_slots"]
if not current_slots or slot.slot == current_slots[-1] + 1:
current_slots.append(slot.slot)
else:
# 如果不连续先把当前连续的slot转换成时间段
if current_slots:
start_time = self._slot_to_time(current_slots[0])
end_time = self._slot_to_time(current_slots[-1] + 1)
vehicle_info[vehicle_id]["time_ranges"].append(f"{start_time}-{end_time}")
current_slots.clear()
current_slots.append(slot.slot)
# 处理最后剩余的连续slot
for info in vehicle_info.values():
if info["current_slots"]:
start_time = self._slot_to_time(info["current_slots"][0])
end_time = self._slot_to_time(info["current_slots"][-1] + 1)
info["time_ranges"].append(f"{start_time}-{end_time}")
info["slots"] = info.pop("current_slots") # 清理临时数据
for item in data:
item.update(vehicle_info.get(item["id"], {}))
return data
@staticmethod
def _slot_to_time(slot):
"""将slot (0-47) 转换为 HH:MM 格式的时间字符串"""
hours = slot // 2
minutes = (slot % 2) * 30
return f"{hours:02d}:{minutes:02d}"
def perform_update(self, serializer):
ins:VehicleUse = self.get_object()
ticket = ins.ticket
if ticket is None or ticket.state.type == 1:
pass
else:
raise ParseError("存在审批单,不允许修改")
if ins.create_by and ins.create_by != self.request.user:
raise ParseError("只允许创建者修改")
return super().perform_update(serializer)
def perform_destroy(self, instance):
if instance.create_by and instance.create_by != self.request.user:
raise ParseError("只允许创建者删除")
ticket = instance.ticket
if ticket is None or ticket.state.type == 1:
pass
else:
raise ParseError("存在审批单,不允许删除")
if ticket:
ticket.delete()
instance.delete()
class VehicleSlotViewSet(CustomListModelMixin, CustomGenericViewSet):
"""list:
会议室预订时段
"""
queryset = VehicleSlot.objects.all()
serializer_class = VehSlotSerializer
filterset_fields = ["vehreg", "vehicle_use", "vdate", "is_inuse"]
class FilerecordViewSet(CustomModelViewSet):
"""list: 文件
@ -256,3 +167,120 @@ class PublicityViewSet(CustomModelViewSet):
ordering = ["-create_time", "number"]
# class PatentInfoViewSet(CustomModelViewSet):
# """list: 专利
# 专利
# """
# queryset = PatentInfo.objects.all()
# serializer_class = PatentInfoSerializer
# filterset_fields = ["name", "author", "type"]
# ordering = ["-create_time", "name", "author", "type"]
# class PapersecretViewSet(CustomModelViewSet):
# """list: 论文申密审批
# 论文申密审批
# """
# queryset = Papersecret.objects.all()
# serializer_class = PaperSeSerializer
# filterset_fields = ["paper_name", "author"]
# ordering = ["-create_time", "paper_name"]
# class PatentRecordViewSet(CustomModelViewSet):
# """list: 专利台账登记
# 专利台账登记
# """
# queryset = PatentRecord.objects.all()
# serializer_class = PatentRecordSerializer
# select_related_fields = ["patent"]
# filterset_fields = ["title", "volume_number","inventors"]
# ordering = ["-create_time", "title", "type"]
# search_fields = ["title", "volume_number", "inventors"]
# def get_queryset(self):
# qs = super().get_queryset()
# patent_type = self.request.query_params.get('patent_type', None)
# if patent_type:
# qs = qs.filter(patent__type=patent_type)
# return qs
# @action(detail=False, methods=['get'])
# def patent_name(self, request):
# """获取专利列表"""
# search = request.query_params.get('search', '')
# queryset = PatentInfo.objects.all()
# if search:
# queryset = queryset.filter(name__icontains=search)
# patents = [{'id': patent.id, 'name': patent.name} for patent in queryset]
# return Response(patents)
# class PlatformViewSet(CustomModelViewSet):
# """list: 平台
# 平台
# """
# queryset = Platform.objects.all()
# serializer_class = PlatformSerializer
# filterset_fields = ["name"]
# ordering = ["create_time", "name"]
# class ProjectViewSet(CustomModelViewSet):
# """list: 项目
# 项目
# """
# queryset = Project.objects.all()
# serializer_class = ProjectSerializer
# filterset_fields = ["name"]
# ordering = ["create_time", "name"]
# class PatentRecordViewSet(CustomModelViewSet):
# """list: 专利台账登记
# 专利台账登记
# """
# queryset = PatentRecord.objects.all()
# serializer_class = ProjectMemberSerializer
# filterset_fields = ["patent", "type"]
# ordering = ["create_time", "patent", "type"]
# class PaperRecordViewSet(CustomModelViewSet):
# """list: 论文台账登记
# 论文台账登记
# """
# queryset = PaperRecord.objects.all()
# serializer_class = ProjectMemberSerializer
# filterset_fields = ["index", "title", "paper_code","paper_type", "authors"]
# ordering = ["create_time", "paper", "type"]
# class ProjectApprovalViewSet(CustomModelViewSet):
# """list: 立项审批表
# 立项审批表
# """
# queryset = ProjectApproval.objects.all()
# serializer_class = ProjectApprovalSerializer
# filterset_fields = ["project_start_date"]
# ordering = ["project_start_date"]
# class ProjectInfoViewSet(CustomModelViewSet):
# """list: 项目信息
# 项目信息
# """
# queryset = ProjectInfo.objects.all()
# serializer_class = ProjectInfoSerializer
# filterset_fields = ["serial_number", "name", "platform", "project_source"]
# ordering = ["serial_number", "name"]

View File

@ -82,8 +82,8 @@ class PmService:
queue.append(input_material_id)
# 返回各Route的任务数量/默认在输入物料时使用target_quantity
return {route.id: step_production.get(route.id, target_quantity if route.material_in.id == target_materialId else 0) for route in route_qs}
# 返回各Route的任务数量
return {route.id: step_production.get(route.id, 0) for route in route_qs}
@classmethod
def cal_real_task_count(cls, count: int, rate_list):
@ -211,18 +211,14 @@ class PmService:
if not gjson_item:
raise ParseError("缺少该产品的生产子图")
rqs = Route.get_routes(routeIds=gjson_item["routes"])
if not rqs.exists():
raise ParseError('未配置工艺路线')
Route.validate_dag(product, rqs)
else:
rqs = Route.get_routes(material=product)
if not rqs.exists():
raise ParseError('未配置工艺路线')
Route.validate_dag(product, rqs, check_final=False)
if not rqs.exists():
raise ParseError('未配置工艺路线')
# 通过出材率校正任务数, out_rate 默认为 100
Route.validate_dag(product, rqs)
r_count_dict = cls.cal_x_task_count(product.id, count, rqs)
# 创建小任务

View File

@ -1,40 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-10 02:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('wf', '0004_workflow_view_path2'),
('pum', '0008_auto_20240731_1829'),
]
operations = [
migrations.CreateModel(
name='SupplierAudit',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='供应商名称')),
('material_name', models.CharField(max_length=100, verbose_name='物料名称')),
('material_cate', models.CharField(max_length=100, verbose_name='物料类别')),
('business_license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_business_license', to='system.file', verbose_name='营业执照')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplieraudit_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('quality_certificate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_quality_certificate', to='system.file', verbose_name='质量证书')),
('survery_form', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_survey_form', to='system.file', verbose_name='调查表')),
('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplieraudit_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 3.2.12 on 2025-12-26 07:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0006_auto_20251215_1645'),
('pum', '0009_supplieraudit'),
]
operations = [
migrations.CreateModel(
name='QuotationApply',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('customer_name', models.CharField(max_length=50, verbose_name='客户名称')),
('product_name', models.CharField(max_length=50, verbose_name='产品名称')),
('contact_person', models.CharField(max_length=50, verbose_name='联系人')),
('contact_phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
('receive_address', models.CharField(blank=True, max_length=100, null=True, verbose_name='收件地址')),
('product_spec_quantity', models.TextField(blank=True, null=True, verbose_name='产品规格/数量')),
('quotation_basis', models.TextField(blank=True, null=True, verbose_name='报价依据')),
('suggested_price_calc', models.TextField(blank=True, null=True, verbose_name='建议价格及计算方式')),
('quotation_range', models.CharField(blank=True, max_length=100, null=True, verbose_name='报价区间')),
('quoter', models.CharField(blank=True, max_length=50, null=True, verbose_name='报价人')),
('apply_date', models.DateField(auto_now_add=True, verbose_name='申请日期')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quo_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,7 +1,6 @@
from django.db import models
from apps.utils.models import CommonBModel, BaseModel, CommonBDModel, CommonADModel
from apps.utils.models import CommonBModel, BaseModel, CommonBDModel
from apps.mtm.models import Material
from apps.wf.models import Ticket
# Create your models here.
@ -16,15 +15,6 @@ class Supplier(CommonBModel):
address = models.CharField('地址', max_length=200, default='', blank=True)
can_outsource = models.BooleanField('是否可外协', default=False)
class SupplierAudit(CommonADModel):
name = models.CharField('供应商名称', max_length=100)
material_name = models.CharField('物料名称', max_length=100)
material_cate = models.CharField('物料类别', max_length=100)
survery_form = models.ForeignKey('system.file', verbose_name='调查表', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_survey_form')
business_license = models.ForeignKey('system.file', verbose_name='营业执照', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_business_license')
quality_certificate = models.ForeignKey('system.file', verbose_name='质量证书', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_quality_certificate')
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True)
class PuPlan(CommonBModel):
"""
@ -107,22 +97,3 @@ class PuPlanItem(CommonBDModel):
null=True, blank=True, related_name='item_puplan')
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
on_delete=models.SET_NULL, null=True, blank=True, related_name='puplan_item_puorder')
class QuotationApply(CommonADModel):
"""
TN:报价申请表
"""
customer_name = models.CharField(max_length=50,verbose_name="客户名称")
product_name = models.CharField(max_length=50,verbose_name="产品名称")
contact_person = models.CharField(max_length=50,verbose_name="联系人")
contact_phone = models.CharField(max_length=20,verbose_name="联系电话", null=True, blank=True)
receive_address = models.CharField(max_length=100,verbose_name="收件地址", null=True, blank=True)
product_spec_quantity = models.TextField(verbose_name="产品规格/数量", null=True, blank=True)
quotation_basis = models.TextField(verbose_name="报价依据", null=True, blank=True)
suggested_price_calc = models.TextField(verbose_name="建议价格及计算方式", null=True, blank=True)
quotation_range = models.CharField(max_length=100,verbose_name="报价区间", null=True, blank=True)
quoter = models.CharField(max_length=50,verbose_name="报价人", null=True, blank=True)
apply_date = models.DateField(verbose_name="申请日期",auto_now_add=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='quo_ticket', null=True, blank=True)

View File

@ -1,14 +1,12 @@
from rest_framework import serializers
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS
from rest_framework.exceptions import ValidationError, ParseError
from rest_framework.exceptions import ValidationError
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem
from apps.mtm.serializers import MaterialSerializer, MaterialSimpleSerializer
from django.db import transaction
from .services import PumService
from apps.wf.serializers import TicketSimpleSerializer
from apps.system.serializers import FileSerializer
class SupplierSerializer(CustomModelSerializer):
@ -141,27 +139,3 @@ class AddSerializer(serializers.Serializer):
label='采购订单ID', queryset=PuOrder.objects.all())
pu_planitems = serializers.PrimaryKeyRelatedField(
label='计划明细ID', queryset=PuPlanItem.objects.all(), many=True)
class SupplierAuditSerializer(CustomModelSerializer):
survery_form_ = FileSerializer(source="survery_form", read_only=True)
business_license_ = FileSerializer(source="business_license", read_only=True)
quality_certificate_ = FileSerializer(source="quality_certificate", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = SupplierAudit
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS_BASE + ['ticket']
def create(self, validated_data):
name = validated_data["name"]
if Supplier.objects.filter(name=name).exists():
raise ParseError('供应商名称已存在')
return super().create(validated_data)
class QuotationApplySerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = QuotationApply
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS

View File

@ -1,9 +1,9 @@
from rest_framework.exceptions import ValidationError
from apps.pum.models import PuOrderItem, PuPlan, PuPlanItem, PuOrder, SupplierAudit
from apps.pum.models import PuOrderItem, PuPlan, PuPlanItem, PuOrder
from django.db.models import F, Sum
from apps.inm.models import MIO, MIOItem
from rest_framework.exceptions import ParseError
from apps.wf.models import Ticket, Transition
class PumService:
@ -95,18 +95,3 @@ class PumService:
puplan.state = PuPlan.PUPLAN_DONE
puplan.save()
def bind_supplieraudit(ticket: Ticket, transition: Transition, new_ticket_data: dict):
ins = SupplierAudit.objects.get(id=new_ticket_data['t_id'])
if ins.ticket and ins.ticket.id != ticket.id:
raise ParseError('重复创建工单')
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'supplier_audit',
't_id': ins.id,
})
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
if ins.ticket is None:
ins.ticket = ticket
ins.save()

View File

@ -1,19 +1,16 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet, SupplierAuditViewSet, QuotationApplyViewSet)
# from apps.pum.views import SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet)
API_BASE_URL = 'api/pum/'
HTML_BASE_URL = 'dhtml/pum/'
router = DefaultRouter()
router.register('supplier', SupplierViewSet, basename='supplier')
router.register('supplieraudit', SupplierAuditViewSet, basename='supplieraudit')
router.register('pu_plan', PuPlanViewSet, basename='pu_plan')
router.register('pu_planitem', PuPlanItemViewSet, basename='pu_planitem')
router.register('pu_order', PuOrderViewSet, basename='pu_order')
router.register('pu_orderitem', PuOrderItemViewSet, basename='pu_orderitem')
router.register('quotation', QuotationApplyViewSet, basename='quotation')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,8 +1,8 @@
from django.shortcuts import render
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer, QuotationApplySerializer,
PuOrderSerializer, PuOrderItemSerializer, AddSerializer, SupplierAuditSerializer)
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer,
PuOrderSerializer, PuOrderItemSerializer, AddSerializer)
from rest_framework.exceptions import ParseError, PermissionDenied
from rest_framework.decorators import action
from rest_framework import serializers
@ -11,8 +11,6 @@ from django.db import transaction
from rest_framework.response import Response
from django.utils import timezone
from apps.pum.services import PumService
from apps.wf.mixins import TicketMixin
from apps.wf.models import Ticket
# Create your views here.
@ -32,23 +30,6 @@ class SupplierViewSet(CustomModelViewSet):
raise ParseError('该供应商存在采购订单不可删除')
instance.delete()
class SupplierAuditViewSet(TicketMixin, CustomModelViewSet):
"""
list: 供应商审核
供应商审核
"""
queryset = SupplierAudit.objects.all()
serializer_class = SupplierAuditSerializer
search_fields = ['name', 'material_name', 'material_cate']
workflow_key = "wf_supplieraudit"
@staticmethod
def add_supplier(ticket: Ticket, transition, new_ticket_data: dict):
supplieraudit = SupplierAudit.objects.get(ticket=ticket)
if Supplier.objects.filter(name=supplieraudit.name).exists():
raise ParseError('供应商名称已存在')
Supplier.objects.create(name=supplieraudit.name)
class PuPlanViewSet(CustomModelViewSet):
"""
@ -210,16 +191,3 @@ class PuOrderItemViewSet(CustomModelViewSet):
item.pu_order = puorder
item.save()
return Response()
class QuotationApplyViewSet(TicketMixin, CustomModelViewSet):
"""
list: 报价申请
报价申请
"""
queryset = QuotationApply.objects.all()
serializer_class = QuotationApplySerializer
filterset_fields = ['product_name', 'customer_name','apply_date', 'quoter']
search_fields = ['product_name', 'customer_name','contact_person']
ordering = ['create_time']
workflow_key = "wf_quotation"

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-26 01:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('qm', '0054_alter_ptest_val_xj'),
]
operations = [
migrations.AlterField(
model_name='ftestitem',
name='ftest',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items_ftest', to='qm.ftest', verbose_name='关联检验'),
),
]

View File

@ -385,7 +385,7 @@ class FtestItem(BaseModel):
TN:检测明细
"""
ftest = models.ForeignKey(
Ftest, verbose_name='关联检验', on_delete=models.CASCADE, related_name='items_ftest')
Ftest, verbose_name='关联检验', on_delete=models.CASCADE)
testitem = models.ForeignKey(
TestItem, verbose_name='质检项目', on_delete=models.CASCADE)
test_equip = models.ForeignKey(Equipment, verbose_name='检测设备', on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -347,8 +347,8 @@ class FtestItemProcessSerializer(CustomModelSerializer):
class FtestProcessSerializer(CustomModelSerializer):
test_user_name = serializers.CharField(
source='test_user.name', read_only=True)
ftestitems = FtestItemProcessSerializer(many=True)
ftestdefects = FtestDefectSerializer(many=True)
ftestitems = FtestItemProcessSerializer(label='检验明细', many=True)
ftestdefects = FtestDefectSerializer(label='缺陷明细', many=True)
class Meta:
model = Ftest
@ -430,8 +430,4 @@ class FtestProcessSerializer(CustomModelSerializer):
instance.defect_main = defect_main
instance.is_ok = is_ok
instance.save()
return instance
class FtestProcessListSerializer(FtestProcessSerializer):
ftestitems = FtestItemProcessSerializer(source='items_ftest', many=True)
ftestdefects = FtestDefectSerializer(source='defects_ftest', many=True)
return instance

View File

@ -157,43 +157,6 @@ def ftestwork_submit(ins:FtestWork, user: User):
# 触发批次统计分析
ana_batch_thread(xbatchs=[ins.batch])
def ftestwork_revert(ins: FtestWork):
wm:WMaterial = ins.wm
if wm and ins.need_update_wm:
fwd_qs = FtestworkDefect.objects.filter(ftestwork=ins)
for item in fwd_qs:
item:FtestworkDefect = item
if item.count > 0:
wm.count = wm.count + item.count
wm.save()
wmstate = WMaterial.WM_OK
if item.defect.okcate == Defect.DEFECT_NOTOK:
wmstate = WMaterial.WM_NOTOK
try:
wmx = WMaterial.objects.get(
material=wm.material,
batch=wm.batch,
mgroup=wm.mgroup,
belong_dept=wm.belong_dept,
state=wmstate,
notok_sign=None,
defect=item.defect,
)
except Exception as e:
raise ParseError(f'找不到{item.defect.name}的车间库存: {str(e)}')
wmx.count = wmx.count - item.count
if wmx.count < 0:
raise ParseError("数量不足,撤回失败")
wmx.save()
else:
raise ParseError("该检验工作不支持撤回")
ins.submit_user = None
ins.submit_time = None
ins.save()
# 触发批次统计分析
ana_batch_thread(xbatchs=[ins.batch])
def bind_ftestwork(ticket: Ticket, transition, new_ticket_data: dict):
ins = FtestWork.objects.get(id=new_ticket_data['t_id'])
if ins.submit_time is not None:

View File

@ -17,7 +17,7 @@ from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.wpm.models import SfLog
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter, QctFilter, FtestFilter
from django.db import transaction
from apps.qm.services import ftestwork_submit, ftestwork_revert
from apps.qm.services import ftestwork_submit
from apps.wpm.services_2 import ana_batch_thread
from apps.wf.models import State
# Create your views here.
@ -327,20 +327,4 @@ class FtestWorkViewSet(CustomModelViewSet):
ftestwork_submit(ins, request.user)
else:
raise ParseError('该检验工作已提交')
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'ftestwork.submit'}, serializer_class=Serializer)
@transaction.atomic
def revert(self, request, *args, **kwargs):
"""撤回检验工作
撤回检验工作
"""
ins:FtestWork = self.get_object()
if ins.submit_time:
if self.request.user != ins.submit_user:
raise ParseError('只能由提交人撤回')
ftestwork_revert(ins)
else:
raise ParseError('该检验工作未提交')
return Response()

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class RemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.rem'

View File

@ -1,41 +0,0 @@
# Generated by Django 3.2.12 on 2026-01-06 01:49
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Project',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.TextField(verbose_name='项目名称')),
('description', models.TextField(verbose_name='项目介绍')),
('start_date', models.DateField(verbose_name='开始日期')),
('end_date', models.DateField(verbose_name='结束日期')),
('participants', models.TextField(blank=True, null=True, verbose_name='项目成员')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('files', models.ManyToManyField(blank=True, to='system.File', verbose_name='附件')),
('leader', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='项目负责人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

Some files were not shown because too many files have changed in this diff Show More