diff --git a/hb_server/apps/inm/migrations/0024_auto_20211227_0948.py b/hb_server/apps/inm/migrations/0024_auto_20211227_0948.py new file mode 100644 index 0000000..16c3b5f --- /dev/null +++ b/hb_server/apps/inm/migrations/0024_auto_20211227_0948.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.9 on 2021-12-27 01:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0038_auto_20211227_0948'), + ('inm', '0023_auto_20211216_0945'), + ] + + operations = [ + migrations.AlterField( + model_name='fifoitemproduct', + name='wproduct', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fifoitem_wproduct', to='wpm.wproduct', verbose_name='关联的动态产品'), + ), + migrations.AlterField( + model_name='iproduct', + name='wproduct', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='iproduct_wproduct', to='wpm.wproduct', verbose_name='关联的动态产品'), + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index e1e9f1a..8937211 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -92,7 +92,8 @@ class IProduct(BaseModel): material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE) warehouse = models.ForeignKey(WareHouse, on_delete=models.CASCADE, verbose_name='所在仓库') batch = models.CharField('所属批次号', max_length=100, default='') - wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True) + wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, + null=True, blank=True, related_name='iproduct_wproduct') is_mtested = models.BooleanField('是否军检', default=False) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True) @@ -103,7 +104,8 @@ class FIFOItemProduct(BaseModel): 出入库产品 """ fifoitem = models.ForeignKey(FIFOItem, verbose_name='关联出入库具体产品', on_delete=models.CASCADE) - wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True) + wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True, + related_name='fifoitem_wproduct') number = models.CharField('物品编号', max_length=50) material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE) iproduct = models.ForeignKey(IProduct, verbose_name='关联库存产品', null=True, blank=True, on_delete=models.SET_NULL) diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index 0956d4f..42006dd 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -74,7 +74,7 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'*': '*'} queryset = FIFOItem.objects.select_related('material', 'fifo').all() serializer_class = FIFOItemSerializer - filterset_fields = ['material', 'fifo'] + filterset_fields = ['material', 'fifo', 'fifo__type', 'is_tested', 'is_testok'] search_fields = [] ordering_fields = ['create_time'] ordering = ['-create_time'] @@ -99,13 +99,7 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): obj = serializer.save(create_by = self.request.user) tris = [] for m in record_data: # 保存记录详情 - form_field = m['form_field'] - m['field_name'] = form_field.field_name - m['field_key'] = form_field.field_key - m['field_type'] = form_field.field_type m['field_value'] = m['field_value'] - m['sort'] = form_field.sort - m['need_judge'] = form_field.need_judge m['is_testok'] = m['is_testok'] if 'is_testok' in m else True m['test_record'] = obj tris.append(TestRecordItem(**m)) diff --git a/hb_server/apps/sam/views.py b/hb_server/apps/sam/views.py index 10a6d97..8a5f004 100644 --- a/hb_server/apps/sam/views.py +++ b/hb_server/apps/sam/views.py @@ -14,6 +14,8 @@ from rest_framework.decorators import action from django.db.models import F from rest_framework.response import Response from django.utils import timezone + +from apps.wf.models import Workflow # Create your views here. class CustomerViewSet(CreateUpdateCustomMixin, ModelViewSet): """ @@ -168,6 +170,10 @@ class SaleViewSet(CreateUpdateCustomMixin, ListModelMixin, RetrieveModelMixin, C FIFOItemProduct.objects.bulk_create(ipxs) # 更新成品库情况 ips.update(is_saled=True) + # 更新动态产品表情况 + from apps.wpm.models import WProduct + WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update( + act_state=WProduct.WPR_ACT_STATE_SELLED) # 更新库存 update_inm(fifo) # 变更审核状态 diff --git a/hb_server/apps/wf/migrations/0020_auto_20211223_1006.py b/hb_server/apps/wf/migrations/0020_auto_20211223_1006.py new file mode 100644 index 0000000..81df3fe --- /dev/null +++ b/hb_server/apps/wf/migrations/0020_auto_20211223_1006.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.9 on 2021-12-23 02:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wf', '0019_auto_20211221_0923'), + ] + + operations = [ + migrations.AddField( + model_name='customfield', + name='is_hidden', + field=models.BooleanField(default=False, help_text='可用于携带不需要用户查看的信息', verbose_name='是否隐藏'), + ), + migrations.AlterField( + model_name='customfield', + name='field_choice', + field=models.JSONField(blank=True, default=list, help_text='选项值,格式为list, 例["id":1, "name":"张三"]', verbose_name='选项值'), + ), + migrations.AlterField( + model_name='customfield', + name='field_type', + field=models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('cascader', '单选级联'), ('cascaders', '多选级联'), ('select_dg', '弹框单选'), ('select_dgs', '弹框多选'), ('textarea', '文本域'), ('file', '附件')], help_text='5.字符串,10.整形,15.浮点型,20.布尔,25.日期,30.日期时间,35.单选框,40.多选框,45.下拉列表,50.多选下拉列表,55.文本域,60.用户名, 70.多选的用户名, 80.附件(只保存路径,多个使用逗号隔开)', max_length=50, verbose_name='类型'), + ), + migrations.AlterField( + model_name='customfield', + name='label', + field=models.CharField(default='', help_text='处理特殊逻辑使用', max_length=1000, verbose_name='标签'), + ), + migrations.AlterField( + model_name='workflow', + name='title_template', + field=models.CharField(blank=True, default='{title}', help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}', max_length=50, null=True, verbose_name='标题模板'), + ), + ] diff --git a/hb_server/apps/wf/migrations/0021_auto_20211224_1426.py b/hb_server/apps/wf/migrations/0021_auto_20211224_1426.py new file mode 100644 index 0000000..8013a5c --- /dev/null +++ b/hb_server/apps/wf/migrations/0021_auto_20211224_1426.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.9 on 2021-12-24 06:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wf', '0020_auto_20211223_1006'), + ] + + operations = [ + migrations.AlterField( + model_name='customfield', + name='description', + field=models.CharField(blank=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述', max_length=100, null=True, verbose_name='描述'), + ), + migrations.AlterField( + model_name='customfield', + name='field_template', + field=models.TextField(blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder', null=True, verbose_name='文本域模板'), + ), + migrations.AlterField( + model_name='customfield', + name='field_type', + field=models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('cascader', '单选级联'), ('cascaders', '多选级联'), ('select_dg', '弹框单选'), ('select_dgs', '弹框多选'), ('textarea', '文本域'), ('file', '附件')], help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, cascader, cascaders,textarea, file', max_length=50, verbose_name='类型'), + ), + migrations.AlterField( + model_name='customfield', + name='placeholder', + field=models.CharField(blank=True, help_text='用户工单详情表单中作为字段的占位符显示', max_length=100, null=True, verbose_name='占位符'), + ), + ] diff --git a/hb_server/apps/wf/migrations/0022_alter_customfield_is_hidden.py b/hb_server/apps/wf/migrations/0022_alter_customfield_is_hidden.py new file mode 100644 index 0000000..c197db2 --- /dev/null +++ b/hb_server/apps/wf/migrations/0022_alter_customfield_is_hidden.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-27 01:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wf', '0021_auto_20211224_1426'), + ] + + operations = [ + migrations.AlterField( + model_name='customfield', + name='is_hidden', + field=models.BooleanField(default=False, help_text='可用于携带不需要用户查看的字段信息', verbose_name='是否隐藏'), + ), + ] diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py index 94c9c44..36a1cb3 100644 --- a/hb_server/apps/wf/models.py +++ b/hb_server/apps/wf/models.py @@ -150,26 +150,32 @@ class CustomField(CommonAModel): ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), + ('cascader', '单选级联'), + ('cascaders', '多选级联'), + ('select_dg', '弹框单选'), + ('select_dgs', '弹框多选'), ('textarea', '文本域'), - ('selectuser', '单选用户'), - ('selectusers', '多选用户'), - ('file', '附件'), - ('draw', '绘图') + ('file', '附件') ) workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') - field_type = models.CharField('类型', max_length=50, choices=field_type_choices, help_text='5.字符串,10.整形,15.浮点型,20.布尔,25.日期,30.日期时间,35.单选框,40.多选框,45.下拉列表,50.多选下拉列表,55.文本域,60.用户名, 70.多选的用户名, 80.附件(只保存路径,多个使用逗号隔开)') + field_type = models.CharField('类型', max_length=50, choices=field_type_choices, + help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, cascader, cascaders,textarea, file') field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突') field_name = models.CharField('字段名称', max_length=50) sort = models.IntegerField('排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列') default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值') - description = models.CharField('描述', max_length=100, blank=True, default='', help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述') - placeholder = models.CharField('占位符', max_length=100, blank=True, default='', help_text='用户工单详情表单中作为字段的占位符显示') - field_template = models.TextField('文本域模板', default='', blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder') + description = models.CharField('描述', max_length=100, blank=True, null=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述') + placeholder = models.CharField('占位符', max_length=100, blank=True, null=True, help_text='用户工单详情表单中作为字段的占位符显示') + field_template = models.TextField('文本域模板', null=True, blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder') boolean_field_display = models.JSONField('布尔类型显示名', default=dict, blank=True, help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"是","0":"否"}或{"1":"需要","0":"不需要"},注意数字也需要引号') - field_choice = models.JSONField('radio、checkbox、select的选项', default=dict, blank=True, - help_text='radio,checkbox,select,multiselect类型可供选择的选项,格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号') - label = models.JSONField('标签', blank=True, default=dict, help_text='自定义标签,json格式,调用方可根据标签自行处理特殊场景逻辑,loonflow只保存文本内容') + + field_choice = models.JSONField('选项值', default=list, blank=True, + help_text='选项值,格式为list, 例["id":1, "name":"张三"]') + + label = models.CharField('标签', max_length=1000, default='', help_text='处理特殊逻辑使用') + # hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值') + is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息') class Ticket(CommonBModel): """ diff --git a/hb_server/apps/wf/serializers.py b/hb_server/apps/wf/serializers.py index de756fe..6daa326 100644 --- a/hb_server/apps/wf/serializers.py +++ b/hb_server/apps/wf/serializers.py @@ -38,12 +38,33 @@ class TransitionSerializer(serializers.ModelSerializer): queryset = queryset.select_related('source_state','destination_state') return queryset +class AllField(serializers.Field): + def to_representation(self, value): + return value + + def to_internal_value(self, data): + return data + +class FieldChoiceSerializer(serializers.Serializer): + id = AllField(label='ID') + name = serializers.CharField(label='名称') + class CustomFieldSerializer(serializers.ModelSerializer): class Meta: model = CustomField fields = '__all__' +class CustomFieldCreateUpdateSerializer(serializers.ModelSerializer): + + field_choice = FieldChoiceSerializer(label='选项列表', many=True, required=False) + class Meta: + model = CustomField + fields = ['workflow', 'field_type', 'field_key', 'field_name', + 'sort', 'default_value', 'description', 'placeholder', 'field_template', + 'boolean_field_display', 'field_choice', 'label', 'is_hidden'] + + class TicketSimpleSerializer(serializers.ModelSerializer): class Meta: model = Ticket @@ -107,8 +128,28 @@ class TicketDetailSerializer(serializers.ModelSerializer): key = i['field_key'] i['field_state'] = state_fields.get(key, 1) i['field_value'] = ticket_data.get(key, None) + i['field_display'] = i['field_value'] # 该字段是用于查看详情直接展示 + if 'sys_user' in i['label']: + if isinstance(i['field_value'], list): + i['field_display'] = ','.join(list(User.objects.filter(id__in=i['field_value']).values_list('name', flat=True))) + else: + i['field_display'] = User.objects.get(id=i['field_value']).name + elif i['field_type'] in ['radio', 'select']: + for m in i['field_choice']: + if m['id'] == i['field_value']: + i['field_display'] = m['name'] + elif i['field_type'] in ['checkbox', 'selects']: + d_list = [] + for m in i['field_choice']: + if m['id'] in i['field_value']: + d_list.append(m['name']) + i['field_display'] = ','.join(d_list) return all_fields_l + def filter_display(self, item, field_value): + if item['id'] == field_value: + return + class TicketFlowSerializer(serializers.ModelSerializer): participant_ = UserSimpleSerializer(source='participant', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True) diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index 2f9b092..02ac6a0 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -9,7 +9,7 @@ from django.core.exceptions import AppRegistryNotReady from rest_framework.response import Response from rest_framework import serializers from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin -from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer +from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer from django.shortcuts import get_object_or_404, render from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.decorators import action, api_view @@ -111,6 +111,11 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, filterset_fields = ['workflow', 'field_type'] ordering = ['sort'] + def get_serializer_class(self): + if self.action in ['create', 'update']: + return CustomFieldCreateUpdateSerializer + return super().get_serializer_class() + class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet): perms_map = {'*':'*'} queryset = Ticket.objects.all() @@ -133,7 +138,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin return super().get_serializer_class() def filter_queryset(self, queryset): - if not self.request.query_params.get('category', None): + if not self.detail and not self.request.query_params.get('category', None): raise APIException('请指定查询分类') return super().filter_queryset(queryset) diff --git a/hb_server/apps/wpm/migrations/0038_auto_20211227_0948.py b/hb_server/apps/wpm/migrations/0038_auto_20211227_0948.py new file mode 100644 index 0000000..d419bcc --- /dev/null +++ b/hb_server/apps/wpm/migrations/0038_auto_20211227_0948.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.9 on 2021-12-27 01:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0037_wproduct_scrap_reason'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='ng_sign', + field=models.PositiveSmallIntegerField(blank=True, choices=[(10, '返工'), (20, '返修'), (30, '报废'), (40, '让步接收'), (50, '偏离许可'), (60, '降级使用'), (70, '退回供方'), (80, '召回')], null=True, verbose_name='不合格标记'), + ), + migrations.AlterField( + model_name='wproduct', + name='act_state', + field=models.IntegerField(choices=[(6, '待复检'), (8, '操作准备中'), (10, '操作进行中'), (20, '待检验'), (26, '待夹层检验'), (30, '已合格'), (40, '已入库'), (50, '不合格'), (60, '待成品检验'), (70, '已报废'), (80, '已售出')], default=0, verbose_name='进行状态'), + ), + migrations.AlterField( + model_name='wprouctticket', + name='decision', + field=models.PositiveSmallIntegerField(blank=True, choices=[(10, '返工'), (20, '返修'), (30, '报废'), (40, '让步接收'), (50, '偏离许可'), (60, '降级使用'), (70, '退回供方'), (80, '召回')], null=True, verbose_name='最终决定'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 169e295..55491ca 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -36,6 +36,7 @@ class WProduct(CommonAModel): WPR_ACT_STATE_NOTOK = 50 WPR_ACT_STATE_TOFINALTEST = 60 WPR_ACT_STATE_SCRAP = 70 + WPR_ACT_STATE_SELLED = 80 act_state_choices=( (WPR_ACT_STATE_TORETEST, '待复检'), (WPR_ACT_STATE_DOWAIT, '操作准备中'), @@ -46,7 +47,8 @@ class WProduct(CommonAModel): (WPR_ACT_STATE_INM, '已入库'), (WPR_ACT_STATE_NOTOK, '不合格'), (WPR_ACT_STATE_TOFINALTEST, '待成品检验'), - (WPR_ACT_STATE_SCRAP, '已报废') + (WPR_ACT_STATE_SCRAP, '已报废'), + (WPR_ACT_STATE_SELLED, '已售出'), ) SCRAP_REASON_QIPAO = 10 SCRAP_REASON_PODIAN = 20 @@ -58,6 +60,26 @@ class WProduct(CommonAModel): (30, '划伤'), (40, '其他') ) + + NG_BACK_WORK = 10 + NG_BACK_FIX = 20 + NG_SCRAP = 30 + NG_ACCEPT = 40 + NG_PERMIT = 50 + NG_DOWN = 60 + NG_BACK_FROM = 70 + NG_RECALL = 80 + + ng_choices = ( + (NG_BACK_WORK, '返工'), + (NG_BACK_FIX, '返修'), + (NG_SCRAP, '报废'), + (NG_ACCEPT, '让步接收'), + (NG_PERMIT, '偏离许可'), + (NG_DOWN, '降级使用'), + (NG_BACK_FROM, '退回供方'), + (NG_RECALL, '召回') + ) number = models.CharField('物品编号', unique=True, null=True, blank=True, max_length=50) material = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE) pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True, on_delete=models.CASCADE, related_name='w_pre_step') @@ -69,6 +91,9 @@ class WProduct(CommonAModel): subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE, related_name='wproduct_subplan') scrap_reason = models.IntegerField('报废原因', choices=scrap_reason_choices, null=True, blank=True) + + ng_sign = models.PositiveSmallIntegerField('不合格标记', choices=ng_choices, null=True, blank=True) + warehouse = models.ForeignKey(WareHouse, verbose_name='所在仓库', on_delete=models.SET_NULL, null=True, blank=True) operation = models.ForeignKey('wpm.operation', verbose_name='当前操作', on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_operation') @@ -89,6 +114,7 @@ class WprouctTicket(CommonAModel): """ 玻璃审批工单 """ + number = models.CharField('物品编号', null=True, blank=True, max_length=50) wproduct = models.ForeignKey(WProduct, verbose_name='关联产品', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='所在物料状态', on_delete=models.CASCADE) @@ -96,7 +122,7 @@ class WprouctTicket(CommonAModel): subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='所在子生产计划', on_delete=models.CASCADE) ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.CASCADE, related_name='wt_ticket') - decision = models.CharField('最终决定', null=True, blank=True, max_length=100) + decision = models.PositiveSmallIntegerField('最终决定', choices=WProduct.ng_choices, null=True, blank=True) class Pick(CommonADModel): """ diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index 01c6315..034c1f8 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -382,6 +382,7 @@ class OperationMaterialCreate2Serailizer(serializers.ModelSerializer): class OperationMaterialCreate2ListSerailizer(serializers.ListSerializer): child=OperationMaterialCreate2Serailizer() + class OperationMaterialCreate3Serializer(serializers.ModelSerializer): material = serializers.PrimaryKeyRelatedField(required=True, queryset=Material.objects.all()) class Meta: diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index 9d56be0..30ba1f2 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -46,17 +46,18 @@ class WpmServies(object): if is_testok: if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: # 复检 wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT + wproduct.ng_sign = None # 把不合格标记去除 elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验 wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST else: wproduct.act_state = WProduct.WPR_ACT_STATE_OK if wproduct.number is None: # 产生半成品编号 wproduct.number = 'WP'+ranstr(7) - # 更新子计划状态 - # 更新子计划主产品数 + + # 更新子计划合格进度 instance = SubProductionProgress.objects.get(subproduction_plan=wproduct.subproduction_plan, is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - instance.count_ok = instance.count_ok + 1 # 这个地方可能会有问题 + instance.count_ok = instance.count_ok + 1 instance.save() else:# 如果不合格 wproduct.act_state = WProduct.WPR_ACT_STATE_NOTOK diff --git a/hb_server/apps/wpm/signals.py b/hb_server/apps/wpm/signals.py index 2c816a2..3d27b38 100644 --- a/hb_server/apps/wpm/signals.py +++ b/hb_server/apps/wpm/signals.py @@ -1,9 +1,12 @@ from django.db.models.signals import post_save +from apps.mtm.models import SubprodctionMaterial +from apps.pm.models import SubProductionProgress from apps.qm.models import TestRecord from apps.wf.models import Ticket from django.dispatch import receiver - +from rest_framework import exceptions from apps.wpm.models import WProduct, WprouctTicket +from apps.wpm.models import OperationWproduct @receiver(post_save, sender=Ticket) @@ -43,18 +46,41 @@ def handleTicket(sender, instance, created, **kwargs): ticket_data = instance.ticket_data wt = instance.wt_ticket wp = wt.wproduct + decision = WProduct.NG_BACK_WORK + if 'decision_1' in ticket_data and ticket_data['decision_1']: - wt.decision = ticket_data['decision_1'] - if ticket_data['decision_1'] in ['返工', '返修']: - pass - elif ticket_data['decision_1'] in ['让步接收']: - wp.act_state = WProduct.WPR_ACT_STATE_OK + decision = ticket_data['decision_1'] elif 'decision_2' in ticket_data and ticket_data['decision_2']: - wp.decision = ticket_data['decision_2'] - if ticket_data['decision_2'] in ['返工', '返修']: - pass - elif ticket_data['decision_2'] in ['让步接收']: - wp.act_state = WProduct.WPR_ACT_STATE_OK - wt.save() - wp.save() + decision = ticket_data['decision_2'] + + + wp.ng_sign = decision + if decision in [WProduct.NG_BACK_WORK, WProduct.NG_BACK_FIX]: + step = ticket_data['back_step'] + wp.step = step + # 找到当时所属的计划 + sp = OperationWproduct.objects.filter(operation__is_submited=True, operation__step=step).first() + if sp: + wp.subproduction_plan = sp + wt.save() + wp.ticket = None # 解除当前工单 + wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT + wp.save() + # 更新子计划合格进度 + instance = SubProductionProgress.objects.get(subproduction_plan=sp, + is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + instance.count_ok = instance.count_ok - 1 #进度计算这里该怎么处理呢 + instance.save() + + else: + raise exceptions.APIException('返回步骤点错误') + + elif decision in [WProduct.NG_ACCEPT, WProduct.NG_PERMIT]: + wp.act_state = WProduct.WPR_ACT_STATE_OK + wp.ng_sign = decision + wt.save() + wp.ticket = None # 解除当前工单 + wp.save() + + \ No newline at end of file diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 34556f6..ff06c97 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -343,7 +343,8 @@ class WProductViewSet(ListModelMixin, GenericViewSet): vdata = serializer.validated_data if obj.act_state == WProduct.WPR_ACT_STATE_NOTOK: pass - elif obj.step.process.id == 1: # 如果是冷加工可直接报废 + elif obj.step.process.id == 1 and \ + obj.act_state in [WProduct.WPR_ACT_STATE_DOWAIT, WProduct.WPR_ACT_STATE_TOTEST]: # 如果是冷加工可直接报废 if vdata.get('scrap_reason', None): obj.scrap_reason = vdata['scrap_reason'] else: @@ -369,12 +370,13 @@ class WProductViewSet(ListModelMixin, GenericViewSet): 发起不合格审理单 """ obj = self.get_object() - if obj.act_state != WProduct.WPR_ACT_STATE_NOTOK: - raise exceptions.APIException('非检验不合格产品不可发起不合格审理') + if obj.act_state != WProduct.WPR_ACT_STATE_NOTOK or obj.ng_sign is not None: + raise exceptions.APIException('该产品不可发起不合格审理') workflow = Workflow.objects.filter(name='不合格品审理单', is_deleted=False).first() if workflow: exist_data = { 'wproduct':obj.id, + 'wproduct_number':obj.number, 'wproduct_name':obj.material.name, 'wproduct_specification':obj.material.specification, 'finder':request.user.id, @@ -537,11 +539,13 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd else: wp.act_state = WProduct.WPR_ACT_STATE_TOTEST wp.material = wsp.main_product - # 更新子计划进度 - instance = SubProductionProgress.objects.get(subproduction_plan=wsp, - is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) - instance.count_real = instance.count_real + 1 # 这个地方可能会有问题,不够严谨 - instance.save() + # 更新子计划生产进度 + # 如果产品有返工标记不做计算 + if wp.ng_sign not in [WProduct.NG_BACK_FIX, WProduct.NG_BACK_WORK]: + instance = SubProductionProgress.objects.get(subproduction_plan=wsp, + is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT) + instance.count_real = instance.count_real + 1 # 这个地方可能会有问题,不够严谨 + instance.save() wp.operation = None wp.update_by = request.user wp.save()