Compare commits

...

55 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
caoqianming 6fb415a9f0 release: 2.8.2025101011 2025-10-10 11:38:43 +08:00
caoqianming 727d190610 feat: Ptest val_xj可为空 2025-10-10 11:12:59 +08:00
caoqianming a6e0fb4f0d feat: quick调用serializer时传入request 2025-10-09 14:44:26 +08:00
caoqianming 9cf900d2ef doc: 添加一些注释 2025-10-09 11:17:14 +08:00
caoqianming 69e8e7b025 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-09-30 11:05:25 +08:00
caoqianming 647603f986 fix: p_create_after 自动创建mlogbw时关于exclude语句导致的查询错误 2025-09-30 11:05:24 +08:00
zty d21c1dc55d Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-09-30 09:58:56 +08:00
zty 7f206bd0a7 feat: ofm-修改model 2025-09-30 09:58:53 +08:00
caoqianming bd763be83a feat: Mroombooking添加字段 2025-09-29 15:53:34 +08:00
caoqianming fe499ffac5 feat: base add_info_for_item 可复用list逻辑 2025-09-29 15:52:54 +08:00
caoqianming 0c1e93bf0b feat: 添加wpr查询参数 2025-09-29 14:50:02 +08:00
caoqianming 5d3c4137fe feat: base cquery支持annotate 2025-09-29 14:44:08 +08:00
caoqianming dbaf121685 feat: mroombooking 返回slots 2025-09-29 11:09:09 +08:00
33 changed files with 950 additions and 488 deletions

View File

@ -62,7 +62,7 @@ class Mpoint(CommonBModel):
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)
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
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:
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:
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:
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)
else:
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)

View File

@ -37,7 +37,9 @@ class MioFilter(filters.FilterSet):
"item_mio__test_user": ["isnull"],
"item_mio__w_mioitem__number": ["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):

View File

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

View File

@ -39,7 +39,7 @@ class MaterialViewSet(CustomModelViewSet):
ordering = ['name', 'model', 'specification',
'type', 'process', 'process__sort', 'sort', 'id', 'number']
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):
from apps.inm.models import MaterialBatch

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

@ -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

