Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shilixia 2022-02-22 15:42:28 +08:00
commit 3a346e3be5
27 changed files with 447 additions and 104 deletions

View File

@ -1,16 +1,38 @@
from django_filters import rest_framework as filters 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 from utils.mixins import DynamicFieldsFilterMixin
class ClockRecordFilterSet(filters.FilterSet): class ClockRecordFilterSet(filters.FilterSet):
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte') 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: class Meta:
model = ClockRecord 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 EmployeeFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
class Meta: class Meta:
model = Employee model = Employee
fields = ['job_state'] 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)

View File

@ -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,
),
]

View File

@ -44,9 +44,7 @@ class NotWorkRemark(CommonAModel):
""" """
离岗说明 离岗说明
""" """
year = models.PositiveSmallIntegerField('', default=2022) not_work_date = models.DateField('未打卡日期')
month = models.PositiveSmallIntegerField('', default=2)
day = models.PositiveSmallIntegerField('', default=1)
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE) user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200) remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
@ -58,4 +56,6 @@ class ClockRecord(CommonADModel):
type_choice = ( type_choice = (
(ClOCK_WORK1, '上班打卡'), (ClOCK_WORK1, '上班打卡'),
) )
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)

View File

@ -3,7 +3,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from utils.mixins import DynamicFieldsSerializerMixin 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 apps.system.serializers import OrganizationSimpleSerializer, UserListSerializer, UserSimpleSerializer
from django.db.models.query import Prefetch from django.db.models.query import Prefetch
@ -30,3 +30,8 @@ class ClockRecordListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ClockRecord model = ClockRecord
fields = '__all__' fields = '__all__'
class NotWorkRemarkListSerializer(serializers.ModelSerializer):
class Meta:
model = NotWorkRemark
fields = '__all__'

View File

@ -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 @shared_task
def update_all_user_facedata_cache(): def update_all_user_facedata_cache():

View File

@ -1,12 +1,13 @@
from django.db.models import base from django.db.models import base
from rest_framework import urlpatterns 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 django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
router = DefaultRouter() router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee') router.register('employee', EmployeeViewSet, basename='employee')
router.register('clock_record', ClockRecordViewSet, basename='clock_record') router.register('clock_record', ClockRecordViewSet, basename='clock_record')
router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_reamrk')
urlpatterns = [ urlpatterns = [
path('facelogin/', FaceLogin.as_view()), path('facelogin/', FaceLogin.as_view()),
path('', include(router.urls)), path('', include(router.urls)),

View File

@ -5,12 +5,12 @@ from django.utils import timezone
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin 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.services import HRMService
from apps.hrm.tasks import update_all_user_facedata_cache from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark 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', '') remark = request.data.get('not_work_remark', '')
obj.not_work_remark = remark obj.not_work_remark = remark
obj.save() obj.save()
now_local = timezone.localtime() now = timezone.now()
instance, created = NotWorkRemark.objects.get_or_create( instance, created = NotWorkRemark.objects.get_or_create(
year = now_local.year, not_work_date = now.date(),
month = now_local.month,
day = now_local.day,
user = obj.user, user = obj.user,
defaults={ defaults={
"year":now_local.year, "not_work_date":now.date(),
"month":now_local.month,
"day":now_local.day,
"user":obj.user, "user":obj.user,
"remark":remark, "remark":remark,
"create_by":request.user, "create_by":request.user,
@ -134,7 +130,15 @@ class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
return Response('非打卡时间范围', status=status.HTTP_400_BAD_REQUEST) 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 import base64

View File

@ -28,6 +28,5 @@ class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte') update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte')
class Meta: class Meta:
model = IProduct model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'material__type', fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end',
'is_saled', 'update_time_start', 'update_time_end',
'to_order', 'need_to_order'] 'to_order', 'need_to_order']

View File

@ -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='状态'),
),
]

View File

