mirror of
https://github.com/himool/HimoolERP.git
synced 2024-12-26 08:51:32 +08:00
feat: 生产管理
This commit is contained in:
parent
0e55eec2e5
commit
2a61632017
8 changed files with 166 additions and 5 deletions
|
@ -1,7 +1,26 @@
|
||||||
from django_filters.rest_framework import FilterSet
|
from django_filters.rest_framework import FilterSet
|
||||||
from django_filters.filters import *
|
from django_filters.filters import *
|
||||||
|
from apps.production.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionOrderFilter(FilterSet):
|
||||||
|
start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='开始日期')
|
||||||
|
end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='结束日期')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductionOrder
|
||||||
|
fields = ['sales_order', 'goods', 'status', 'creator', 'start_date', 'end_date']
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionRecordFilter(FilterSet):
|
||||||
|
start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='开始日期')
|
||||||
|
end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='结束日期')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProductionRecord
|
||||||
|
fields = ['production_order', 'goods', 'creator', 'start_date', 'end_date']
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'ProductionOrderFilter', 'ProductionRecordFilter',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
|
from extensions.common.base import *
|
||||||
from extensions.models import *
|
from extensions.models import *
|
||||||
|
|
||||||
|
|
||||||
class ProductionOrder(Model):
|
class ProductionOrder(Model):
|
||||||
"""生产单据"""
|
"""生产单据"""
|
||||||
|
|
||||||
|
class Status(TextChoices):
|
||||||
|
IN_PLAN = ('in_plan', '计划中')
|
||||||
|
IN_PROGRESS = ('in_progress', '进行中')
|
||||||
|
COMPLETED = ('completed', '已完成')
|
||||||
|
CLOSED = ('closed', '强制关闭')
|
||||||
|
|
||||||
number = CharField(max_length=32, verbose_name='编号')
|
number = CharField(max_length=32, verbose_name='编号')
|
||||||
is_related = BooleanField(default=False, verbose_name='关联状态')
|
is_related = BooleanField(default=False, verbose_name='关联状态')
|
||||||
sales_order = ForeignKey('sales.SalesOrder', on_delete=PROTECT, null=True, related_name='production_orders', verbose_name='销售单')
|
sales_order = ForeignKey('sales.SalesOrder', on_delete=PROTECT, null=True, related_name='production_orders', verbose_name='销售单')
|
||||||
goods = ForeignKey('goods.Goods', on_delete=PROTECT, related_name='production_orders', verbose_name='产品')
|
goods = ForeignKey('goods.Goods', on_delete=PROTECT, related_name='production_orders', verbose_name='产品')
|
||||||
total_quantity = FloatField(verbose_name='生产总数')
|
total_quantity = FloatField(verbose_name='生产总数')
|
||||||
|
quantity_produced = FloatField(verbose_name='已生产数量')
|
||||||
remain_quantity = FloatField(verbose_name='剩余数量')
|
remain_quantity = FloatField(verbose_name='剩余数量')
|
||||||
start_time = DateTimeField(null=True, verbose_name='开始时间')
|
start_time = DateTimeField(null=True, verbose_name='开始时间')
|
||||||
end_time = DateTimeField(null=True, verbose_name='结束时间')
|
end_time = DateTimeField(null=True, verbose_name='结束时间')
|
||||||
|
status = CharField(max_length=32, choices=Status.choices, default=Status.IN_PLAN, verbose_name='状态')
|
||||||
creator = ForeignKey('system.User', on_delete=PROTECT,
|
creator = ForeignKey('system.User', on_delete=PROTECT,
|
||||||
related_name='created_production_orders', verbose_name='创建人')
|
related_name='created_production_orders', verbose_name='创建人')
|
||||||
create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间')
|
||||||
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='production_orders')
|
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='production_orders')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_number(cls, team):
|
||||||
|
start_date, end_date = pendulum.today().to_datetime_string(), pendulum.tomorrow().to_datetime_string()
|
||||||
|
instance = cls.objects.filter(team=team, create_time__gte=start_date, create_time__lt=end_date).last()
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = re.match('^(.*?)([1-9]+)$', instance.number)
|
||||||
|
number = result.group(1) + str(int(result.group(2)) + 1)
|
||||||
|
except AttributeError:
|
||||||
|
number = 'SC' + pendulum.today().format('YYYYMMDD') + '0001'
|
||||||
|
|
||||||
|
return number
|
||||||
|
|
||||||
|
|
||||||
class ProductionRecord(Model):
|
class ProductionRecord(Model):
|
||||||
"""生产记录"""
|
"""生产记录"""
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
from extensions.permissions import ModelPermission
|
from extensions.permissions import ModelPermission
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
class ProductionOrderPermission(ModelPermission):
|
||||||
|
code = 'production_order'
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionRecordPermission(ModelPermission):
|
||||||
|
code = 'production_record'
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'ProductionOrderPermission', 'ProductionRecordPermission',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from extensions.serializers import *
|
from extensions.serializers import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
class NumberResponse(Serializer):
|
||||||
|
number = CharField(label='编号')
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'NumberResponse',
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,13 +9,16 @@ from apps.goods.models import *
|
||||||
class ProductionOrderSerializer(BaseSerializer):
|
class ProductionOrderSerializer(BaseSerializer):
|
||||||
"""生产单据"""
|
"""生产单据"""
|
||||||
|
|
||||||
|
sales_order_number = CharField(source='sales_order.number', read_only=True, label='销售单号')
|
||||||
goods_number = CharField(source='goods.number', read_only=True, label='产品编号')
|
goods_number = CharField(source='goods.number', read_only=True, label='产品编号')
|
||||||
goods_name = CharField(source='goods.name', read_only=True, label='产品名称')
|
goods_name = CharField(source='goods.name', read_only=True, label='产品名称')
|
||||||
|
status_display = CharField(source='get_status_display', read_only=True, label='状态')
|
||||||
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
|
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProductionOrder
|
model = ProductionOrder
|
||||||
read_only_fields = ['id', 'remain_quantity', 'goods_number', 'goods_name', 'creator',
|
read_only_fields = ['id', 'sales_order_number', 'remain_quantity', 'goods_number', 'goods_name',
|
||||||
|
'quantity_produced', 'remain_quantity', 'status', 'status_display', 'creator',
|
||||||
'creator_name', 'create_time']
|
'creator_name', 'create_time']
|
||||||
fields = ['number', 'is_related', 'sales_order', 'goods', 'total_quantity',
|
fields = ['number', 'is_related', 'sales_order', 'goods', 'total_quantity',
|
||||||
'start_time', 'end_time', *read_only_fields]
|
'start_time', 'end_time', *read_only_fields]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from extensions.routers import *
|
from extensions.routers import *
|
||||||
|
from apps.production.views import *
|
||||||
|
|
||||||
|
|
||||||
router = BaseRouter()
|
router = BaseRouter()
|
||||||
|
router.register('production_orders', ProductionOrderViewSet, 'production_order')
|
||||||
|
router.register('production_records', ProductionRecordViewSet, 'production_record')
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|
|
@ -3,8 +3,103 @@ from extensions.common.base import *
|
||||||
from extensions.permissions import *
|
from extensions.permissions import *
|
||||||
from extensions.exceptions import *
|
from extensions.exceptions import *
|
||||||
from extensions.viewsets import *
|
from extensions.viewsets import *
|
||||||
|
from apps.production.serializers import *
|
||||||
|
from apps.production.permissions import *
|
||||||
|
from apps.production.filters import *
|
||||||
|
from apps.production.schemas import *
|
||||||
|
from apps.production.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionOrderViewSet(ModelViewSet):
|
||||||
|
"""生产单据"""
|
||||||
|
|
||||||
|
serializer_class = ProductionOrderSerializer
|
||||||
|
permission_classes = [IsAuthenticated, ProductionOrderPermission]
|
||||||
|
filterset_class = ProductionOrderFilter
|
||||||
|
search_fields = ['number', 'sales_order__number', 'goods__number', 'goods__name']
|
||||||
|
ordering_fields = ['number', 'total_quantity', 'quantity_produced', 'remain_quantity',
|
||||||
|
'start_time', 'end_time', 'create_time']
|
||||||
|
select_related_fields = ['sales_order', 'goods', 'creator']
|
||||||
|
queryset = ProductionOrder.objects.all()
|
||||||
|
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
instance = serializer.instance
|
||||||
|
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||||
|
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法编辑')
|
||||||
|
return super().perform_update(serializer)
|
||||||
|
|
||||||
|
def perform_destroy(self, instance):
|
||||||
|
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||||
|
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法删除')
|
||||||
|
return super().perform_destroy(instance)
|
||||||
|
|
||||||
|
@extend_schema(responses={200: NumberResponse})
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def number(self, request, *args, **kwargs):
|
||||||
|
"""获取编号"""
|
||||||
|
|
||||||
|
number = ProductionOrder.get_number(self.team)
|
||||||
|
return Response(data={'number': number}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@extend_schema(responses={200: ProductionOrderSerializer})
|
||||||
|
@action(detail=True, methods=['get'])
|
||||||
|
def issue(self, request, *args, **kwargs):
|
||||||
|
"""发布工单"""
|
||||||
|
|
||||||
|
instance = self.get_object()
|
||||||
|
if instance.status != ProductionOrder.Status.IN_PLAN:
|
||||||
|
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法发布工单')
|
||||||
|
|
||||||
|
instance.status = ProductionOrder.Status.IN_PROGRESS
|
||||||
|
instance.save(update_fields=['status'])
|
||||||
|
|
||||||
|
serializer = ProductionOrderSerializer(instance=instance)
|
||||||
|
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@extend_schema(responses={200: ProductionOrderSerializer})
|
||||||
|
@action(detail=True, methods=['get'])
|
||||||
|
def close(self, request, *args, **kwargs):
|
||||||
|
"""关闭工单"""
|
||||||
|
|
||||||
|
instance = self.get_object()
|
||||||
|
if instance.status != ProductionOrder.Status.IN_PROGRESS:
|
||||||
|
raise ValidationError(f'工单{instance.number}{instance.get_status_display()}, 无法关闭工单')
|
||||||
|
|
||||||
|
instance.status = ProductionOrder.Status.CLOSED
|
||||||
|
instance.save(update_fields=['status'])
|
||||||
|
|
||||||
|
serializer = ProductionOrderSerializer(instance=instance)
|
||||||
|
return Response(data=serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionRecordViewSet(BaseViewSet, ListModelMixin, RetrieveModelMixin, CreateModelMixin):
|
||||||
|
"""生产记录"""
|
||||||
|
|
||||||
|
serializer_class = ProductionRecordSerializer
|
||||||
|
permission_classes = [IsAuthenticated, ProductionRecordPermission]
|
||||||
|
filterset_class = ProductionRecordFilter
|
||||||
|
search_fields = ['production_order__number', 'goods__number', 'goods__name']
|
||||||
|
ordering_fields = ['production_quantity', 'create_time']
|
||||||
|
select_related_fields = ['production_order', 'goods', 'creator']
|
||||||
|
queryset = ProductionRecord.objects.all()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
validated_data = serializer.validated_data
|
||||||
|
production_order = validated_data['production_order']
|
||||||
|
production_quantity = validated_data['production_quantity']
|
||||||
|
|
||||||
|
if production_order.remain_quantity < production_quantity:
|
||||||
|
raise ValidationError('生产数量错误')
|
||||||
|
|
||||||
|
production_order.remain_quantity = NP.minus(production_order.remain_quantity, production_quantity)
|
||||||
|
if production_order.remain_quantity == 0:
|
||||||
|
production_order.status = ProductionOrder.Status.COMPLETED
|
||||||
|
production_order.save(update_fields=['remain_quantity', 'status'])
|
||||||
|
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'ProductionOrderViewSet', 'ProductionRecordViewSet',
|
||||||
]
|
]
|
||||||
|
|
|
@ -54,6 +54,13 @@ PERMISSIONS = [
|
||||||
{'name': '销售退货', 'code': 'sales_return_order'},
|
{'name': '销售退货', 'code': 'sales_return_order'},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': '生产管理',
|
||||||
|
'permissions': [
|
||||||
|
{'name': '生产计划', 'code': 'production_order'},
|
||||||
|
{'name': '生产记录', 'code': 'production_record'},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'name': '库内管理',
|
'name': '库内管理',
|
||||||
'permissions': [
|
'permissions': [
|
||||||
|
|
Loading…
Reference in a new issue