@ -3,6 +3,7 @@ from apps.utils.models import CommonADModel, BaseModel, CommonBDModel
from apps.system.models import User
from django.core.validators import RegexValidator
from datetime import datetime
from rest_framework.exceptions import ParseError
# Create your models here.
@ -25,11 +26,15 @@ class Mroom(CommonADModel):
location = models.CharField('位置', max_length=100)
capacity = models.PositiveIntegerField('容纳人数')
class MroomBooking(CommonADModel):
class MroomBooking(CommonBDModel):
"""TN: 会议室预定信息"""
# belong_dept 是预定部门
title = models.CharField('会议主题', max_length=100)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联会议室',
on_delete=models.SET_NULL, related_name='mrooms_ticket', null=True, blank=True, db_constraint=False)
note = models.TextField('备注', null=True, blank=True)
participant_count = models.PositiveIntegerField('参会人数', default=0)
key_participants = models.TextField("主要参会领导", null=True, blank=True)
class MroomSlot(BaseModel):
@ -40,9 +45,6 @@ class MroomSlot(BaseModel):
slot = models.PositiveIntegerField('时段', help_text='0-47')
is_inuse = models.BooleanField('是否占用', default=True)
class Meta:
unique_together = ('mroom', 'mdate', 'slot', 'is_inuse')
# class Seal(BaseModel):
# """TN: 印章类型"""
@ -84,7 +86,10 @@ class Vehicle(CommonBDModel):
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:
self.actual_km = self.end_km - self.start_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)
@ -114,12 +119,12 @@ class BorrowRecord(CommonBDModel):
class Publicity(CommonBDModel):
"""TN: 公示栏"""
number = models.CharField('记录编号', max_length=50)
number = models.CharField('记录编号', max_length=50, blank=True, null=True)
title = models.CharField('送审稿件标题', max_length=100)
participants = models.CharField('所有撰稿人', max_length=50)
pub_dept = models.CharField('部室/研究院', null=True, blank=True, max_length=50)
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=[
"武器装备科研生产综合事项",
"其它"
@ -129,59 +134,58 @@ class Publicity(CommonBDModel):
channel = models.JSONField('发布渠道', default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'])
other_channel = 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=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'])
dept_opinion = 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=['同意', '不同意'], 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)
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='关联工单',
on_delete=models.SET_NULL, related_name='publicity_ticket', null=True, blank=True, db_constraint=False)
# 记录编号自动生成
def save(self, *args, **kwargs):
if not self.number:
with transaction.atomic():
# 加行锁,防止并发取到相同 last_number
last_number = self.__class__.objects.select_for_update(skip_locked=True).order_by('-id').first() # skip_locked 锁定行,避免并发冲突
if last_number:
try:
last_num = int(last_number.number.split('-')[-1])
except ValueError:
last_num = 0
new_num= last_num + 1
else:
new_num = 1
# 格式化编号,带补零
self.number = f"(GXKG-{datetime.now().year}-{new_num:02d})"
last_number = self.__class__.objects.filter(number__startswith=f"GXKG-{datetime.now().year}-").order_by('-number').first()
if last_number:
try:
last_num = int(last_number.number.split('-')[-1])
except ValueError:
last_num = 0
else:
last_num =0
# 格式化编号,带补零
self.number = f"GXKG-{datetime.now().year}-{last_num+1:02d}"
super().save(*args, **kwargs)
# class PatentInfo(CommonBDModel):
# """TN: 专利申密审批表单样式"""
# PATENT_TYPE_CHOICES = (
# ('invention', '发明专利'),
# ('utility', '实用新型专利'),
# ('design', '外观设计专利'),
# )
# APPLY_AREAS = (
# ('Domestic', '国内申请'),
# ('Foreign', '国外申请'),
# (' PCT', 'PCT申请'),
# )
# name = models.CharField('拟申请专利名称', max_length=100)
# author = models.CharField('发明人(设计人)', max_length=100)
# type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention')
# is_public = models.BooleanField('是否公开', default=False)
# area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic')
# identified = models.BooleanField('是否进行过科技成果鉴定', default=False)
# published_article = models.BooleanField('是否发表过文章', default=False)
# exhibited = models.BooleanField('是否参与过展会展出', default=False)
# applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False)
# participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False)
# tech_background_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)
# diagrams_or_photos_pages = models.PositiveIntegerField('图/照片页数或张数', null=True, blank=True)
class PatentInfo(CommonBDModel):
"""TN: 专利申密审批表单样式"""
PATENT_TYPE_CHOICES = (
('invention', '发明专利'),
('utility', '实用新型专利'),
('design', '外观设计专利'),
)
APPLY_AREAS = (
('Domestic', '国内申请'),
('Foreign', '国外申请'),
(' PCT', 'PCT申请'),
)
name = models.CharField('拟申请专利名称', max_length=100)
author = models.CharField('发明人(设计人)', max_length=100)
type = models.CharField('专利类型', max_length=50, choices=PATENT_TYPE_CHOICES, default='invention')
is_public = models.BooleanField('是否公开', default=False)
area = models.CharField('拟申请地域', max_length=50, choices=APPLY_AREAS, default='Domestic')
identified = models.BooleanField('是否进行过科技成果鉴定', default=False)
published_article = models.BooleanField('是否发表过文章', default=False)
exhibited = models.BooleanField('是否参与过展会展出', default=False)
applied_to_production = models.BooleanField('是否参与应用于生产/销售', default=False)
participated_in_exchange = models.BooleanField('是否参与过技术交流', default=False)
tech_background_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)
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):
# """TN: 论文申密审批表单"""

View File

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

View File