@ -3,6 +3,7 @@ from django.db.models.base import Model
import django.utils.timezone as timezone import django.utils.timezone as timezone
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from apps.pum.models import PuOrder, PuOrderItem, Vendor 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 apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File
from utils.model import SoftModel, BaseModel from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
@ -86,6 +87,8 @@ class FIFO(CommonADModel):
on_delete=models.CASCADE, null=True, blank=True) on_delete=models.CASCADE, null=True, blank=True)
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
null=True, blank=True, on_delete=models.CASCADE) 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): 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) number = models.CharField('物品编号', unique=True, max_length=50)
material = models.ForeignKey( material = models.ForeignKey(
Material, verbose_name='物料类型', on_delete=models.CASCADE) Material, verbose_name='物料类型', on_delete=models.CASCADE)
@ -121,7 +132,7 @@ class IProduct(BaseModel):
batch = models.CharField('所属批次号', max_length=100, default='') batch = models.CharField('所属批次号', max_length=100, default='')
wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False,
null=True, blank=True, related_name='iproduct_wproduct') 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): class FIFOItemProduct(BaseModel):
@ -139,4 +150,4 @@ class FIFOItemProduct(BaseModel):
Material, verbose_name='物料类型', on_delete=models.CASCADE) Material, verbose_name='物料类型', on_delete=models.CASCADE)
iproduct = models.ForeignKey( iproduct = models.ForeignKey(
IProduct, verbose_name='关联库存产品', IProduct, verbose_name='关联库存产品',
null=True, blank=True, on_delete=models.SET_NULL) null=True, blank=True, on_delete=models.SET_NULL)

View File

@ -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',
),
]

View File

@ -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='装箱配件'),
),
]

View File

@ -23,7 +23,7 @@ class Material(CommonAModel):
(MA_TYPE_GOOD, '成品'), (MA_TYPE_GOOD, '成品'),
(MA_TYPE_HALFGOOD, '半成品'), (MA_TYPE_HALFGOOD, '半成品'),
(MA_TYPE_MAINSO, '主要原料'), (MA_TYPE_MAINSO, '主要原料'),
(MA_TYPE_HELPSO, '辅助材料') , (MA_TYPE_HELPSO, '辅助材料'),
(MA_TYPE_TOOL, '加工工具'), (MA_TYPE_TOOL, '加工工具'),
(MA_TYPE_HELPTOOL, '辅助工装') (MA_TYPE_HELPTOOL, '辅助工装')
) )
@ -54,8 +54,10 @@ class PackItem(CommonAModel):
""" """
装箱项目 装箱项目
""" """
material = models.ForeignKey(Material, verbose_name='关联成品', product = models.ForeignKey(Material, verbose_name='装箱产品',
on_delete=models.CASCADE) 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) name = models.CharField('名称', max_length=100)
specification = models.CharField('型号', max_length=100, null=True, blank=True) specification = models.CharField('型号', max_length=100, null=True, blank=True)
unit = models.CharField('单位', max_length=10) unit = models.CharField('单位', max_length=10)
@ -105,7 +107,6 @@ class Step(CommonAModel):
name = models.CharField('工序步骤名称', max_length=100) name = models.CharField('工序步骤名称', max_length=100)
number = models.CharField('步骤编号', max_length=100, null=True, blank=True) number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
instruction_content = models.TextField('相应操作指导', null=True, blank=True) instruction_content = models.TextField('相应操作指导', null=True, blank=True)
need_test = models.BooleanField('是否需要检验', default=False)
sort = models.IntegerField('排序号', default=1) sort = models.IntegerField('排序号', default=1)
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips') equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips')

View File

@ -32,7 +32,7 @@ class PackItemSerializer(serializers.ModelSerializer):
class PackItemCreateSerializer(serializers.ModelSerializer): class PackItemCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = PackItem model = PackItem
fields = ['material', 'name', 'specification', 'unit', 'count', 'sort'] fields = ['product', 'material', 'name', 'specification', 'unit', 'count', 'sort']
class PackItemUpdateSerializer(serializers.ModelSerializer): class PackItemUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:

View File

