Compare commits

...

42 Commits

Author SHA1 Message Date
zty 388e225108 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-21 14:10:44 +08:00
zty 25ee92602b add : ofm-create-model-patent 新建专利申请 curd 2025-10-21 14:10:43 +08:00
caoqianming 241df0beca feat: mio 添加查询条件 2025-10-20 16:33:46 +08:00
caoqianming 5ea7980a1b feat: mloguser添加字段 2025-10-20 10:09:38 +08:00
caoqianming a3416cfc0d feat: 修改mroombooking表约束 2025-10-17 14:51:13 +08:00
caoqianming 2ca47b8949 feat: base 获取流转时排序按attribute_type倒序 2025-10-17 12:32:18 +08:00
caoqianming 10792d090c Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-16 16:30:15 +08:00
caoqianming bfc8454ac7 feat: base workflow添加view_path2字段 2025-10-16 16:30:14 +08:00
zty 02b14ec2c6 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-16 11:18:26 +08:00
zty acb4c802e4 feat: ofm-services 修改绑定反存的接口 2025-10-16 11:18:25 +08:00
caoqianming 260c9893eb feat: base 获取流转时排序按attribute_type2 2025-10-16 10:55:33 +08:00
caoqianming 666a9c169c Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-16 10:05:13 +08:00
caoqianming b869521221 feat: base 获取流转时排序按attribute_type 2025-10-16 10:05:12 +08:00
zty 99d8144bdf feat: ofm-views 修改文件列表排序 2025-10-15 15:09:09 +08:00
zty 8a87ba356e feat: ofm-BorrowRecord - 档案借阅反存ticket-data 2025-10-15 14:10:33 +08:00
zty 830bf18132 feat: ofm-service-vehicle 用车管理反存 实际归还里程树 2025-10-14 16:08:16 +08:00
zty b52e90a11f Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-14 14:58:11 +08:00
zty ee67e6896a feat: ofm-service 反向存储ticket_data 到 Lendingseal 2025-10-14 14:58:11 +08:00
caoqianming 54f8b82c98 feat: mio增加筛选条件inout_date 2025-10-14 14:18:27 +08:00
caoqianming 6125139fbf Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-14 13:46:53 +08:00
caoqianming 2169bbea68 feat: do_out和do_in在处理into_wm不应直接忽略 2025-10-14 13:46:53 +08:00
zty 6dfab46b4d Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-14 10:21:52 +08:00
zty 477976f86c feat: ofm-views 修改时间倒叙 2025-10-14 10:21:50 +08:00
caoqianming c37ff77eda Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-13 15:45:04 +08:00
caoqianming 1aa7d51769 feat: material添加create_time的排序条件 2025-10-13 15:45:04 +08:00
zty e06cc8c38e fix : ofm-models-publicity 修改记录编号自动生成 2025-10-13 14:26:40 +08:00
zty b93024ca44 fix : ofm-models-publicity 修改记录编号自动生成 2025-10-13 14:18:03 +08:00
zty 7d87c79dd1 feat: ofm-service-pulicity 反存ticket_data 到 obj 2025-10-13 14:04:40 +08:00
zty 9f030ece6d feat: ofm-service-pulicity 反存ticket_data 到 obj 2025-10-13 13:50:04 +08:00
zty 67f9cbb700 feat: ofm--Alter field publicity_opinion on publicity 2025-10-13 09:01:44 +08:00
zty 8fe2b8ca48 feat: ofm-models 修改字段属性 2025-10-11 14:28:09 +08:00
zty 7732ddc88e Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-11 11:32:06 +08:00
zty 6713693c6c feat: ofm-models -pulicity 修改字段属性 2025-10-11 11:32:05 +08:00
caoqianming 20604ef7cb fix: work_start_time 对光子的兼容 2025-10-11 10:28:07 +08:00
caoqianming eb2deb02c2 fix: work_start_time 可不填2 2025-10-11 10:22:19 +08:00
caoqianming f5f6c136d9 fix: work_start_time 可不填 2025-10-11 10:21:22 +08:00
caoqianming 8eee09678a fix: handle_date和shift可传 2025-10-11 10:16:13 +08:00
caoqianming efd40d1d32 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-11 10:11:19 +08:00
caoqianming 3ab9682b07 fix: work_start_time 可不填 2025-10-11 10:11:18 +08:00
zty 1983f7b121 feat: 修改 ofm-moedels - pulicity字段为空值 2025-10-11 09:23:33 +08:00
zty cec6837d00 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-10-10 16:32:21 +08:00
zty e214c6115a feat: ofm-models 增加字段 2025-10-10 16:32:20 +08:00
24 changed files with 480 additions and 156 deletions

View File