@ -1,7 +1,7 @@
from apps.wf.models import Ticket
# 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
@ -31,6 +31,7 @@ def mroombooking_reject(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.actual_return_date = None
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'LendingSeal',
@ -42,6 +43,19 @@ def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
if ins.ticket is None:
ins.ticket = ticket
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):
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_id': ins.id,
})
ins.actual_km = None
ins.end_time = None
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
@ -57,6 +73,16 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
ins.ticket = ticket
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']}
for k, v in data_save.items():
setattr(obj, k, v)
obj.save()
def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
ins = BorrowRecord.objects.get(id=new_ticket_data['t_id'])
@ -65,6 +91,7 @@ def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
't_model': 'BorrowRecord',
't_id': ins.id,
})
ins.return_date = None
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
@ -72,6 +99,16 @@ def bind_file(ticket: Ticket, transition, new_ticket_data: dict):
ins.ticket = ticket
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):
ins = Publicity.objects.get(id=new_ticket_data['t_id'])
@ -80,6 +117,36 @@ def bind_publicity(ticket: Ticket, transition, new_ticket_data: dict):
't_model': 'publicity',
'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.create_by = ins.create_by
ticket.save()

View File

@ -1,8 +1,9 @@
from django.urls import path, include
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,
# , PublicityViewSet, PatentInfoViewSet, PaperViewSet, PlatformViewSet,
# , PublicityViewSet, , PaperViewSet, PlatformViewSet,
# ProjectViewSet, PatentRecordViewSet, PaperRecordViewSet, ProjectApprovalViewSet, ProjectInfoViewSet)
API_BASE_URL = 'api/ofm/'
@ -18,7 +19,7 @@ router.register('vehicle', VehicleViewSet, basename='vehicle')
router.register('filerecord', FilerecordViewSet, basename='filerecord')
router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
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('platform', PlatformViewSet, basename='platform')
# router.register('project', ProjectViewSet, basename='project')

View File

