diff --git a/hb_client/src/api/hrm.js b/hb_client/src/api/hrm.js index 6a2a20c..3aef7e2 100644 --- a/hb_client/src/api/hrm.js +++ b/hb_client/src/api/hrm.js @@ -29,3 +29,22 @@ export function getEmployee(data) { data }) } + + +//个人离岗说明 + +export function getNotWorkList(query) { + return request({ + url: '/hrm/not_work_remark/', + method: 'get', + params: query + }) +} + +export function getClockrecordList(query) { + return request({ + url: '/hrm/clock_record/', + method: 'get', + params: query + }) +} diff --git a/hb_client/src/api/mtm.js b/hb_client/src/api/mtm.js index e1adb34..5f6ab9c 100644 --- a/hb_client/src/api/mtm.js +++ b/hb_client/src/api/mtm.js @@ -377,3 +377,34 @@ export function deletetechdoc(id, data) { }) } +//装箱 +//记录表格字段表 + +export function getpackitemList(query) { + return request({ + url: '/mtm/packitem/', + method: 'get', + params: query + }) +} +export function createpackitem(data) { + return request({ + url: '/mtm/packitem/', + method: 'post', + data + }) +} +export function updatepackitem(id, data) { + return request({ + url: `/mtm/packitem/${id}/`, + method: 'put', + data + }) +} +export function deletepackitem(id, data) { + return request({ + url: `/mtm/packitem/${id}/`, + method: 'delete', + data + }) +} \ No newline at end of file diff --git a/hb_client/src/views/mtm/materialDetail.vue b/hb_client/src/views/mtm/materialDetail.vue index 52233cc..1bedbef 100644 --- a/hb_client/src/views/mtm/materialDetail.vue +++ b/hb_client/src/views/mtm/materialDetail.vue @@ -1,223 +1,371 @@ - - - {{materialdetail.number}} - {{materialdetail.name}} - {{materialdetail.specification}} - {{materialdetail.unit}} - {{materialdetail.count}} - - - - + + + + {{ materialdetail.number }} + + {{ materialdetail.name }} + + {{ materialdetail.specification }} + + {{ materialdetail.unit }} + + + {{ materialdetail.count }} + + - - + + - + + + + + {{ scope.row.batch }} + + + {{ + scope.row.material_.name + }} + + + {{ + scope.row.material_.specification + }} + - - - - {{ scope.row.batch }} - - - {{ scope.row.material_.name }} - - - {{ scope.row.material_.specification }} - - - - {{ - scope.row.material_.number - }} - - - {{ - scope.row.warehouse_.name - }} - - - {{ - scope.row.warehouse_.number - }} - - - {{ scope.row.count }} - - + + {{ + scope.row.material_.number + }} + + + {{ + scope.row.warehouse_.name + }} + + + {{ + scope.row.warehouse_.number + }} + + + {{ scope.row.count }} + + + - - - - - - - - {{ scope.row.number }} + + + + + + {{ scope.row.number }} + + + {{ + scope.row.customer_.name + }} + + + {{ + scope.row.contract_.name + }} + + + {{ + scope.row.product_.name + }} + + + {{ + scope.row.product_.specification + }} + + + {{ scope.row.count }} + + + {{ + scope.row.delivery_date + }} + + + {{ + scope.row.create_time + }} + + + + + + + + + {{ scope.row.number }} + + + {{ + scope.row.order_.number + }} + + + {{ + scope.row.order_.contract_.number + }} + + + + {{ scope.row.count }} + + + {{ scope.row.start_date }} + + + {{ scope.row.end_date }} + + + {{ + scope.row.order_.delivery_date + }} + + + + + 否 + 是 + + + + {{ + scope.row.create_time + }} + + + + + + + + + {{ scope.row.number }} + + + + {{ scope.row.step_.name }} + + + {{ + actstate_[scope.row.act_state] + }} + + + {{ + scope.row.warehouse_.name + }} + + + + + + + 新增装箱单 + + + + + + {{ scope.row.name }} + + + + {{ scope.row.unit }} + + + + {{ + scope.row.count + }} + + + + + 编辑 + + + 删除 + + - - {{ scope.row.customer_.name }} - - - {{ scope.row.contract_.name }} - - - {{ scope.row.product_.name }} - - - {{ scope.row.product_.specification }} - - - {{ scope.row.count }} - - - {{ scope.row.delivery_date }} - - - {{ scope.row.create_time }} - - - - - - - - - - - - {{ scope.row.number }} - - - {{ scope.row.order_.number }} - - - {{ scope.row.order_.contract_.number }} - - - - - {{ scope.row.count }} - - - {{ scope.row.start_date }} - - - {{ scope.row.end_date }} - - - {{ scope.row.order_.delivery_date }} - - - - - 否 - 是 - - - - {{ scope.row.create_time }} - - - - - - + + + - - - - - - {{ scope.row.number }} - - - - {{ - scope.row.step_.name - }} - - - {{ - actstate_[scope.row.act_state] - }} - - - {{ scope.row.warehouse_.name }} - - warehouse_ - - - - + + + + + + + + + + + + + + + + + + + + + + + 取消 + 确认 + + + + diff --git a/hb_client/src/views/mtm/step.vue b/hb_client/src/views/mtm/step.vue index 3cdebbe..7dcd245 100644 --- a/hb_client/src/views/mtm/step.vue +++ b/hb_client/src/views/mtm/step.vue @@ -94,7 +94,7 @@ - + { if (response.data) { this.recordformList = response.data; diff --git a/hb_client/src/views/personnel/attendance.vue b/hb_client/src/views/personnel/attendance.vue index d76e1dd..e9c60bd 100644 --- a/hb_client/src/views/personnel/attendance.vue +++ b/hb_client/src/views/personnel/attendance.vue @@ -2,154 +2,149 @@ - - - - - {{ scope.row.number }} - - - {{ scope.row.name }} - - - - 在岗 + + + + + {{ scope.row.number }} + + + {{ scope.row.name }} + + + + 在岗 - 离岗 - - + 离岗 + + - {{ scope.row.not_work_remark }} - + {{ + scope.row.not_work_remark + }} + - - {{ - scope.row.dept_.name - }} - - - - 离岗说明 - - - - - - - - - - - - - - - - - 取消 - 确认 - - - - - - + + {{ + scope.row.dept_.name + }} + + + + 离岗说明 + + + + - - 年、月 - - - 查询 - - - - - - - {{ scope.row.number }} - - - {{ scope.row.name }} - - - - {{ scope.row.dept_name }} - - - {{ scope.row.count }} - - - {{ scope.row.count_workday }} - - - - - - - - - - - {{ data.day.split('-').slice(1).join('-') }} {{dealMyDate(data.day)}} - - - - - - - - - + + + + + + + + + + 取消 + 确认 + + + + + + + + 年、月 + + + 查询 + + + + + {{ scope.row.number }} + + + {{ scope.row.name }} + + + + {{ + scope.row.dept_name + }} + + + {{ scope.row.count }} + + + {{ + scope.row.count_workday + }} + + + + + + + + {{ data.day.split("-").slice(1).join("-") }} + {{ dealMyDate(data.day) }} + + + + + + + @@ -181,9 +176,9 @@ diff --git a/hb_client/src/views/pm/resources.vue b/hb_client/src/views/pm/resources.vue index f82a527..c29f641 100644 --- a/hb_client/src/views/pm/resources.vue +++ b/hb_client/src/views/pm/resources.vue @@ -192,33 +192,29 @@ - - - 禁用 - - 合格 - - - - {{ state_[scope.row.state] }} - - - {{ state_[scope.row.state] }} - - - {{ state_[scope.row.state] }} - - - {{ state_[scope.row.state] }} - - - - - {{ state_[scope.row.state] }} + + + 禁用 + + + 合格 + + + + + {{ state_[scope.row.state] }} + + + {{ state_[scope.row.state] }} + + + {{ state_[scope.row.state] }} + + + {{ state_[scope.row.state] }} + + + diff --git a/hb_server/apps/hrm/filters.py b/hb_server/apps/hrm/filters.py index 8ec72fe..0fd0be5 100644 --- a/hb_server/apps/hrm/filters.py +++ b/hb_server/apps/hrm/filters.py @@ -1,16 +1,38 @@ from django_filters import rest_framework as filters -from apps.hrm.models import ClockRecord, Employee +from apps.hrm.models import ClockRecord, Employee, NotWorkRemark from utils.mixins import DynamicFieldsFilterMixin class ClockRecordFilterSet(filters.FilterSet): create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte') + year = filters.NumberFilter(method='filter_year') + month = filters.NumberFilter(method='filter_month') class Meta: model = ClockRecord - fields = ['create_by', 'create_time_start', 'create_time_end'] + fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month'] + + def filter_year(self, queryset, name, value): + return queryset.filter(create_time_date__year=value) + + def filter_month(self, queryset, name, value): + return queryset.filter(create_time_date__month=value) class EmployeeFilterSet(DynamicFieldsFilterMixin, filters.FilterSet): class Meta: model = Employee - fields = ['job_state'] \ No newline at end of file + fields = ['job_state'] + + +class NotWorkRemarkFilterSet(filters.FilterSet): + year = filters.NumberFilter(method='filter_year') + month = filters.NumberFilter(method='filter_month') + class Meta: + model = NotWorkRemark + fields = ['year', 'month', 'user'] + + def filter_year(self, queryset, name, value): + return queryset.filter(not_work_date__year=value) + + def filter_month(self, queryset, name, value): + return queryset.filter(not_work_date__month=value) \ No newline at end of file diff --git a/hb_server/apps/hrm/migrations/0008_auto_20220222_1112.py b/hb_server/apps/hrm/migrations/0008_auto_20220222_1112.py new file mode 100644 index 0000000..406a088 --- /dev/null +++ b/hb_server/apps/hrm/migrations/0008_auto_20220222_1112.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.9 on 2022-02-22 03:12 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('hrm', '0007_auto_20220218_0843'), + ] + + operations = [ + migrations.RemoveField( + model_name='notworkremark', + name='day', + ), + migrations.RemoveField( + model_name='notworkremark', + name='month', + ), + migrations.RemoveField( + model_name='notworkremark', + name='year', + ), + migrations.AddField( + model_name='notworkremark', + name='not_work_date', + field=models.DateField(default=django.utils.timezone.now, verbose_name='未打卡日期'), + preserve_default=False, + ), + ] diff --git a/hb_server/apps/hrm/models.py b/hb_server/apps/hrm/models.py index fb202be..091c2b1 100644 --- a/hb_server/apps/hrm/models.py +++ b/hb_server/apps/hrm/models.py @@ -44,9 +44,7 @@ class NotWorkRemark(CommonAModel): """ 离岗说明 """ - year = models.PositiveSmallIntegerField('年', default=2022) - month = models.PositiveSmallIntegerField('月', default=2) - day = models.PositiveSmallIntegerField('日', default=1) + not_work_date = models.DateField('未打卡日期') user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE) remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200) @@ -58,4 +56,6 @@ class ClockRecord(CommonADModel): type_choice = ( (ClOCK_WORK1, '上班打卡'), ) - type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) \ No newline at end of file + type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) + + diff --git a/hb_server/apps/hrm/serializers.py b/hb_server/apps/hrm/serializers.py index 1aaf018..56a91ae 100644 --- a/hb_server/apps/hrm/serializers.py +++ b/hb_server/apps/hrm/serializers.py @@ -3,7 +3,7 @@ from rest_framework.serializers import ModelSerializer from rest_framework import serializers from utils.mixins import DynamicFieldsSerializerMixin -from .models import ClockRecord, Employee +from .models import ClockRecord, Employee, NotWorkRemark from apps.system.serializers import OrganizationSimpleSerializer, UserListSerializer, UserSimpleSerializer from django.db.models.query import Prefetch @@ -30,3 +30,8 @@ class ClockRecordListSerializer(serializers.ModelSerializer): class Meta: model = ClockRecord fields = '__all__' + +class NotWorkRemarkListSerializer(serializers.ModelSerializer): + class Meta: + model = NotWorkRemark + fields = '__all__' \ No newline at end of file diff --git a/hb_server/apps/hrm/tasks.py b/hb_server/apps/hrm/tasks.py index a96d2cf..b365f58 100644 --- a/hb_server/apps/hrm/tasks.py +++ b/hb_server/apps/hrm/tasks.py @@ -10,7 +10,7 @@ def update_all_employee_not_atwork(): """ 将所有员工设为非在岗状态 """ - Employee.objects.all().update(is_atwork=False, last_check_time = None) + Employee.objects.all().update(is_atwork=False, last_check_time = None, not_work_remark=None) @shared_task def update_all_user_facedata_cache(): diff --git a/hb_server/apps/hrm/urls.py b/hb_server/apps/hrm/urls.py index 55c2e55..9db9637 100644 --- a/hb_server/apps/hrm/urls.py +++ b/hb_server/apps/hrm/urls.py @@ -1,12 +1,13 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin +from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin, NotWorkRemarkViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('employee', EmployeeViewSet, basename='employee') router.register('clock_record', ClockRecordViewSet, basename='clock_record') +router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_reamrk') urlpatterns = [ path('facelogin/', FaceLogin.as_view()), path('', include(router.urls)), diff --git a/hb_server/apps/hrm/views.py b/hb_server/apps/hrm/views.py index 8313ad4..575f324 100644 --- a/hb_server/apps/hrm/views.py +++ b/hb_server/apps/hrm/views.py @@ -5,12 +5,12 @@ from django.utils import timezone from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin -from apps.hrm.filters import ClockRecordFilterSet, EmployeeFilterSet +from apps.hrm.filters import ClockRecordFilterSet, EmployeeFilterSet, NotWorkRemarkFilterSet from apps.hrm.services import HRMService from apps.hrm.tasks import update_all_user_facedata_cache from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from apps.hrm.models import ClockRecord, Employee, NotWorkRemark -from apps.hrm.serializers import ClockRecordListSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer +from apps.hrm.serializers import ClockRecordListSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer, NotWorkRemarkListSerializer @@ -67,16 +67,12 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix remark = request.data.get('not_work_remark', '') obj.not_work_remark = remark obj.save() - now_local = timezone.localtime() + now = timezone.now() instance, created = NotWorkRemark.objects.get_or_create( - year = now_local.year, - month = now_local.month, - day = now_local.day, + not_work_date = now.date(), user = obj.user, defaults={ - "year":now_local.year, - "month":now_local.month, - "day":now_local.day, + "not_work_date":now.date(), "user":obj.user, "remark":remark, "create_by":request.user, @@ -134,7 +130,15 @@ class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): return Response('非打卡时间范围', status=status.HTTP_400_BAD_REQUEST) - +class NotWorkRemarkViewSet(ListModelMixin, GenericViewSet): + """ + 离岗说明 + """ + perms_map = {'get':'*'} + queryset = NotWorkRemark.objects.select_related('user').all() + serializer_class = NotWorkRemarkListSerializer + filterset_class = NotWorkRemarkFilterSet + ordering = ['-pk'] import base64 diff --git a/hb_server/apps/inm/filters.py b/hb_server/apps/inm/filters.py index 7f16ac4..e4f6409 100644 --- a/hb_server/apps/inm/filters.py +++ b/hb_server/apps/inm/filters.py @@ -28,6 +28,5 @@ class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet): update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte') class Meta: model = IProduct - fields = ['material', 'warehouse', 'batch', 'order', 'material__type', - 'is_saled', 'update_time_start', 'update_time_end', + fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end', 'to_order', 'need_to_order'] diff --git a/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py b/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py new file mode 100644 index 0000000..98c8747 --- /dev/null +++ b/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.9 on 2022-02-22 01:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0013_auto_20220222_0941'), + ('inm', '0031_fifoitem_pu_order_item'), + ] + + operations = [ + migrations.RemoveField( + model_name='iproduct', + name='is_saled', + ), + migrations.AddField( + model_name='fifo', + name='sale', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.sale', verbose_name='关联销售记录'), + ), + migrations.AddField( + model_name='iproduct', + name='state', + field=models.IntegerField(choices=[(10, '可销售'), (20, '已锁定'), (30, '已售出')], default=10, verbose_name='状态'), + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index 64b4108..4053765 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -3,6 +3,7 @@ from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet from apps.pum.models import PuOrder, PuOrderItem, Vendor +from apps.sam.models import Sale from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords @@ -86,6 +87,8 @@ class FIFO(CommonADModel): on_delete=models.CASCADE, null=True, blank=True) pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', null=True, blank=True, on_delete=models.CASCADE) + sale = models.ForeignKey(Sale, verbose_name='关联销售记录', + null=True, blank=True, on_delete=models.CASCADE) class FIFOItem(BaseModel): @@ -113,6 +116,14 @@ class IProduct(BaseModel): """ 具体产品条目 """ + SALE_OK = 10 + SALE_LOCK = 20 + SALED = 30 + state_choices = ( + (SALE_OK, '可销售'), + (SALE_LOCK, '已锁定'), + (SALED, '已售出') + ) number = models.CharField('物品编号', unique=True, max_length=50) material = models.ForeignKey( Material, verbose_name='物料类型', on_delete=models.CASCADE) @@ -121,7 +132,7 @@ class IProduct(BaseModel): 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, related_name='iproduct_wproduct') - is_saled = models.BooleanField('是否售出', default=False) + state = models.IntegerField('状态', default=SALE_OK, choices=state_choices) class FIFOItemProduct(BaseModel): @@ -139,4 +150,4 @@ class FIFOItemProduct(BaseModel): Material, verbose_name='物料类型', on_delete=models.CASCADE) iproduct = models.ForeignKey( IProduct, verbose_name='关联库存产品', - null=True, blank=True, on_delete=models.SET_NULL) + null=True, blank=True, on_delete=models.SET_NULL) \ No newline at end of file diff --git a/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py b/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py new file mode 100644 index 0000000..0d05c19 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.9 on 2022-02-21 06:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0047_packitem'), + ] + + operations = [ + migrations.RemoveField( + model_name='step', + name='need_test', + ), + ] diff --git a/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py b/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py new file mode 100644 index 0000000..e7ede50 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.9 on 2022-02-22 01:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0048_remove_step_need_test'), + ] + + operations = [ + migrations.AddField( + model_name='packitem', + name='product', + field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='pack_product', to='mtm.material', verbose_name='装箱产品'), + preserve_default=False, + ), + migrations.AlterField( + model_name='packitem', + name='material', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pack_material', to='mtm.material', verbose_name='装箱配件'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index cd9be3e..9ea0abd 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -23,7 +23,7 @@ class Material(CommonAModel): (MA_TYPE_GOOD, '成品'), (MA_TYPE_HALFGOOD, '半成品'), (MA_TYPE_MAINSO, '主要原料'), - (MA_TYPE_HELPSO, '辅助材料') , + (MA_TYPE_HELPSO, '辅助材料'), (MA_TYPE_TOOL, '加工工具'), (MA_TYPE_HELPTOOL, '辅助工装') ) @@ -54,8 +54,10 @@ class PackItem(CommonAModel): """ 装箱项目 """ - material = models.ForeignKey(Material, verbose_name='关联成品', - on_delete=models.CASCADE) + product = models.ForeignKey(Material, verbose_name='装箱产品', + on_delete=models.CASCADE, related_name='pack_product') + material = models.ForeignKey(Material, verbose_name='装箱配件', + on_delete=models.CASCADE, null=True, blank=True, related_name='pack_material') name = models.CharField('名称', max_length=100) specification = models.CharField('型号', max_length=100, null=True, blank=True) unit = models.CharField('单位', max_length=10) @@ -105,7 +107,6 @@ class Step(CommonAModel): name = models.CharField('工序步骤名称', max_length=100) number = models.CharField('步骤编号', max_length=100, null=True, blank=True) instruction_content = models.TextField('相应操作指导', null=True, blank=True) - need_test = models.BooleanField('是否需要检验', default=False) sort = models.IntegerField('排序号', default=1) equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips') diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 603ff5c..b3e3b71 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -24,6 +24,8 @@ class MaterialDetailSerializer(serializers.ModelSerializer): objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).distinct().order_by('number') return ProcessSimpleSerializer(instance=objs, many=True).data + + class PackItemSerializer(serializers.ModelSerializer): class Meta: model = PackItem @@ -32,7 +34,7 @@ class PackItemSerializer(serializers.ModelSerializer): class PackItemCreateSerializer(serializers.ModelSerializer): class Meta: model = PackItem - fields = ['material', 'name', 'specification', 'unit', 'count', 'sort'] + fields = ['product', 'material', 'name', 'specification', 'unit', 'count', 'sort'] class PackItemUpdateSerializer(serializers.ModelSerializer): class Meta: @@ -44,6 +46,12 @@ class MaterialSimpleSerializer(serializers.ModelSerializer): model = Material fields = ['id', 'name', 'number', 'unit','specification', 'type'] +class PackItemDetailSerializer(serializers.ModelSerializer): + material_ = MaterialSimpleSerializer(source='material', read_only=True) + class Meta: + model = PackItem + fields = '__all__' + class ProcessSerializer(serializers.ModelSerializer): instruction_ = FileSimpleSerializer(source='instruction', read_only=True) workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True) diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py index 4a02aa5..4a972cf 100644 --- a/hb_server/apps/mtm/views.py +++ b/hb_server/apps/mtm/views.py @@ -40,7 +40,7 @@ class PackItemViewSet(CreateUpdateModelAMixin, ModelViewSet): queryset = PackItem.objects.all() serializer_class = PackItemSerializer search_fields = ['name', 'number'] - filterset_fields = ['material'] + filterset_fields = ['material', 'product'] ordering = ['sort'] def get_serializer_class(self): diff --git a/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py b/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py new file mode 100644 index 0000000..ee1ca1b --- /dev/null +++ b/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.9 on 2022-02-21 02:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0025_alter_testrecord_type'), + ('pm', '0026_auto_20220218_1636'), + ] + + operations = [ + migrations.RemoveField( + model_name='subproductionplan', + name='form', + ), + migrations.RemoveField( + model_name='subproductionplan', + name='is_testok', + ), + migrations.RemoveField( + model_name='subproductionplan', + name='tester', + ), + migrations.AddField( + model_name='subproductionplan', + name='first_test', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='qm.testrecord'), + ), + migrations.DeleteModel( + name='FirstItem', + ), + ] diff --git a/hb_server/apps/pm/migrations/0028_alter_subproductionplan_first_test.py b/hb_server/apps/pm/migrations/0028_alter_subproductionplan_first_test.py new file mode 100644 index 0000000..eaf73f6 --- /dev/null +++ b/hb_server/apps/pm/migrations/0028_alter_subproductionplan_first_test.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-02-22 01:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0026_alter_testrecord_is_testok'), + ('pm', '0027_auto_20220221_1027'), + ] + + operations = [ + migrations.AlterField( + model_name='subproductionplan', + name='first_test', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.testrecord'), + ), + ] diff --git a/hb_server/apps/pm/migrations/0029_alter_subproductionplan_first_test.py b/hb_server/apps/pm/migrations/0029_alter_subproductionplan_first_test.py new file mode 100644 index 0000000..c3e682c --- /dev/null +++ b/hb_server/apps/pm/migrations/0029_alter_subproductionplan_first_test.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-02-22 02:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0026_alter_testrecord_is_testok'), + ('pm', '0028_alter_subproductionplan_first_test'), + ] + + operations = [ + migrations.AlterField( + model_name='subproductionplan', + name='first_test', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.testrecord', verbose_name='首件检验'), + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 3b4607d..7781bfa 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -1,4 +1,5 @@ +from apps.qm.models import TestRecord from apps.system.models import CommonAModel, Organization, User from django.db import models from django.contrib.auth.models import AbstractUser @@ -69,6 +70,7 @@ class SubProductionPlan(CommonAModel): (SUBPLAN_STATE_WORKING, '生产中'), (SUBPLAN_STATE_DONE, '已完成'), ) + number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE, related_name='subplan_plan') subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='subplan_subprod') @@ -90,12 +92,9 @@ class SubProductionPlan(CommonAModel): start_date_real = models.DateField('实际开工日期', null=True, blank=True) end_date_real = models.DateField('实际完工日期', null=True, blank=True) is_picked = models.BooleanField('是否已领料', default=False) - - # wproducts = models.JSONField('半成品表', default=list, blank=True) - is_testok = models.BooleanField('首件是否合格', null=True, blank=True) - form = models.ForeignKey(RecordForm, verbose_name='首件检查表', on_delete=models.CASCADE, null=True, blank=True) - tester = models.ForeignKey(User, on_delete=models.CASCADE, - verbose_name="首件检查员", null=True, blank=True, related_name='first_tester') + + first_test = models.ForeignKey('qm.testrecord', on_delete=models.SET_NULL, + null=True, blank=True, verbose_name='首件检验') leader_1 = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1') leader_2 = models.ForeignKey(User, on_delete=models.CASCADE, @@ -108,16 +107,10 @@ class SubProductionPlan(CommonAModel): class Meta: verbose_name = '子生产计划' verbose_name_plural = verbose_name - -class FirstItem(BaseModel): - """ - 首件确认表记录条目 - """ - form_field = models.ForeignKey(RecordFormField, verbose_name='关联自定义表格字段', on_delete=models.CASCADE) - field_value = models.JSONField('录入值', null=True, blank=True) - is_hidden = models.BooleanField('是否隐藏', default=False) - is_testok = models.BooleanField('是否合格', null=True, blank=True) - subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联的子计划', on_delete=models.CASCADE, related_name='item_test_record') + + # @property + # def first_test(self): + # return self.test_subplan.filter(type=TestRecord.TEST_FIRST, is_deleted=False).first() class SubProductionProgress(BaseModel): """ diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index d098511..0d653fd 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -1,6 +1,8 @@ from apps.mtm.models import RecordForm from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from rest_framework import serializers +from apps.qm.models import TestRecord +from apps.qm.serializers import TestRecordShortSerializer from apps.sam.serializers import OrderSerializer, OrderSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, SubProductionSimpleSerializer from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer @@ -33,12 +35,17 @@ class ResourceConvertListSerializer(serializers.ListSerializer): class ResourceCalListSerializer(serializers.ListSerializer): child = ResourceCalSerializer() -class SubProductionPlanListSerializer(serializers.ModelSerializer): +class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.ModelSerializer): workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True) process_ = ProcessSimpleSerializer(source='process', read_only=True) subproduction_ = SubProductionSimpleSerializer(source='subproduction', read_only=True) product_ = MaterialSimpleSerializer(source='product', read_only=True) plan_product_ = serializers.SerializerMethodField() + leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True) + leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True) + leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True) + first_test_ = TestRecordShortSerializer(source='first_test', read_only=True) + # first_test_ = serializers.SerializerMethodField() class Meta: model=SubProductionPlan fields = '__all__' @@ -46,7 +53,10 @@ class SubProductionPlanListSerializer(serializers.ModelSerializer): def get_plan_product_(self, obj): return MaterialSimpleSerializer(instance=obj.production_plan.product).data - + # def get_first_test_(self, obj): + # if obj.first_test: + # return TestRecordShortSerializer(instance=obj.first_test).data + # return None class SubProductionPlanUpdateSerializer(serializers.ModelSerializer): @@ -81,20 +91,6 @@ class SubProductionProgressSerializer(serializers.ModelSerializer): class FirstTestInitSerializer(serializers.Serializer): form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=True) -class FirstTestDetailSerializer(serializers.ModelSerializer): - tester_ = UserSimpleSerializer(source='tester', read_only=True) - leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True) - leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True) - leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True) - form_ = RecordFormSimpleSerializer(source='form', read_only=True) - # record_data = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True) - record_data = serializers.SerializerMethodField() - class Meta: - model = SubProductionPlan - fields = ['id', 'form', 'form_', 'is_testok', 'remark', 'first_sign_time' - 'tester', 'tester_', 'leader_1', 'leader_1_', 'leader_2', - 'leader_2_', 'leader_3', 'leader_3_'] - - def get_record_data(self, obj): - return None \ No newline at end of file +class FirstTestAuditSerializer(serializers.Serializer): + leader = serializers.CharField() \ No newline at end of file diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index e7bcfd2..cb02d67 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -5,23 +5,29 @@ from rest_framework import serializers from rest_framework.views import APIView from apps.em.models import Equipment from apps.em.serializers import EquipmentSimpleSerializer +from apps.hrm.services import HRMService from apps.inm.models import MaterialBatch from apps.inm.serializers import MaterialBatchSerializer -from apps.mtm.models import Material, Step, SubProduction, SubprodctionMaterial +from apps.mtm.models import Material, RecordFormField, Step, SubProduction, SubprodctionMaterial from apps.pm.filters import PlanFilterSet, SubproductionProgressFilterSet +from apps.qm.models import TestRecord, TestRecordItem +from apps.qm.serializers import TestRecordDetailBaseSerializer from apps.system.mixins import CreateUpdateModelAMixin -from apps.pm.serializers import FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer +from apps.pm.serializers import FirstTestAuditSerializer, FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan from rest_framework.viewsets import GenericViewSet, ModelViewSet from django.shortcuts import render from apps.sam.models import Order -from rest_framework.exceptions import APIException, ParseError +from rest_framework.exceptions import APIException, ParseError, ValidationError from rest_framework.response import Response from rest_framework.decorators import action from django.db.models import F +from apps.system.serializers import UserSimpleSerializer from utils.tools import ranstr from django.db import transaction +from rest_framework import status +from django.utils import timezone # Create your views here. def updateOrderPlanedCount(order): @@ -145,7 +151,9 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 子生产计划-列表/修改 """ perms_map = {'get': '*', 'put':'subplan_update'} - queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'product', 'production_plan__product') + queryset = SubProductionPlan.objects.select_related('process', + 'workshop', 'subproduction', 'product', + 'production_plan__product', 'leader_1', 'leader_2', 'leader_3', 'first_test') search_fields = [] serializer_class = SubProductionPlanListSerializer filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop'] @@ -229,20 +237,68 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo have = MaterialBatchSerializer(instance=objs, many=True).data return Response({'need':need, 'have':have}) - @action(methods=['put'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer) + @action(methods=['post'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer) @transaction.atomic def first_test_init(self, request, pk=None): """ - 获取首件检查表 + 首件检查表初始化 """ obj = self.get_object() - if obj.is_testok is None: + first_test = obj.first_test + if first_test: + return Response(TestRecordDetailBaseSerializer(instance=first_test).data) + else: rdata = request.data serializer = self.get_serializer(data=rdata) serializer.is_valid(raise_exception=True) form = serializer.validated_data.get('form') - - raise APIException('已经过首件确认') + savedict = dict( + create_by=request.user, + subproduction_plan=obj, + type = TestRecord.TEST_FIRST, + form=form) + tr = TestRecord.objects.create(**savedict) + for i in RecordFormField.objects.filter(form=form, is_deleted=False): + tri = TestRecordItem() + tri.test_record = tr + tri.form_field = i + tri.is_hidden = i.is_hidden + tri.create_by = request.user + tri.save() + obj.first_test = tr + obj.save() + return Response(TestRecordDetailBaseSerializer(instance=tr).data) + + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=FirstTestAuditSerializer) + @transaction.atomic + def first_audit(self, request, pk=None): + """ + 首件审核 + """ + obj = self.get_object() + if obj.leader_1 and obj.leader_2 and obj.leader_3: + raise ValidationError('首件确认已完成') + if obj.first_test is None: + raise ValidationError('未进行首件检查') + if not obj.first_test.is_submited: + raise ValidationError('首件检查未提交') + if not obj.first_test.is_testok: + raise ValidationError('首件检查不合格') + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + le = vdata.get('leader') + if le not in ['leader_1', 'leader_2', 'leader_3']: + return Response('审核人有误', status=status.HTTP_400_BAD_REQUEST) + if vdata.get('leader') == 'leader_1': + obj.leader_1 = request.user + elif vdata.get('leader') == 'leader_2': + obj.leader_2 = request.user + else: + obj.leader_3 = request.user + obj.first_sign_time = timezone.now() + obj.save() + return Response(UserSimpleSerializer(instance=request.user).data) diff --git a/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py b/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py new file mode 100644 index 0000000..5d7404c --- /dev/null +++ b/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-02-21 02:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0024_rename_is_midtesing_testrecord_is_midtesting'), + ] + + operations = [ + migrations.AlterField( + model_name='testrecord', + name='type', + field=models.PositiveSmallIntegerField(choices=[(20, '工序检验'), (30, '工序复检'), (36, '夹层检验'), (40, '成品检验'), (10, '首件检验')], default=20), + ), + ] diff --git a/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py b/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py new file mode 100644 index 0000000..0d2ce86 --- /dev/null +++ b/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-02-21 07:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0025_alter_testrecord_type'), + ] + + operations = [ + migrations.AlterField( + model_name='testrecord', + name='is_testok', + field=models.BooleanField(blank=True, null=True, verbose_name='是否合格'), + ), + ] diff --git a/hb_server/apps/qm/migrations/0027_alter_testrecord_subproduction_plan.py b/hb_server/apps/qm/migrations/0027_alter_testrecord_subproduction_plan.py new file mode 100644 index 0000000..c6bd369 --- /dev/null +++ b/hb_server/apps/qm/migrations/0027_alter_testrecord_subproduction_plan.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-02-22 02:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pm', '0029_alter_subproductionplan_first_test'), + ('qm', '0026_alter_testrecord_is_testok'), + ] + + operations = [ + migrations.AlterField( + model_name='testrecord', + name='subproduction_plan', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='test_subplan', to='pm.subproductionplan', verbose_name='关联的生产子计划'), + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index db776de..acfbcd3 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -47,6 +47,7 @@ class TestRecord(CommonADModel): """ 检验记录 """ + TEST_FIRST = 10 TEST_PROCESS = 20 TEST_PROCESS_RE = 30 TEST_COMB = 36 @@ -55,16 +56,17 @@ class TestRecord(CommonADModel): (TEST_PROCESS, '工序检验'), (TEST_PROCESS_RE, '工序复检'), (TEST_COMB, '夹层检验'), - (TEST_FINAL, '成品检验') + (TEST_FINAL, '成品检验'), + (TEST_FIRST, '首件检验') ) form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE) type = models.PositiveSmallIntegerField(choices=type_choice, default=TEST_PROCESS) - is_testok = models.BooleanField('是否合格', default=True) + is_testok = models.BooleanField('是否合格', null=True, blank=True) number = models.CharField('产品编号', null=True, blank=True, max_length=50) wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True, related_name='test_wproduct') material = models.ForeignKey('mtm.material', verbose_name='关联的物料状态', on_delete=models.CASCADE, null=True, blank=True) step = models.ForeignKey('mtm.step', verbose_name='关联的工序步骤', on_delete=models.CASCADE, null=True, blank=True) - subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True) + subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True, related_name='test_subplan') fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) origin_test = models.ForeignKey('self', verbose_name='原检验记录', on_delete=models.CASCADE, null=True, blank=True) is_submited = models.BooleanField('是否提交', default=False) diff --git a/hb_server/apps/qm/serializers.py b/hb_server/apps/qm/serializers.py index d1379c6..28a8829 100644 --- a/hb_server/apps/qm/serializers.py +++ b/hb_server/apps/qm/serializers.py @@ -63,7 +63,11 @@ class TestRecordItemSerializer(serializers.ModelSerializer): class Meta: model = TestRecordItem fields = '__all__' - + +class TestRecordShortSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecord + fields = ['id', 'form', 'is_testok', 'is_submited'] class TestRecordCreateSerializer(serializers.ModelSerializer): record_data = TestRecordItemCreateSerializer(many=True) class Meta: diff --git a/hb_server/apps/qm/views.py b/hb_server/apps/qm/views.py index 1151491..d46d6ce 100644 --- a/hb_server/apps/qm/views.py +++ b/hb_server/apps/qm/views.py @@ -93,7 +93,8 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De with transaction.atomic(): obj.is_submited=True obj.save() - WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 + if obj.wproduct: + WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 return Response() # def create(self, request, *args, **kwargs): diff --git a/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py b/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py new file mode 100644 index 0000000..70b8f3e --- /dev/null +++ b/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py @@ -0,0 +1,53 @@ +# Generated by Django 3.2.9 on 2022-02-22 01:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0012_alter_order_delivery_date'), + ] + + operations = [ + migrations.AddField( + model_name='sale', + name='count_real', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='实际发货数量'), + ), + migrations.AddField( + model_name='sale', + name='edelivery_date', + field=models.DateField(blank=True, null=True, verbose_name='预计发货日期'), + ), + migrations.AddField( + model_name='sale', + name='receiver', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收货人'), + ), + migrations.AddField( + model_name='sale', + name='receiver_address', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收获地址'), + ), + migrations.AddField( + model_name='sale', + name='receiver_phone', + field=models.CharField(blank=True, max_length=20, null=True, verbose_name='收货人联系电话'), + ), + migrations.AddField( + model_name='sale', + name='remark', + field=models.CharField(blank=True, max_length=200, null=True, verbose_name='备注'), + ), + migrations.AddField( + model_name='sale', + name='trans_mode', + field=models.IntegerField(blank=True, choices=[(10, '铁路快运'), (20, '铁路慢件'), (30, '铁路整车'), (40, '汽车运输'), (50, '空运')], null=True, verbose_name='运输方式'), + ), + migrations.AlterField( + model_name='sale', + name='count', + field=models.PositiveIntegerField(default=0, verbose_name='预计发货数量'), + ), + ] diff --git a/hb_server/apps/sam/migrations/0014_auto_20220222_1530.py b/hb_server/apps/sam/migrations/0014_auto_20220222_1530.py new file mode 100644 index 0000000..8f72513 --- /dev/null +++ b/hb_server/apps/sam/migrations/0014_auto_20220222_1530.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.9 on 2022-02-22 07:30 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0049_auto_20220222_0944'), + ('sam', '0013_auto_20220222_0941'), + ] + + operations = [ + migrations.AddField( + model_name='saleproduct', + name='packnum', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='装箱单号'), + ), + migrations.CreateModel( + name='SalePack', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('count', models.PositiveSmallIntegerField(verbose_name='打包数量')), + ('packitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.packitem', verbose_name='打包项目')), + ('sale_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.saleproduct', verbose_name='关联销售产品')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/sam/models.py b/hb_server/apps/sam/models.py index ce2ef62..0477da0 100644 --- a/hb_server/apps/sam/models.py +++ b/hb_server/apps/sam/models.py @@ -7,7 +7,7 @@ from django.db.models.query import QuerySet from utils.model import SoftModel, BaseModel -from apps.mtm.models import Material +from apps.mtm.models import Material, PackItem @@ -69,12 +69,30 @@ class Sale(CommonADModel): """ 销售记录 """ + TRANS_RAIL_FAST = 10 + TRANS_RAIL_SLOW = 20 + TRANS_RAIL_WHOLE = 30 + TRANS_LORRY = 40 + TRANS_AIR = 50 + trans_choices=( + (TRANS_RAIL_FAST, '铁路快运'), + (TRANS_RAIL_SLOW, '铁路慢件'), + (TRANS_RAIL_WHOLE, '铁路整车'), + (TRANS_LORRY, '汽车运输'), + (TRANS_AIR, '空运'), + ) customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE) order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True) product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE) - count = models.PositiveIntegerField('交货数量', default=0) + count = models.PositiveIntegerField('预计发货数量', default=0) + count_real = models.PositiveIntegerField('实际发货数量', null=True, blank=True) is_audited = models.BooleanField('是否审核', default=False) - + edelivery_date = models.DateField('预计发货日期', null=True, blank=True) + trans_mode = models.IntegerField('运输方式', null=True, blank=True, choices=trans_choices) + receiver = models.CharField('收货人', null=True, blank=True, max_length=200) + receiver_phone = models.CharField('收货人联系电话', null=True, blank=True, max_length=20) + receiver_address = models.CharField('收获地址', null=True, blank=True, max_length=200) + remark = models.CharField('备注', null=True, blank=True, max_length=200) class SaleProduct(BaseModel): """ @@ -84,11 +102,22 @@ class SaleProduct(BaseModel): number = models.CharField('物品编号', max_length=50) iproduct = models.ForeignKey('inm.iproduct', verbose_name='关联库存产品', on_delete=models.CASCADE, related_name='sale_iproduct') remark = models.TextField('备注', null=True, blank=True) - + packnum = models.CharField('装箱单号', max_length=100, null=True, blank=True) class Meta: unique_together = ( ('sale','iproduct'), # 联合唯一 ) +class SalePack(BaseModel): + """ + 销售打包 + """ + sale_product = models.ForeignKey(SaleProduct, verbose_name='关联销售产品', + on_delete=models.CASCADE) + packitem = models.ForeignKey(PackItem, verbose_name='打包项目', + on_delete=models.CASCADE) + count = models.PositiveSmallIntegerField('打包数量') + + diff --git a/hb_server/apps/sam/serializers_sale.py b/hb_server/apps/sam/serializers_sale.py index 627c4f2..1dda868 100644 --- a/hb_server/apps/sam/serializers_sale.py +++ b/hb_server/apps/sam/serializers_sale.py @@ -2,15 +2,20 @@ from rest_framework import serializers from rest_framework import exceptions from apps.inm.models import IProduct from apps.inm.serializers import IProductListSerializer -from apps.mtm.serializers import MaterialSimpleSerializer -from apps.sam.models import Sale, SaleProduct +from apps.mtm.models import Material, PackItem +from apps.mtm.serializers import MaterialSimpleSerializer, PackItemDetailSerializer +from apps.sam.models import Sale, SalePack, SaleProduct from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer +from django.db import transaction +from rest_framework.exceptions import ValidationError class SaleCreateSerializer(serializers.ModelSerializer): iproducts = serializers.PrimaryKeyRelatedField(queryset= IProduct.objects.all(), many=True) class Meta: model = Sale - fields = ['customer', 'order', 'product', 'iproducts'] + fields = ['customer', 'order', 'product', 'iproducts', + 'edelivery_date', 'trans_mode', 'receiver', 'receiver_phone' + , 'receiver_address', 'remark'] def validate(self, attrs): order = attrs.get('order', None) @@ -18,9 +23,11 @@ class SaleCreateSerializer(serializers.ModelSerializer): if order.customer: attrs['customer'] = order.customer attrs['product'] = order.product - for i in attrs['iproducts']: - if i.material != attrs['product']: - raise exceptions.APIException('产品选取错误') + ips = IProduct.objects.filter(id__in=[i.id for i in attrs['iproducts']]) + if ips.exclude(state=IProduct.SALE_OK).exists(): + raise exceptions.APIException('选取了非可用的产品') + if ips.count() != ips.filter(material=attrs['product']).count(): + raise exceptions.APIException('产品选取错误') return super().validate(attrs) @@ -44,8 +51,43 @@ class SaleProductCreateSerializer(serializers.ModelSerializer): fields = ['sale', 'iproduct'] def create(self, validated_data): - validated_data['number'] = validated_data['iproduct'].number - instance = SaleProduct.objects.create(**validated_data) - instance.sale.count = SaleProduct.objects.filter(sale=instance.sale).count() - instance.sale.save() - return instance \ No newline at end of file + with transaction.atomic(): + validated_data['number'] = validated_data['iproduct'].number + instance = SaleProduct.objects.create(**validated_data) + sale = instance.sale + sale.count = SaleProduct.objects.filter(sale=instance.sale).count() + sale.save() + order = sale.order + if order: + if sale.count+order.delivered_count>order.count: + raise exceptions.APIException('超过订单所需数量') + return instance + +class SPackItemSerializer(serializers.ModelSerializer): + name = serializers.CharField(source='packitem.name', read_only=True) + specification = serializers.CharField(source='packitem.specification', read_only=True) + unit = serializers.CharField(source='packitem.unit', read_only=True) + material_ = MaterialSimpleSerializer(source='packitem.material', read_only=True) + + class Meta: + model = SalePack + fields = '__all__' + +class SaleProductPackDetailSerializer(serializers.ModelSerializer): + detail = serializers.SerializerMethodField() + class Meta: + model = ['packnum', 'detail'] + + def get_detail(self, obj): + return SPackItemSerializer(instance=SalePack.objects.filter(sale_product=obj) + , many=True).data + +class SPackItemCreateSerializer(serializers.Serializer): + id = serializers.PrimaryKeyRelatedField(queryset=PackItem.objects.all()) + count = serializers.IntegerField() + +class SaleProductPackSerializer(serializers.ModelSerializer): + detail = SPackItemCreateSerializer(many=True) + class Meta: + model = SaleProduct + fields = ['packnum', 'detail'] diff --git a/hb_server/apps/sam/views_sale.py b/hb_server/apps/sam/views_sale.py index 1eb6717..dad03e2 100644 --- a/hb_server/apps/sam/views_sale.py +++ b/hb_server/apps/sam/views_sale.py @@ -1,11 +1,12 @@ + from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModelMixin, RetrieveModelMixin from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse from apps.inm.services import InmService -from apps.mtm.models import Material -from apps.sam.models import Sale, SaleProduct -from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer +from apps.mtm.models import Material, PackItem +from apps.sam.models import Sale, SalePack, SaleProduct +from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer, SaleProductPackDetailSerializer, SaleProductPackSerializer from rest_framework import exceptions from django.db import transaction from rest_framework.decorators import action @@ -35,6 +36,13 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C elif self.action == 'retrieve': return SaleListSerializer return super().get_serializer_class() + + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + if obj.is_audited: + raise exceptions.APIException('该销售记录已审核,不可删除') + obj.delete() + IProduct.objects.filter(sale_iproduct__sale=obj).update() def create(self, request, *args, **kwargs): data = request.data @@ -49,6 +57,8 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C sale = Sale.objects.create(**vdata) i_l = [] for i in iproducts: + i.state = IProduct.SALE_LOCK + i.save() i_d ={} i_d['sale'] = sale i_d['number'] = i.number @@ -66,56 +76,59 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C obj = self.get_object() if obj.is_audited: raise exceptions.APIException('已审核通过') + if obj.order: + if obj.count + obj.order.delivered_count > obj.order.count: + raise exceptions.APIException('超过订单所需数量') # 创建出库记录 fifo = FIFO() + fifo.sale = obj fifo.type = FIFO.FIFO_TYPE_SALE_OUT - fifo.is_audited = True + fifo.is_audited = False fifo.auditor = request.user fifo.inout_date = timezone.now() fifo.create_by = request.user fifo.number = 'CK' + ranstr(7) fifo.save() # 创建出库条目 - ips = IProduct.objects.filter(sale_iproduct__sale=obj) - items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id')) - for i in items: - warehouse = WareHouse.objects.get(id=i['warehouse']) - material = Material.objects.get(id=i['material']) - fifoitem = FIFOItem() - fifoitem.need_test = False - fifoitem.warehouse = warehouse - fifoitem.material = material - fifoitem.count = i['total'] - fifoitem.batch = i['batch'] - fifoitem.fifo = fifo - fifoitem.save() - items_p = ips.filter(warehouse=warehouse, batch=i['batch']) - ipxs = [] - for i in items_p: - # 创建出库明细半成品 - ip = {} - ip['fifoitem'] = fifoitem - ip['number'] = i.number - ip['material'] = i.material - ip['iproduct'] = i - ipxs.append(FIFOItemProduct(**ip)) - FIFOItemProduct.objects.bulk_create(ipxs) - # 更新成品库情况 - ips.update(is_saled=True) + # ips = IProduct.objects.filter(sale_iproduct__sale=obj) + # items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id')) + # for i in items: + # warehouse = WareHouse.objects.get(id=i['warehouse']) + # material = Material.objects.get(id=i['material']) + # fifoitem = FIFOItem() + # fifoitem.need_test = False + # fifoitem.warehouse = warehouse + # fifoitem.material = material + # fifoitem.count = i['total'] + # fifoitem.batch = i['batch'] + # fifoitem.fifo = fifo + # fifoitem.save() + # items_p = ips.filter(warehouse=warehouse, batch=i['batch']) + # ipxs = [] + # for i in items_p: + # # 创建出库明细半成品 + # ip = {} + # ip['fifoitem'] = fifoitem + # ip['number'] = i.number + # ip['material'] = i.material + # ip['iproduct'] = i + # ipxs.append(FIFOItemProduct(**ip)) + # FIFOItemProduct.objects.bulk_create(ipxs) + # 更新动态产品表情况 - from apps.wpm.models import WProduct - WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update( - act_state=WProduct.WPR_ACT_STATE_SELLED) + # from apps.wpm.models import WProduct + # WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update( + # act_state=WProduct.WPR_ACT_STATE_SELLED) # 更新库存 - InmService.update_inm(fifo) - # 变更审核状态 + # InmService.update_inm(fifo) + # 变更销售提货审核状态 obj.is_audited = True obj.save() # 变更订单状态 - if obj.order: - order = obj.order - order.delivered_count = order.delivered_count + obj.count - order.save() + # if obj.order: + # order = obj.order + # order.delivered_count = order.delivered_count + obj.count + # order.save() return Response() @@ -136,13 +149,41 @@ class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, Ge if self.action == 'create': return SaleProductCreateSerializer return super().get_serializer_class() - + def destroy(self, request, *args, **kwargs): obj = self.get_object() sale = obj.sale if sale.is_audited: raise exceptions.APIException('该销售记录已审核,不可删除产品') + obj.delete() sale.count = SaleProduct.objects.filter(sale=obj.sale).count() sale.save() - obj.delete() - return Response() \ No newline at end of file + return Response() + + + @action(methods=['get', 'post'], detail=True, perms_map={'post':'sale_pack'}, serializer_class=serializers.Serializer) + @transaction.atomic + def pack(self, request, pk=None): + """ + 打包装箱 + """ + obj = self.get_object() + if request.method == 'GET': + for i in PackItem.objects.filter(product=obj.product.material, is_deleted=False): + SalePack.objects.get_or_create(sale_product=obj, packitem=i, + defaults={ + "sale_product":obj, + "packitem":i, + "count":i.count + }) + return Response(SaleProductPackDetailSerializer(instance=obj).data) + elif request.method == 'POST': + serializer = SaleProductPackSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + obj.packnum = vdata['packnum'] + for i in vdata['detail']: + pi = i['id'] + pi.count = i['count'] + pi.save() + return Response() \ No newline at end of file diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index 3040daa..f223264 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -90,7 +90,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = State.objects.all() serializer_class = StateSerializer search_fields = ['name'] @@ -99,7 +99,7 @@ class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, Destr class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = Transition.objects.all() serializer_class = TransitionSerializer search_fields = ['name'] @@ -108,7 +108,7 @@ class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = CustomField.objects.all() serializer_class = CustomFieldSerializer search_fields = ['field_name'] diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py index 776757f..2f60656 100644 --- a/hb_server/utils/response.py +++ b/hb_server/utils/response.py @@ -62,8 +62,8 @@ class FitJSONRenderer(JSONRenderer): if isinstance(data, list): data = data[0] - response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert + response_body.msg = prefix + str(data) # 取一部分放入msg,方便前端alert else: response_body.data = data renderer_context.get("response").status_code = 200 # 统一成200响应, 可用body里code区分业务异常 - return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context) + return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context) \ No newline at end of file
- {{ data.day.split('-').slice(1).join('-') }} {{dealMyDate(data.day)}} -
+ {{ data.day.split("-").slice(1).join("-") }} + {{ dealMyDate(data.day) }} +