@ -62,7 +62,7 @@ class Mpoint(CommonBModel):
cal_coefficient = models.FloatField("计算系数", null=True, blank=True) cal_coefficient = models.FloatField("计算系数", null=True, blank=True)
mpoint_from = models.ForeignKey("self", verbose_name="来源自采测点", related_name="mp_mpoint_from", on_delete=models.SET_NULL, null=True, blank=True) mpoint_from = models.ForeignKey("self", verbose_name="来源自采测点", related_name="mp_mpoint_from", on_delete=models.SET_NULL, null=True, blank=True)
cal_related_mgroup_running = models.PositiveSmallIntegerField("与工段运行状态的关联", default=10, choices=[(10, "运行时统计")], null=True, blank=True) cal_related_mgroup_running = models.PositiveSmallIntegerField("与工段运行状态的关联", default=10, choices=[(10, "不涉及"), (20, "运行时统计")], null=True, blank=True)
@classmethod @classmethod
def cache_key(cls, code: str): def cache_key(cls, code: str):

View File

@ -167,11 +167,11 @@ def get_first_stlog_time_from_duration(mgroup:Mgroup, dt_start:datetime, dt_end:
if st: if st:
return st, "ending" return st, "ending"
st = st_qs.filter(start_time__gte=dt_start, start_time__lte=dt_end, duration_sec__lte=600).order_by("start_time").last() st = st_qs.filter(start_time__gte=dt_start, start_time__lte=dt_end, duration_sec__gte=600).order_by("start_time").last()
if st: if st:
return st, "start" return st, "start"
st = st_qs.filter(end_time__gte=dt_start, end_time__lte=dt_end, duration_sec__lte=600).order_by("end_time").first() st = st_qs.filter(end_time__gte=dt_start, end_time__lte=dt_end, duration_sec__gte=600).order_by("end_time").first()
if st: if st:
return st, "end" return st, "end"
@ -213,7 +213,7 @@ def cal_mpointstat_hour(mpointId: str, year: int, month: int, day: int, hour: in
val = abs(first_val - last_val) val = abs(first_val - last_val)
else: else:
xtype = "normal" xtype = "normal"
if mpointfrom and mpoint.cal_related_mgroup_running == 10: if mpointfrom and mpoint.cal_related_mgroup_running == 20:
stlog, xtype = get_first_stlog_time_from_duration(mpoint.mgroup, dt, dt_hour_n) stlog, xtype = get_first_stlog_time_from_duration(mpoint.mgroup, dt, dt_hour_n)

View File

@ -37,7 +37,9 @@ class MioFilter(filters.FilterSet):
"item_mio__test_user": ["isnull"], "item_mio__test_user": ["isnull"],
"item_mio__w_mioitem__number": ["exact"], "item_mio__w_mioitem__number": ["exact"],
"mgroup": ["exact"], "mgroup": ["exact"],
"item_mio__batch": ["exact"] "item_mio__batch": ["exact"],
"inout_date": ["gte", "lte", "exact"],
"belong_dept": ["exact"]
} }
def filter_materials__type(self, queryset, name, value): def filter_materials__type(self, queryset, name, value):

View File