@ -1,8 +1,9 @@
from django.shortcuts import render
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity
# Publicity, PatentInfo, PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer, VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,)
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity, PatentInfo
# Publicity, , PaperOfm, Platform, Project, PatentRecord, PaperRecord, ProjectApproval, ProjectInfo)
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer,
VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer, PatentInfoSerializer)
# ,SealSerializer,
# LendingSealSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer,
# PatentInfoSerializer, PaperSerializer, PlatformSerializer, ProjectSerializer, ProjectMemberSerializer, PaperRecordSerializer, ProjectApprovalSerializer, ProjectInfoSerializer)
@ -28,7 +29,7 @@ class MroomBookingViewSet(CustomModelViewSet):
"""
queryset = MroomBooking.objects.all()
serializer_class = MroomBookingSerializer
select_related_fields = ["create_by", "ticket"]
select_related_fields = ["create_by", "ticket", "belong_dept"]
filterset_class = MroomBookingFilterset
def add_info_for_list(self, data):
@ -66,7 +67,7 @@ class MroomBookingViewSet(CustomModelViewSet):
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.pop("current_slots") # 清理临时数据
info["slots"] = info.pop("current_slots") # 清理临时数据
for item in data:
item.update(booking_info.get(item["id"], {}))
@ -123,7 +124,7 @@ class LendingSealViewSet(CustomModelViewSet):
queryset = LendingSeal.objects.all()
serializer_class = LendingSealSerializer
filterset_class = SealFilter
ordering = ["create_time"]
ordering = ["-create_time"]
data_filter = True
@ -134,7 +135,7 @@ class VehicleViewSet(CustomModelViewSet):
"""
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
ordering = ["create_time"]
ordering = ["-create_time"]
class FilerecordViewSet(CustomModelViewSet):
@ -145,7 +146,7 @@ class FilerecordViewSet(CustomModelViewSet):
queryset = FileRecord.objects.all()
serializer_class = FileRecordSerializer
filterset_fields = [ "name", "number"]
ordering = ["create_time", "number", "name"]
ordering = ["-create_time", "number", "name"]
class FileborrowViewSet(CustomModelViewSet):
@ -156,7 +157,7 @@ class FileborrowViewSet(CustomModelViewSet):
queryset = BorrowRecord.objects.all()
serializer_class = BorrowRecordSerializer
filterset_class = BorrowRecordFilter
ordering = ["create_time"]
ordering = ["-create_time"]
class PublicityViewSet(CustomModelViewSet):
@ -167,18 +168,18 @@ class PublicityViewSet(CustomModelViewSet):
queryset = Publicity.objects.all()
serializer_class = PublicitySerializer
filterset_fields = ["title","number"]
ordering = ["create_time", "number"]
ordering = ["-create_time", "number"]
# class PatentInfoViewSet(CustomModelViewSet):
# """list: 专利
class PatentInfoViewSet(CustomModelViewSet):
"""list: 专利
# 专利
# """
# queryset = PatentInfo.objects.all()
# serializer_class = PatentInfoSerializer
# filterset_fields = ["name", "author", "type"]
# ordering = ["create_time", "name", "author", "type"]
专利
"""
queryset = PatentInfo.objects.all()
serializer_class = PatentInfoSerializer
filterset_fields = ["name", "author", "type"]
ordering = ["-create_time", "name", "author", "type"]
# class PaperViewSet(CustomModelViewSet):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-10-10 01:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0053_alter_ftest_is_ok'),
]
operations = [
migrations.AlterField(
model_name='ptest',
name='val_xj',
field=models.CharField(blank=True, choices=[('S', '析晶'), ('R', '不析晶'), ('θ', '未化')], help_text=[('S', '析晶'), ('R', '不析晶'), ('θ', '未化')], max_length=10, null=True, verbose_name='析晶'),
),
]

View File

@ -445,7 +445,7 @@ class Ptest(CommonAModel):
val_tg = models.FloatField("Tg", help_text='', null=True, blank=True)
val_tf = models.FloatField("Tf", help_text='', null=True, blank=True)
val_xj = models.CharField(
'析晶', max_length=10, default='S', choices=PTEST_XJ_VALS, help_text=list(PTEST_XJ_VALS))
'析晶', max_length=10, null=True, blank=True, choices=PTEST_XJ_VALS, help_text=list(PTEST_XJ_VALS))
val_pzxs = models.FloatField(
'膨胀系数', help_text='30-300℃', null=True, blank=True)
val_zgwd = models.FloatField('升至最高温度', null=True, blank=True)

View File

@ -195,6 +195,8 @@ class CustomRetrieveModelMixin(RetrieveModelMixin):
给dict返回数据添加额外信息
"""
if hasattr(self, 'add_info_for_list'):
return self.add_info_for_list([data])[0]
return data
class CustomListModelMixin(ListModelMixin):
@ -243,6 +245,8 @@ class ComplexQueryMixin:
vdata = sr.validated_data
queryset = self.get_queryset()
querys = vdata.get('querys', [])
annotate_field_list = vdata.get('annotate_field_list', [])
if not querys:
new_qs = queryset
else:
@ -264,17 +268,29 @@ class ComplexQueryMixin:
new_qs = new_qs | one_qs
except Exception as e:
raise ParseError(str(e))
if annotate_field_list:
annotate_dict = getattr(self, "annotate_dict", {})
if annotate_dict:
filtered_annotate_dict = { key: annotate_dict[key] for key in annotate_field_list if key in annotate_dict}
new_qs = new_qs.annotate(**filtered_annotate_dict)
ordering = vdata.get('ordering', None)
if not ordering:
ordering = getattr(self, 'ordering', None)
if isinstance(ordering, str):
ordering = ordering.replace('\n', '').replace(' ', '')
ordering = ordering.split(',')
order_fields = []
if ordering:
for item in ordering:
if item.startswith('-'):
new_qs = new_qs.order_by(F(item[1:]).desc(nulls_last=True))
# JSONField 排序只能用字符串,不要 F
order_fields.append(F(item[1:]).desc(nulls_last=True) if '__' not in item else item)
else:
new_qs = new_qs.order_by(item)
order_fields.append(F(item).asc(nulls_last=True) if '__' not in item else item)
new_qs = new_qs.order_by(*order_fields)
page = self.paginate_queryset(new_qs)
if page is not None:
serializer = self.get_serializer(page, many=True)