@ -40,7 +40,7 @@ class PackItemViewSet(CreateUpdateModelAMixin, ModelViewSet):
queryset = PackItem.objects.all() queryset = PackItem.objects.all()
serializer_class = PackItemSerializer serializer_class = PackItemSerializer
search_fields = ['name', 'number'] search_fields = ['name', 'number']
filterset_fields = ['material'] filterset_fields = ['material', 'product']
ordering = ['sort'] ordering = ['sort']
def get_serializer_class(self): def get_serializer_class(self):

View File

@ -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'),
),
]

View File

@ -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='首件检验'),
),
]

View File

@ -1,4 +1,5 @@
from apps.qm.models import TestRecord
from apps.system.models import CommonAModel, Organization, User from apps.system.models import CommonAModel, Organization, User
from django.db import models from django.db import models
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
@ -92,7 +93,8 @@ class SubProductionPlan(CommonAModel):
end_date_real = models.DateField('实际完工日期', null=True, blank=True) end_date_real = models.DateField('实际完工日期', null=True, blank=True)
is_picked = models.BooleanField('是否已领料', default=False) is_picked = models.BooleanField('是否已领料', default=False)
first_test = models.ForeignKey('qm.testrecord', on_delete=models.CASCADE, null=True, blank=True) 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, leader_1 = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1') verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1')
leader_2 = models.ForeignKey(User, on_delete=models.CASCADE, leader_2 = models.ForeignKey(User, on_delete=models.CASCADE,
@ -105,6 +107,10 @@ class SubProductionPlan(CommonAModel):
class Meta: class Meta:
verbose_name = '子生产计划' verbose_name = '子生产计划'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
# @property
# def first_test(self):
# return self.test_subplan.filter(type=TestRecord.TEST_FIRST, is_deleted=False).first()
class SubProductionProgress(BaseModel): class SubProductionProgress(BaseModel):
""" """

View File

@ -1,6 +1,7 @@
from apps.mtm.models import RecordForm from apps.mtm.models import RecordForm
from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress
from rest_framework import serializers from rest_framework import serializers
from apps.qm.models import TestRecord
from apps.qm.serializers import TestRecordShortSerializer from apps.qm.serializers import TestRecordShortSerializer
from apps.sam.serializers import OrderSerializer, OrderSimpleSerializer from apps.sam.serializers import OrderSerializer, OrderSimpleSerializer
from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, SubProductionSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, SubProductionSimpleSerializer
@ -44,6 +45,7 @@ class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.
leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True) leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True)
leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True) leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True)
first_test_ = TestRecordShortSerializer(source='first_test', read_only=True) first_test_ = TestRecordShortSerializer(source='first_test', read_only=True)
# first_test_ = serializers.SerializerMethodField()
class Meta: class Meta:
model=SubProductionPlan model=SubProductionPlan
fields = '__all__' fields = '__all__'
@ -51,7 +53,10 @@ class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.
def get_plan_product_(self, obj): def get_plan_product_(self, obj):
return MaterialSimpleSerializer(instance=obj.production_plan.product).data 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): class SubProductionPlanUpdateSerializer(serializers.ModelSerializer):
@ -88,5 +93,4 @@ class FirstTestInitSerializer(serializers.Serializer):
class FirstTestAuditSerializer(serializers.Serializer): class FirstTestAuditSerializer(serializers.Serializer):
leader = serializers.CharField() leader = serializers.CharField()
base64 = serializers.CharField()

View File