@ -10,7 +10,7 @@ from apps.wpmw.models import Wpr
from apps.qm.models import Ftest, Defect from apps.qm.models import Ftest, Defect
from django.db.models import Count, Q from django.db.models import Count, Q
def do_out(item: MIOItem): def do_out(item: MIOItem, is_reverse: bool = False):
""" """
生产领料到车间 生产领料到车间
""" """
@ -23,8 +23,6 @@ def do_out(item: MIOItem):
mgroup = mio.mgroup mgroup = mio.mgroup
do_user = mio.do_user do_user = mio.do_user
material:Material = item.material material:Material = item.material
if material.into_wm is False: # 用于混料的原料不与车间库存交互, 这个是配置项目
return
# 获取defect # 获取defect
defect:Defect = None defect:Defect = None
@ -98,22 +96,24 @@ def do_out(item: MIOItem):
else: else:
mb.save() mb.save()
if material.into_wm:
# 领到车间库存(或工段) # 领到车间库存(或工段)
wm, new_create = WMaterial.objects.get_or_create( wm, new_create = WMaterial.objects.get_or_create(
batch=xbatch, material=xmaterial, batch=xbatch, material=xmaterial,
belong_dept=belong_dept, mgroup=mgroup, belong_dept=belong_dept, mgroup=mgroup,
state=WMaterial.WM_OK, defect=defect) state=WMaterial.WM_OK, defect=defect)
if new_create: if new_create:
wm.create_by = do_user wm.create_by = do_user
wm.batch_ofrom = mb.batch if mb else None wm.batch_ofrom = mb.batch if mb else None
wm.material_ofrom = mb.material if mb else None wm.material_ofrom = mb.material if mb else None
wm.count = wm.count + item.count wm.count = wm.count + item.count
wm.update_by = do_user wm.update_by = do_user
wm.save() wm.save()
# 开始变动wpr # 开始变动wpr
if xmaterial.tracking == Material.MA_TRACKING_SINGLE: if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
if material.into_wm is False:
raise ParseError("追踪单个物料不支持不进行车间库存的操作")
mioitemws = MIOItemw.objects.filter(mioitem=item) mioitemws = MIOItemw.objects.filter(mioitem=item)
if mioitemws.count() != item.count: if mioitemws.count() != item.count:
raise ParseError("出入库与明细数量不一致,操作失败") raise ParseError("出入库与明细数量不一致,操作失败")
@ -141,8 +141,7 @@ def do_in(item: MIOItem):
mgroup = mio.mgroup mgroup = mio.mgroup
do_user = mio.do_user do_user = mio.do_user
material = item.material material = item.material
if material.into_wm is False: # 根据配置不进行入车间库存的处理
return
action_list = [] action_list = []
mias = MIOItemA.objects.filter(mioitem=item) mias = MIOItemA.objects.filter(mioitem=item)
is_zhj = False # 是否组合件入仓库 is_zhj = False # 是否组合件入仓库
@ -177,38 +176,39 @@ def do_in(item: MIOItem):
raise ParseError("存在非正数!") raise ParseError("存在非正数!")
xbatchs.append(xbatch) xbatchs.append(xbatch)
if material.into_wm:
wm_qs = WMaterial.objects.filter( wm_qs = WMaterial.objects.filter(
batch=xbatch, batch=xbatch,
material=xmaterial, material=xmaterial,
belong_dept=belong_dept, belong_dept=belong_dept,
mgroup=mgroup, mgroup=mgroup,
defect=defect, defect=defect,
state=WMaterial.WM_OK) state=WMaterial.WM_OK)
count_x = wm_qs.count() count_x = wm_qs.count()
if count_x == 1: if count_x == 1:
wm = wm_qs.first() wm = wm_qs.first()
elif count_x == 0: elif count_x == 0:
raise ParseError( raise ParseError(
f'{str(xmaterial)}-{xbatch}-批次库存不存在!') f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
else: else:
raise ParseError( raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!') f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
# 扣减车间库存
new_count = wm.count - xcount
if new_count >= 0:
wm.count = new_count
wm.update_by = do_user
wm.save()
else:
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept
if production_dept is None:
production_dept = wm_production_dept
elif wm_production_dept and production_dept != wm_production_dept:
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不属于同一车间')
# 扣减车间库存
new_count = wm.count - xcount
if new_count >= 0:
wm.count = new_count
wm.update_by = do_user
wm.save()
else:
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept
if production_dept is None:
production_dept = wm_production_dept
elif wm_production_dept and production_dept != wm_production_dept:
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不属于同一车间')
# 增加mb # 增加mb
if not is_zhj: if not is_zhj:
mb, _ = MaterialBatch.objects.get_or_create( mb, _ = MaterialBatch.objects.get_or_create(
@ -231,6 +231,8 @@ def do_in(item: MIOItem):
# 开始变动wpr # 开始变动wpr
if xmaterial.tracking == Material.MA_TRACKING_SINGLE: if xmaterial.tracking == Material.MA_TRACKING_SINGLE:
if material.into_wm is False:
raise ParseError("追踪单个物料不支持不进行车间库存的操作")
mioitemws = MIOItemw.objects.filter(mioitem=item) mioitemws = MIOItemw.objects.filter(mioitem=item)
if mioitemws.count() != item.count: if mioitemws.count() != item.count:
raise ParseError("出入库与明细数量不一致,操作失败") raise ParseError("出入库与明细数量不一致,操作失败")

View File

@ -39,7 +39,7 @@ class MaterialViewSet(CustomModelViewSet):
ordering = ['name', 'model', 'specification', ordering = ['name', 'model', 'specification',
'type', 'process', 'process__sort', 'sort', 'id', 'number'] 'type', 'process', 'process__sort', 'sort', 'id', 'number']
ordering_fields = ['name', 'model', 'specification', ordering_fields = ['name', 'model', 'specification',
'type', 'process', 'process__sort', 'sort', 'id', 'number'] 'type', 'process', 'process__sort', 'sort', 'id', 'number', 'create_time']
def perform_destroy(self, instance): def perform_destroy(self, instance):
from apps.inm.models import MaterialBatch from apps.inm.models import MaterialBatch

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

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-10-13 01:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0020_auto_20251011_1427'),
]
operations = [
migrations.AlterField(
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

@ -44,9 +44,6 @@ class MroomSlot(BaseModel):
mdate = models.DateField('会议日期', db_index=True) mdate = models.DateField('会议日期', db_index=True)
slot = models.PositiveIntegerField('时段', help_text='0-47') slot = models.PositiveIntegerField('时段', help_text='0-47')
is_inuse = models.BooleanField('是否占用', default=True) is_inuse = models.BooleanField('是否占用', default=True)
class Meta:
unique_together = ('mroom', 'mdate', 'slot', 'is_inuse')
# class Seal(BaseModel): # class Seal(BaseModel):
@ -122,12 +119,12 @@ class BorrowRecord(CommonBDModel):
class Publicity(CommonBDModel): class Publicity(CommonBDModel):
"""TN: 公示栏""" """TN: 公示栏"""
number = models.CharField('记录编号', max_length=50) number = models.CharField('记录编号', max_length=50, blank=True, null=True)
title = models.CharField('送审稿件标题', max_length=100) title = models.CharField('送审稿件标题', max_length=100)
participants = models.CharField('所有撰稿人', max_length=50) participants = models.CharField('所有撰稿人', max_length=50)
pub_dept = models.CharField('部室/研究院', null=True, blank=True, max_length=50) pub_dept = models.CharField('部室/研究院', null=True, blank=True, max_length=50)
pfile = models.CharField('稿件路径', null=True, blank=True, max_length=100) pfile = models.CharField('稿件路径', null=True, blank=True, max_length=100)
level = models.JSONField('用途', default=list, help_text=['重要', '一般', '非涉密']) level = models.JSONField('涉密等级', default=list, help_text=['重要', '一般', '非涉密'])
content = models.JSONField('稿件内容涉及', default=list, help_text=[ content = models.JSONField('稿件内容涉及', default=list, help_text=[
"武器装备科研生产综合事项", "武器装备科研生产综合事项",
"其它" "其它"
@ -137,59 +134,58 @@ class Publicity(CommonBDModel):
channel = models.JSONField('发布渠道', default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它']) channel = models.JSONField('发布渠道', default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'])
other_channel = models.CharField('其它渠道', max_length=50, blank=True, null=True) other_channel = models.CharField('其它渠道', max_length=50, blank=True, null=True)
report_name = models.CharField('报道名称', max_length=50, blank=True, null=True) report_name = models.CharField('报道名称', max_length=50, blank=True, null=True)
review = models.JSONField('第一撰稿人自审', default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布']) review = models.JSONField('第一撰稿人自审', default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True,blank=True)
dept_opinion = models.JSONField('部门负责人意见', default=list, help_text=['同意', '不同意']) dept_opinion = models.JSONField('部门负责人意见', default=list, help_text=['同意', '不同意'], null=True, blank=True)
secret_period = models.CharField('秘密期限', max_length=50, blank=True, null=True)
dept_opinion_review = models.CharField('部门审查意见', max_length=100, blank=True, null=True) dept_opinion_review = models.CharField('部门审查意见', max_length=100, blank=True, null=True)
publicity_opinion = models.JSONField('宣传统战部审查意见', default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道']) publicity_opinion = models.CharField('宣传报道意见', max_length=100, blank=True, null=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='publicity_ticket', null=True, blank=True, db_constraint=False) on_delete=models.SET_NULL, related_name='publicity_ticket', null=True, blank=True, db_constraint=False)
# 记录编号自动生成 # 记录编号自动生成
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.number: if not self.number:
with transaction.atomic(): last_number = self.__class__.objects.filter(number__startswith=f"GXKG-{datetime.now().year}-").order_by('-number').first()
# 加行锁,防止并发取到相同 last_number if last_number:
last_number = self.__class__.objects.select_for_update(skip_locked=True).order_by('-id').first() # skip_locked 锁定行,避免并发冲突 try:
if last_number: last_num = int(last_number.number.split('-')[-1])
try: except ValueError:
last_num = int(last_number.number.split('-')[-1]) last_num = 0
except ValueError: else:
last_num = 0 last_num =0
new_num= last_num + 1 # 格式化编号,带补零
else: self.number = f"GXKG-{datetime.now().year}-{last_num+1:02d}"
new_num = 1
# 格式化编号,带补零
self.number = f"(GXKG-{datetime.now().year}-{new_num:02d})"
super().save(*args, **kwargs) super().save(*args, **kwargs)
# class PatentInfo(CommonBDModel): class PatentInfo(CommonBDModel):
# """TN: 专利申密审批表单样式""" """TN: 专利申密审批表单样式"""
# PATENT_TYPE_CHOICES = ( PATENT_TYPE_CHOICES = (
# ('invention', '发明专利'), ('invention', '发明专利'),
# ('utility', '实用新型专利'), ('utility', '实用新型专利'),
# ('design', '外观设计专利'), ('design', '外观设计专利'),
# ) )
# APPLY_AREAS = ( APPLY_AREAS = (
# ('Domestic', '国内申请'), ('Domestic', '国内申请'),
# ('Foreign', '国外申请'), ('Foreign', '国外申请'),
# (' PCT', 'PCT申请'), (' PCT', 'PCT申请'),
# ) )
# name = models.CharField('拟申请专利名称', max_length=100) name = models.CharField('拟申请专利名称', max_length=100)
# author = models.CharField('发明人(设计人)', max_length=100) author = models.CharField('发明人(设计人)', max_length=100)
# type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention') type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention')
# is_public = models.BooleanField('是否公开', default=False) is_public = models.BooleanField('是否公开', default=False)
# area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic') area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic')
# identified = models.BooleanField('是否进行过科技成果鉴定', default=False) identified = models.BooleanField('是否进行过科技成果鉴定', default=False)
# published_article = models.BooleanField('是否发表过文章', default=False) published_article = models.BooleanField('是否发表过文章', default=False)
# exhibited = models.BooleanField('是否参与过展会展出', default=False) exhibited = models.BooleanField('是否参与过展会展出', default=False)
# applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False) applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False)
# participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False) participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False)
# tech_background_pages = models.PositiveIntegerField('技术背景材料页数', null=True, blank=True) tech_background_pages = models.PositiveIntegerField('技术背景材料页数', null=True, blank=True)
# tech_disclosure_pages = models.PositiveIntegerField('技术交底材料页数', null=True, blank=True) tech_disclosure_pages = models.PositiveIntegerField('技术交底材料页数', null=True, blank=True)
# novelty_report_pages = models.PositiveIntegerField('查新检索报告页数', null=True, blank=True) novelty_report_pages = models.PositiveIntegerField('查新检索报告页数', null=True, blank=True)
# diagrams_or_photos_pages = models.PositiveIntegerField('图/照片页数或张数', null=True, blank=True) diagrams_or_photos_pages = models.PositiveIntegerField('图/照片页数或张数', null=True, blank=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='patentInfo_ticket', null=True, blank=True, db_constraint=False)
# class PaperOfm(CommonADModel): # class PaperOfm(CommonADModel):
# """TN: 论文申密审批表单""" # """TN: 论文申密审批表单"""

View File

@ -1,5 +1,5 @@
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity) from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity, PatentInfo)
# Publicity, PatentInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo) # Publicity, PatetInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers from rest_framework import serializers
from django.db import transaction from django.db import transaction
@ -37,10 +37,10 @@ class MroomBookingSerializer(CustomModelSerializer):
for slot in slots: for slot in slots:
if slot < 0 or slot > 47: if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围") raise ParseError("时段索引超出范围")
try: ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True) if ms_exists:
except Exception as e: raise ParseError("时段已预订,请刷新重选")
raise ParseError(f"时段已预订,请刷新重选-{e}") MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking return booking
def update(self, instance, validated_data): def update(self, instance, validated_data):
@ -52,10 +52,10 @@ class MroomBookingSerializer(CustomModelSerializer):
for slot in slots: for slot in slots:
if slot < 0 or slot > 47: if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围") raise ParseError("时段索引超出范围")
try: ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True) if ms_exists:
except Exception as e: raise ParseError("时段已预订,请刷新重选")
raise ParseError(f"时段已预订,请刷新重选-{e}") MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking return booking
@ -122,11 +122,11 @@ class PublicitySerializer(CustomModelSerializer):
read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
# class PatentInfoSerializer(CustomModelSerializer): class PatentInfoSerializer(CustomModelSerializer):
# class Meta: class Meta:
# model = PatentInfo model = PatentInfo
# fields = '__all__' fields = '__all__'
# read_only_fields = EXCLUDE_FIELDS read_only_fields = EXCLUDE_FIELDS
# class PaperSerializer(CustomModelSerializer): # class PaperSerializer(CustomModelSerializer):

View File

@ -1,7 +1,7 @@
from apps.wf.models import Ticket from apps.wf.models import Ticket
# TicketFlow, Transition, Workflow, CustomField, State, # TicketFlow, Transition, Workflow, CustomField, State,
from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord, Publicity, MroomBooking, MroomSlot from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord, Publicity, MroomBooking, MroomSlot, PatentInfo
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
@ -31,6 +31,7 @@ def mroombooking_reject(ticket: Ticket, transition, new_ticket_data: dict):
def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict): def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
ins = LendingSeal.objects.get(id=new_ticket_data['t_id']) ins = LendingSeal.objects.get(id=new_ticket_data['t_id'])
ins.actual_return_date = None
ticket_data = ticket.ticket_data ticket_data = ticket.ticket_data
ticket_data.update({ ticket_data.update({
't_model': 'LendingSeal', 't_model': 'LendingSeal',
@ -42,6 +43,19 @@ def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
if ins.ticket is None: if ins.ticket is None:
ins.ticket = ticket ins.ticket = ticket
ins.save() ins.save()
# 如果驳回到开始状态
def lending_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
obj = LendingSeal.objects.get(id=new_ticket_data['t_id'])
except LendingSeal.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']}
for k, v in data_save.items():
setattr(obj, k, v)
obj.save()
def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict): def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
ins = Vehicle.objects.get(id=new_ticket_data['t_id']) ins = Vehicle.objects.get(id=new_ticket_data['t_id'])
@ -50,6 +64,8 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
't_model': 'Vehicle', 't_model': 'Vehicle',
't_id': ins.id, 't_id': ins.id,
}) })
ins.actual_km = None
ins.end_time = None
ticket.ticket_data = ticket_data ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by ticket.create_by = ins.create_by
ticket.save() ticket.save()
@ -57,11 +73,16 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
ins.ticket = ticket ins.ticket = ticket
ins.save() ins.save()
def vehicle_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
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']}
# def update_vehicle_endkm(ticket: Ticket, transition, new_ticket_data: dict): for k, v in data_save.items():
# ins = Vehicle.objects.get(id=new_ticket_data['t_id']) setattr(obj, k, v)
# ins.end_km = new_ticket_data['end_km'] obj.save()
# ins.save()
def bind_file(ticket: Ticket, transition, new_ticket_data: dict): def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
ins = BorrowRecord.objects.get(id=new_ticket_data['t_id']) ins = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
@ -70,13 +91,24 @@ def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
't_model': 'BorrowRecord', 't_model': 'BorrowRecord',
't_id': ins.id, 't_id': ins.id,
}) })
ins.return_date = None
ticket.ticket_data = ticket_data ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by ticket.create_by = ins.create_by
ticket.save() ticket.save()
if ins.ticket is None: if ins.ticket is None:
ins.ticket = ticket ins.ticket = ticket
ins.save() ins.save()
def file_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
obj = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
except BorrowRecord.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']}
for k, v in data_save.items():
setattr(obj, k, v)
obj.save()
def bind_publicity(ticket: Ticket, transition, new_ticket_data: dict): def bind_publicity(ticket: Ticket, transition, new_ticket_data: dict):
ins = Publicity.objects.get(id=new_ticket_data['t_id']) ins = Publicity.objects.get(id=new_ticket_data['t_id'])
@ -85,6 +117,36 @@ def bind_publicity(ticket: Ticket, transition, new_ticket_data: dict):
't_model': 'publicity', 't_model': 'publicity',
't_id': ins.id, 't_id': ins.id,
}) })
ins.dept_opinion = None
ins.secret_period = None
ins.dept_opinion_review = None
ins.publicity_opinion = None
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
if ins.ticket is None:
ins.ticket = ticket
ins.save()
def save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
obj = Publicity.objects.get(id=new_ticket_data['t_id'])
except Publicity.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']}
for k, v in data_save.items():
setattr(obj, k, v)
obj.save()
def bind_patent(ticket: Ticket, transition, new_ticket_data: dict):
ins = PatentInfo.objects.get(id=new_ticket_data['t_id'])
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'patent',
't_id': ins.id,
})
ticket.ticket_data = ticket_data ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by ticket.create_by = ins.create_by
ticket.save() ticket.save()