View File

@ -85,3 +85,4 @@ class ComplexSerializer(serializers.Serializer):
ordering = serializers.CharField(required=False)
querys = serializers.ListField(child=QuerySerializer(
many=True), label="查询列表", required=False)
annotate_field_list = serializers.ListField(child=serializers.CharField(), label="RawSQL字段列表", required=False)

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(
'内容模板', 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_path2 = models.TextField('前端自定义页面路径2', null=True, blank=True)
class Meta:
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
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)
handle_user = models.ForeignKey(User, 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)
handle_date = models.DateField('操作日期')
@ -524,7 +527,7 @@ class Mlogbw(BaseModel):
mlogb.count_real = count
count_notok = 0
count_notok_full = 0
# 个追踪的合格b类不分
# 个追踪的合格b类不分这里会导致count_ok_full与count_ok一样了暂时不做处理
tqs = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False)
tqs_a = Mlogbw.objects.filter(mlogb=mlogb, ftest__defect_main__isnull=False).values("ftest__defect_main").annotate(xcount=Count('id'))
defect_ids = tqs.values_list("ftest__defect_main", flat=True)

View File

@ -355,12 +355,12 @@ class MlogSerializer(CustomModelSerializer):
model = Mlog
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + \
['submit_time', 'submit_user', 'material_outs', "handle_date", "shift"]
['submit_time', 'submit_user', 'material_outs']
extra_kwargs = {
"batch": {"required": True},
"shift": {"required": False},
"material_out": {"required": True},
"work_start_time": {"required": True}
"work_start_time": {"required": False}
}
def create(self, validated_data):
@ -634,14 +634,20 @@ class MlogSerializer(CustomModelSerializer):
# 时间
mgroup:Mgroup = attrs['mgroup']
work_start_time:datetime = attrs['work_start_time']
handle_date, attrs["shift"] = mgroup.get_shift(work_start_time)
if mtask and mtask.start_date == mtask.end_date:
attrs['handle_date'] = mtask.end_date
if attrs['handle_date'] != handle_date:
raise ParseError('任务日期与生产日期不一致')
work_start_time:datetime = attrs.get('work_start_time', None)
if work_start_time:
attrs['handle_date'], attrs["shift"] = mgroup.get_shift(work_start_time)
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)
if handle_user is None and hasattr(self, "request"):
@ -1486,10 +1492,17 @@ class MlogUserSerializer(CustomModelSerializer):
class Meta:
model = MlogUser
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):
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:
raise ParseError("该日志已提交")
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.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.wf.models import Ticket
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('操作开始时间不能晚于当前时间')
if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
raise ParseError('操作结束时间不能早于操作开始时间')
@ -385,7 +385,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
mlog.submit_user = user
mlog.stored_notok = stored_notok
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.save()
@ -1026,6 +1026,10 @@ def mlog_submit_validate(ins: Mlog):
raise ParseError('该日志未指定消耗!')
if Mlogb.objects.filter(material_out__isnull=False, count_real=0, mlog=ins).exists():
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):
ins = Mlog.objects.get(id=new_ticket_data['t_id'])

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ from rest_framework.exceptions import ParseError
from django.db import transaction
from apps.wpmw.filters import WprFilter
from apps.utils.sql import query_one_dict
from django.db.models.expressions import RawSQL
class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet):
@ -17,6 +18,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
动态产品
"""
perms_map = {"get": "*"}
select_related_fields = ["wm", "mb", "material", "wm__material_ofrom"]
prefetch_related_fields = ["defects"]
@ -27,6 +29,10 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
ordering = ["number", "create_time"]
ordering_fields = ["number", "create_time", "update_time"]
search_fields = ["number", "material__name", "material__model", "material__specification", "number_out"]
annotate_dict = {
"number_prefix": RawSQL("regexp_replace(number, '(\\d+)$', '')", []),
"number_suffix": RawSQL("COALESCE(NULLIF(regexp_replace(number, '.*?(\\d+)$', '\\1'), ''), '0')::bigint", []),
}
def filter_queryset(self, queryset):
qs = super().filter_queryset(queryset)
@ -36,7 +42,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
qs.exclude(mb=None, wm=None)
return qs
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WprNewSerializer)
@action(methods=["post"], detail=False, perms_map={"post": "*"}, serializer_class=WprNewSerializer)
def new_number(self, request, *args, **kwargs):
"""获取新的编号"""
data = request.data
@ -47,16 +53,17 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
wpr_last = wps_qs.order_by("number").last()
count = wps_qs.count()
last_number = wpr_last.number if count > 0 else None
last_number_count = int(last_number.split("-")[-1].lstrip('0'))
last_number_count = int(last_number.split("-")[-1].lstrip("0"))
mat = Material.objects.get(id=material_start)
return Response({"count": count, "last_number": last_number, "material_model":mat.model, "last_number_count": last_number_count})
return Response({"count": count, "last_number": last_number, "material_model": mat.model, "last_number_count": last_number_count})
@action(methods=['get'], detail=False, perms_map={'get': '*'})
@action(methods=["get"], detail=False, perms_map={"get": "*"})
def number_out_last(self, request, *args, **kwargs):
"""获取最新的出库对外编号
获取最新的出库对外编号(get请求传入prefix参数和with_unsubmit参数with_unsubmit默认为yes表示是否包含未出库的记录prefix为前缀'WPR-2023-0)"""
from apps.inm.models import MIOItemw
prefix = request.query_params.get("prefix", None)
if not prefix:
raise ParseError("请传入前缀参数")
@ -67,7 +74,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
SELECT id, number_out FROM wpmw_wpr
WHERE number_out ~ %s order by number_out desc limit 1
"""
pattern = f'^{prefix}[0-9]+$'
pattern = f"^{prefix}[0-9]+$"
number_outs = []
wpr_qs_last = query_one_dict(query, [pattern])
if wpr_qs_last:
@ -87,7 +94,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
if number_outs:
number_outs.sort()
number_out_last = number_outs[-1]
number_int_last = number_out_last.lstrip(prefix).lstrip('0')
number_int_last = number_out_last.lstrip(prefix).lstrip("0")
try:
number_int_last = int(number_int_last)
except ValueError:
@ -96,8 +103,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
else:
return Response({"number_out_last": None})
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproutListSerializer)
@action(methods=["post"], detail=False, perms_map={"post": "*"}, serializer_class=WproutListSerializer)
@transaction.atomic
def assgin_number_out(self, request, *args, **kwargs):
"""分配出库对外编号
@ -108,7 +114,7 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
vdata = sr.validated_data
items = vdata["items"]
number_outs = [i["number_out"] for i in items]
existing_numbers = Wpr.objects.filter(number_out__in=number_outs, number_out__isnull=False).values_list('number_out', flat=True)
existing_numbers = Wpr.objects.filter(number_out__in=number_outs, number_out__isnull=False).values_list("number_out", flat=True)
if existing_numbers.exists():
used_numbers = list(existing_numbers)
raise ParseError(f"以下对外编号已被使用: {used_numbers[0]}{len(used_numbers)}")

View File

@ -1,3 +1,15 @@
## 2.8.2025101011
- feat: 新增功能
- Ptest val_xj可为空 [caoqianming]
- quick调用serializer时传入request [caoqianming]
- ofm-修改model [zty]
- Mroombooking添加字段 [caoqianming]
- base add_info_for_item 可复用list逻辑 [caoqianming]
- 添加wpr查询参数 [caoqianming]
- base cquery支持annotate [caoqianming]
- mroombooking 返回slots [caoqianming]
- fix: 问题修复
- p_create_after 自动创建mlogbw时关于exclude语句导致的查询错误 [caoqianming]
## 2.8.2025092816
- feat: 新增功能
- handover revert撤回时做校验 [caoqianming]

View File

@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
ALLOWED_HOSTS = ['*']
SYS_NAME = '星途工厂综合管理系统'
SYS_VERSION = '2.8.2025092816'
SYS_VERSION = '2.8.2025101011'
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition