Compare commits
55 Commits
3.0.202512
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
d29fcce935 | |
|
|
a534bde086 | |
|
|
63002f27c8 | |
|
|
4bbae8b7df | |
|
|
dc26c7cc46 | |
|
|
0d80e182cd | |
|
|
2759114ede | |
|
|
80f832aa85 | |
|
|
70e49eb27e | |
|
|
e99b2ecbbc | |
|
|
146e842642 | |
|
|
47b1887c4b | |
|
|
1ffbe0cc44 | |
|
|
3e173f7a72 | |
|
|
fce66da1d9 | |
|
|
feb8bd6770 | |
|
|
43f5f11ca8 | |
|
|
d5ea72a021 | |
|
|
143d9cb719 | |
|
|
cf6633592a | |
|
|
b39b0e7923 | |
|
|
70563a6c02 | |
|
|
def22f6b18 | |
|
|
f9eee5a523 | |
|
|
2ecaeadff7 | |
|
|
6eee0e1e53 | |
|
|
3417515e72 | |
|
|
43abcbaa48 | |
|
|
e2a92b6faa | |
|
|
02e3265133 | |
|
|
65cdeb0e7c | |
|
|
238d1dd074 | |
|
|
f7a78431c5 | |
|
|
5b60318bfb | |
|
|
0127e2a149 | |
|
|
f5b1b13a63 | |
|
|
f7b09ab1df | |
|
|
b362fc3b89 | |
|
|
8f791ac8de | |
|
|
afa3b8b9ad | |
|
|
3d6fcfac8a | |
|
|
db910f0804 | |
|
|
7dc7a78695 | |
|
|
952cdb1bc7 | |
|
|
9ed78f8d32 | |
|
|
52ebac68a0 | |
|
|
81770e89fa | |
|
|
6ac7c020bd | |
|
|
e385a558e9 | |
|
|
c37e71d60f | |
|
|
c3c7675ac5 | |
|
|
29f4e2f76a | |
|
|
ec13b8b166 | |
|
|
c56f908b42 | |
|
|
a8ae8ee32a |
|
|
@ -11,7 +11,7 @@ router.register('question', QuestionViewSet, basename='question')
|
|||
router.register('paper', PaperViewSet, basename='paper')
|
||||
router.register('exam', ExamViewSet, basename='exam')
|
||||
router.register('examrecord', ExamRecordViewSet, basename='examrecord')
|
||||
router.register('training', TrainRecordViewSet, basename='examrecord')
|
||||
router.register('training', TrainRecordViewSet, basename='training')
|
||||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# Generated by Django 3.2.12 on 2026-01-07 01:18
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wf', '0006_auto_20251215_1645'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('hrm', '0024_emppersoninfo_post'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Leave',
|
||||
fields=[
|
||||
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
|
||||
('start_date', models.DateTimeField(verbose_name='开始日期')),
|
||||
('end_date', models.DateTimeField(verbose_name='结束日期')),
|
||||
('leave_type', models.PositiveSmallIntegerField(blank=True, choices=[(10, '事假'), (20, '病假'), (30, '婚假'), (40, '丧假'), (50, '公假'), (60, '工伤'), (70, '产假'), (80, '护理假'), (90, '其他')], null=True, verbose_name='请假类型')),
|
||||
('reason', models.TextField(verbose_name='请假事由')),
|
||||
('hour', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='请假时长')),
|
||||
('file', models.TextField(blank=True, null=True, verbose_name='证明')),
|
||||
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='员工')),
|
||||
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_ticket', to='wf.ticket', verbose_name='关联工单')),
|
||||
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -246,3 +246,28 @@ class EmpPersonInfo(CommonADModel):
|
|||
post = models.CharField('岗位', max_length=20, null=True, blank=True)
|
||||
note = models.TextField('备注', null=True, blank=True)
|
||||
|
||||
|
||||
class Leave(CommonADModel):
|
||||
"""
|
||||
TN:员工请假
|
||||
"""
|
||||
E_TYPE_CHOISE = (
|
||||
(10, '事假'),
|
||||
(20, '病假'),
|
||||
(30, '婚假'),
|
||||
(40, '丧假'),
|
||||
(50, '公假'),
|
||||
(60, '工伤'),
|
||||
(70, '产假'),
|
||||
(80, '护理假'),
|
||||
(90, '其他'),
|
||||
)
|
||||
employee = models.ForeignKey(Employee, verbose_name='员工', on_delete=models.CASCADE)
|
||||
start_date = models.DateTimeField('开始日期')
|
||||
end_date = models.DateTimeField('结束日期')
|
||||
leave_type = models.PositiveSmallIntegerField('请假类型', choices=E_TYPE_CHOISE, null=True, blank=True)
|
||||
reason = models.TextField('请假事由')
|
||||
hour = models.PositiveSmallIntegerField('请假时长', null=True, blank=True)
|
||||
file = models.TextField('证明', null=True, blank=True)
|
||||
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
|
||||
on_delete=models.CASCADE, related_name='leave_ticket', null=True, blank=True)
|
||||
|
|
@ -9,7 +9,7 @@ from django.utils import timezone
|
|||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.utils.constants import EXCLUDE_FIELDS
|
||||
from apps.hrm.models import (Certificate, ClockRecord, Employee,
|
||||
NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo)
|
||||
NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, Leave)
|
||||
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
|
||||
from django.db import transaction
|
||||
from django.core.cache import cache
|
||||
|
|
@ -368,3 +368,12 @@ class EmpPersonInfoSerializer(CustomModelSerializer):
|
|||
'note',
|
||||
)
|
||||
|
||||
class LeaveSerializer(CustomModelSerializer):
|
||||
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||
employee_name = serializers.CharField(source='employee.name', read_only=True)
|
||||
post_name = serializers.CharField(source="employee.post.name", read_only=True)
|
||||
belong_dept_name = serializers.CharField(source='employee.belong_dept.name', read_only=True)
|
||||
class Meta:
|
||||
model = Leave
|
||||
fields = '__all__'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet, EmpNeedViewSet,
|
||||
AttendanceViewSet, ResignationViewSet, EmpJoinViewSet)
|
||||
AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, LeaveViewSet)
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ router.register('attendance', AttendanceViewSet, basename='attendance')
|
|||
router.register('resignation', ResignationViewSet, basename='resignation')
|
||||
router.register('empneed', EmpNeedViewSet, basename='empneed')
|
||||
router.register('empjoin', EmpJoinViewSet, basename='empjoin')
|
||||
router.register('leave', LeaveViewSet, basename='leave')
|
||||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from rest_framework.response import Response
|
|||
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
|
||||
from apps.hrm.filters import (CertificateFilterSet, ClockRecordFilterSet, EmployeeFilterSet,
|
||||
NotWorkRemarkFilterSet)
|
||||
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin
|
||||
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, Leave
|
||||
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer,
|
||||
ClockRecordListSerializer,
|
||||
EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer,
|
||||
|
|
@ -20,7 +20,7 @@ from apps.hrm.serializers import (CertificateCreateUpdateSerializer, Certificate
|
|||
EmployeeSerializer,
|
||||
ClockRecordSimpleSerializer, ClockRecordCreateSerializer,
|
||||
NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer,
|
||||
ResignationSerializer, EmpNeedSerializer)
|
||||
ResignationSerializer, EmpNeedSerializer, LeaveSerializer)
|
||||
from apps.hrm.services import HrmService
|
||||
|
||||
from apps.third.dahua import dhClient
|
||||
|
|
@ -453,3 +453,19 @@ class EmpJoinViewSet(TicketMixin, EuModelViewSet):
|
|||
serializer = EmpPersonInfoSerializer(data=person, many=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
|
||||
class LeaveViewSet(TicketMixin, EuModelViewSet):
|
||||
select_related_fields = [
|
||||
'employee',
|
||||
'employee__belong_dept',
|
||||
'ticket',
|
||||
]
|
||||
queryset = Leave.objects.all()
|
||||
serializer_class = LeaveSerializer
|
||||
filterset_fields = ['leave_type', 'employee__belong_dept']
|
||||
search_fields = ["employee__name", "leave_type"]
|
||||
workflow_key = "wf_leave"
|
||||
|
||||
def gen_other_ticket_data(self, instance):
|
||||
return {"hour": instance.hour if instance.hour else None}
|
||||
|
|
@ -247,6 +247,15 @@ class MIOItemAListSerializer(CustomModelSerializer):
|
|||
read_only_fields = EXCLUDE_FIELDS_BASE
|
||||
|
||||
|
||||
class MIOItemListSimpleSerializer(CustomModelSerializer):
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
material_name = serializers.StringRelatedField(
|
||||
source='material', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = MIOItem
|
||||
fields = ["id", "mio", "material", "warehouse", "material_name", "warehouse_name", "batch", "count", "test_date", "count_notok"]
|
||||
|
||||
class MIOItemSerializer(CustomModelSerializer):
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
material_ = MaterialSerializer(source='material', read_only=True)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ router.register('warehouse', WarehouseVIewSet, basename='warehouse')
|
|||
router.register('materialbatch', MaterialBatchViewSet,
|
||||
basename='materialbatch')
|
||||
router.register('mio', MIOViewSet, basename='mio')
|
||||
router.register('mio/do', MioDoViewSet)
|
||||
router.register('mio/sale', MioSaleViewSet)
|
||||
router.register('mio/pur', MioPurViewSet)
|
||||
router.register('mio/other', MioOtherViewSet)
|
||||
router.register('mio/do', MioDoViewSet, basename='mio_do')
|
||||
router.register('mio/sale', MioSaleViewSet, basename='mio_sale')
|
||||
router.register('mio/pur', MioPurViewSet, basename='mio_pur')
|
||||
router.register('mio/other', MioOtherViewSet, basename='mio_other')
|
||||
router.register('mioitem', MIOItemViewSet, basename='mioitem')
|
||||
router.register('mioitemw', MIOItemwViewSet, basename='mioitemw')
|
||||
# router.register('pack', PackViewSet, basename='pack')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from apps.inm.serializers import (
|
|||
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
|
||||
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
|
||||
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
|
||||
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer)
|
||||
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer, MIOItemListSimpleSerializer)
|
||||
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from apps.inm.services import InmService
|
||||
|
|
@ -163,20 +163,39 @@ class MIOViewSet(CustomModelViewSet):
|
|||
return mio
|
||||
|
||||
def add_info_for_list(self, data):
|
||||
# 获取检验状态
|
||||
mio_dict = {}
|
||||
for item in data:
|
||||
item['is_tested'] = None
|
||||
mio_dict[item['id']] = item
|
||||
mioitems = list(MIOItem.objects.filter(mio__id__in=mio_dict.keys()).values_list("mio__id", "test_date"))
|
||||
# 1. 收集所有mio的ID
|
||||
mio_ids = [item['id'] for item in data]
|
||||
|
||||
# 2. 预初始化mio字典和items列表
|
||||
mio_dict = {item['id']: {
|
||||
**item,
|
||||
'is_tested': False, # 默认值设为False
|
||||
'mioitems': []
|
||||
} for item in data}
|
||||
|
||||
# 3. 批量查询MIOItem数据
|
||||
if mio_ids: # 避免空查询
|
||||
mioitems = MIOItemListSimpleSerializer(
|
||||
instance=MIOItem.objects.filter(
|
||||
mio__id__in=mio_ids
|
||||
).select_related("warehouse", "material"),
|
||||
many=True
|
||||
).data
|
||||
|
||||
# 4. 单次循环处理所有item
|
||||
for item in mioitems:
|
||||
mioId, test_date = item
|
||||
is_tested = False
|
||||
if test_date:
|
||||
is_tested = True
|
||||
mio_dict[mioId]['is_tested'] = is_tested
|
||||
datax = [mio_dict[key] for key in mio_dict.keys()]
|
||||
return datax
|
||||
mio_id = item['mio']
|
||||
if mio_id in mio_dict:
|
||||
mio_dict[mio_id]['mioitems'].append(item)
|
||||
# 更新is_tested状态(只要有一个item有test_date就为True)
|
||||
if item.get('test_date'):
|
||||
mio_dict[mio_id]['is_tested'] = True
|
||||
|
||||
# 5. 直接返回原始data列表,避免额外转换
|
||||
for item in data:
|
||||
item.update(mio_dict[item['id']])
|
||||
|
||||
return data
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action in ['create', 'update', 'partial_update']:
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ class Mgroup(CommonBModel):
|
|||
# 如果当前时间在结束时间之前,属于前一天
|
||||
else:
|
||||
return (w_s_time - timedelta(days=1)).date(), shift
|
||||
# return w_s_time.date(), None
|
||||
raise ParseError(f"工段{self.name}的班次规则未覆盖时间点{w_s_time.strftime('%H:%M:%S')}")
|
||||
|
||||
|
||||
class TeamMember(BaseModel):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.12 on 2026-01-04 06:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ofm', '0004_auto_20251218_1636'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vehicleuse',
|
||||
name='end_km',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='归还公里数'),
|
||||
),
|
||||
]
|
||||
|
|
@ -60,7 +60,7 @@ class VehicleUse(CommonBDModel):
|
|||
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
|
||||
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
|
||||
start_km = models.PositiveIntegerField('出发公里数', null=True, blank=True)
|
||||
end_km = models.PositiveIntegerField('归还公里数')
|
||||
end_km = models.PositiveIntegerField('归还公里数', default=0)
|
||||
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
|
||||
is_city = models.BooleanField('是否市内用车', default=True)
|
||||
reason = models.CharField('用车事由', max_length=100)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# Generated by Django 3.2.12 on 2025-12-26 07:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('wf', '0006_auto_20251215_1645'),
|
||||
('pum', '0009_supplieraudit'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuotationApply',
|
||||
fields=[
|
||||
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
|
||||
('customer_name', models.CharField(max_length=50, verbose_name='客户名称')),
|
||||
('product_name', models.CharField(max_length=50, verbose_name='产品名称')),
|
||||
('contact_person', models.CharField(max_length=50, verbose_name='联系人')),
|
||||
('contact_phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
|
||||
('receive_address', models.CharField(blank=True, max_length=100, null=True, verbose_name='收件地址')),
|
||||
('product_spec_quantity', models.TextField(blank=True, null=True, verbose_name='产品规格/数量')),
|
||||
('quotation_basis', models.TextField(blank=True, null=True, verbose_name='报价依据')),
|
||||
('suggested_price_calc', models.TextField(blank=True, null=True, verbose_name='建议价格及计算方式')),
|
||||
('quotation_range', models.CharField(blank=True, max_length=100, null=True, verbose_name='报价区间')),
|
||||
('quoter', models.CharField(blank=True, max_length=50, null=True, verbose_name='报价人')),
|
||||
('apply_date', models.DateField(auto_now_add=True, verbose_name='申请日期')),
|
||||
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quo_ticket', to='wf.ticket', verbose_name='关联工单')),
|
||||
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -107,3 +107,22 @@ class PuPlanItem(CommonBDModel):
|
|||
null=True, blank=True, related_name='item_puplan')
|
||||
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
|
||||
on_delete=models.SET_NULL, null=True, blank=True, related_name='puplan_item_puorder')
|
||||
|
||||
|
||||
class QuotationApply(CommonADModel):
|
||||
"""
|
||||
TN:报价申请表
|
||||
"""
|
||||
customer_name = models.CharField(max_length=50,verbose_name="客户名称")
|
||||
product_name = models.CharField(max_length=50,verbose_name="产品名称")
|
||||
contact_person = models.CharField(max_length=50,verbose_name="联系人")
|
||||
contact_phone = models.CharField(max_length=20,verbose_name="联系电话", null=True, blank=True)
|
||||
receive_address = models.CharField(max_length=100,verbose_name="收件地址", null=True, blank=True)
|
||||
product_spec_quantity = models.TextField(verbose_name="产品规格/数量", null=True, blank=True)
|
||||
quotation_basis = models.TextField(verbose_name="报价依据", null=True, blank=True)
|
||||
suggested_price_calc = models.TextField(verbose_name="建议价格及计算方式", null=True, blank=True)
|
||||
quotation_range = models.CharField(max_length=100,verbose_name="报价区间", null=True, blank=True)
|
||||
quoter = models.CharField(max_length=50,verbose_name="报价人", null=True, blank=True)
|
||||
apply_date = models.DateField(verbose_name="申请日期",auto_now_add=True)
|
||||
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
|
||||
on_delete=models.CASCADE, related_name='quo_ticket', null=True, blank=True)
|
||||
|
|
@ -3,7 +3,7 @@ from apps.utils.serializers import CustomModelSerializer
|
|||
from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS
|
||||
from rest_framework.exceptions import ValidationError, ParseError
|
||||
|
||||
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit
|
||||
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
|
||||
from apps.mtm.serializers import MaterialSerializer, MaterialSimpleSerializer
|
||||
from django.db import transaction
|
||||
from .services import PumService
|
||||
|
|
@ -158,3 +158,10 @@ class SupplierAuditSerializer(CustomModelSerializer):
|
|||
if Supplier.objects.filter(name=name).exists():
|
||||
raise ParseError('供应商名称已存在')
|
||||
return super().create(validated_data)
|
||||
|
||||
class QuotationApplySerializer(CustomModelSerializer):
|
||||
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
|
||||
class Meta:
|
||||
model = QuotationApply
|
||||
fields = "__all__"
|
||||
read_only_fields = EXCLUDE_FIELDS
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet, SupplierAuditViewSet)
|
||||
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet, SupplierAuditViewSet, QuotationApplyViewSet)
|
||||
# from apps.pum.views import SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet
|
||||
|
||||
API_BASE_URL = 'api/pum/'
|
||||
HTML_BASE_URL = 'dhtml/pum/'
|
||||
|
|
@ -12,6 +13,7 @@ router.register('pu_plan', PuPlanViewSet, basename='pu_plan')
|
|||
router.register('pu_planitem', PuPlanItemViewSet, basename='pu_planitem')
|
||||
router.register('pu_order', PuOrderViewSet, basename='pu_order')
|
||||
router.register('pu_orderitem', PuOrderItemViewSet, basename='pu_orderitem')
|
||||
router.register('quotation', QuotationApplyViewSet, basename='quotation')
|
||||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
from django.shortcuts import render
|
||||
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit
|
||||
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
|
||||
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer,
|
||||
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer, QuotationApplySerializer,
|
||||
PuOrderSerializer, PuOrderItemSerializer, AddSerializer, SupplierAuditSerializer)
|
||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||
from rest_framework.decorators import action
|
||||
|
|
@ -210,3 +210,16 @@ class PuOrderItemViewSet(CustomModelViewSet):
|
|||
item.pu_order = puorder
|
||||
item.save()
|
||||
return Response()
|
||||
|
||||
class QuotationApplyViewSet(TicketMixin, CustomModelViewSet):
|
||||
"""
|
||||
list: 报价申请
|
||||
|
||||
报价申请
|
||||
"""
|
||||
queryset = QuotationApply.objects.all()
|
||||
serializer_class = QuotationApplySerializer
|
||||
filterset_fields = ['product_name', 'customer_name','apply_date', 'quoter']
|
||||
search_fields = ['product_name', 'customer_name','contact_person']
|
||||
ordering = ['create_time']
|
||||
workflow_key = "wf_quotation"
|
||||
|
|
@ -157,6 +157,43 @@ def ftestwork_submit(ins:FtestWork, user: User):
|
|||
# 触发批次统计分析
|
||||
ana_batch_thread(xbatchs=[ins.batch])
|
||||
|
||||
|
||||
def ftestwork_revert(ins: FtestWork):
|
||||
wm:WMaterial = ins.wm
|
||||
if wm and ins.need_update_wm:
|
||||
fwd_qs = FtestworkDefect.objects.filter(ftestwork=ins)
|
||||
for item in fwd_qs:
|
||||
item:FtestworkDefect = item
|
||||
if item.count > 0:
|
||||
wm.count = wm.count + item.count
|
||||
wm.save()
|
||||
wmstate = WMaterial.WM_OK
|
||||
if item.defect.okcate == Defect.DEFECT_NOTOK:
|
||||
wmstate = WMaterial.WM_NOTOK
|
||||
try:
|
||||
wmx = WMaterial.objects.get(
|
||||
material=wm.material,
|
||||
batch=wm.batch,
|
||||
mgroup=wm.mgroup,
|
||||
belong_dept=wm.belong_dept,
|
||||
state=wmstate,
|
||||
notok_sign=None,
|
||||
defect=item.defect,
|
||||
)
|
||||
except Exception as e:
|
||||
raise ParseError(f'找不到{item.defect.name}的车间库存: {str(e)}')
|
||||
wmx.count = wmx.count - item.count
|
||||
if wmx.count < 0:
|
||||
raise ParseError("数量不足,撤回失败")
|
||||
wmx.save()
|
||||
else:
|
||||
raise ParseError("该检验工作不支持撤回")
|
||||
ins.submit_user = None
|
||||
ins.submit_time = None
|
||||
ins.save()
|
||||
# 触发批次统计分析
|
||||
ana_batch_thread(xbatchs=[ins.batch])
|
||||
|
||||
def bind_ftestwork(ticket: Ticket, transition, new_ticket_data: dict):
|
||||
ins = FtestWork.objects.get(id=new_ticket_data['t_id'])
|
||||
if ins.submit_time is not None:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
|||
from apps.wpm.models import SfLog
|
||||
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter, QctFilter, FtestFilter
|
||||
from django.db import transaction
|
||||
from apps.qm.services import ftestwork_submit
|
||||
from apps.qm.services import ftestwork_submit, ftestwork_revert
|
||||
from apps.wpm.services_2 import ana_batch_thread
|
||||
from apps.wf.models import State
|
||||
# Create your views here.
|
||||
|
|
@ -328,3 +328,19 @@ class FtestWorkViewSet(CustomModelViewSet):
|
|||
else:
|
||||
raise ParseError('该检验工作已提交')
|
||||
return Response()
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'ftestwork.submit'}, serializer_class=Serializer)
|
||||
@transaction.atomic
|
||||
def revert(self, request, *args, **kwargs):
|
||||
"""撤回检验工作
|
||||
|
||||
撤回检验工作
|
||||
"""
|
||||
ins:FtestWork = self.get_object()
|
||||
if ins.submit_time:
|
||||
if self.request.user != ins.submit_user:
|
||||
raise ParseError('只能由提交人撤回')
|
||||
ftestwork_revert(ins)
|
||||
else:
|
||||
raise ParseError('该检验工作未提交')
|
||||
return Response()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RemConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.rem'
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# Generated by Django 3.2.12 on 2026-01-06 01:49
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('system', '0006_auto_20241213_1249'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Project',
|
||||
fields=[
|
||||
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, 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='删除标记')),
|
||||
('name', models.TextField(verbose_name='项目名称')),
|
||||
('description', models.TextField(verbose_name='项目介绍')),
|
||||
('start_date', models.DateField(verbose_name='开始日期')),
|
||||
('end_date', models.DateField(verbose_name='结束日期')),
|
||||
('participants', models.TextField(blank=True, null=True, verbose_name='项目成员')),
|
||||
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('files', models.ManyToManyField(blank=True, to='system.File', verbose_name='附件')),
|
||||
('leader', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='项目负责人')),
|
||||
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from django.db import models
|
||||
from apps.utils.models import CommonADModel
|
||||
from apps.system.models import User, File
|
||||
# Create your models here.
|
||||
|
||||
class Project(CommonADModel):
|
||||
name = models.TextField("项目名称")
|
||||
description = models.TextField("项目介绍")
|
||||
start_date = models.DateField("开始日期")
|
||||
end_date = models.DateField("结束日期")
|
||||
leader = models.ForeignKey(User, verbose_name="项目负责人", on_delete=models.CASCADE)
|
||||
participants = models.TextField("项目成员", blank=True, null=True)
|
||||
files = models.ManyToManyField(File, verbose_name="附件", blank=True)
|
||||
note = models.TextField("备注", blank=True, null=True)
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
from apps.rem.models import Project
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.system.serializers import FileSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class ProjectSerializer(CustomModelSerializer):
|
||||
leader_name = serializers.CharField(source="leader.name", read_only=True)
|
||||
files_ = FileSerializer(source="files", many=True, read_only=True)
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = '__all__'
|
||||
|
||||
class ProjectUpdateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ["id", "participants", "files", "note"]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import ProjectViewSet
|
||||
|
||||
API_BASE_URL = 'api/rem/'
|
||||
HTML_BASE_URL = 'dhtml/rem/'
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('project', ProjectViewSet, basename='research_project')
|
||||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
from django.shortcuts import render
|
||||
from apps.utils.viewsets import CustomModelViewSet
|
||||
from apps.rem.models import Project
|
||||
from apps.rem.serializers import ProjectSerializer, ProjectUpdateSerializer
|
||||
# Create your views here.
|
||||
|
||||
class ProjectViewSet(CustomModelViewSet):
|
||||
queryset = Project.objects.all()
|
||||
serializer_class = ProjectSerializer
|
||||
update_serializer_class = ProjectUpdateSerializer
|
||||
select_related_fields = ['leader']
|
||||
search_fields = ["name", "description", "leader__name"]
|
||||
|
|
@ -7,6 +7,7 @@ from rest_framework.exceptions import ParseError
|
|||
class UserFilterSet(filters.FilterSet):
|
||||
ubelong_dept__name = filters.CharFilter(label='归属于该部门及以下(按名称)', method='filter_ubelong_dept__name')
|
||||
ubelong_dept = filters.CharFilter(label='归属于该部门及以下', method='filter_ubelong_dept')
|
||||
has_perm = filters.CharFilter(label='拥有指定权限标识', method='filter_has_perm')
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
|
@ -38,6 +39,9 @@ class UserFilterSet(filters.FilterSet):
|
|||
raise ParseError(f"部门ID错误: {value} {str(e)}")
|
||||
return queryset.filter(belong_dept__in=depts)
|
||||
|
||||
def filter_has_perm(self, queryset, name, value):
|
||||
return queryset.filter(up_user__post__pr_post__role__perms__codes__contains=value)
|
||||
|
||||
|
||||
class DeptFilterSet(filters.FilterSet):
|
||||
|
||||
|
|
@ -45,5 +49,6 @@ class DeptFilterSet(filters.FilterSet):
|
|||
model = Dept
|
||||
fields = {
|
||||
'type': ['exact', 'in'],
|
||||
'name': ['exact', 'in', 'contains']
|
||||
'name': ['exact', 'in', 'contains'],
|
||||
"parent": ['exact', 'isnull'],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
# Generated by Django 4.2.27 on 2026-01-16 06:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0006_auto_20241213_1249'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='dept',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dept',
|
||||
name='third_info',
|
||||
field=models.JSONField(blank=True, default=dict, verbose_name='三方系统信息'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dept',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dictionary',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dictionary',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dicttype',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dicttype',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='file',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='file',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='myschedule',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='myschedule',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='post',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='post',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postrole',
|
||||
name='post',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pr_post', to='system.post', verbose_name='关联岗位'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='postrole',
|
||||
name='role',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pr_role', to='system.role', verbose_name='关联角色'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='belong_dept',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_belong_dept', to='system.dept', verbose_name='所属部门'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='roles',
|
||||
field=models.ManyToManyField(blank=True, to='system.role', verbose_name='关联角色'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
]
|
||||
|
|
@ -54,7 +54,7 @@ class Dept(ParentModel, CommonAModel):
|
|||
name = models.CharField('名称', max_length=60)
|
||||
type = models.CharField('类型', max_length=20, default='dept')
|
||||
sort = models.PositiveSmallIntegerField('排序标记', default=1)
|
||||
third_info = models.JSONField('三方系统信息', default=dict)
|
||||
third_info = models.JSONField('三方系统信息', default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '部门'
|
||||
|
|
@ -107,9 +107,9 @@ class PostRole(BaseModel):
|
|||
data_range = models.PositiveSmallIntegerField('数据权限范围', choices=DataFilter.choices,
|
||||
default=DataFilter.THISLEVEL_AND_BELOW)
|
||||
post = models.ForeignKey(Post, verbose_name='关联岗位',
|
||||
on_delete=models.CASCADE)
|
||||
on_delete=models.CASCADE, related_name="pr_post")
|
||||
role = models.ForeignKey(Role, verbose_name='关联角色',
|
||||
on_delete=models.CASCADE)
|
||||
on_delete=models.CASCADE, related_name='pr_role')
|
||||
|
||||
|
||||
class SoftDeletableUserManager(SoftDeletableManagerMixin, UserManager):
|
||||
|
|
@ -132,7 +132,7 @@ class User(AbstractUser, CommonBModel):
|
|||
posts = models.ManyToManyField(
|
||||
Post, through='system.userpost', related_name='user_posts')
|
||||
depts = models.ManyToManyField(Dept, through='system.userpost')
|
||||
roles = models.ManyToManyField(Role, verbose_name='关联角色')
|
||||
roles = models.ManyToManyField(Role, verbose_name='关联角色', blank=True)
|
||||
|
||||
# 关联账号
|
||||
secret = models.CharField('密钥', max_length=100, null=True, blank=True)
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ def phone_exist(phone):
|
|||
|
||||
|
||||
def user_exist(username):
|
||||
if User.objects.filter(username=username).exists():
|
||||
if User.objects.get_queryset(all=True).filter(username=username).exists():
|
||||
raise serializers.ValidationError(**USERNAME_EXIST)
|
||||
return username
|
||||
|
||||
|
|
|
|||
|
|
@ -300,9 +300,15 @@ class ComplexQueryMixin:
|
|||
page = self.paginate_queryset(new_qs)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
rdata = serializer.data
|
||||
if hasattr(self, 'add_info_for_list'):
|
||||
rdata = self.add_info_for_list(rdata)
|
||||
return self.get_paginated_response(rdata)
|
||||
serializer = self.get_serializer(new_qs, many=True)
|
||||
return Response(serializer.data)
|
||||
rdata = serializer.data
|
||||
if hasattr(self, 'add_info_for_list'):
|
||||
rdata = self.add_info_for_list(rdata)
|
||||
return Response(rdata)
|
||||
|
||||
class MyLoggingMixin(object):
|
||||
"""Mixin to log requests"""
|
||||
|
|
|
|||
|
|
@ -151,6 +151,32 @@ class BaseModel(models.Model):
|
|||
time.sleep(0.1 * (attempt + 1))
|
||||
|
||||
|
||||
@classmethod
|
||||
def locked_get_or_create(cls, defaults: dict, **kwargs):
|
||||
"""
|
||||
仅用于事务内
|
||||
并发安全的 get_or_create
|
||||
"""
|
||||
if not connection.in_atomic_block:
|
||||
raise RuntimeError("locked_get_or_create 必须在事务中调用")
|
||||
|
||||
defaults = defaults or {}
|
||||
|
||||
qs = cls.objects.select_for_update().filter(**kwargs)
|
||||
|
||||
cnt = qs.count()
|
||||
if cnt > 1:
|
||||
raise RuntimeError(
|
||||
f"{cls.__name__} 数据异常:定位条件 {kwargs} 命中 {cnt} 条"
|
||||
)
|
||||
|
||||
if cnt == 1:
|
||||
return qs.get(), False
|
||||
|
||||
params = {**kwargs, **defaults}
|
||||
obj = cls.objects.create(**params)
|
||||
return obj, True
|
||||
|
||||
def handle_parent(self):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from apps.wpm.models import (SfLog, StLog, WMaterial, Mlog, Mlogbw,
|
||||
Handover, Mgroup, Mlogb, Mtask, BatchSt)
|
||||
Handover, Mgroup, Mlogb, Mtask, BatchSt, Handoverb)
|
||||
from apps.mtm.models import Route, Material
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, Exists, OuterRef
|
||||
from rest_framework.exceptions import ParseError
|
||||
from datetime import datetime
|
||||
|
||||
|
|
@ -43,6 +43,7 @@ class WMaterialFilter(filters.FilterSet):
|
|||
material__process__exclude = filters.CharFilter(field_name="material__process", lookup_expr="exact", exclude=True)
|
||||
mlog_date_start = filters.DateFilter(label="产出开始", method="filter_mlog_date_start")
|
||||
mlog_date_end = filters.DateFilter(label="产出结束", method="filter_mlog_date_end")
|
||||
current_merged = filters.BooleanFilter(label="是否本工段新合成的批", method="filter_current_merged")
|
||||
|
||||
def filter_mlog_date_start(self, queryset, name, value):
|
||||
mgroupId = self.data.get("mgroup", None)
|
||||
|
|
@ -101,6 +102,18 @@ class WMaterialFilter(filters.FilterSet):
|
|||
raise ParseError('生产路线不存在!')
|
||||
return queryset.filter(material=route.material_in)|queryset.filter(material__in=route.materials.all())
|
||||
|
||||
def filter_current_merged(self, queryset, name, value):
|
||||
sub_qs = Handoverb.objects.filter(
|
||||
wm_to=OuterRef("pk"),
|
||||
handover__mtype=Handover.H_MERGE,
|
||||
handover__submit_time__isnull=False
|
||||
)
|
||||
if value is True:
|
||||
return queryset.annotate(_has_merge=Exists(sub_qs)).filter(_has_merge=True)
|
||||
elif value is False:
|
||||
return queryset.annotate(_has_merge=Exists(sub_qs)).filter(_has_merge=False)
|
||||
return queryset
|
||||
|
||||
class Meta:
|
||||
model = WMaterial
|
||||
fields = {
|
||||
|
|
@ -154,11 +167,17 @@ class MlogFilter(filters.FilterSet):
|
|||
class HandoverFilter(filters.FilterSet):
|
||||
cbatch = filters.CharFilter(label='批次号', method='filter_cbatch')
|
||||
mgroup = filters.CharFilter(label='MgroupId', method='filter_mgroup')
|
||||
mgroupx = filters.CharFilter(label='MgroupId', method='filter_mgroupx')
|
||||
dept = filters.CharFilter(label='DeptId', method='filter_dept')
|
||||
|
||||
def filter_mgroup(self, queryset, name, value):
|
||||
return queryset.filter(send_mgroup__id=value)|queryset.filter(recive_mgroup__id=value)
|
||||
|
||||
def filter_mgroupx(self, queryset, name, value):
|
||||
dept = Mgroup.objects.get(id=value).belong_dept
|
||||
return (queryset.filter(send_mgroup__id=value)|queryset.filter(recive_mgroup__id=value)|
|
||||
queryset.filter(send_dept=dept, send_mgroup__isnull=True)|queryset.filter(recive_dept=dept, recive_mgroup__isnull=True))
|
||||
|
||||
def filter_dept(self, queryset, name, value):
|
||||
return queryset.filter(send_dept__id=value)|queryset.filter(recive_dept__id=value)
|
||||
|
||||
|
|
@ -223,6 +242,10 @@ class MlogbFilter(filters.FilterSet):
|
|||
class BatchStFilter(filters.FilterSet):
|
||||
batch__startswith__in = filters.CharFilter(method='filter_batch')
|
||||
data__has_key = filters.CharFilter(method='filter_data')
|
||||
with_source_near = filters.CharFilter(label='来源', method='filter_source_near')
|
||||
|
||||
def filter_source_near(self, queryset, name, value):
|
||||
return queryset
|
||||
|
||||
def filter_data(self, queryset, name, value):
|
||||
return queryset.filter(data__has_key=value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
# Generated by Django 4.2.27 on 2026-01-16 07:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('system', '0007_alter_dept_create_by_alter_dept_third_info_and_more'),
|
||||
('wpm', '0126_auto_20251208_1337'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='handoverb',
|
||||
name='oinfo_json',
|
||||
field=models.JSONField(blank=True, default=dict, verbose_name='其他信息'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='attlog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='attlog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fmlog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fmlog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='handover',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='handover',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mlog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mlog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='otherlog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='otherlog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sflog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sflog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sflogexp',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sflogexp',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='stlog',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='stlog',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='wmaterial',
|
||||
name='belong_dept',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_belong_dept', to='system.dept', verbose_name='所属部门'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='wmaterial',
|
||||
name='create_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='wmaterial',
|
||||
name='update_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
|
||||
),
|
||||
]
|
||||
|
|
@ -14,9 +14,11 @@ from django.db.models import Count
|
|||
from django.db import transaction
|
||||
from django.db.models import Max
|
||||
import re
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q, F
|
||||
import django.utils.timezone as timezone
|
||||
from apps.utils.sql import query_all_dict
|
||||
import logging
|
||||
myLogger = logging.getLogger('log')
|
||||
|
||||
# Create your models here.
|
||||
class SfLog(CommonADModel):
|
||||
|
|
@ -125,6 +127,10 @@ class WMaterial(CommonBDModel):
|
|||
material_ofrom = models.ForeignKey(Material, verbose_name='原料物料', on_delete=models.SET_NULL, null=True, blank=True, related_name='wm_mofrom')
|
||||
number_from = models.TextField("来源于个号", null=True, blank=True)
|
||||
|
||||
@property
|
||||
def belong_dept_or_mgroup_id(self):
|
||||
return self.mgroup.id if self.mgroup else self.belong_dept.id
|
||||
|
||||
@property
|
||||
def count_working(self):
|
||||
return Mlogb.objects.filter(wm_in=self, mlog__submit_time__isnull=True).aggregate(count=Sum('count_use'))['count'] or 0
|
||||
|
|
@ -162,6 +168,30 @@ class WMaterial(CommonBDModel):
|
|||
state__in=[WMaterial.WM_OK, WMaterial.WM_REPAIR]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def increase(cls, wm_id: str, user:User, count, count_eweight=None):
|
||||
updates = {}
|
||||
if count:
|
||||
updates['count'] = F('count') + count
|
||||
if count_eweight:
|
||||
updates['count_eweight'] = count_eweight
|
||||
if not updates:
|
||||
return 0
|
||||
updates["update_by"] = user
|
||||
updates['update_time'] = timezone.now()
|
||||
return cls.objects.filter(id=wm_id).update(**updates)
|
||||
|
||||
@classmethod
|
||||
def decrease(cls, wm_id: str, user:User, count):
|
||||
if not count:
|
||||
return 0
|
||||
updated = cls.objects.filter(id=wm_id, count__gte= count).update(
|
||||
count=F('count') - count, update_by=user, update_time=timezone.now())
|
||||
if updated == 0:
|
||||
batch = WMaterial.objects.get(id=wm_id).batch
|
||||
raise ParseError(f'{batch}_库存不足,无法完成扣减')
|
||||
return updated
|
||||
|
||||
class Fmlog(CommonADModel):
|
||||
"""TN: 父级生产日志
|
||||
"""
|
||||
|
|
@ -639,6 +669,7 @@ class Handoverb(BaseModel):
|
|||
wm_to = models.ForeignKey(WMaterial, verbose_name='所到车间库存', on_delete=models.SET_NULL,
|
||||
null=True, blank=True, related_name='handoverb_wm_to')
|
||||
count = models.DecimalField('送料数', default=0, max_digits=11, decimal_places=1)
|
||||
oinfo_json = models.JSONField('其他信息', default=dict, blank=True)
|
||||
|
||||
@property
|
||||
def handoverbw(self):
|
||||
|
|
@ -837,41 +868,46 @@ class BatchLog(BaseModel):
|
|||
|
||||
@classmethod
|
||||
def batches_to(cls, batch:str):
|
||||
|
||||
# query = """
|
||||
# SELECT batch FROM wpm_batchst
|
||||
# WHERE batch ~ %s
|
||||
# """
|
||||
query = """
|
||||
SELECT batch
|
||||
SELECT
|
||||
batch,
|
||||
CAST(substring(batch FROM LENGTH(%s) + 2) AS INTEGER) AS batch_num
|
||||
FROM wpm_batchst
|
||||
WHERE batch ~ %s
|
||||
ORDER BY
|
||||
-- 先按前缀部分排序(例如 'A')
|
||||
SUBSTRING(batch FROM '^(.*)-') DESC,
|
||||
-- 再按后缀的数值部分排序(将 '2', '11' 转为整数)
|
||||
CAST(SUBSTRING(batch FROM '-([0-9]+)$') AS INTEGER) DESC
|
||||
""" # 排序可在sql层处理
|
||||
query_ = """SELECT batch FROM wpm_batchst WHERE batch ~ %s"""
|
||||
pattern = f'^{batch}-[0-9]+$'
|
||||
|
||||
"""可以用如下方法直接查询
|
||||
WHERE batch LIKE %s AND translate(
|
||||
substring(batch FROM LENGTH(%s) + 2),
|
||||
'0123456789',
|
||||
''
|
||||
) = ''
|
||||
ORDER BY batch_num DESC
|
||||
"""
|
||||
# batches = BatchLog.objects.filter(source__batch=batch, relation_type="split").values_list("target__batch", flat=True).distinct()
|
||||
# batches = sorted(list(batches), key=custom_key)
|
||||
batches_r = query_all_dict(query_, params=(pattern,))
|
||||
batches = [b["batch"] for b in batches_r]
|
||||
batches = sorted(list(batches), key=custom_key)
|
||||
last_batch_num = None
|
||||
if batches:
|
||||
last_batch = batches[-1]
|
||||
last_batch_list = last_batch.split("-")
|
||||
if last_batch_list:
|
||||
|
||||
prefix = batch
|
||||
params = (
|
||||
prefix,
|
||||
f"{prefix}-%",
|
||||
prefix
|
||||
)
|
||||
|
||||
try:
|
||||
last_batch_num = int(last_batch_list[-1])
|
||||
except Exception:
|
||||
pass
|
||||
return {"batches": batches, "last_batch_num": last_batch_num, "last_batch": last_batch}
|
||||
return {"batches": [], "last_batch_num": None, "last_batch": None}
|
||||
rows = query_all_dict(query, params=params)
|
||||
except Exception as e:
|
||||
myLogger.error(f"BatchLog.batches_to error: {(str(e), query, params)}")
|
||||
raise
|
||||
|
||||
if not rows:
|
||||
return {
|
||||
"batches": [],
|
||||
"last_batch_num": None,
|
||||
"last_batch": None,
|
||||
}
|
||||
|
||||
batches = [r["batch"] for r in rows]
|
||||
last = rows[0]
|
||||
|
||||
return {
|
||||
"batches": batches,
|
||||
"last_batch_num": last["batch_num"],
|
||||
"last_batch": last["batch"],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from apps.wpm.models import BatchSt
|
||||
import logging
|
||||
from apps.qm.models import Defect
|
||||
from apps.qm.models import Defect, FtestWork, FtestworkDefect
|
||||
from apps.wpm.models import Mlogb, MlogbDefect
|
||||
from apps.mtm.models import Mgroup
|
||||
import decimal
|
||||
|
|
@ -117,25 +117,54 @@ def main(batch: str, mgroup_obj):
|
|||
data[f"外观检验_返修_缺陷_{item['defect__name']}"] = item["total"]
|
||||
data[f"外观检验_返修_缺陷_{item['defect__name']}_比例"] = round((item["total"] / data["外观检验_返修_count_real"])*100, 2)
|
||||
|
||||
# 车间库存抽检
|
||||
ft_qs = FtestWork.objects.filter(type2=FtestWork.TYPE2_SOME, wm__mgroup__name="外观检验", batch=batch, submit_time__isnull=False)
|
||||
if ft_qs.exists():
|
||||
data["外观检验_车间库存抽检_日期"] = []
|
||||
data["外观检验_车间库存抽检_操作人"] = []
|
||||
data["外观检验_车间库存抽检_count_notok"] = 0
|
||||
for item in ft_qs:
|
||||
if item.test_user:
|
||||
data["外观检验_车间库存抽检_操作人"].append(item.test_user)
|
||||
if item.test_date:
|
||||
data["外观检验_车间库存抽检_日期"].append(item.test_date)
|
||||
data["外观检验_车间库存抽检_count_notok"] += item.count_notok if item.count_notok else 0
|
||||
|
||||
data["外观检验_车间库存抽检_日期"] = list(set(data["外观检验_车间库存抽检_日期"]))
|
||||
data["外观检验_车间库存抽检_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["外观检验_车间库存抽检_日期"]])
|
||||
data["外观检验_车间库存抽检_操作人"] = list(set(data["外观检验_车间库存抽检_操作人"]))
|
||||
data["外观检验_车间库存抽检_操作人"] = ";".join([item.name for item in data["外观检验_车间库存抽检_操作人"]])
|
||||
|
||||
# 车间库存抽检缺陷
|
||||
ftd_qs = FtestworkDefect.objects.filter(ftestwork__in=ft_qs, count__gt=0).values("defect__name").annotate(total=Sum("count"))
|
||||
for item in ftd_qs:
|
||||
data[f"外观检验_车间库存抽检_缺陷_{item['defect__name']}"] = item["total"]
|
||||
|
||||
if "外观检验_count_ok" in data:
|
||||
data["外观检验_总合格数"] = data["外观检验_count_ok"] + data["外观检验_返修_count_ok"] if "外观检验_返修_count_ok" in data else 0
|
||||
data["外观检验_总合格数"] = data["外观检验_count_ok"] + data.get("外观检验_返修_count_ok", 0)
|
||||
try:
|
||||
data["外观检验_总合格率"] = round((data["外观检验_总合格数"] / data["外观检验_count_real"])*100, 2)
|
||||
except decimal.InvalidOperation:
|
||||
data["外观检验_总合格率"] = 0
|
||||
|
||||
data["外观检验_完全总合格数"] = data["外观检验_count_ok_full"] + data["外观检验_返修_count_ok_full"] if "外观检验_返修_count_ok_full" in data else 0
|
||||
data["外观检验_完全总合格数"] = data["外观检验_count_ok_full"] + data.get("外观检验_返修_count_ok_full", 0)
|
||||
try:
|
||||
data["外观检验_完全总合格率"] = round((data["外观检验_完全总合格数"] / data["外观检验_count_real"])*100, 2)
|
||||
except decimal.InvalidOperation:
|
||||
data["外观检验_完全总合格率"] = 0
|
||||
|
||||
data["外观检验_直通合格数"] = data["外观检验_总合格数"] - data.get("外观检验_车间库存抽检_count_notok", 0)
|
||||
if "尺寸检验_合格率" in data:
|
||||
try:
|
||||
data["外观检验_直通合格率"] = round((data["外观检验_总合格率"]* data["尺寸检验_合格率"])/100, 2)
|
||||
except decimal.InvalidOperation:
|
||||
data["外观检验_直通合格率"] = 0
|
||||
|
||||
try:
|
||||
data["外观检验_直通合格率2"] = round((data["外观检验_直通合格数"]/data["尺寸检验_count_use"])*100, 2)
|
||||
except (decimal.InvalidOperation, ZeroDivisionError):
|
||||
data["外观检验_直通合格率2"] = 0
|
||||
|
||||
if "尺寸检验_完全合格率" in data:
|
||||
try:
|
||||
data["外观检验_完全直通合格率"] = round((data["外观检验_完全总合格率"]* data["尺寸检验_完全合格率"])/100, 2)
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ def main(batch: str, mgroup_obj=None):
|
|||
data["六车间交接领料_接料人"] = ";".join([item.name for item in data["六车间交接领料_接料人"]])
|
||||
|
||||
# 六车间工段生产数据
|
||||
mgroup_list = ["平头", "粘铁头", "粗中细磨", "平磨", "掏管", "抛光", "开槽", "倒角"]
|
||||
mgroup_list = ["平头", "粘铁头", "粗中细磨", "平磨", "掏管", "抛光", "开槽", "倒角", "加工前检验", "中检"]
|
||||
for mgroup_name in mgroup_list:
|
||||
if mgroup_name == '粗中细磨':
|
||||
mgroups = Mgroup.objects.filter(name__in=['粗磨', '粗中磨', '粗中细磨'])
|
||||
|
|
|
|||
|
|
@ -1,17 +1,49 @@
|
|||
from apps.wpmw.models import Wpr
|
||||
from apps.wpm.models import Mlogbw
|
||||
from apps.qm.models import Ftest, FtestDefect, FtestItem
|
||||
from apps.wpm.models import Mlogbw, Mlog, MlogUser
|
||||
from apps.qm.models import Ftest, FtestDefect, FtestItem, TestItem
|
||||
from rest_framework.exceptions import ParseError
|
||||
from apps.mtm.models import Mgroup
|
||||
|
||||
def main(wprId, mgroup:Mgroup):
|
||||
def main(wprId, mgroup:Mgroup=None):
|
||||
wpr = Wpr.objects.get(id=wprId)
|
||||
if mgroup is None:
|
||||
mgroup_ids = Mlogbw.objects.filter(
|
||||
wpr=wpr,
|
||||
mlogb__mlog__submit_time__isnull=False,
|
||||
mlogb__mlog__is_fix=False
|
||||
).values_list(
|
||||
'mlogb__mlog__mgroup',
|
||||
flat=True
|
||||
).distinct()
|
||||
mgroups = Mgroup.objects.filter(id__in=mgroup_ids)
|
||||
else:
|
||||
mgroups = [mgroup]
|
||||
data = {}
|
||||
for mgroup in mgroups:
|
||||
mgroup_name = mgroup.name
|
||||
mlogbw = Mlogbw.objects.filter(wpr=wpr, mlogb__mlog__mgroup=mgroup, mlogb__mlog__submit_time__isnull=False).order_by("-update_time").first()
|
||||
mlogbw = Mlogbw.objects.filter(wpr=wpr,
|
||||
mlogb__mlog__mgroup=mgroup,
|
||||
mlogb__mlog__submit_time__isnull=False, mlogb__mlog__is_fix=False).order_by("-update_time").first()
|
||||
if mlogbw:
|
||||
mlog:Mlog = mlogbw.mlogb.mlog
|
||||
data[f"{mgroup_name}_批次号"] = mlogbw.mlogb.batch
|
||||
data[f"{mgroup_name}_日期"] = mlogbw.mlogb.mlog.handle_date.strftime("%Y-%m-%d")
|
||||
data[f"{mgroup_name}_设备编号"] = mlog.equipment.number if mlog.equipment else None
|
||||
data[f"{mgroup_name}_操作人"] = mlog.handle_user.name if mlog.handle_user else None
|
||||
data[f"{mgroup_name}_日期"] = mlog.handle_date.strftime("%Y-%m-%d")
|
||||
# 日志操作数据
|
||||
if mlog.oinfo_json:
|
||||
oinfo_keys = list(mlog.oinfo_json.keys())
|
||||
oinfo_keys_qs = TestItem.objects.filter(id__in=oinfo_keys)
|
||||
for item in oinfo_keys_qs:
|
||||
data[f"{mgroup_name}_操作项_{item.name}"] = mlog.oinfo_json[item.id]
|
||||
# 子工序操作人和日期
|
||||
mlogusers = MlogUser.objects.filter(mlog=mlog)
|
||||
if mlogusers.exists():
|
||||
datab = mlogusers.values("handle_user__name", "process__name", "handle_date")
|
||||
for ind, item in enumerate(datab):
|
||||
data[f"{mgroup_name}_{item['process__name']}_操作人"] = item["handle_user__name"]
|
||||
data[f"{mgroup_name}_{item['process__name']}_日期"] = item["handle_date"].strftime("%Y-%m-%d")
|
||||
# 检测数据
|
||||
ftestitems = FtestItem.objects.filter(ftest__mlogbw_ftest__wpr=wpr,
|
||||
ftest__mlogbw_ftest__mlogb__mlog__mgroup=mgroup,
|
||||
ftest__mlogbw_ftest__mlogb__mlog__submit_time__isnull=False,
|
||||
|
|
@ -25,7 +57,6 @@ def main(wprId, mgroup:Mgroup):
|
|||
ftest__mlogbw_ftest__mlogb__mlog__is_fix=False)
|
||||
for ftestdefect in ftestdefects:
|
||||
data[f"{mgroup_name}_缺陷项_{ftestdefect.defect.name}"] = 1 if ftestdefect.has is True else 0
|
||||
|
||||
old_data:dict = wpr.data
|
||||
if old_data:
|
||||
for item in list(old_data.keys()):
|
||||
|
|
|
|||
|
|
@ -1049,6 +1049,11 @@ class MlogbwStartTestSerializer(serializers.Serializer):
|
|||
test_equip=test_equip
|
||||
)
|
||||
|
||||
class MlogbOutPatchUpdateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Mlogb
|
||||
fields = ["batch"]
|
||||
|
||||
class MlogbOutUpdateSerializer(CustomModelSerializer):
|
||||
mlogbdefect = MlogbDefectSerializer(many=True, required=False)
|
||||
count_json = CountJsonSerializer(required=False, many=True)
|
||||
|
|
@ -1256,12 +1261,18 @@ class HandoverSerializer(CustomModelSerializer):
|
|||
next_mat = new_wm.material
|
||||
next_state = new_wm.state
|
||||
next_defect = new_wm.defect
|
||||
deptOrmgroupId = None
|
||||
for ind, item in enumerate(attrs['handoverb']):
|
||||
if item["count"] > 0:
|
||||
pass
|
||||
else:
|
||||
raise ParseError(f'第{ind+1}行-交接数量必须大于0')
|
||||
wm = item["wm"]
|
||||
wm: WMaterial = item["wm"]
|
||||
current_mdept_id = wm.belong_dept_or_mgroup_id
|
||||
if deptOrmgroupId is None:
|
||||
deptOrmgroupId = current_mdept_id
|
||||
elif deptOrmgroupId != current_mdept_id:
|
||||
raise ParseError(f'第{ind+1}行-交接物料所属工段/车间不一致')
|
||||
if mtype == Handover.H_MERGE:
|
||||
if next_mat is None:
|
||||
next_mat = wm.material
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from ..qm.models import Defect, Ftest
|
|||
from django.db.models import Count, Q
|
||||
from apps.utils.tasks import ctask_run
|
||||
from apps.mtm.models import Process
|
||||
from apps.utils.lock import lock_model_record_d_func
|
||||
from django.db.models import F
|
||||
|
||||
myLogger = logging.getLogger('log')
|
||||
|
||||
|
|
@ -150,11 +150,12 @@ def get_pcoal_heat(year_s: int, month_s: int, day_s: int):
|
|||
myLogger.error(f'获取煤粉热值失败,{e}, {year_s}, {month_s}, {day_s}', exc_info=True)
|
||||
return 25000
|
||||
|
||||
# @lock_model_record_d_func(Mlog)
|
||||
|
||||
def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||
"""
|
||||
生产日志提交后需要执行的操作
|
||||
"""
|
||||
mlog = Mlog.objects.select_for_update().get(id=mlog.id)
|
||||
if mlog.work_start_time and mlog.work_start_time > timezone.now():
|
||||
raise ParseError('操作开始时间不能晚于当前时间')
|
||||
if mlog.work_start_time and mlog.work_end_time and mlog.work_end_time < mlog.work_start_time:
|
||||
|
|
@ -172,13 +173,15 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
|
||||
mgroup = mlog.mgroup
|
||||
process = mgroup.process
|
||||
into_wm_mgroup = process.into_wm_mgroup
|
||||
need_store_notok = process.store_notok
|
||||
stored_mgroup = process.into_wm_mgroup
|
||||
stored_notok = process.store_notok
|
||||
belong_dept = mgroup.belong_dept
|
||||
material_out: Material = mlog.material_out
|
||||
material_in: Material = mlog.material_in
|
||||
supplier = mlog.supplier # 外协
|
||||
is_fix = mlog.is_fix
|
||||
if is_fix: # 如果是返工,直接放到工段下
|
||||
stored_mgroup = True
|
||||
m_ins_list = []
|
||||
m_ins_bl_list = []
|
||||
|
||||
|
|
@ -221,7 +224,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
# 需要判断领用数是否合理
|
||||
# 优先使用工段库存
|
||||
if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
|
||||
wm_qs = WMaterial.objects.filter(id=mlog_or_b.wm_in.id)
|
||||
wm = WMaterial.objects.select_for_update().get(id=mlog_or_b.wm_in.id)
|
||||
else:
|
||||
wm_qs = WMaterial.objects.filter(batch=mi_batch, material=mi_ma, mgroup=mgroup, state=WMaterial.WM_OK)
|
||||
if not wm_qs.exists():
|
||||
|
|
@ -229,7 +232,7 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
belong_dept=belong_dept, mgroup=None, state=WMaterial.WM_OK)
|
||||
count_x = wm_qs.count()
|
||||
if count_x == 1:
|
||||
wm = wm_qs.first()
|
||||
wm = WMaterial.objects.select_for_update().get(id=wm_qs.first().id)
|
||||
elif count_x == 0:
|
||||
raise ParseError(
|
||||
f'{str(mi_ma)}-{mi_batch}-批次库存不存在!')
|
||||
|
|
@ -252,13 +255,13 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
Wpr.change_or_new(wpr=item.wpr, old_wm=wm, ftest=item.ftest)
|
||||
|
||||
# 针对加工前不良的暂时额外处理
|
||||
if need_store_notok:
|
||||
if stored_notok:
|
||||
for item in m_ins_bl_list:
|
||||
material, batch, count, defect, mi_ = item
|
||||
if count <= 0:
|
||||
raise ParseError('存在非正数!')
|
||||
lookup = {'batch': batch, 'material': material, 'mgroup': mgroup, 'defect': defect, 'state': WMaterial.WM_NOTOK}
|
||||
wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
||||
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
||||
wm.count = wm.count + count
|
||||
if is_create:
|
||||
wm.create_by = user
|
||||
|
|
@ -275,12 +278,10 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
|
||||
|
||||
mlogb_out_qs = Mlogb.objects.filter(mlog=mlog, material_out__isnull=False)
|
||||
stored_mgroup = into_wm_mgroup
|
||||
stored_notok = need_store_notok
|
||||
if mlogb_out_qs.exists():
|
||||
mlogb_out_qs = mlogb_out_qs.filter(need_inout=True)
|
||||
m_outs_list = [(mo.material_out, mo.batch if mo.batch else mlog.batch, mo.count_ok_full if mo.count_ok_full is not None else mo.count_ok, mlog.count_real_eweight, None, mo) for mo in mlogb_out_qs.all()]
|
||||
if need_store_notok:
|
||||
if stored_notok:
|
||||
for item in mlogb_out_qs:
|
||||
mbd_qs = MlogbDefect.get_defect_qs_from_mlogb(item)
|
||||
if item.qct is not None or mbd_qs.exists():
|
||||
|
|
@ -309,7 +310,6 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if 'count_n_' in f.name and getattr(item, f.name) > 0:
|
||||
notok_sign = f.name.replace('count_n_', '')
|
||||
m_outs_list.append( (item.material_out, item.batch if item.batch else mlog.batch, getattr(item, f.name), mlog.count_real_eweight, notok_sign, item))
|
||||
stored_notok = True
|
||||
# 这里有一个漏洞,在产出物为兄弟件的情况下,不合格品的数量是记录在mlog上的,
|
||||
# 而不是mlogb上,以上的额外处理就没有效果了, 不过光子不记录不合格品
|
||||
else:
|
||||
|
|
@ -339,12 +339,12 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
lookup['defect'] = notok_sign_or_defect
|
||||
elif notok_sign_or_defect is not None:
|
||||
lookup['notok_sign'] = notok_sign_or_defect
|
||||
if into_wm_mgroup:
|
||||
if stored_mgroup:
|
||||
lookup['mgroup'] = mgroup
|
||||
else:
|
||||
lookup['belong_dept'] = belong_dept
|
||||
|
||||
wm, is_create2 = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
|
||||
wm, is_create2 = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
||||
wm.count = wm.count + mo_count
|
||||
wm.count_eweight = mo_count_eweight
|
||||
wm.update_by = user
|
||||
|
|
@ -402,16 +402,17 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
ana_batch_thread(xbatchs=xbatches, mgroup=mlog.mgroup)
|
||||
|
||||
# 触发单个统计
|
||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||
if wprIds:
|
||||
ana_wpr_thread(wprIds, mlog.mgroup)
|
||||
|
||||
# @lock_model_record_d_func(Mlog)
|
||||
|
||||
def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||
"""日志撤回
|
||||
"""
|
||||
# if mlog.submit_time is None:
|
||||
# return
|
||||
mlog = Mlog.objects.select_for_update().get(id=mlog.id)
|
||||
if now is None:
|
||||
now = timezone.now()
|
||||
|
||||
|
|
@ -507,6 +508,8 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
else:
|
||||
raise ParseError(
|
||||
f'{str(mo_ma)}-{mo_batch}-存在多个相同批次!')
|
||||
|
||||
wm = WMaterial.objects.select_for_update().get(id=wm.id)
|
||||
wm.count = wm.count - mo_count
|
||||
if wm.count < 0:
|
||||
raise ParseError(f'{wm.batch} 车间库存不足, 产物无法回退')
|
||||
|
|
@ -529,7 +532,6 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
# 再生成消耗
|
||||
m_ins_list = []
|
||||
m_ins_bl_list = []
|
||||
into_wm_mgroup = process.into_wm_mgroup
|
||||
m_ins = Mlogb.objects.filter(mlog=mlog, material_in__isnull=False)
|
||||
if m_ins.exists():
|
||||
m_ins = m_ins.filter(need_inout=True)
|
||||
|
|
@ -549,17 +551,17 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
if mi_count <= 0:
|
||||
raise ParseError('存在非正数!')
|
||||
if isinstance(mlog_or_b, Mlogb) and mlog_or_b.wm_in:
|
||||
wm = WMaterial.objects.get(id=mlog_or_b.wm_in.id)
|
||||
wm = WMaterial.objects.select_for_update().get(id=mlog_or_b.wm_in.id)
|
||||
else:
|
||||
# 针对光子的情况,实际上必须需要wm_in
|
||||
lookup = {'batch': mi_batch, 'material': mi_ma, 'mgroup': None, 'state': WMaterial.WM_OK}
|
||||
if into_wm_mgroup:
|
||||
if stored_mgroup:
|
||||
# 退回到本工段
|
||||
lookup['mgroup'] = mgroup
|
||||
else:
|
||||
lookup['belong_dept'] = belong_dept
|
||||
|
||||
wm, _ = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
|
||||
wm, _ = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
||||
wm.count = wm.count + mi_count
|
||||
wm.update_by = user
|
||||
wm.save()
|
||||
|
|
@ -581,7 +583,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
lookup['mgroup'] = mgroup
|
||||
else:
|
||||
lookup['belong_dept'] = belong_dept
|
||||
wm, is_create = WMaterial.objects.get_or_create(**lookup, defaults={**lookup, "belong_dept": belong_dept})
|
||||
wm, is_create = WMaterial.locked_get_or_create(**lookup, defaults={"belong_dept": belong_dept})
|
||||
wm.count = wm.count - count
|
||||
if wm.count < 0:
|
||||
raise ParseError('加工前不良数量大于库存量')
|
||||
|
|
@ -621,7 +623,7 @@ def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
|||
ana_batch_thread(xbatches, mgroup=mlog.mgroup)
|
||||
|
||||
# 触发单个统计
|
||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, ftest__isnull=False, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||
wprIds = list(Mlogbw.objects.filter(mlogb__mlog=mlog, wpr__isnull=False).values_list('wpr__id', flat=True))
|
||||
if wprIds:
|
||||
ana_wpr_thread(wprIds, mlog.mgroup)
|
||||
|
||||
|
|
@ -698,11 +700,15 @@ def update_mtask(mtask: Mtask, fill_way: int = 10):
|
|||
# utask.state = Utask.UTASK_SUBMIT
|
||||
utask.save()
|
||||
|
||||
@lock_model_record_d_func(Handover)
|
||||
def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime, None]):
|
||||
"""
|
||||
交接提交后需要执行的操作
|
||||
"""
|
||||
handover = (
|
||||
Handover.objects
|
||||
.select_for_update()
|
||||
.get(pk=handover.pk)
|
||||
)
|
||||
if handover.submit_time is not None:
|
||||
return
|
||||
now = timezone.now()
|
||||
|
|
@ -746,7 +752,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
wmId, xcount, handover_or_b = item
|
||||
if xcount <= 0:
|
||||
raise ParseError("存在非正数!")
|
||||
wm_from = WMaterial.objects.get(id=wmId)
|
||||
wm_from = (
|
||||
WMaterial.objects
|
||||
.select_for_update()
|
||||
.get(id=wmId)
|
||||
)
|
||||
mids.append(wm_from.material.id)
|
||||
|
||||
# 合并为新批
|
||||
|
|
@ -770,25 +780,17 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
batch = wm_from.batch
|
||||
batches.append(batch)
|
||||
|
||||
if wm_from is None:
|
||||
raise ParseError(f'{wm_from.batch} 找不到车间库存')
|
||||
|
||||
count_x = wm_from.count - xcount
|
||||
if count_x < 0:
|
||||
raise ParseError(f'{wm_from.batch} 车间库存不足!')
|
||||
else:
|
||||
wm_from.count = count_x
|
||||
wm_from.save()
|
||||
WMaterial.decrease(wm_id=wm_from.id, user=user, count=xcount)
|
||||
|
||||
if need_add:
|
||||
# 开始变动
|
||||
if handover.type == Handover.H_NORMAL:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = handover.new_wm
|
||||
if wm_to.state != WMaterial.WM_OK or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.state != wm_from.state or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
raise ParseError("正常合并到的车间库存状态或物料异常")
|
||||
else:
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
wm_to, _ = WMaterial.locked_get_or_create(
|
||||
batch=batch,
|
||||
material=material,
|
||||
mgroup=recive_mgroup,
|
||||
|
|
@ -808,11 +810,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
recive_mgroup = handover.recive_mgroup
|
||||
wm_state = WMaterial.WM_REPAIR
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = handover.new_wm
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.state != WMaterial.WM_REPAIR or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
raise ParseError("返修合并到的车间库存状态或物料异常")
|
||||
elif recive_mgroup:
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
wm_to, _ = WMaterial.locked_get_or_create(
|
||||
batch=batch,
|
||||
material=material,
|
||||
mgroup=recive_mgroup,
|
||||
|
|
@ -830,28 +832,13 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
)
|
||||
else:
|
||||
raise ParseError("返工交接必须指定接收工段")
|
||||
elif handover.type == Handover.H_TEST:
|
||||
raise ParseError("检验交接已废弃")
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
batch=batch,
|
||||
material=material,
|
||||
mgroup=recive_mgroup,
|
||||
state=WMaterial.WM_TEST,
|
||||
belong_dept=recive_dept,
|
||||
defaults={
|
||||
"count_xtest": 0,
|
||||
"batch_ofrom": wm_from.batch_ofrom,
|
||||
"material_ofrom": wm_from.material_ofrom,
|
||||
"create_by": user
|
||||
},
|
||||
)
|
||||
elif handover.type == Handover.H_SCRAP:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = handover.new_wm
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.state != WMaterial.WM_SCRAP or wm_to.material != wm_from.material or wm_to.defect != wm_from.defect:
|
||||
raise ParseError("报废合并到的车间库存状态或物料异常")
|
||||
elif recive_mgroup:
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
wm_to, _ = WMaterial.locked_get_or_create(
|
||||
batch=batch,
|
||||
material=material,
|
||||
mgroup=recive_mgroup,
|
||||
|
|
@ -870,11 +857,11 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
raise ParseError("不支持非工段报废")
|
||||
elif handover.type == Handover.H_CHANGE:
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = handover.new_wm
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
if wm_to.material != handover.material_changed or wm_to.state != handover.state_changed:
|
||||
raise ParseError("改版合并到的车间库存状态或物料异常")
|
||||
elif handover.recive_mgroup:
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
wm_to, _ = WMaterial.locked_get_or_create(
|
||||
batch=batch,
|
||||
material=handover.material_changed,
|
||||
state=handover.state_changed,
|
||||
|
|
@ -897,9 +884,9 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
if wm_from and wm_from.state != WMaterial.WM_OK:
|
||||
raise ParseError("仅合格品支持退回")
|
||||
if mtype == Handover.H_MERGE and handover.new_wm:
|
||||
wm_to = handover.new_wm
|
||||
wm_to = WMaterial.objects.select_for_update().get(id=handover.new_wm.id)
|
||||
else:
|
||||
wm_to, _ = WMaterial.objects.get_or_create(
|
||||
wm_to, _ = WMaterial.locked_get_or_create(
|
||||
batch=batch,
|
||||
material=material,
|
||||
mgroup=recive_mgroup,
|
||||
|
|
@ -917,9 +904,7 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
else:
|
||||
raise ParseError("不支持该交接类型")
|
||||
|
||||
wm_to.count = wm_to.count + xcount
|
||||
wm_to.count_eweight = handover.count_eweight # 这行代码有隐患
|
||||
wm_to.save()
|
||||
WMaterial.increase(wm_id=wm_to.id, user=user,count=xcount, count_eweight=handover.count_eweight if handover.count_eweight else None)
|
||||
handover_or_b.wm_to = wm_to
|
||||
handover_or_b.save()
|
||||
if material.tracking == Material.MA_TRACKING_SINGLE:
|
||||
|
|
@ -934,7 +919,8 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
for item in handoverbws:
|
||||
wpr:Wpr = item.wpr
|
||||
Wpr.change_or_new(wpr=wpr, wm=wm_to, old_wm=wpr.wm, old_mb=wpr.mb)
|
||||
if wm_to.count != Wpr.objects.filter(wm=wm_to).count():
|
||||
db_count = WMaterial.objects.filter(id=wm_to.id).values_list("count", flat=True).get()
|
||||
if db_count != Wpr.objects.filter(wm=wm_to).count():
|
||||
raise ParseError("交接与明细数量不一致2,操作失败")
|
||||
|
||||
handover.submit_user = user
|
||||
|
|
@ -945,14 +931,14 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
|||
|
||||
ana_batch_thread(xbatchs=batches)
|
||||
|
||||
@lock_model_record_d_func(Handover)
|
||||
def handover_revert(handover:Handover, handler:User=None):
|
||||
handover = Handover.objects.select_for_update().get(id=handover.id)
|
||||
if handover.submit_time is None:
|
||||
raise ParseError('该交接单未提交!')
|
||||
ticket:Ticket = handover.ticket
|
||||
if ticket:
|
||||
# 首先把ticket改回开始状态
|
||||
WfService.retreat(ticket=ticket, suggestion="撤销交接单", handler=handler, next_handler=handover.create_by)
|
||||
WfService.retreat(ticket=ticket, suggestion="撤回交接单", handler=handler, next_handler=handover.create_by)
|
||||
mids = []
|
||||
# handover_type = handover.type
|
||||
# handover_mtype = handover.mtype
|
||||
|
|
@ -973,21 +959,18 @@ def handover_revert(handover:Handover, handler:User=None):
|
|||
wm = item.wm
|
||||
wm_to = item.wm_to
|
||||
if wm is None or wm_to is None:
|
||||
raise ParseError('该交接单不支持撤销2!')
|
||||
raise ParseError('该交接单不支持撤回2!')
|
||||
if wm == wm_to:
|
||||
# 此时是自己交给自己,不需要做任何操作
|
||||
pass
|
||||
else:
|
||||
wm.count = wm.count + item.count
|
||||
wm.save()
|
||||
wm_to.count = wm_to.count - item.count
|
||||
if wm_to.count < 0:
|
||||
raise ParseError('库存不足无法撤回!')
|
||||
wm_to.save()
|
||||
WMaterial.increase(wm_id=wm.id, user=handler, count=item.count)
|
||||
WMaterial.decrease(wm_id=wm_to.id, user=handler, count=item.count)
|
||||
if material.tracking == Material.MA_TRACKING_SINGLE:
|
||||
handoverbws = Handoverbw.objects.filter(handoverb=item)
|
||||
if handoverbws.count() != item.count:
|
||||
raise ParseError("交接与明细数量不一致,操作失败")
|
||||
wm = WMaterial.objects.get(id=wm.id)
|
||||
for item in handoverbws:
|
||||
wpr:Wpr = item.wpr
|
||||
Wpr.change_or_new(wpr=wpr, wm=wm, old_wm=wpr.wm, old_mb=wpr.mb, add_version=False)
|
||||
|
|
@ -1064,6 +1047,7 @@ def get_batch_dag(batch_number: str, method="full"):
|
|||
}
|
||||
|
||||
if method == "full":
|
||||
raise ParseError("不支持获取全局关系链条")
|
||||
# 完整DAG模式 - 收集所有相关批次和边(原逻辑)
|
||||
nodes_set = {batch_ins.id}
|
||||
edges = []
|
||||
|
|
@ -1120,33 +1104,33 @@ def get_batch_dag(batch_number: str, method="full"):
|
|||
})
|
||||
|
||||
# 查询作为source的其他关系
|
||||
leftLogs = BatchLog.objects.filter(source_id__in=left_source_ids).exclude(id__in=exist_log_ids)
|
||||
for log in leftLogs:
|
||||
source = log.source.id
|
||||
target = log.target.id
|
||||
nodes_set.add(log.target.id)
|
||||
edges.append({
|
||||
'id': log.id,
|
||||
'source': source,
|
||||
'target': target,
|
||||
"handover": log.handover.id if log.handover else None,
|
||||
"mlog": log.mlog.id if log.mlog else None,
|
||||
'label': r_dict.get(log.relation_type, ""),
|
||||
})
|
||||
# leftLogs = BatchLog.objects.filter(source_id__in=left_source_ids).exclude(id__in=exist_log_ids)
|
||||
# for log in leftLogs:
|
||||
# source = log.source.id
|
||||
# target = log.target.id
|
||||
# nodes_set.add(log.target.id)
|
||||
# edges.append({
|
||||
# 'id': log.id,
|
||||
# 'source': source,
|
||||
# 'target': target,
|
||||
# "handover": log.handover.id if log.handover else None,
|
||||
# "mlog": log.mlog.id if log.mlog else None,
|
||||
# 'label': r_dict.get(log.relation_type, ""),
|
||||
# })
|
||||
|
||||
rightLogs = BatchLog.objects.filter(target_id__in=right_target_ids).exclude(id__in=exist_log_ids)
|
||||
for log in rightLogs:
|
||||
source = log.source.id
|
||||
target = log.target.id
|
||||
nodes_set.add(log.source.id)
|
||||
edges.append({
|
||||
'id': log.id,
|
||||
'source': source,
|
||||
'target': target,
|
||||
"handover": log.handover.id if log.handover else None,
|
||||
"mlog": log.mlog.id if log.mlog else None,
|
||||
'label': r_dict.get(log.relation_type, ""),
|
||||
})
|
||||
# rightLogs = BatchLog.objects.filter(target_id__in=right_target_ids).exclude(id__in=exist_log_ids)
|
||||
# for log in rightLogs:
|
||||
# source = log.source.id
|
||||
# target = log.target.id
|
||||
# nodes_set.add(log.source.id)
|
||||
# edges.append({
|
||||
# 'id': log.id,
|
||||
# 'source': source,
|
||||
# 'target': target,
|
||||
# "handover": log.handover.id if log.handover else None,
|
||||
# "mlog": log.mlog.id if log.mlog else None,
|
||||
# 'label': r_dict.get(log.relation_type, ""),
|
||||
# })
|
||||
|
||||
else:
|
||||
raise ParseError("不支持的查询方法,请使用'full'或'direct'")
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ router.register('sflogexp', SfLogExpViewSet, basename='sflogexp')
|
|||
router.register('wmaterial', WMaterialViewSet, basename='wmaterial')
|
||||
router.register('fmlog', FmlogViewSet, basename='fmlog')
|
||||
router.register('mlog', MlogViewSet, basename='mlog')
|
||||
router.register('mlogb', MlogbViewSet)
|
||||
router.register('mlogb/in', MlogbInViewSet)
|
||||
router.register('mlogb/out', MlogbOutViewSet)
|
||||
router.register('mlogb', MlogbViewSet, basename='mlogb')
|
||||
router.register('mlogb/in', MlogbInViewSet, basename='mlogb_in')
|
||||
router.register('mlogb/out', MlogbOutViewSet, basename='mlogb_out')
|
||||
router.register('handover', HandoverViewSet, basename='handover')
|
||||
router.register('attlog', AttlogViewSet, basename='attlog')
|
||||
router.register('otherlog', OtherLogViewSet, basename='otherlog')
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ from .serializers import (
|
|||
MlogQuickSerializer,
|
||||
MlogbwStartTestSerializer,
|
||||
HandoverListSerializer,
|
||||
BatchChangeSerializer
|
||||
BatchChangeSerializer,
|
||||
MlogbOutPatchUpdateSerializer
|
||||
)
|
||||
from .services import mlog_submit, handover_submit, mlog_revert, get_batch_dag, handover_revert
|
||||
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
||||
|
|
@ -446,9 +447,9 @@ class MlogViewSet(CustomModelViewSet):
|
|||
raise ParseError("该日志存在审批!")
|
||||
user = request.user
|
||||
if ins.submit_time is None:
|
||||
raise ParseError("日志未提交不可撤销")
|
||||
raise ParseError("日志未提交不可撤回")
|
||||
if user != ins.submit_user:
|
||||
raise ParseError("非提交人不可撤销!")
|
||||
raise ParseError("非提交人不可撤回!")
|
||||
now = timezone.now()
|
||||
mlog_revert(ins, user, now)
|
||||
return Response(MlogSerializer(instance=ins).data)
|
||||
|
|
@ -836,7 +837,7 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode
|
|||
"batch": mlogbin.batch,
|
||||
"batch_ofrom": wm_in.batch_ofrom,
|
||||
"material_ofrom": wm_in.material_ofrom,
|
||||
"qct": Qct.get(material_out, "process", "out"),
|
||||
"qct": Qct.get(material_out, "fix" if mlog.is_fix else "process", "out"),
|
||||
}
|
||||
if mtype == Process.PRO_DIV and material_in.tracking == Material.MA_TRACKING_SINGLE:
|
||||
pass
|
||||
|
|
@ -1020,11 +1021,15 @@ class MlogbInViewSet(BulkCreateModelMixin, BulkUpdateModelMixin, BulkDestroyMode
|
|||
|
||||
|
||||
class MlogbOutViewSet(BulkUpdateModelMixin, CustomGenericViewSet):
|
||||
perms_map = {"put": "mlog.update"}
|
||||
perms_map = {"put": "mlog.update", "patch": "mlog.update"}
|
||||
queryset = Mlogb.objects.filter(material_out__isnull=False)
|
||||
serializer_class = MlogbOutUpdateSerializer
|
||||
partial_update_serializer_class = MlogbOutPatchUpdateSerializer
|
||||
|
||||
def perform_update(self, serializer):
|
||||
if self.request.method == "PATCH":
|
||||
serializer.save()
|
||||
else:
|
||||
ins: Mlogb = serializer.instance
|
||||
mlog = MlogViewSet.lock_and_check_can_update(ins.mlog)
|
||||
material_out = serializer.validated_data.get("material_out")
|
||||
|
|
@ -1072,6 +1077,16 @@ class BatchStViewSet(CustomListModelMixin, ComplexQueryMixin, CustomGenericViewS
|
|||
ordering = ["batch"]
|
||||
filterset_class = BatchStFilter
|
||||
|
||||
def add_info_for_list(self, data):
|
||||
if (self.request.query_params.get("with_source_near", None) == "yes" or
|
||||
self.request.data.get("with_source_near", None) == "yes"):
|
||||
batchstIds = [ins["id"] for ins in data]
|
||||
batchlog_qs = BatchLog.objects.filter(target__id__in=batchstIds).values("id", "source", "target")
|
||||
source_data = BatchStSerializer(instance=BatchSt.objects.filter(id__in=[ins["source"] for ins in batchlog_qs]), many=True).data
|
||||
source_data_dict = {ins["id"]: ins for ins in source_data}
|
||||
for item in data:
|
||||
item["source_near"] = [source_data_dict[ins["source"]] for ins in batchlog_qs if ins["target"] == item["id"]]
|
||||
return data
|
||||
|
||||
class MlogbwViewSet(CustomModelViewSet):
|
||||
perms_map = {"get": "*", "post": "mlog.update", "put": "mlog.update", "delete": "mlog.update", "patch": "mlog.update"}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ class WprFilter(filters.FilterSet):
|
|||
"mb": ["exact", "isnull"],
|
||||
"wm": ["exact", "isnull"],
|
||||
"material__process": ["exact"],
|
||||
"material__name": ["exact", "contains"],
|
||||
"wpr_from": ["exact", "isnull"],
|
||||
"state": ["exact"],
|
||||
"defects": ["exact"],
|
||||
"number": ["exact"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework.decorators import action
|
||||
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
|
||||
from apps.utils.mixins import CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin
|
||||
from apps.utils.mixins import CustomListModelMixin, CustomRetrieveModelMixin, ComplexQueryMixin
|
||||
|
||||
from apps.wpmw.models import Wpr, WprDefect
|
||||
from apps.wpmw.serializers import WprSerializer, WprNewSerializer, WprDetailSerializer, WproutListSerializer, WprChangeNumberSerializer
|
||||
|
|
@ -13,7 +13,7 @@ from apps.utils.sql import query_one_dict
|
|||
from django.db.models.expressions import RawSQL
|
||||
|
||||
|
||||
class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet):
|
||||
class WprViewSet(CustomListModelMixin, CustomRetrieveModelMixin, ComplexQueryMixin, CustomGenericViewSet):
|
||||
"""动态产品
|
||||
|
||||
动态产品
|
||||
|
|
@ -30,10 +30,20 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
|
|||
ordering_fields = ["number", "create_time", "update_time"]
|
||||
search_fields = ["number", "material__name", "material__model", "material__specification", "number_out"]
|
||||
annotate_dict = {
|
||||
"number_prefix": RawSQL("regexp_replace(number, '(\\d+)$', '')", []),
|
||||
"number_suffix": RawSQL("COALESCE(NULLIF(regexp_replace(number, '.*?(\\d+)$', '\\1'), ''), '0')::bigint", []),
|
||||
"number_prefix": RawSQL("regexp_replace(wpmw_wpr.number, '(\\d+)$', '')", []),
|
||||
"number_suffix": RawSQL("COALESCE(NULLIF(regexp_replace(wpmw_wpr.number, '.*?(\\d+)$', '\\1'), ''), '0')::bigint", []),
|
||||
}
|
||||
|
||||
def add_info_for_list(self, data):
|
||||
parent_ids = [item["wpr_from"] for item in data if item.get("wpr_from", False)]
|
||||
if parent_ids:
|
||||
parent_data = Wpr.objects.filter(id__in=parent_ids).values("id", "number", "data")
|
||||
parent_map = {item["id"]: item for item in parent_data}
|
||||
for item in data:
|
||||
if item["wpr_from"]:
|
||||
item["wpr_from_"] = parent_map[item["wpr_from"]]
|
||||
return data
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
qs = super().filter_queryset(queryset)
|
||||
if "mb__isnull" in self.request.query_params or "wm__isnull" in self.request.query_params:
|
||||
|
|
@ -92,11 +102,20 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
|
|||
# 使用原始sql
|
||||
query = """
|
||||
SELECT id, number_out FROM wpmw_wpr
|
||||
WHERE number_out ~ %s order by number_out desc limit 1
|
||||
WHERE number_out LIKE %s
|
||||
AND translate(
|
||||
substring(number_out FROM LENGTH(%s) + 2),
|
||||
'0123456789',
|
||||
''
|
||||
) = ''
|
||||
order by number_out desc limit 1
|
||||
"""
|
||||
pattern = f"^{prefix}[0-9]+$"
|
||||
params = (
|
||||
f"{prefix}-%",
|
||||
prefix
|
||||
)
|
||||
number_outs = []
|
||||
wpr_qs_last = query_one_dict(query, [pattern])
|
||||
wpr_qs_last = query_one_dict(query, [params])
|
||||
if wpr_qs_last:
|
||||
number_outs.append(wpr_qs_last["number_out"])
|
||||
# 查找未出库的记录
|
||||
|
|
@ -106,9 +125,15 @@ class WprViewSet(CustomListModelMixin, RetrieveModelMixin, ComplexQueryMixin, Cu
|
|||
query2 = """
|
||||
select mioitemw.id, mioitemw.number_out from inm_mioitemw mioitemw left join inm_mioitem mioitem on mioitem.id = mioitemw.mioitem_id
|
||||
left join inm_mio mio on mio.id = mioitem.mio_id
|
||||
where mio.submit_time is null and mioitemw.number_out ~ %s order by mioitemw.number_out desc limit 1
|
||||
where mio.submit_time is null and mioitemw.number_out LIKE %s
|
||||
AND translate(
|
||||
substring(mioitemw.number_out FROM LENGTH(%s) + 2),
|
||||
'0123456789',
|
||||
''
|
||||
) = ''
|
||||
order by mioitemw.number_out desc limit 1
|
||||
"""
|
||||
mioitemw_last = query_one_dict(query2, [pattern])
|
||||
mioitemw_last = query_one_dict(query2, [params])
|
||||
if mioitemw_last:
|
||||
number_outs.append(mioitemw_last["number_out"])
|
||||
if number_outs:
|
||||
|
|
|
|||
20
changelog.md
20
changelog.md
|
|
@ -1,3 +1,23 @@
|
|||
## 3.0.2026010716
|
||||
- feat: 新增功能
|
||||
- get_shift需要报错 [caoqianming]
|
||||
- 添加rem模块 [caoqianming]
|
||||
- 捕获除0异常 [caoqianming]
|
||||
- 按需求修改光芯批次统计分析 [caoqianming]
|
||||
- 批次统计数据支持返回source_near [caoqianming]
|
||||
- 光芯OA 审批系统新增报价单审核 [TianyangZhang]
|
||||
- get_batch_dag还是只返回直接前后级别 [caoqianming]
|
||||
- mlogbbpatch修改批次号 [caoqianming]
|
||||
- 出入库记录返回子表部分信息 [caoqianming]
|
||||
- 统一撤回和撤销的表述 [caoqianming]
|
||||
- 车间库存检验支持撤回 [caoqianming]
|
||||
- wpr添加material_name查询条件 [caoqianming]
|
||||
- 优化mlog_submit 返工后产品放在本工段下 [caoqianming]
|
||||
- mlogbin qct 可依据fix选择 [caoqianming]
|
||||
- base dept filter支持parent isnull查询 [caoqianming]
|
||||
- fix: 问题修复
|
||||
- wpr list annotate明确number指向 [caoqianming]
|
||||
- 正常交接支持new_wm且支持不合格品 [caoqianming]
|
||||
## 3.0.2025122514
|
||||
- feat: 新增功能
|
||||
- mlogbw patch权限 [caoqianming]
|
||||
|
|
|
|||
101
requirements.txt
101
requirements.txt
|
|
@ -1,37 +1,82 @@
|
|||
celery==5.2.3
|
||||
Django==3.2.12
|
||||
django-celery-beat==2.3.0
|
||||
django-celery-results==2.4.0
|
||||
django-cors-headers==3.11.0
|
||||
django-filter==21.1
|
||||
djangorestframework==3.13.1
|
||||
djangorestframework-simplejwt==5.1.0
|
||||
drf-yasg==1.21.7
|
||||
psutil==5.9.0
|
||||
pillow==9.0.1
|
||||
opencv-python==4.5.5.62
|
||||
redis==4.4.0
|
||||
django-redis==5.2.0
|
||||
user-agents==2.2.0
|
||||
daphne==4.0.0
|
||||
channels-redis==4.0.0
|
||||
# =======================
|
||||
# Core
|
||||
# =======================
|
||||
Django==4.2.27
|
||||
|
||||
djangorestframework==3.16.1
|
||||
django-filter==23.5
|
||||
django-cors-headers==4.9.0
|
||||
|
||||
djangorestframework-simplejwt==5.5.1
|
||||
django-restql==0.15.2
|
||||
|
||||
# =======================
|
||||
# Celery
|
||||
# =======================
|
||||
celery==5.6.2
|
||||
django-celery-beat==2.8.1
|
||||
django-celery-results==2.6.0
|
||||
redis==7.1.0
|
||||
django-redis==6.0.0
|
||||
cron-descriptor==1.2.35
|
||||
|
||||
# =======================
|
||||
# Channels / ASGI
|
||||
# =======================
|
||||
channels==4.3.2
|
||||
daphne==4.0.0
|
||||
channels-redis==4.3.0
|
||||
|
||||
# =======================
|
||||
# API Docs
|
||||
# =======================
|
||||
drf-yasg==1.21.7
|
||||
|
||||
# =======================
|
||||
# Auth / Utils
|
||||
# =======================
|
||||
user-agents==2.2.0
|
||||
psutil==5.9.0
|
||||
|
||||
# =======================
|
||||
# Media / Image / CV
|
||||
# =======================
|
||||
pillow==9.5.0
|
||||
opencv-python==4.5.5.62
|
||||
shapely==1.8.3
|
||||
aliyun-python-sdk-core==2.13.36
|
||||
baidu-aip==4.16.6
|
||||
chardet==5.0.0
|
||||
requests==2.28.1
|
||||
|
||||
# =======================
|
||||
# Network / RPC
|
||||
# =======================
|
||||
requests==2.32.5
|
||||
grpcio==1.47.0
|
||||
grpcio-tools==1.47.0
|
||||
protobuf==3.20.1
|
||||
pycryptodome==3.15.0
|
||||
|
||||
# =======================
|
||||
# Cloud SDK
|
||||
# =======================
|
||||
aliyun-python-sdk-core==2.13.36
|
||||
baidu-aip==4.16.6
|
||||
|
||||
# =======================
|
||||
# Crypto
|
||||
# =======================
|
||||
pycryptodome==3.15.0
|
||||
|
||||
# =======================
|
||||
# Excel / Docs
|
||||
# =======================
|
||||
xlwt==1.3.0
|
||||
openpyxl==3.1.0
|
||||
cron-descriptor==1.2.35
|
||||
pymysql==1.0.3
|
||||
# face-recognition==1.3.0
|
||||
openpyxl==3.1.5
|
||||
docxtpl==0.16.7
|
||||
|
||||
# =======================
|
||||
# DB
|
||||
# =======================
|
||||
pymysql==1.0.3
|
||||
|
||||
# =======================
|
||||
# IoT / MQTT
|
||||
# =======================
|
||||
paho-mqtt==2.0.0
|
||||
# deepface==0.0.79
|
||||
# edge-tts==6.1.12
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
|
|||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
django.setup()
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
from apps.utils.middlewares import TokenAuthMiddleware
|
||||
import apps.ws.routing
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings')
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
"http": get_asgi_application(),
|
||||
"websocket": TokenAuthMiddleware(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
|
|||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
SYS_NAME = '星途工厂综合管理系统'
|
||||
SYS_VERSION = '3.0.2025122514'
|
||||
SYS_VERSION = '3.0.2026010716'
|
||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||
|
||||
# Application definition
|
||||
|
|
@ -88,6 +88,7 @@ INSTALLED_APPS = [
|
|||
'apps.ofm',
|
||||
'apps.srm',
|
||||
'apps.asm',
|
||||
'apps.rem'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ urlpatterns = [
|
|||
path('', include('apps.ofm.urls')),
|
||||
path('', include('apps.srm.urls')),
|
||||
path('', include('apps.asm.urls')),
|
||||
path('', include('apps.rem.urls')),
|
||||
|
||||
# 前端页面入口
|
||||
path('', TemplateView.as_view(template_name="index.html")),
|
||||
|
|
|
|||
Loading…
Reference in New Issue