View File

@ -1,8 +1,9 @@
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet, FileborrowViewSet, PublicityViewSet) from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet,
FileborrowViewSet, PublicityViewSet, PatentInfoViewSet)
# SealModelViewSet, # SealModelViewSet,
# , PublicityViewSet, PatentInfoViewSet, PaperViewSet, PlatformViewSet, # , PublicityViewSet, , PaperViewSet, PlatformViewSet,
# ProjectViewSet, PatentRecordViewSet, PaperRecordViewSet, ProjectApprovalViewSet, ProjectInfoViewSet) # ProjectViewSet, PatentRecordViewSet, PaperRecordViewSet, ProjectApprovalViewSet, ProjectInfoViewSet)
API_BASE_URL = 'api/ofm/' API_BASE_URL = 'api/ofm/'
@ -18,7 +19,7 @@ router.register('vehicle', VehicleViewSet, basename='vehicle')
router.register('filerecord', FilerecordViewSet, basename='filerecord') router.register('filerecord', FilerecordViewSet, basename='filerecord')
router.register('fileborrow', FileborrowViewSet, basename='fileborrow') router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
router.register('publicity', PublicityViewSet, basename='publicity') router.register('publicity', PublicityViewSet, basename='publicity')
# router.register('patentinfo', PatentInfoViewSet, basename='patentinfo') router.register('patentinfo', PatentInfoViewSet, basename='patentinfo')
# router.register('paper', PaperViewSet, basename='paper') # router.register('paper', PaperViewSet, basename='paper')
# router.register('platform', PlatformViewSet, basename='platform') # router.register('platform', PlatformViewSet, basename='platform')
# router.register('project', ProjectViewSet, basename='project') # router.register('project', ProjectViewSet, basename='project')

