Merge branch 'master' of https://e.coding.net/ctcdevteam/ehs/ehs_server
This commit is contained in:
commit
e5d2d5ef84
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.12 on 2023-11-28 07:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('em', '0008_equipment_accuracy_level'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, default='', max_length=200, verbose_name='描述'),
|
||||
),
|
||||
]
|
|
@ -62,7 +62,8 @@ class Equipment(CommonBModel):
|
|||
count = models.PositiveIntegerField('数量', default=1)
|
||||
keeper = models.ForeignKey(
|
||||
User, verbose_name='责任人', on_delete=models.CASCADE, null=True, blank=True)
|
||||
description = models.CharField('描述', max_length=200, default='', null=True)
|
||||
description = models.CharField(
|
||||
'描述', max_length=200, default='', blank=True)
|
||||
|
||||
# 以下是计量检测设备单独字段
|
||||
# mgmtype = models.IntegerField('管理类别', choices=mgmtype_choices, default=1)
|
||||
|
|
|
@ -9,5 +9,7 @@ class MaterialBatchFilter(filters.FilterSet):
|
|||
fields = {
|
||||
"warehouse": ["exact"],
|
||||
"material": ["exact"],
|
||||
"material__type": ["exact", "in"],
|
||||
"material__process": ["exact", "in"],
|
||||
"count": ["exact", "gte", "lte"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-15 11:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inm', '0010_auto_20231116_1904'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='mioitem',
|
||||
old_name='is_bgtest_ok',
|
||||
new_name='is_testok',
|
||||
),
|
||||
]
|
|
@ -121,7 +121,7 @@ class MIOItem(BaseModel):
|
|||
count_n_jsqx = models.PositiveIntegerField('结石气线', default=0)
|
||||
count_n_qt = models.PositiveIntegerField('其他', default=0)
|
||||
|
||||
is_bgtest_ok = models.BooleanField('配套件是否合格', default=True)
|
||||
is_testok = models.BooleanField('配套件是否合格', default=True)
|
||||
|
||||
|
||||
class MIOItemA(BaseModel):
|
||||
|
|
|
@ -94,7 +94,7 @@ class MIOItemCreateSerializer(CustomModelSerializer):
|
|||
class Meta:
|
||||
model = MIOItem
|
||||
fields = ['mio', 'warehouse', 'material',
|
||||
'batch', 'count', 'assemb', 'is_bgtest_ok']
|
||||
'batch', 'count', 'assemb', 'is_testok']
|
||||
|
||||
def create(self, validated_data):
|
||||
mio = validated_data['mio']
|
||||
|
|
|
@ -49,7 +49,8 @@ class MaterialBatchViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
retrieve_serializer_class = MaterialBatchDetailSerializer
|
||||
select_related_fields = ['warehouse', 'material']
|
||||
filterset_class = MaterialBatchFilter
|
||||
search_fields = ['material__name']
|
||||
search_fields = ['material__name', 'material__number',
|
||||
'material__model', 'material__specification', 'batch']
|
||||
|
||||
|
||||
class MioDoViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, CustomGenericViewSet):
|
||||
|
@ -123,7 +124,12 @@ class MIOViewSet(CustomModelViewSet):
|
|||
'submit_user', 'supplier', 'order', 'customer', 'pu_order']
|
||||
serializer_class = MIOListSerializer
|
||||
retrieve_serializer_class = MIODetailSerializer
|
||||
filterset_fields = ['state', 'type', 'pu_order', 'order']
|
||||
filterset_fields = {
|
||||
'state': ["exact", "in"],
|
||||
"type": ["exact", "in"],
|
||||
"pu_order": ["exact"],
|
||||
"order": ["exact"]
|
||||
}
|
||||
search_fields = ['number']
|
||||
data_filter = True
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-07 01:35
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('monitor', '0003_alter_drfrequestlog_view_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuditLog',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('action', models.CharField(max_length=20, verbose_name='动作')),
|
||||
('model_name', models.CharField(max_length=20, verbose_name='模型名')),
|
||||
('instance_id', models.CharField(editable=False, max_length=20, verbose_name='记录ID')),
|
||||
('change_reason', models.CharField(default='', max_length=50, verbose_name='变更原因')),
|
||||
('change_time', models.DateTimeField(verbose_name='变更时间')),
|
||||
('val_new', models.JSONField(default=dict, verbose_name='变更后完整数据')),
|
||||
('difference', models.JSONField(default=list, verbose_name='变更情况')),
|
||||
('change_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='操作人')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -4,6 +4,19 @@ from django.db import models
|
|||
from apps.utils.models import BaseModel
|
||||
|
||||
|
||||
class AuditLog(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
action = models.CharField('动作', max_length=20)
|
||||
model_name = models.CharField('模型名', max_length=20)
|
||||
instance_id = models.CharField('记录ID', max_length=20, editable=False)
|
||||
change_reason = models.CharField('变更原因', default='', max_length=50)
|
||||
change_user = models.ForeignKey(
|
||||
'system.user', on_delete=models.SET_NULL, verbose_name='操作人', null=True, blank=True)
|
||||
change_time = models.DateTimeField('变更时间')
|
||||
val_new = models.JSONField('变更后完整数据', default=dict)
|
||||
difference = models.JSONField('变更情况', default=list)
|
||||
|
||||
|
||||
class DrfRequestLog(BaseModel):
|
||||
"""Logs Django rest framework API requests"""
|
||||
|
||||
|
@ -42,7 +55,8 @@ class DrfRequestLog(BaseModel):
|
|||
response = models.TextField(null=True, blank=True)
|
||||
errors = models.TextField(null=True, blank=True)
|
||||
agent = models.TextField(null=True, blank=True)
|
||||
status_code = models.PositiveIntegerField(null=True, blank=True, db_index=True)
|
||||
status_code = models.PositiveIntegerField(
|
||||
null=True, blank=True, db_index=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "DRF请求日志"
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
from rest_framework import serializers
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.monitor.models import AuditLog
|
||||
|
||||
|
||||
class DbbackupDeleteSerializer(serializers.Serializer):
|
||||
filepaths = serializers.ListField(child=serializers.CharField(), label="文件地址列表")
|
||||
filepaths = serializers.ListField(
|
||||
child=serializers.CharField(), label="文件地址列表")
|
||||
|
||||
|
||||
class AuditLogSerializer(CustomModelSerializer):
|
||||
change_user_name = serializers.CharField(
|
||||
source='change_user.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AuditLog
|
||||
fields = '__all__'
|
||||
|
|
|
@ -1,4 +1,63 @@
|
|||
import psutil
|
||||
from apps.monitor.models import AuditLog
|
||||
from apps.system.models import User
|
||||
from datetime import datetime
|
||||
from apps.utils.tools import compare_values
|
||||
from apps.utils.models import get_model_info
|
||||
|
||||
|
||||
def delete_auditlog(model, instance_id):
|
||||
"""
|
||||
删除其对应的审计记录
|
||||
"""
|
||||
model_name = get_model_info(model)
|
||||
AuditLog.objects.filter(model_name=model_name,
|
||||
instance_id=instance_id).delete()
|
||||
|
||||
|
||||
def create_auditlog(action: str, instance, val_new: dict, val_old: dict = None, change_reason: str = '', delete_time: datetime = None, delete_user: User = None):
|
||||
"""
|
||||
生成审计日志
|
||||
action: create/update/delete/其他action
|
||||
"""
|
||||
app_label_model_name = get_model_info(instance)
|
||||
if val_old is None:
|
||||
val_old = {}
|
||||
difference = []
|
||||
has_changed = False
|
||||
if action == 'create':
|
||||
has_changed = True
|
||||
change_user = instance.create_by
|
||||
change_time = instance.create_time
|
||||
elif action == 'delete':
|
||||
has_changed = True
|
||||
change_user = delete_user if delete_user else instance.update_by
|
||||
change_time = delete_time if delete_time else instance.update_time
|
||||
else:
|
||||
change_user = instance.update_by
|
||||
change_time = instance.update_time
|
||||
for k, v in val_new.items():
|
||||
if k not in ['create_by', 'update_by', 'create_time', 'update_time', 'id']:
|
||||
if k not in val_old:
|
||||
difference.append(
|
||||
{'field': k, 'action': 'create', 'val_old': None, 'val_new': v})
|
||||
elif not compare_values(val_new.get(k), val_old.get(k), ignore_order=True):
|
||||
difference.append(
|
||||
{'field': k, 'action': 'update', 'val_old': val_old[k], 'val_new': v})
|
||||
if difference:
|
||||
has_changed = True
|
||||
if has_changed:
|
||||
AuditLog.objects.create(
|
||||
action=action,
|
||||
model_name=app_label_model_name,
|
||||
instance_id=instance.id,
|
||||
val_new=val_new,
|
||||
difference=difference,
|
||||
change_reason=change_reason,
|
||||
change_user=change_user,
|
||||
change_time=change_time
|
||||
)
|
||||
|
||||
|
||||
class ServerService:
|
||||
@classmethod
|
||||
|
@ -17,7 +76,7 @@ class ServerService:
|
|||
ret['count'] = psutil.cpu_count(logical=False)
|
||||
ret['percent'] = psutil.cpu_percent(interval=1)
|
||||
return ret
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_disk_dict(cls):
|
||||
ret = {}
|
||||
|
@ -29,4 +88,4 @@ class ServerService:
|
|||
|
||||
@classmethod
|
||||
def get_full(cls):
|
||||
return {'cpu': cls.get_cpu_dict(), 'memory': cls.get_memory_dict(), 'disk': cls.get_disk_dict()}
|
||||
return {'cpu': cls.get_cpu_dict(), 'memory': cls.get_memory_dict(), 'disk': cls.get_disk_dict()}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.urls import path
|
||||
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView
|
||||
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView, AuditlogViewSet
|
||||
|
||||
API_BASE_URL = 'api/monitor/'
|
||||
HTML_BASE_URL = 'monitor/'
|
||||
|
@ -13,5 +13,8 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'log/<str:name>/', LogDetailView.as_view()),
|
||||
path(API_BASE_URL + 'dbbackup/', DbBackupView.as_view()),
|
||||
path(API_BASE_URL + 'server/', ServerInfoView.as_view()),
|
||||
path(API_BASE_URL + 'request_log/', DrfRequestLogViewSet.as_view({'get': 'list'}), name='requestlog_view')
|
||||
path(API_BASE_URL + 'request_log/',
|
||||
DrfRequestLogViewSet.as_view({'get': 'list'}), name='requestlog_view'),
|
||||
path(API_BASE_URL + 'auditlog/',
|
||||
AuditlogViewSet.as_view({'get': 'list'}), name='auditlog_view')
|
||||
]
|
||||
|
|
|
@ -7,13 +7,13 @@ from rest_framework.permissions import IsAuthenticated
|
|||
from django.conf import settings
|
||||
import os
|
||||
from rest_framework import serializers
|
||||
from apps.monitor.serializers import DbbackupDeleteSerializer
|
||||
from apps.monitor.serializers import DbbackupDeleteSerializer, AuditLogSerializer
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework.mixins import ListModelMixin
|
||||
from apps.monitor.filters import DrfLogFilterSet
|
||||
from apps.monitor.models import DrfRequestLog
|
||||
from apps.monitor.models import DrfRequestLog, AuditLog
|
||||
|
||||
from apps.monitor.errors import LOG_NOT_FONED
|
||||
from apps.monitor.services import ServerService
|
||||
|
@ -188,3 +188,16 @@ class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
ordering = ['-requested_at']
|
||||
filterset_class = DrfLogFilterSet
|
||||
search_fields = ['path', 'view']
|
||||
|
||||
|
||||
class AuditlogViewSet(ListModelMixin, CustomGenericViewSet):
|
||||
"""审计日志
|
||||
|
||||
审计日志
|
||||
"""
|
||||
perms_map = {'get': '*'}
|
||||
queryset = AuditLog.objects.all()
|
||||
list_serializer_class = AuditLogSerializer
|
||||
ordering = ['-change_time']
|
||||
filterset_fields = ['change_user', 'model_name', 'action', 'instance_id']
|
||||
search_fields = ['model_name', 'action']
|
||||
|
|
|
@ -15,6 +15,7 @@ class MaterialFilter(filters.FilterSet):
|
|||
"is_hidden": ["exact"],
|
||||
"is_assemb": ["exact"],
|
||||
"need_route": ["exact"],
|
||||
"process": ["exact", "in", "isnull"],
|
||||
"orderitem_material__order": ['exact'],
|
||||
"pu_orderitem_material__pu_order": ["exact"],
|
||||
"route_material_out__mgroup": ["exact"]
|
||||
|
@ -48,4 +49,7 @@ class RouteFilter(filters.FilterSet):
|
|||
"process": ["exact", "in"],
|
||||
"is_autotask": ["exact"],
|
||||
"mgroup": ["exact", "in", "isnull"],
|
||||
"mgroup__name": ["exact", "contains"],
|
||||
"mgroup__belong_dept": ["exact"],
|
||||
"mgroup__belong_dept__name": ["exact", "contains"]
|
||||
}
|
||||
|
|
|
@ -15,10 +15,12 @@ class ShiftSerializer(CustomModelSerializer):
|
|||
|
||||
|
||||
class MaterialSimpleSerializer(CustomModelSerializer):
|
||||
process_name = serializers.CharField(source='process.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Material
|
||||
fields = ['id', 'name', 'number', 'model',
|
||||
'specification', 'type', 'cate', 'brothers']
|
||||
'specification', 'type', 'cate', 'brothers', 'process_name']
|
||||
|
||||
|
||||
class MaterialSerializer(CustomModelSerializer):
|
||||
|
@ -124,15 +126,16 @@ class RouteSerializer(CustomModelSerializer):
|
|||
fields = '__all__'
|
||||
read_only_fields = EXCLUDE_FIELDS
|
||||
|
||||
# def validate(self, attrs):
|
||||
# material = attrs['material']
|
||||
# if material.type != Material.MA_TYPE_GOOD:
|
||||
# raise ValidationError('请选择最终产品')
|
||||
# return super().validate(attrs)
|
||||
def validate(self, attrs):
|
||||
if 'mgroup' in attrs and attrs['mgroup']:
|
||||
attrs['process'] = attrs['mgroup'].process
|
||||
if attrs.get('process', None) is None:
|
||||
raise ParseError('未提供操作工序')
|
||||
return super().validate(attrs)
|
||||
|
||||
def gen_material_out(self, instance):
|
||||
"""
|
||||
废弃不用了
|
||||
自动形成物料
|
||||
"""
|
||||
name = f'{instance.material.name}-中'
|
||||
instance.material_out, _ = Material.objects.get_or_create(type=Material.MA_TYPE_HALFGOOD, parent=instance.material, process=instance.process,
|
||||
|
@ -140,6 +143,7 @@ class RouteSerializer(CustomModelSerializer):
|
|||
'is_hidden': True, 'name': name,
|
||||
'number': instance.material.number,
|
||||
'specification': instance.material.specification,
|
||||
'model': instance.material.model,
|
||||
'type': Material.MA_TYPE_HALFGOOD,
|
||||
'create_by': self.request.user,
|
||||
'update_by': self.request.user,
|
||||
|
@ -147,24 +151,41 @@ class RouteSerializer(CustomModelSerializer):
|
|||
instance.save()
|
||||
|
||||
def create(self, validated_data):
|
||||
process = validated_data.get('process', None)
|
||||
if process and Route.objects.filter(material=validated_data['material'], process=process).exists():
|
||||
process = validated_data['process']
|
||||
material = validated_data.get('material', None)
|
||||
if material and process and Route.objects.filter(material=material, process=process).exists():
|
||||
raise ValidationError('已选择该工序')
|
||||
with transaction.atomic():
|
||||
instance = super().create(validated_data)
|
||||
# if 'material_out' in validated_data and validated_data['material_out'] and instance.material:
|
||||
# pass
|
||||
# else:
|
||||
# self.gen_material_out(instance)
|
||||
material_out = instance.material_out
|
||||
if material_out:
|
||||
if material_out.process is None:
|
||||
material_out.process = process
|
||||
if instance.material:
|
||||
material_out.parent = instance.material
|
||||
material_out.save()
|
||||
elif material_out.process != process:
|
||||
raise ParseError('物料工序错误!请重新选择')
|
||||
else:
|
||||
if instance.material:
|
||||
self.gen_material_out()
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
validated_data.pop('material', None)
|
||||
validated_data.pop('process', None)
|
||||
process = validated_data.pop('process', None)
|
||||
with transaction.atomic():
|
||||
instance = super().update(instance, validated_data)
|
||||
# if 'material_out' in validated_data and validated_data['material_out'] and instance.material:
|
||||
# pass
|
||||
# else:
|
||||
# self.gen_material_out(instance)
|
||||
material_out = instance.material_out
|
||||
if material_out:
|
||||
if material_out.process is None:
|
||||
material_out.process = process
|
||||
if instance.material:
|
||||
material_out.parent = instance.material
|
||||
material_out.save()
|
||||
elif material_out.process != process:
|
||||
raise ParseError('物料工序错误!请重新选择')
|
||||
else:
|
||||
if instance.material:
|
||||
self.gen_material_out()
|
||||
return instance
|
||||
|
|
|
@ -51,7 +51,13 @@ class MgroupViewSet(CustomModelViewSet):
|
|||
queryset = Mgroup.objects.all()
|
||||
serializer_class = MgroupSerializer
|
||||
select_related_fields = ['create_by', 'belong_dept', 'process']
|
||||
filterset_fields = ['belong_dept', 'process', 'cate', 'belong_dept__name']
|
||||
filterset_fields = {
|
||||
"belong_dept": ["exact"],
|
||||
"process": ["exact"],
|
||||
"cate": ["exact"],
|
||||
"belong_dept__name": ["exact", "contains"],
|
||||
"name": ["exact", "contains"]
|
||||
}
|
||||
search_fields = ['number']
|
||||
ordering = ['sort', 'create_time']
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class UtaskFilter(filters.FilterSet):
|
|||
|
||||
|
||||
class MtaskFilter(filters.FilterSet):
|
||||
tag = filters.CharFilter(method='filter_tag')
|
||||
tag = filters.CharFilter(method='filter_tag', label='done, not_done')
|
||||
|
||||
class Meta:
|
||||
model = Mtask
|
||||
|
@ -55,17 +55,22 @@ class MtaskFilter(filters.FilterSet):
|
|||
"material_out__type": ["exact"],
|
||||
"material_out__is_hidden": ["exact"],
|
||||
"mgroup__belong_dept__name": ["exact"],
|
||||
"mgroup__belong_dept": ["exact"],
|
||||
"utask": ["exact"]
|
||||
}
|
||||
|
||||
def filter_tag(self, queryset, name, value):
|
||||
now = timezone.now()
|
||||
day7_after = now + timedelta(days=7)
|
||||
if value == 'near_done':
|
||||
queryset = queryset.filter(count_ok__lt=F('count'),
|
||||
end_date__lte=day7_after.date(),
|
||||
end_date__gte=now.date())
|
||||
elif value == 'out_done':
|
||||
queryset = queryset.filter(count_ok__lt=F('count'),
|
||||
end_date__lt=now.date())
|
||||
# now = timezone.now()
|
||||
# day7_after = now + timedelta(days=7)
|
||||
# if value == 'near_done':
|
||||
# queryset = queryset.filter(count_ok__lt=F('count'),
|
||||
# end_date__lte=day7_after.date(),
|
||||
# end_date__gte=now.date())
|
||||
# elif value == 'out_done':
|
||||
# queryset = queryset.filter(count_ok__lt=F('count'),
|
||||
# end_date__lt=now.date())
|
||||
if value == 'done':
|
||||
queryset = queryset.filter(count_ok__gte=F('count'))
|
||||
elif value == 'not_done':
|
||||
queryset = queryset.filter(count_ok__lt=F('count'))
|
||||
return queryset
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.12 on 2023-11-30 08:28
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('pm', '0015_utask_count_day'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mtask',
|
||||
name='submit_time',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='提交时间'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mtask',
|
||||
name='submit_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='mtask_submit_user', to=settings.AUTH_USER_MODEL, verbose_name='提交人'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-05 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pm', '0016_auto_20231130_1628'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mtask',
|
||||
name='type',
|
||||
field=models.CharField(default='mass', help_text="(('mass', '量产'), ('pilot', '中试'))", max_length=10, verbose_name='任务类型'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='utask',
|
||||
name='type',
|
||||
field=models.CharField(default='mass', help_text="(('mass', '量产'), ('pilot', '中试'))", max_length=10, verbose_name='任务类型'),
|
||||
),
|
||||
]
|
|
@ -4,6 +4,11 @@ from apps.mtm.models import Material, Mgroup
|
|||
|
||||
# Create your models here.
|
||||
|
||||
TASK_TYPE = (
|
||||
('mass', '量产'),
|
||||
('pilot', '中试')
|
||||
)
|
||||
|
||||
|
||||
class Utask(CommonBDModel):
|
||||
"""
|
||||
|
@ -14,15 +19,17 @@ class Utask(CommonBDModel):
|
|||
UTASK_ASSGINED = 20
|
||||
UTASK_WORKING = 30
|
||||
UTASK_STOP = 34
|
||||
UTASK_DONE = 40
|
||||
UTASK_SUBMIT = 40
|
||||
UTASK_STATES = (
|
||||
(UTASK_CREATED, '创建中'),
|
||||
(UTASK_DECOMPOSE, '已分解'),
|
||||
(UTASK_ASSGINED, '已下达'),
|
||||
(UTASK_WORKING, '生产中'),
|
||||
(UTASK_STOP, '已停止'),
|
||||
(UTASK_DONE, '已提交')
|
||||
(UTASK_SUBMIT, '已提交')
|
||||
)
|
||||
type = models.CharField('任务类型', max_length=10,
|
||||
help_text=str(TASK_TYPE), default='mass')
|
||||
state = models.PositiveIntegerField(
|
||||
'状态', choices=UTASK_STATES, default=UTASK_CREATED, help_text=str(UTASK_STATES))
|
||||
number = models.CharField('编号', max_length=50, unique=True)
|
||||
|
@ -48,13 +55,15 @@ class Mtask(CommonADModel):
|
|||
MTASK_CREATED = 10
|
||||
MTASK_ASSGINED = 20
|
||||
MTASK_STOP = 34
|
||||
MTASK_DONE = 40
|
||||
MTASK_SUBMIT = 40
|
||||
MTASK_STATES = (
|
||||
(MTASK_CREATED, '创建中'),
|
||||
(MTASK_ASSGINED, '已下达'),
|
||||
(MTASK_STOP, '已停止'),
|
||||
(MTASK_DONE, '已提交')
|
||||
(MTASK_SUBMIT, '已提交')
|
||||
)
|
||||
type = models.CharField('任务类型', max_length=10,
|
||||
help_text=str(TASK_TYPE), default='mass')
|
||||
state = models.PositiveIntegerField(
|
||||
'状态', choices=MTASK_STATES, default=MTASK_CREATED, help_text=str(MTASK_STATES))
|
||||
number = models.CharField('编号', max_length=50, unique=True)
|
||||
|
@ -75,6 +84,10 @@ class Mtask(CommonADModel):
|
|||
Utask, verbose_name='关联大任务', on_delete=models.CASCADE, related_name='mtask_utask', null=True, blank=True)
|
||||
peifen_kg = models.FloatField('配粉料数', default=0)
|
||||
|
||||
submit_time = models.DateTimeField('提交时间', null=True, blank=True)
|
||||
submit_user = models.ForeignKey(
|
||||
'system.user', verbose_name='提交人', on_delete=models.CASCADE, null=True, blank=True, related_name='mtask_submit_user')
|
||||
|
||||
@property
|
||||
def related(self):
|
||||
"""
|
||||
|
|
|
@ -12,6 +12,7 @@ from apps.wpm.models import Mlog
|
|||
|
||||
class UtaskSerializer(CustomModelSerializer):
|
||||
material_ = MaterialSimpleSerializer(source='material', read_only=True)
|
||||
mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
|
||||
belong_dept = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Dept.objects.all(), required=False)
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ class PmService:
|
|||
for i in range(rela_days):
|
||||
task_date = start_date + timedelta(days=i)
|
||||
Mtask.objects.create(**{
|
||||
'type': utask.type,
|
||||
'number': f'{number}_{i+1}',
|
||||
'material_out': utask.material,
|
||||
'material_in': utask.material_in,
|
||||
|
@ -124,6 +125,7 @@ class PmService:
|
|||
task_date = start_date + timedelta(days=i)
|
||||
Mtask.objects.create(**{
|
||||
'number': f'{number}_r{ind+1}_{i+1}',
|
||||
'type': utask.type,
|
||||
'material_out': halfgood,
|
||||
'material_in': material_in,
|
||||
'mgroup': mgroup,
|
||||
|
@ -251,19 +253,23 @@ class PmService:
|
|||
change_order_state_when_schedue(orderitemIds)
|
||||
|
||||
@classmethod
|
||||
def mtasks_submit(cls, mtasks: QuerySet[Mtask], user: User):
|
||||
def mtask_submit(cls, mtask: Mtask, user: User):
|
||||
"""
|
||||
锁定生产任务
|
||||
"""
|
||||
from apps.wpm.models import Mlog
|
||||
from apps.wpm.services import mlog_submit, update_mtask
|
||||
now = timezone.now()
|
||||
for mtask in mtasks:
|
||||
if mtask.state == Mtask.MTASK_ASSGINED:
|
||||
mlogs = Mlog.objects.filter(mtask=mtask)
|
||||
if mlogs.count() == 0:
|
||||
raise ParseError(f'{mtask.mgroup.name}_未填写日志')
|
||||
for mlog in mlogs:
|
||||
mlog_submit(mlog, user, now)
|
||||
update_mtask(mtask)
|
||||
mtask.state = Mtask.MTASK_DONE
|
||||
mtask.state = Mtask.MTASK_SUBMIT
|
||||
mtask.submit_time = now
|
||||
mtask.submit_user = user
|
||||
mtask.save()
|
||||
else:
|
||||
raise ParseError('该任务状态不可提交')
|
||||
|
|
|
@ -158,18 +158,16 @@ class MtaskViewSet(CustomModelViewSet):
|
|||
raise ParseError('该任务非创建中不可删除')
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'mtask.submit'}, serializer_class=PkSerializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'mtask.submit'}, serializer_class=PkSerializer)
|
||||
@transaction.atomic
|
||||
def submit(self, request, *args, **kwargs):
|
||||
"""提交任务(根据任务ID)
|
||||
|
||||
提交任务后不可更新日志
|
||||
"""
|
||||
ids = request.data.get('ids', [])
|
||||
mtask: Mtask = self.get_object()
|
||||
user = request.user
|
||||
mtasks = Mtask.objects.filter(
|
||||
id__in=ids, state=Mtask.MTASK_ASSGINED)
|
||||
PmService.mtasks_submit(mtasks, user)
|
||||
PmService.mtask_submit(mtask, user)
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'mtask.submit'}, serializer_class=Serializer)
|
||||
|
@ -181,5 +179,6 @@ class MtaskViewSet(CustomModelViewSet):
|
|||
"""
|
||||
mtask = self.get_object()
|
||||
mtasks = mtask.related
|
||||
PmService.mtasks_submit(mtasks)
|
||||
for mtask in mtasks:
|
||||
PmService.mtask_submit(mtask, self.request.user)
|
||||
return Response()
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-01 05:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0025_auto_20231120_1139'),
|
||||
('pum', '0002_alter_puorderitem_material'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='puorder',
|
||||
name='materials',
|
||||
field=models.ManyToManyField(blank=True, related_name='pu_order_materials', through='pum.PuOrderItem', to='mtm.Material', verbose_name='多个物料'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-01 05:50
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('pum', '0003_puorder_materials'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='puorder',
|
||||
name='submit_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submit_user_puorder', to=settings.AUTH_USER_MODEL, verbose_name='提交人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='puplan',
|
||||
name='submit_user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submit_user_puplan', to=settings.AUTH_USER_MODEL, verbose_name='提交人'),
|
||||
),
|
||||
]
|
|
@ -35,6 +35,8 @@ class PuPlan(CommonBModel):
|
|||
number = models.CharField('编号', max_length=20)
|
||||
name = models.CharField('名称', max_length=50, null=True, blank=True)
|
||||
submit_time = models.DateTimeField('提交时间', null=True, blank=True)
|
||||
submit_user = models.ForeignKey(
|
||||
'system.user', verbose_name='提交人', related_name='submit_user_puplan', on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
|
||||
class PuOrder(CommonBModel):
|
||||
|
@ -58,6 +60,10 @@ class PuOrder(CommonBModel):
|
|||
Supplier, verbose_name='供应商', on_delete=models.CASCADE)
|
||||
delivery_date = models.DateField('截止到货日期', null=True, blank=True)
|
||||
submit_time = models.DateTimeField('提交时间', null=True, blank=True)
|
||||
submit_user = models.ForeignKey(
|
||||
'system.user', verbose_name='提交人', related_name='submit_user_puorder', on_delete=models.CASCADE, null=True, blank=True)
|
||||
materials = models.ManyToManyField(
|
||||
Material, verbose_name='多个物料', blank=True, through='pum.puorderitem', related_name='pu_order_materials')
|
||||
|
||||
|
||||
class PuOrderItem(BaseModel):
|
||||
|
|
|
@ -4,7 +4,7 @@ from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE, EXCLU
|
|||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem
|
||||
from apps.mtm.serializers import MaterialSerializer
|
||||
from apps.mtm.serializers import MaterialSerializer, MaterialSimpleSerializer
|
||||
|
||||
|
||||
class SupplierSerializer(CustomModelSerializer):
|
||||
|
@ -83,6 +83,8 @@ class PuOrderSerializer(CustomModelSerializer):
|
|||
source='create_by.name', read_only=True)
|
||||
update_by_name = serializers.CharField(
|
||||
source='update_by.name', read_only=True)
|
||||
materials_ = MaterialSimpleSerializer(
|
||||
source='materials', many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PuOrder
|
||||
|
|
|
@ -46,7 +46,7 @@ class PuPlanViewSet(CustomModelViewSet):
|
|||
raise ParseError('该计划存在明细不可删除')
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'pu_plan.update'}, serializer_class=serializers.Serializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'pu_plan.submit'}, serializer_class=serializers.Serializer)
|
||||
def submit(self, request, *args, **kwargs):
|
||||
"""提交采购计划
|
||||
|
||||
|
@ -59,6 +59,7 @@ class PuPlanViewSet(CustomModelViewSet):
|
|||
if puplan.state != PuPlan.PUPLAN_CREATE:
|
||||
raise ParseError('采购计划状态异常')
|
||||
puplan.submit_time = timezone.now()
|
||||
puplan.submit_user = user
|
||||
puplan.state = PuPlan.PUPLAN_SUBMITED
|
||||
puplan.save()
|
||||
return Response()
|
||||
|
@ -106,7 +107,7 @@ class PuOrderViewSet(CustomModelViewSet):
|
|||
raise ParseError('采购订单非创建中不可删除')
|
||||
instance.delete(soft=False)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'pu_order.update'}, serializer_class=serializers.Serializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'pu_order.submit'}, serializer_class=serializers.Serializer)
|
||||
@transaction.atomic
|
||||
def submit(self, request, *args, **kwargs):
|
||||
"""提交采购订单
|
||||
|
@ -122,6 +123,7 @@ class PuOrderViewSet(CustomModelViewSet):
|
|||
if puorder.state != PuOrder.PUORDER_CREATE:
|
||||
raise ParseError('采购计划状态异常')
|
||||
puorder.submit_time = timezone.now()
|
||||
puorder.submit_user = user
|
||||
puorder.state = PuOrder.PUORDER_SUBMITED
|
||||
puorder.save()
|
||||
PumService.change_puplan_state_when_puorder_sumbit(puorder)
|
||||
|
|
|
@ -60,6 +60,13 @@ class OrderViewSet(CustomModelViewSet):
|
|||
search_fields = ['number']
|
||||
filter_fields = ['contract', 'customer']
|
||||
|
||||
@transaction.atomic
|
||||
def perform_destroy(self, instance):
|
||||
order = instance.order
|
||||
if order.state != Order.ORDER_CREATE:
|
||||
raise ParseError('订单非创建中不可删除')
|
||||
instance.delete()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'order.update'}, serializer_class=serializers.Serializer)
|
||||
@transaction.atomic
|
||||
def submit(self, request, *args, **kwargs):
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 3.2.12 on 2023-12-04 00:37
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0002_myschedule'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='permission',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.permission', verbose_name='父'),
|
||||
),
|
||||
]
|
|
@ -26,10 +26,11 @@ class Permission(BaseModel):
|
|||
(PERM_TYPE_BUTTON, '按钮')
|
||||
)
|
||||
name = models.CharField('名称', max_length=30)
|
||||
type = models.PositiveSmallIntegerField('类型', choices=menu_type_choices, default=30)
|
||||
type = models.PositiveSmallIntegerField(
|
||||
'类型', choices=menu_type_choices, default=30)
|
||||
sort = models.PositiveSmallIntegerField('排序标记', default=1)
|
||||
parent = models.ForeignKey('self', null=True, blank=True,
|
||||
on_delete=models.SET_NULL, verbose_name='父')
|
||||
on_delete=models.SET_NULL, verbose_name='父', db_constraint=False)
|
||||
codes = models.JSONField('权限标识', default=list, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -67,7 +68,8 @@ class Role(CommonADModel):
|
|||
"""
|
||||
name = models.CharField('名称', max_length=32)
|
||||
code = models.CharField('角色标识', max_length=32, null=True, blank=True)
|
||||
perms = models.ManyToManyField(Permission, blank=True, verbose_name='功能权限', related_name='role_perms')
|
||||
perms = models.ManyToManyField(
|
||||
Permission, blank=True, verbose_name='功能权限', related_name='role_perms')
|
||||
description = models.CharField('描述', max_length=50, blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -106,8 +108,10 @@ class PostRole(BaseModel):
|
|||
"""
|
||||
data_range = models.PositiveSmallIntegerField('数据权限范围', choices=DataFilter.choices,
|
||||
default=DataFilter.THISLEVEL_AND_BELOW)
|
||||
post = models.ForeignKey(Post, verbose_name='关联岗位', on_delete=models.CASCADE)
|
||||
role = models.ForeignKey(Role, verbose_name='关联角色', on_delete=models.CASCADE)
|
||||
post = models.ForeignKey(Post, verbose_name='关联岗位',
|
||||
on_delete=models.CASCADE)
|
||||
role = models.ForeignKey(Role, verbose_name='关联角色',
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
|
||||
class SoftDeletableUserManager(SoftDeletableManagerMixin, UserManager):
|
||||
|
@ -127,16 +131,21 @@ class User(AbstractUser, CommonBModel):
|
|||
'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管')
|
||||
post = models.ForeignKey(Post, verbose_name='主要岗位', on_delete=models.SET_NULL,
|
||||
null=True, blank=True)
|
||||
posts = models.ManyToManyField(Post, through='system.userpost', related_name='user_posts')
|
||||
posts = models.ManyToManyField(
|
||||
Post, through='system.userpost', related_name='user_posts')
|
||||
depts = models.ManyToManyField(Dept, through='system.userpost')
|
||||
roles = models.ManyToManyField(Role, verbose_name='关联角色')
|
||||
|
||||
# 关联账号
|
||||
secret = models.CharField('密钥', max_length=100, null=True, blank=True)
|
||||
wx_openid = models.CharField('微信公众号OpenId', max_length=100, null=True, blank=True)
|
||||
wx_nickname = models.CharField('微信昵称', max_length=100, null=True, blank=True)
|
||||
wx_headimg = models.CharField('微信头像', max_length=100, null=True, blank=True)
|
||||
wxmp_openid = models.CharField('微信小程序OpenId', max_length=100, null=True, blank=True)
|
||||
wx_openid = models.CharField(
|
||||
'微信公众号OpenId', max_length=100, null=True, blank=True)
|
||||
wx_nickname = models.CharField(
|
||||
'微信昵称', max_length=100, null=True, blank=True)
|
||||
wx_headimg = models.CharField(
|
||||
'微信头像', max_length=100, null=True, blank=True)
|
||||
wxmp_openid = models.CharField(
|
||||
'微信小程序OpenId', max_length=100, null=True, blank=True)
|
||||
|
||||
objects = SoftDeletableUserManager()
|
||||
|
||||
|
@ -154,9 +163,12 @@ class UserPost(BaseModel):
|
|||
用户岗位关系表
|
||||
"""
|
||||
name = models.CharField('名称', max_length=20, null=True, blank=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='up_user')
|
||||
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='up_post')
|
||||
dept = models.ForeignKey(Dept, on_delete=models.CASCADE, related_name='up_dept')
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, related_name='up_user')
|
||||
post = models.ForeignKey(
|
||||
Post, on_delete=models.CASCADE, related_name='up_post')
|
||||
dept = models.ForeignKey(
|
||||
Dept, on_delete=models.CASCADE, related_name='up_dept')
|
||||
sort = models.PositiveSmallIntegerField('排序', default=1)
|
||||
|
||||
class Meta:
|
||||
|
@ -229,7 +241,8 @@ class File(CommonAModel):
|
|||
(FILE_TYPE_OTHER, '其它')
|
||||
)
|
||||
mime = models.CharField('文件格式', max_length=120, null=True, blank=True)
|
||||
type = models.CharField('文件类型', max_length=50, choices=type_choices, default='文档')
|
||||
type = models.CharField('文件类型', max_length=50,
|
||||
choices=type_choices, default='文档')
|
||||
path = models.CharField('地址', max_length=200, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -250,5 +263,7 @@ class MySchedule(CommonAModel):
|
|||
)
|
||||
name = models.CharField('名称', max_length=200)
|
||||
type = models.PositiveSmallIntegerField('周期类型', default=10)
|
||||
interval = models.ForeignKey(IntervalSchedule, on_delete=models.PROTECT, null=True, blank=True)
|
||||
crontab = models.ForeignKey(CrontabSchedule, on_delete=models.PROTECT, null=True, blank=True)
|
||||
interval = models.ForeignKey(
|
||||
IntervalSchedule, on_delete=models.PROTECT, null=True, blank=True)
|
||||
crontab = models.ForeignKey(
|
||||
CrontabSchedule, on_delete=models.PROTECT, null=True, blank=True)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import time
|
||||
import django.utils.timezone as timezone
|
||||
from django.db import models
|
||||
from django.db.models import Model
|
||||
from django.db.models.query import QuerySet
|
||||
from apps.utils.snowflake import idWorker
|
||||
from django.db import IntegrityError
|
||||
|
@ -184,8 +185,17 @@ class CommonBDModel(BaseModel):
|
|||
abstract = True
|
||||
|
||||
|
||||
# class Smslog(BaseModel):
|
||||
# """
|
||||
# 短信发送记录表
|
||||
# """
|
||||
# phone = models.CharField('号码')
|
||||
def get_model_info(cls_or_instance):
|
||||
"""
|
||||
返回类似 system.dept 的字符
|
||||
"""
|
||||
if isinstance(cls_or_instance, Model):
|
||||
# 是一个模型实例
|
||||
app_label = cls_or_instance._meta.app_label
|
||||
model_name = cls_or_instance._meta.model_name
|
||||
else:
|
||||
# 假定是一个模型类
|
||||
app_label = cls_or_instance._meta.app_label
|
||||
model_name = cls_or_instance._meta.model_name
|
||||
|
||||
return f'{app_label}.{model_name}'
|
||||
|
|
|
@ -9,10 +9,11 @@ import requests
|
|||
from io import BytesIO
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
|
||||
def tran64(s):
|
||||
missing_padding = len(s) % 4
|
||||
if missing_padding != 0:
|
||||
s = s+'='* (4 - missing_padding)
|
||||
s = s+'=' * (4 - missing_padding)
|
||||
return s
|
||||
|
||||
|
||||
|
@ -202,4 +203,37 @@ def check_phone_e(phone):
|
|||
re_phone = r'^1\d{10}$'
|
||||
if not re.match(re_phone, phone):
|
||||
raise ValidationError('手机号格式错误')
|
||||
return phone
|
||||
return phone
|
||||
|
||||
|
||||
def compare_dicts(dict1, dict2, ignore_order=False):
|
||||
if ignore_order:
|
||||
for key in sorted(dict1.keys()):
|
||||
if key not in dict2 or not compare_values(dict1[key], dict2[key], ignore_order):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
return dict1 == dict2
|
||||
|
||||
|
||||
def compare_lists_of_dicts(list1, list2, ignore_order=False):
|
||||
"""比较两个列表,这里的列表包含字典(对象)"""
|
||||
if ignore_order:
|
||||
# 转换列表中的字典为元组列表,然后排序进行比较
|
||||
sorted_list1 = sorted((tuple(sorted(d.items())) for d in list1))
|
||||
sorted_list2 = sorted((tuple(sorted(d.items())) for d in list2))
|
||||
return sorted_list1 == sorted_list2
|
||||
else:
|
||||
# 按顺序比较列表中的字典
|
||||
return all(compare_dicts(obj1, obj2) for obj1, obj2 in zip(list1, list2))
|
||||
|
||||
|
||||
def compare_values(val1, val2, ignore_order=False):
|
||||
"""通用比较函数,也可以处理字典和列表。"""
|
||||
if isinstance(val1, list) and isinstance(val2, list):
|
||||
# 假设这里我们关心列表中对象的顺序
|
||||
return compare_lists_of_dicts(val1, val2, ignore_order)
|
||||
elif isinstance(val1, dict) and isinstance(val2, dict):
|
||||
return compare_dicts(val1, val2, ignore_order)
|
||||
else:
|
||||
return val1 == val2
|
||||
|
|
|
@ -35,6 +35,8 @@ class WMaterialFilter(filters.FilterSet):
|
|||
model = WMaterial
|
||||
fields = {
|
||||
"material": ["exact", "in"],
|
||||
"material__type": ["exact", "in"],
|
||||
"material__process": ["exact", "in"],
|
||||
"belong_dept": ["exact"],
|
||||
"belong_dept__name": ["exact"],
|
||||
"batch": ["exact"],
|
||||
|
@ -50,9 +52,10 @@ class MlogFilter(filters.FilterSet):
|
|||
"batch": ["exact"],
|
||||
"handle_date": ["exact"],
|
||||
"handle_user": ["exact"],
|
||||
"mtask__mgroup__belong_dept__name": ["exact"],
|
||||
"mgroup__belong_dept__name": ["exact", "in"],
|
||||
"mgroup__name": ["exact", "in"],
|
||||
"mgroup": ["exact"],
|
||||
"mtask__mgroup__belong_dept__name": ["exact", "contains", "in"],
|
||||
"mgroup__belong_dept__name": ["exact", "in", "contains"],
|
||||
"mgroup__name": ["exact", "in", "contains"],
|
||||
"submit_time": ["isnull"]
|
||||
}
|
||||
|
||||
|
@ -69,5 +72,7 @@ class HandoverFilter(filters.FilterSet):
|
|||
"recive_dept__name": ["exact"],
|
||||
"send_date": ["exact"],
|
||||
"material__type": ["exact", "in"],
|
||||
"submit_time": ["isnull"]
|
||||
"submit_time": ["isnull"],
|
||||
"mlog": ["isnull"],
|
||||
"send_mgroup": ["exact"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.2.12 on 2023-11-29 08:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0025_auto_20231120_1139'),
|
||||
('wpm', '0035_otherlog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='handover',
|
||||
name='send_mgroup',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.mgroup', verbose_name='送料工段'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.2.12 on 2023-11-30 02:47
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('mtm', '0025_auto_20231120_1139'),
|
||||
('wpm', '0036_handover_send_mgroup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='handover',
|
||||
name='material',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='handover',
|
||||
name='send_user',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='handover_send_user', to='system.user', verbose_name='交送人'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -129,9 +129,9 @@ class Mlog(CommonADModel):
|
|||
|
||||
handle_date = models.DateField('操作日期')
|
||||
handle_user = models.ForeignKey(
|
||||
User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True)
|
||||
User, verbose_name='操作人', on_delete=models.CASCADE, related_name='mlog_handle_user', null=True, blank=True) # 成型人
|
||||
handle_user_2 = models.ForeignKey(
|
||||
User, verbose_name='操作人2', on_delete=models.CASCADE, related_name='mlog_handle_user_2', null=True, blank=True)
|
||||
User, verbose_name='操作人2', on_delete=models.CASCADE, related_name='mlog_handle_user_2', null=True, blank=True) # 切料人
|
||||
handle_users = models.ManyToManyField(
|
||||
User, verbose_name='操作人(多选)', blank=True)
|
||||
handle_leader = models.ForeignKey(
|
||||
|
@ -163,12 +163,15 @@ class Handover(CommonADModel):
|
|||
"""
|
||||
send_date = models.DateField('送料日期')
|
||||
send_user = models.ForeignKey(
|
||||
User, verbose_name='交送人', on_delete=models.CASCADE, related_name='handover_send_user', null=True, blank=True)
|
||||
User, verbose_name='交送人', on_delete=models.CASCADE, related_name='handover_send_user')
|
||||
send_mgroup = models.ForeignKey(
|
||||
Mgroup, verbose_name='送料工段', on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
send_dept = models.ForeignKey(
|
||||
Dept, verbose_name='送料部门', on_delete=models.CASCADE, related_name='handover_send_dept')
|
||||
batch = models.CharField('批次号', max_length=50)
|
||||
material = models.ForeignKey(
|
||||
Material, verbose_name='物料', on_delete=models.CASCADE, null=True, blank=True)
|
||||
Material, verbose_name='物料', on_delete=models.CASCADE)
|
||||
count = models.PositiveIntegerField('送料数', default=0)
|
||||
count_eweight = models.FloatField('单数重量', default=0)
|
||||
recive_dept = models.ForeignKey(
|
||||
|
|
|
@ -223,6 +223,7 @@ class MlogSerializer(CustomModelSerializer):
|
|||
validated_data['material_in'] = mtask.material_in
|
||||
material_out = mtask.material_out
|
||||
validated_data['material_out'] = material_out
|
||||
validated_data['handle_date'] = mtask.start_date
|
||||
# if not WMaterial.objects.filter(batch=batch).exists():
|
||||
# raise ValidationError('批次号不存在')
|
||||
else:
|
||||
|
@ -298,13 +299,19 @@ class DeptBatchSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class HandoverSerializer(CustomModelSerializer):
|
||||
material = serializers.PrimaryKeyRelatedField(required=True, label='物料ID', queryset=Material.objects.all())
|
||||
material = serializers.PrimaryKeyRelatedField(
|
||||
required=True, label='物料ID', queryset=Material.objects.all())
|
||||
send_user_name = serializers.CharField(
|
||||
source='send_user.name', read_only=True)
|
||||
recive_user_name = serializers.CharField(
|
||||
source='recive_user.name', read_only=True)
|
||||
material_ = MaterialSimpleSerializer(source='material', read_only=True)
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs.get('mlog', None):
|
||||
attrs['send_mgroup'] = attrs['mlog'].mgroup
|
||||
return super().validate(attrs)
|
||||
|
||||
class Meta:
|
||||
model = Handover
|
||||
fields = '__all__'
|
||||
|
|
|
@ -144,10 +144,12 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
return
|
||||
if now is None:
|
||||
now = timezone.now()
|
||||
if now.date() < mlog.handle_date:
|
||||
raise ParseError('不可提交未来的日志')
|
||||
belong_dept = mlog.mgroup.belong_dept
|
||||
material_out = mlog.material_out
|
||||
material_in = mlog.material_in
|
||||
if material_in and material_in.is_hidden is False: # 需要进行车间库存管理
|
||||
if material_in: # 需要进行车间库存管理
|
||||
# 需要判断领用数是否合理
|
||||
material_has_qs = WMaterial.objects.filter(
|
||||
batch=mlog.batch, material=material_in, belong_dept=belong_dept)
|
||||
|
@ -163,7 +165,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
else:
|
||||
material_has.count = material_has.count - mlog.count_use
|
||||
material_has.save()
|
||||
if material_out and material_out.is_hidden is False: # 需要入车间库存
|
||||
if material_out: # 需要入车间库存
|
||||
# 有多个产物的情况
|
||||
if material_out.brothers and Mlogb.objects.filter(mlog=mlog).exists():
|
||||
for item in Mlogb.objects.filter(mlog=mlog):
|
||||
|
@ -185,7 +187,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
|
||||
def update_mtask(mtask: Mtask):
|
||||
from apps.pm.models import Utask
|
||||
res = Mlog.objects.filter(mtask=mtask).aggregate(sum_count_real=Sum(
|
||||
res = Mlog.objects.filter(mtask=mtask).exclude(submit_time=None).aggregate(sum_count_real=Sum(
|
||||
'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
|
||||
mtask.count_real = res['sum_count_real'] if res['sum_count_real'] else 0
|
||||
mtask.count_ok = res['sum_count_ok'] if res['sum_count_ok'] else 0
|
||||
|
@ -200,8 +202,8 @@ def update_mtask(mtask: Mtask):
|
|||
utask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
|
||||
if utask.count_ok > 0 and utask.state == Utask.UTASK_ASSGINED:
|
||||
utask.state = Utask.UTASK_WORKING
|
||||
if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_DONE).count() == 0:
|
||||
utask.state = Mtask.MTASK_DONE
|
||||
if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_SUBMIT).count() == 0:
|
||||
utask.state = Utask.UTASK_SUBMIT
|
||||
utask.save()
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer,
|
|||
MlogSerializer, MlogRelatedSerializer, DeptBatchSerializer, HandoverSerializer,
|
||||
GenHandoverSerializer, GenHandoverWmSerializer, MlogAnaSerializer, AttLogSerializer, OtherLogSerializer)
|
||||
from .services import mlog_submit, update_mtask, handover_submit
|
||||
from apps.monitor.services import create_auditlog, delete_auditlog
|
||||
# Create your views here.
|
||||
|
||||
|
||||
|
@ -102,7 +103,7 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
perms_map = {'get': '*'}
|
||||
queryset = WMaterial.objects.all()
|
||||
serializer_class = WMaterialSerializer
|
||||
select_related_fields = ['material', 'belong_dept']
|
||||
select_related_fields = ['material', 'belong_dept', 'material__process']
|
||||
search_fields = ['material__name',
|
||||
'material__number', 'material__specification']
|
||||
filterset_class = WMaterialFilter
|
||||
|
@ -135,10 +136,26 @@ class MlogViewSet(CustomModelViewSet):
|
|||
prefetch_related_fields = ['handle_users', 'material_outs', 'b_mlog']
|
||||
filterset_class = MlogFilter
|
||||
|
||||
@transaction.atomic
|
||||
def perform_create(self, serializer):
|
||||
ins = serializer.save()
|
||||
data = MlogSerializer(ins).data
|
||||
create_auditlog('create', ins, data)
|
||||
|
||||
@transaction.atomic
|
||||
def perform_destroy(self, instance):
|
||||
if instance.submit_time is not None:
|
||||
raise ParseError('日志已提交不可变动')
|
||||
return super().perform_destroy(instance)
|
||||
delete_auditlog(instance, instance.id)
|
||||
instance.delete()
|
||||
|
||||
@transaction.atomic
|
||||
def perform_update(self, serializer):
|
||||
ins = serializer.instance
|
||||
val_old = MlogSerializer(instance=ins).data
|
||||
serializer.save()
|
||||
val_new = MlogSerializer(instance=ins).data
|
||||
create_auditlog('update', ins, val_new, val_old)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'mlog.submit'}, serializer_class=Serializer)
|
||||
@transaction.atomic
|
||||
|
@ -148,10 +165,13 @@ class MlogViewSet(CustomModelViewSet):
|
|||
日志提交
|
||||
"""
|
||||
ins: Mlog = self.get_object()
|
||||
vdata_old = MlogSerializer(ins).data
|
||||
if ins.submit_time is None:
|
||||
mlog_submit(ins, self.request.user, None)
|
||||
if ins.mtask:
|
||||
update_mtask(ins.mtask)
|
||||
vdata_new = MlogSerializer(ins).data
|
||||
create_auditlog('submit', ins, vdata_new, vdata_old)
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MlogRelatedSerializer)
|
||||
|
@ -241,7 +261,7 @@ class HandoverViewSet(CustomModelViewSet):
|
|||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer)
|
||||
@transaction.atomic
|
||||
def submit(self, request):
|
||||
def submit(self, request, *args, **kwargs):
|
||||
"""交接记录提交(变动车间库存)
|
||||
|
||||
交接记录提交
|
||||
|
@ -283,7 +303,7 @@ class HandoverViewSet(CustomModelViewSet):
|
|||
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'handover.create'}, serializer_class=GenHandoverSerializer)
|
||||
@transaction.atomic
|
||||
def gen_by_mlogs(self, request):
|
||||
def gen_by_mlog(self, request):
|
||||
"""从生产日志生成交接记录
|
||||
|
||||
从生产日志生成交接记录
|
||||
|
@ -306,6 +326,7 @@ class HandoverViewSet(CustomModelViewSet):
|
|||
count=mlog.count_real,
|
||||
count_eweight=mlog.count_real_eweight,
|
||||
mlog=mlog,
|
||||
send_mgroup=mlog.mgroup,
|
||||
create_by=user
|
||||
)
|
||||
return Response()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import os
|
||||
|
||||
from . import conf
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
app = Celery('ehs')
|
||||
app = Celery(conf.BASE_PROJECT_CODE)
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
# the configuration object to child processes.
|
||||
|
|
|
@ -70,7 +70,7 @@ DEBUG = conf.DEBUG
|
|||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
SYS_NAME = 'XT_EHS'
|
||||
SYS_VERSION = '2.2.2'
|
||||
SYS_VERSION = '2.3.0'
|
||||
|
||||
|
||||
# Application definition
|
||||
|
@ -282,7 +282,7 @@ AUTHENTICATION_BACKENDS = (
|
|||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": "redis://127.0.0.1:6379/2",
|
||||
"LOCATION": conf.CACHE_LOCATION,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
}
|
||||
|
@ -290,7 +290,8 @@ CACHES = {
|
|||
}
|
||||
|
||||
# celery配置,celery正常运行必须安装redis
|
||||
CELERY_BROKER_URL = "redis://127.0.0.1:6379/3" # 任务存储
|
||||
CELERY_BROKER_URL = conf.CELERY_BROKER_URL # 任务存储
|
||||
CELERY_TASK_DEFAULT_QUEUE = conf.CELERY_TASK_DEFAULT_QUEUE # 任务队列
|
||||
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行100个任务就会被销毁,可防止内存泄露
|
||||
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
|
||||
CELERY_ENABLE_UTC = True # 启动时区设置
|
||||
|
|
Loading…
Reference in New Issue