@ -11,6 +11,7 @@ from apps.inm.serializers import MaterialBatchSerializer
from apps.mtm.models import Material, RecordFormField, Step, SubProduction, SubprodctionMaterial from apps.mtm.models import Material, RecordFormField, Step, SubProduction, SubprodctionMaterial
from apps.pm.filters import PlanFilterSet, SubproductionProgressFilterSet from apps.pm.filters import PlanFilterSet, SubproductionProgressFilterSet
from apps.qm.models import TestRecord, TestRecordItem from apps.qm.models import TestRecord, TestRecordItem
from apps.qm.serializers import TestRecordDetailBaseSerializer
from apps.system.mixins import CreateUpdateModelAMixin from apps.system.mixins import CreateUpdateModelAMixin
from apps.pm.serializers import FirstTestAuditSerializer, 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 rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
@ -22,6 +23,7 @@ from rest_framework.exceptions import APIException, ParseError, ValidationError
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from django.db.models import F from django.db.models import F
from apps.system.serializers import UserSimpleSerializer
from utils.tools import ranstr from utils.tools import ranstr
from django.db import transaction from django.db import transaction
from rest_framework import status from rest_framework import status
@ -151,7 +153,7 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
perms_map = {'get': '*', 'put':'subplan_update'} perms_map = {'get': '*', 'put':'subplan_update'}
queryset = SubProductionPlan.objects.select_related('process', queryset = SubProductionPlan.objects.select_related('process',
'workshop', 'subproduction', 'product', 'workshop', 'subproduction', 'product',
'production_plan__product', 'leader_1', 'leader_2', 'leader_3') 'production_plan__product', 'leader_1', 'leader_2', 'leader_3', 'first_test')
search_fields = [] search_fields = []
serializer_class = SubProductionPlanListSerializer serializer_class = SubProductionPlanListSerializer
filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop'] filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop']
@ -242,7 +244,10 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
首件检查表初始化 首件检查表初始化
""" """
obj = self.get_object() obj = self.get_object()
if obj.first_test is None: first_test = obj.first_test
if first_test:
return Response(TestRecordDetailBaseSerializer(instance=first_test).data)
else:
rdata = request.data rdata = request.data
serializer = self.get_serializer(data=rdata) serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -260,10 +265,11 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
tri.is_hidden = i.is_hidden tri.is_hidden = i.is_hidden
tri.create_by = request.user tri.create_by = request.user
tri.save() tri.save()
return Response() obj.first_test = tr
raise APIException('首件检查已存在') obj.save()
return Response(TestRecordDetailBaseSerializer(instance=tr).data)
@action(methods=['post'], detail=True, perms_map={'post':'first_test_audit'}, serializer_class=FirstTestAuditSerializer) @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=FirstTestAuditSerializer)
@transaction.atomic @transaction.atomic
def first_audit(self, request, pk=None): def first_audit(self, request, pk=None):
obj = self.get_object() obj = self.get_object()
@ -278,21 +284,18 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
user, msg = HRMService.face_compare_from_base64(vdata.get('base64')) le = vdata.get('leader')
if user: if le not in ['leader_1', 'leader_2', 'leader_3']:
le = vdata.get('leader') return Response('审核人有误', status=status.HTTP_400_BAD_REQUEST)
if le not in ['leader_1', 'leader_2', 'leader_3']: if vdata.get('leader') == 'leader_1':
return Response('审核人有误', status=status.HTTP_400_BAD_REQUEST) obj.leader_1 = request.user
if vdata.get('leader') == 'leader_1': elif vdata.get('leader') == 'leader_2':
obj.leader_1 = user obj.leader_2 = request.user
elif vdata.get('leader') == 'leader_2': else:
obj.leader_2 = user obj.leader_3 = request.user
else: obj.first_sign_time = timezone.now()
obj.leader_3 = user obj.save()
obj.first_sign_time = timezone.now() return Response(UserSimpleSerializer(instance=request.user).data)
obj.save()
return Response()
return Response(msg, status=status.HTTP_400_BAD_REQUEST)

View File

@ -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='是否合格'),
),
]

View File

@ -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='关联的生产子计划'),
),
]

View File

@ -66,7 +66,7 @@ class TestRecord(CommonADModel):
wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True, related_name='test_wproduct') 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) 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) 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) 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) origin_test = models.ForeignKey('self', verbose_name='原检验记录', on_delete=models.CASCADE, null=True, blank=True)
is_submited = models.BooleanField('是否提交', default=False) is_submited = models.BooleanField('是否提交', default=False)

View File

@ -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='预计发货数量'),
),
]

View File