View File

@ -1,8 +1,9 @@
from django.shortcuts import render from django.shortcuts import render
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity, PatentInfo
# Publicity, PatentInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo) # Publicity, , PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer, VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,) from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer,
VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer, PatentInfoSerializer)
# ,SealSerializer, # ,SealSerializer,
# LendingSealSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer, # LendingSealSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,
# PatentInfoSerializer, PaperSerializer, PlatformSerializer, ProjectSerializer, ProjectMemberSerializer, PaperRecordSerializer, ProjectApprovalSerializer, ProjectInfoSerializer) # PatentInfoSerializer, PaperSerializer, PlatformSerializer, ProjectSerializer, ProjectMemberSerializer, PaperRecordSerializer, ProjectApprovalSerializer, ProjectInfoSerializer)
@ -123,7 +124,7 @@ class LendingSealViewSet(CustomModelViewSet):
queryset = LendingSeal.objects.all() queryset = LendingSeal.objects.all()
serializer_class = LendingSealSerializer serializer_class = LendingSealSerializer
filterset_class = SealFilter filterset_class = SealFilter
ordering = ["create_time"] ordering = ["-create_time"]
data_filter = True data_filter = True
@ -134,7 +135,7 @@ class VehicleViewSet(CustomModelViewSet):
""" """
queryset = Vehicle.objects.all() queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer serializer_class = VehicleSerializer
ordering = ["create_time"] ordering = ["-create_time"]
class FilerecordViewSet(CustomModelViewSet): class FilerecordViewSet(CustomModelViewSet):
@ -145,7 +146,7 @@ class FilerecordViewSet(CustomModelViewSet):
queryset = FileRecord.objects.all() queryset = FileRecord.objects.all()
serializer_class = FileRecordSerializer serializer_class = FileRecordSerializer
filterset_fields = [ "name", "number"] filterset_fields = [ "name", "number"]
ordering = ["create_time", "number", "name"] ordering = ["-create_time", "number", "name"]
class FileborrowViewSet(CustomModelViewSet): class FileborrowViewSet(CustomModelViewSet):
@ -156,7 +157,7 @@ class FileborrowViewSet(CustomModelViewSet):
queryset = BorrowRecord.objects.all() queryset = BorrowRecord.objects.all()
serializer_class = BorrowRecordSerializer serializer_class = BorrowRecordSerializer
filterset_class = BorrowRecordFilter filterset_class = BorrowRecordFilter
ordering = ["create_time"] ordering = ["-create_time"]
class PublicityViewSet(CustomModelViewSet): class PublicityViewSet(CustomModelViewSet):
@ -167,18 +168,18 @@ class PublicityViewSet(CustomModelViewSet):
queryset = Publicity.objects.all() queryset = Publicity.objects.all()
serializer_class = PublicitySerializer serializer_class = PublicitySerializer
filterset_fields = ["title","number"] filterset_fields = ["title","number"]
ordering = ["create_time", "number"] ordering = ["-create_time", "number"]
# class PatentInfoViewSet(CustomModelViewSet): class PatentInfoViewSet(CustomModelViewSet):
# """list: 专利 """list: 专利
# 专利 专利
# """ """
# queryset = PatentInfo.objects.all() queryset = PatentInfo.objects.all()
# serializer_class = PatentInfoSerializer serializer_class = PatentInfoSerializer
# filterset_fields = ["name", "author", "type"] filterset_fields = ["name", "author", "type"]
# ordering = ["create_time", "name", "author", "type"] ordering = ["-create_time", "name", "author", "type"]
# class PaperViewSet(CustomModelViewSet): # class PaperViewSet(CustomModelViewSet):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-10-16 08:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
]
operations = [
migrations.AddField(
model_name='workflow',
name='view_path2',
field=models.TextField(blank=True, null=True, verbose_name='前端自定义页面路径2'),
),
]

