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 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']
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)
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)
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 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__'

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

View File

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

View File

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

View File

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

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

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_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')

View File

@ -32,7 +32,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:

View File

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

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 django.db import models
from django.contrib.auth.models import AbstractUser
@ -92,7 +93,8 @@ class SubProductionPlan(CommonAModel):
end_date_real = models.DateField('实际完工日期', null=True, blank=True)
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,
verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1')
leader_2 = models.ForeignKey(User, on_delete=models.CASCADE,
@ -105,6 +107,10 @@ class SubProductionPlan(CommonAModel):
class Meta:
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):
"""

View File

@ -1,6 +1,7 @@
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
@ -44,6 +45,7 @@ class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.
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__'
@ -51,7 +53,10 @@ class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.
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):
@ -88,5 +93,4 @@ class FirstTestInitSerializer(serializers.Serializer):
class FirstTestAuditSerializer(serializers.Serializer):
leader = serializers.CharField()
base64 = serializers.CharField()
leader = 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.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 FirstTestAuditSerializer, FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer
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.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
@ -151,7 +153,7 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
perms_map = {'get': '*', 'put':'subplan_update'}
queryset = SubProductionPlan.objects.select_related('process',
'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 = []
serializer_class = SubProductionPlanListSerializer
filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop']
@ -242,7 +244,10 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
首件检查表初始化
"""
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
serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True)
@ -260,10 +265,11 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
tri.is_hidden = i.is_hidden
tri.create_by = request.user
tri.save()
return Response()
raise APIException('首件检查已存在')
obj.first_test = tr
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
def first_audit(self, request, pk=None):
obj = self.get_object()
@ -278,21 +284,18 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
user, msg = HRMService.face_compare_from_base64(vdata.get('base64'))
if user:
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 = user
elif vdata.get('leader') == 'leader_2':
obj.leader_2 = user
else:
obj.leader_3 = user
obj.first_sign_time = timezone.now()
obj.save()
return Response()
return Response(msg, status=status.HTTP_400_BAD_REQUEST)
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)

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')
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)

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 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)
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 +103,21 @@ 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)

View File

@ -5,12 +5,16 @@ from apps.inm.serializers import IProductListSerializer
from apps.mtm.serializers import MaterialSimpleSerializer
from apps.sam.models import Sale, 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 +22,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 +50,14 @@ 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()
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

View File

@ -35,6 +35,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 +56,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 +75,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 +148,13 @@ 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()