@ -7,7 +7,7 @@ from django.db.models.query import QuerySet
from utils.model import SoftModel, BaseModel from utils.model import SoftModel, BaseModel
from apps.mtm.models import Material from apps.mtm.models import Material, PackItem
@ -69,11 +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) customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE)
order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True) order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True)
product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE) 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) 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): class SaleProduct(BaseModel):
@ -84,11 +103,21 @@ class SaleProduct(BaseModel):
number = models.CharField('物品编号', max_length=50) number = models.CharField('物品编号', max_length=50)
iproduct = models.ForeignKey('inm.iproduct', verbose_name='关联库存产品', on_delete=models.CASCADE, related_name='sale_iproduct') iproduct = models.ForeignKey('inm.iproduct', verbose_name='关联库存产品', on_delete=models.CASCADE, related_name='sale_iproduct')
remark = models.TextField('备注', null=True, blank=True) remark = models.TextField('备注', null=True, blank=True)
packnum = models.CharField('装箱单号', max_length=100, null=True, blank=True)
class Meta: class Meta:
unique_together = ( unique_together = (
('sale','iproduct'), # 联合唯一 ('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)

View File

@ -5,12 +5,16 @@ from apps.inm.serializers import IProductListSerializer
from apps.mtm.serializers import MaterialSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer
from apps.sam.models import Sale, SaleProduct from apps.sam.models import Sale, SaleProduct
from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer
from django.db import transaction
from rest_framework.exceptions import ValidationError
class SaleCreateSerializer(serializers.ModelSerializer): class SaleCreateSerializer(serializers.ModelSerializer):
iproducts = serializers.PrimaryKeyRelatedField(queryset= iproducts = serializers.PrimaryKeyRelatedField(queryset=
IProduct.objects.all(), many=True) IProduct.objects.all(), many=True)
class Meta: class Meta:
model = Sale 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): def validate(self, attrs):
order = attrs.get('order', None) order = attrs.get('order', None)
@ -18,9 +22,11 @@ class SaleCreateSerializer(serializers.ModelSerializer):
if order.customer: if order.customer:
attrs['customer'] = order.customer attrs['customer'] = order.customer
attrs['product'] = order.product attrs['product'] = order.product
for i in attrs['iproducts']: ips = IProduct.objects.filter(id__in=[i.id for i in attrs['iproducts']])
if i.material != attrs['product']: if ips.exclude(state=IProduct.SALE_OK).exists():
raise exceptions.APIException('产品选取错误') raise exceptions.APIException('选取了非可用的产品')
if ips.count() != ips.filter(material=attrs['product']).count():
raise exceptions.APIException('产品选取错误')
return super().validate(attrs) return super().validate(attrs)
@ -44,8 +50,14 @@ class SaleProductCreateSerializer(serializers.ModelSerializer):
fields = ['sale', 'iproduct'] fields = ['sale', 'iproduct']
def create(self, validated_data): def create(self, validated_data):
validated_data['number'] = validated_data['iproduct'].number with transaction.atomic():
instance = SaleProduct.objects.create(**validated_data) validated_data['number'] = validated_data['iproduct'].number
instance.sale.count = SaleProduct.objects.filter(sale=instance.sale).count() instance = SaleProduct.objects.create(**validated_data)
instance.sale.save() 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 return instance

View File

@ -35,6 +35,13 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
elif self.action == 'retrieve': elif self.action == 'retrieve':
return SaleListSerializer return SaleListSerializer
return super().get_serializer_class() 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): def create(self, request, *args, **kwargs):
data = request.data data = request.data
@ -49,6 +56,8 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
sale = Sale.objects.create(**vdata) sale = Sale.objects.create(**vdata)
i_l = [] i_l = []
for i in iproducts: for i in iproducts:
i.state = IProduct.SALE_LOCK
i.save()
i_d ={} i_d ={}
i_d['sale'] = sale i_d['sale'] = sale
i_d['number'] = i.number i_d['number'] = i.number
@ -66,56 +75,59 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
obj = self.get_object() obj = self.get_object()
if obj.is_audited: if obj.is_audited:
raise exceptions.APIException('已审核通过') raise exceptions.APIException('已审核通过')
if obj.order:
if obj.count + obj.order.delivered_count > obj.order.count:
raise exceptions.APIException('超过订单所需数量')
# 创建出库记录 # 创建出库记录
fifo = FIFO() fifo = FIFO()
fifo.sale = obj
fifo.type = FIFO.FIFO_TYPE_SALE_OUT fifo.type = FIFO.FIFO_TYPE_SALE_OUT
fifo.is_audited = True fifo.is_audited = False
fifo.auditor = request.user fifo.auditor = request.user
fifo.inout_date = timezone.now() fifo.inout_date = timezone.now()
fifo.create_by = request.user fifo.create_by = request.user
fifo.number = 'CK' + ranstr(7) fifo.number = 'CK' + ranstr(7)
fifo.save() fifo.save()
# 创建出库条目 # 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=obj) # ips = IProduct.objects.filter(sale_iproduct__sale=obj)
items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id')) # items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
for i in items: # for i in items:
warehouse = WareHouse.objects.get(id=i['warehouse']) # warehouse = WareHouse.objects.get(id=i['warehouse'])
material = Material.objects.get(id=i['material']) # material = Material.objects.get(id=i['material'])
fifoitem = FIFOItem() # fifoitem = FIFOItem()
fifoitem.need_test = False # fifoitem.need_test = False
fifoitem.warehouse = warehouse # fifoitem.warehouse = warehouse
fifoitem.material = material # fifoitem.material = material
fifoitem.count = i['total'] # fifoitem.count = i['total']
fifoitem.batch = i['batch'] # fifoitem.batch = i['batch']
fifoitem.fifo = fifo # fifoitem.fifo = fifo
fifoitem.save() # fifoitem.save()
items_p = ips.filter(warehouse=warehouse, batch=i['batch']) # items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
ipxs = [] # ipxs = []
for i in items_p: # for i in items_p:
# 创建出库明细半成品 # # 创建出库明细半成品
ip = {} # ip = {}
ip['fifoitem'] = fifoitem # ip['fifoitem'] = fifoitem
ip['number'] = i.number # ip['number'] = i.number
ip['material'] = i.material # ip['material'] = i.material
ip['iproduct'] = i # ip['iproduct'] = i
ipxs.append(FIFOItemProduct(**ip)) # ipxs.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ipxs) # FIFOItemProduct.objects.bulk_create(ipxs)
# 更新成品库情况
ips.update(is_saled=True)
# 更新动态产品表情况 # 更新动态产品表情况
from apps.wpm.models import WProduct # from apps.wpm.models import WProduct
WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update( # WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
act_state=WProduct.WPR_ACT_STATE_SELLED) # act_state=WProduct.WPR_ACT_STATE_SELLED)
# 更新库存 # 更新库存
InmService.update_inm(fifo) # InmService.update_inm(fifo)
# 变更审核状态 # 变更销售提货审核状态
obj.is_audited = True obj.is_audited = True
obj.save() obj.save()
# 变更订单状态 # 变更订单状态
if obj.order: # if obj.order:
order = obj.order # order = obj.order
order.delivered_count = order.delivered_count + obj.count # order.delivered_count = order.delivered_count + obj.count
order.save() # order.save()
return Response() return Response()
@ -136,13 +148,13 @@ class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, Ge
if self.action == 'create': if self.action == 'create':
return SaleProductCreateSerializer return SaleProductCreateSerializer
return super().get_serializer_class() return super().get_serializer_class()
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
obj = self.get_object() obj = self.get_object()
sale = obj.sale sale = obj.sale
if sale.is_audited: if sale.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除产品') raise exceptions.APIException('该销售记录已审核,不可删除产品')
obj.delete()
sale.count = SaleProduct.objects.filter(sale=obj.sale).count() sale.count = SaleProduct.objects.filter(sale=obj.sale).count()
sale.save() sale.save()
obj.delete()
return Response() return Response()