View File

@ -22,6 +22,7 @@ class Workflow(CommonAModel):
content_template = models.CharField( content_template = models.CharField(
'内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}') '内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
view_path = models.TextField('前端自定义页面路径', null=True, blank=True) view_path = models.TextField('前端自定义页面路径', null=True, blank=True)
view_path2 = models.TextField('前端自定义页面路径2', null=True, blank=True)
class Meta: class Meta:
verbose_name = '工作流' verbose_name = '工作流'

View File

@ -77,7 +77,7 @@ class WfService(object):
""" """
获取状态可执行的操作 获取状态可执行的操作
""" """
return Transition.objects.filter(is_deleted=False, source_state=state).all() return Transition.objects.filter(is_deleted=False, source_state=state).all().order_by("-attribute_type", "-id")
@classmethod @classmethod
def get_ticket_steps(cls, ticket: Ticket): def get_ticket_steps(cls, ticket: Ticket):

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.12 on 2025-10-20 02:09
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('em', '0022_equipment_cd_req_addr'),
('wpm', '0123_mlogbdefect_count_has'),
]
operations = [
migrations.AddField(
model_name='mloguser',
name='equipment',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mloguser_equipment', to='em.equipment', verbose_name='生产设备'),
),
migrations.AddField(
model_name='mloguser',
name='work_start_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='生产开始时间'),
),
]

View File

@ -368,6 +368,9 @@ class MlogUser(BaseModel):
mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE) mlog = models.ForeignKey(Mlog, verbose_name='关联日志', on_delete=models.CASCADE)
handle_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE) handle_user = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE)
process = models.ForeignKey(Process, verbose_name='子工序', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='子工序', on_delete=models.CASCADE)
work_start_time = models.DateTimeField('生产开始时间', null=True, blank=True)
equipment = models.ForeignKey(
Equipment, verbose_name='生产设备', on_delete=models.CASCADE, null=True, blank=True, related_name='mloguser_equipment')
shift = models.ForeignKey(Shift, verbose_name='关联班次', on_delete=models.CASCADE) shift = models.ForeignKey(Shift, verbose_name='关联班次', on_delete=models.CASCADE)
handle_date = models.DateField('操作日期') handle_date = models.DateField('操作日期')

View File

@ -355,12 +355,12 @@ class MlogSerializer(CustomModelSerializer):
model = Mlog model = Mlog
fields = '__all__' fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + \ read_only_fields = EXCLUDE_FIELDS + \
['submit_time', 'submit_user', 'material_outs', "handle_date", "shift"] ['submit_time', 'submit_user', 'material_outs']
extra_kwargs = { extra_kwargs = {
"batch": {"required": True}, "batch": {"required": True},
"shift": {"required": False}, "shift": {"required": False},
"material_out": {"required": True}, "material_out": {"required": True},
"work_start_time": {"required": True} "work_start_time": {"required": False}
} }
def create(self, validated_data): def create(self, validated_data):
@ -634,14 +634,20 @@ class MlogSerializer(CustomModelSerializer):
# 时间 # 时间
mgroup:Mgroup = attrs['mgroup'] mgroup:Mgroup = attrs['mgroup']
work_start_time:datetime = attrs['work_start_time'] work_start_time:datetime = attrs.get('work_start_time', None)
handle_date, attrs["shift"] = mgroup.get_shift(work_start_time) if work_start_time:
if mtask and mtask.start_date == mtask.end_date: attrs['handle_date'], attrs["shift"] = mgroup.get_shift(work_start_time)
attrs['handle_date'] = mtask.end_date
if attrs['handle_date'] != handle_date:
raise ParseError('任务日期与生产日期不一致')
else: else:
attrs['handle_date'] = handle_date if "handle_date" in attrs and attrs["handle_date"]:
pass
else:
raise ParseError('缺少生产日期')
if mtask and mtask.start_date == mtask.end_date:
if attrs['handle_date'] != mtask.end_date:
if work_start_time:
raise ParseError('任务日期与生产日期不一致')
else:
attrs['handle_date'] = mtask.end_date
handle_user = attrs.get('handle_user', None) handle_user = attrs.get('handle_user', None)
if handle_user is None and hasattr(self, "request"): if handle_user is None and hasattr(self, "request"):
@ -1486,10 +1492,17 @@ class MlogUserSerializer(CustomModelSerializer):
class Meta: class Meta:
model = MlogUser model = MlogUser
fields = "__all__" fields = "__all__"
read_only_fields = EXCLUDE_FIELDS_BASE read_only_fields = EXCLUDE_FIELDS_BASE + ["shift", "handle_date"]
extra_kwargs = {
"work_start_time": {"required": True}
}
def create(self, validated_data): def create(self, validated_data):
mlog:Mlog = validated_data["mlog"] mlog:Mlog = validated_data["mlog"]
work_start_time:datetime = validated_data["work_start_time"]
if mlog.work_start_time < mlog.work_start_time:
raise ParseError("操作时间不能早于日志开始时间")
validated_data["handle_date"], validated_data["shift"] = mlog.mgroup.get_shift(work_start_time)
if mlog.submit_time is not None: if mlog.submit_time is not None:
raise ParseError("该日志已提交") raise ParseError("该日志已提交")
process:Process = validated_data["process"] process:Process = validated_data["process"]

View File

@ -11,7 +11,7 @@ from apps.system.models import User
from apps.pm.models import Mtask from apps.pm.models import Mtask
from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule from apps.mtm.models import Mgroup, Shift, Material, Route, RoutePack, Team, Srule
from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect, BatchLog, BatchSt from .models import SfLog, WMaterial, Mlog, Mlogb, Mlogbw, Handover, Handoverb, Handoverbw, MlogbDefect, BatchLog, BatchSt, MlogUser
from apps.mtm.services_2 import cal_material_count from apps.mtm.services_2 import cal_material_count
from apps.wf.models import Ticket from apps.wf.models import Ticket
from apps.wf.services import WfService from apps.wf.services import WfService
@ -152,7 +152,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
""" """
生产日志提交后需要执行的操作 生产日志提交后需要执行的操作
""" """
if mlog.work_start_time > timezone.now(): if mlog.work_start_time and mlog.work_start_time > timezone.now():
raise ParseError('操作开始时间不能晚于当前时间') raise ParseError('操作开始时间不能晚于当前时间')
if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time: if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
raise ParseError('操作结束时间不能早于操作开始时间') raise ParseError('操作结束时间不能早于操作开始时间')
@ -385,7 +385,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
mlog.submit_user = user mlog.submit_user = user
mlog.stored_notok = stored_notok mlog.stored_notok = stored_notok
mlog.stored_mgroup = stored_mgroup mlog.stored_mgroup = stored_mgroup
if mlog.work_end_time is None: if mlog.work_end_time is None and mlog.work_start_time is not None:
mlog.work_end_time = now mlog.work_end_time = now
mlog.save() mlog.save()
@ -1026,6 +1026,10 @@ def mlog_submit_validate(ins: Mlog):
raise ParseError('该日志未指定消耗!') raise ParseError('该日志未指定消耗!')
if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists(): if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists():
raise ParseError('产出数量不能为0!') raise ParseError('产出数量不能为0!')
if ins.is_fix is False and ins.route:
process = ins.route.process
if Process.objects.filter(parent=process).exists() and not MlogUser.objects.filter(mlog=ins).exists():
raise ParseError('该日志子工序信息未完善!')
def bind_mlog(ticket: Ticket, transition, new_ticket_data: dict): def bind_mlog(ticket: Ticket, transition, new_ticket_data: dict):
ins = Mlog.objects.get(id=new_ticket_data['t_id']) ins = Mlog.objects.get(id=new_ticket_data['t_id'])