Compare commits

...

50 Commits

Author SHA1 Message Date
caoqianming db22300fd0 feat: mioitem添加排序字段 2025-05-06 10:54:31 +08:00
caoqianming 962ca5b1be feat: 完善光子批次统计数据 2025-04-30 08:51:00 +08:00
caoqianming 283c566b71 fix: getattr(item, field) is not None 2025-04-29 15:14:20 +08:00
caoqianming d6504fa072 feat: 组合件和入库检验追加统计分析 2025-04-29 14:45:30 +08:00
caoqianming 5857b77aea feat: get_alldata_with_batch添加内容 2025-04-29 14:43:34 +08:00
caoqianming dd0d6751c1 fix: 生产入库时存入生产车间 2025-04-25 13:20:16 +08:00
caoqianming a13ac84ccb fix: do_in保证production_dept赋值 2025-04-24 16:40:48 +08:00
caoqianming 21e9a49069 fix: 完善负数校验2 2025-04-24 16:16:00 +08:00
caoqianming d815d84333 fix: 完善负数校验 2025-04-24 16:00:09 +08:00
caoqianming 0a5cbca4ed feat: 6车间合格率统计decimal invalid 2025-04-24 13:15:14 +08:00
caoqianming ee7908f6bc feat: 通过django settings延迟获取BASE_PROJECT_CODE 2025-04-24 13:14:53 +08:00
caoqianming f4bb1d952f feat: base 将配置文件放到单独的config文件夹中防止误操作 2025-04-24 13:14:43 +08:00
caoqianming f501f2a6ea fix: count_working获取逻辑优化2 2025-04-24 13:14:19 +08:00
caoqianming e47c853578 fix: count_working获取逻辑优化 2025-04-24 13:14:06 +08:00
caoqianming 9362a81bc6 feat: 日志完善负值校验 2025-04-24 13:13:39 +08:00
caoqianming 5498d98e38 feat: base增加PositiveDecimalField 2025-04-24 13:11:54 +08:00
caoqianming a31aa7e337 feat: update_material_count时更新组合件数量 2025-04-24 13:11:28 +08:00
caoqianming a97804e455 fix: 订单检索条件错误 2025-04-24 13:08:25 +08:00
caoqianming 2f9e054558 feat: 工艺步骤增加排序字段 2025-04-24 13:06:54 +08:00
caoqianming 29e15f0f8e feat: 物料筛选low_inm进行优化 2025-04-24 13:06:22 +08:00
caoqianming cafa1c9c87 fix: mio_saleout时正确计算delivered_count 2025-04-24 13:06:12 +08:00
caoqianming bf368bcd26 feat: 变更order状态 2025-04-24 13:05:57 +08:00
caoqianming b9372204a6 feat: update_inm关于销售发货/其他出库的bug 2025-04-24 13:04:48 +08:00
caoqianming 9ad947770a feat: fmlog添加is_fix字段校验 2025-04-24 13:04:35 +08:00
caoqianming 1623d2d684 feat: fmlog添加is_fix字段 2025-04-24 13:03:56 +08:00
caoqianming 4a5f6d9d0e fix: get_alldata_with_batch中小数计算异常捕获 2025-04-24 13:02:54 +08:00
caoqianming b948688ab9 feat: ptest性能检验样品编号字段更改为text 2025-04-24 13:02:42 +08:00
caoqianming 91a499c00b fix: 光子综合查询对小数和None的处理 2025-04-24 13:02:17 +08:00
caoqianming 3b78c4e993 fix: decimal存入json字段时使用myjsondecoder 2025-04-24 13:02:02 +08:00
caoqianming c8ce78f50d feat: 添加MyJSONEncoder以支持decimal 2025-04-24 13:01:49 +08:00
caoqianming b5514afa2b fix: get_alldata_with_batch_and_store时保存json数据采用DjangoJSONEncoder以处理decimal 2025-04-24 13:01:31 +08:00
caoqianming 04daccb733 fix: 组合件入库后未添加count的bug 2025-04-24 13:01:21 +08:00
caoqianming e8cd841ef1 feat: 销售发货编号非必填 2025-04-24 13:01:10 +08:00
caoqianming 2831c9b58b feat: HTML_BASE_URL 前缀统一修改 2025-04-24 13:00:43 +08:00
caoqianming 3175bcf4dc feat: sysbaseview返回系统版本号 2025-04-24 13:00:15 +08:00
caoqianming 5354c064a1 fix: base log delay=True减少冲突 2025-04-24 12:59:59 +08:00
caoqianming 6e9b2c8264 change: base 改变log backupcount为30 2025-04-24 12:59:44 +08:00
caoqianming 6aae02044f perf: base settings里日志记录handler优化 2025-04-24 12:59:30 +08:00
caoqianming 4bccc5a62b feat: 完善版本追踪 2025-04-24 12:59:13 +08:00
caoqianming da5400996f feat: utask编号可不填2 2025-04-24 11:35:10 +08:00
caoqianming 0b20199284 feat: utask编号可不填 2025-04-24 11:34:59 +08:00
caoqianming 88bc901c84 feat: mio 编号可不填 2025-04-24 11:34:11 +08:00
caoqianming 87b935ab04 feat: MIODo 可不填belong_dept 2025-04-24 11:33:14 +08:00
caoqianming c3108641f3 feat: mio number不设置为只读 2025-04-24 11:33:03 +08:00
caoqianming 7b1a6853ab fix: wmaterial search bug 2025-04-24 11:32:46 +08:00
caoqianming 5ae9e235df fix: wmaterial search bug3 2025-04-24 11:32:37 +08:00
caoqianming b327da2342 fix: wmaterial search bug2 2025-04-24 11:32:25 +08:00
caoqianming a581d40ef9 fix: wmaterial search bug 2025-04-24 11:31:56 +08:00
caoqianming 3a263735b0 feat: mio utask number非必填 2025-04-24 11:31:14 +08:00
caoqianming 4cc8236bcb fix: wmaterial 去除 material__process__exclude 字段 2025-04-24 11:30:33 +08:00
53 changed files with 405 additions and 118 deletions

2
.gitignore vendored
View File

@ -20,6 +20,8 @@ db.sqlite3
server/conf*.py
server/conf.ini
server/conf*.json
config/conf*.py
config/conf*.json
sh/*
temp/*
nohup.out

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/am/'
HTML_BASE_URL = 'am/'
HTML_BASE_URL = 'dhtml/am/'
router = DefaultRouter()
router.register('area', AreaViewSet, basename='area')

View File

@ -4,8 +4,8 @@ from django.utils import timezone
from apps.bi.models import Dataset, DatasetRecord
from apps.bi.services import exec_dataset
import json
from apps.utils.tools import MyJSONEncoder
@shared_task()
def exec_dataset_and_store(code: str, query: str = ''):
dt = Dataset.objects.get(code=code)
@ -16,4 +16,5 @@ def exec_dataset_and_store(code: str, query: str = ''):
if query:
squery = json.loads(query)
dtr.full_sql, dtr.result = exec_dataset(dt, squery)
dtr.result = json.loads(json.dumps(dtr.result, cls=MyJSONEncoder))
dtr.save()

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.bi.views import DatasetViewSet, DatasetRecordViewSet
API_BASE_URL = 'api/bi/'
HTML_BASE_URL = 'bi/'
HTML_BASE_URL = 'dhtml/bi/'
router = DefaultRouter()
router.register('dataset', DatasetViewSet, basename='dataset')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.cm.views import LableMatViewSet
API_BASE_URL = 'api/cm/'
HTML_BASE_URL = 'cm/'
HTML_BASE_URL = 'dhtml/cm/'
router = DefaultRouter()
router.register('labelmat', LableMatViewSet, basename='labelmat')

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/cms/'
HTML_BASE_URL = 'cms/'
HTML_BASE_URL = 'dhtml/cms/'
router = DefaultRouter()
router.register('article', ArticleViewSet, basename='article')

View File

@ -3,7 +3,7 @@ from apps.develop.views import BackupDatabase, BackupMedia, ReloadClientGit, Rel
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/develop/'
HTML_BASE_URL = 'develop/'
HTML_BASE_URL = 'dhtml/develop/'
router = DefaultRouter()
router.register('test', TestViewSet, basename='api_test')
router.register('correct', CorrectViewSet, basename='correct')

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/dpm/'
HTML_BASE_URL = 'dpm/'
HTML_BASE_URL = 'dhtml/dpm/'
# cache_areas_info()
router = DefaultRouter()
router.register('riskpoint', RiskPointViewSet, basename='riskpoint')

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/ecm/'
HTML_BASE_URL = 'ecm/'
HTML_BASE_URL = 'dhtml/ecm/'
router = DefaultRouter()
router.register('event_cate', EventCateViewSet, basename='event_cate')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.edu.views import QuestioncatViewSet, QuestionViewSet, PaperViewSet, ExamViewSet, ExamRecordViewSet, TrainRecordViewSet
API_BASE_URL = 'api/edu/'
HTML_BASE_URL = 'edu/'
HTML_BASE_URL = 'dhtml/edu/'
router = DefaultRouter()
router.register('questioncat', QuestioncatViewSet, basename='questioncat')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView
API_BASE_URL = 'api/em/'
HTML_BASE_URL = 'em/'
HTML_BASE_URL = 'dhtml/em/'
router = DefaultRouter()
router.register('equipment', EquipmentViewSet, basename='equipment')

View File

@ -4,7 +4,7 @@ from apps.enm.views import (MpointViewSet, MpLogxViewSet, MpointStatViewSet,
EnStatViewSet, EnStat2ViewSet, XscriptViewSet)
API_BASE_URL = 'api/enm/'
HTML_BASE_URL = 'enm/'
HTML_BASE_URL = 'dhtml/enm/'
router = DefaultRouter()
router.register('mpoint', MpointViewSet, basename='mpoint')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from .views import DrainViewSet, DrainEquipViewSet, VehicleAccessViewSet, EnvDataViewSet, CarWashViewSet
API_BASE_URL = 'api/enp/'
HTML_BASE_URL = 'enp/'
HTML_BASE_URL = 'dhtml/enp/'
router = DefaultRouter()
router.register('drain', DrainViewSet, basename='drain')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.fim.views import (PriceSetViewSet, FeeSetViewSet, FeeViewSet)
API_BASE_URL = 'api/fim/'
HTML_BASE_URL = 'fim/'
HTML_BASE_URL = 'dhtml/fim/'
router = DefaultRouter()
router.register('priceset', PriceSetViewSet, basename='priceset')

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/hrm/'
HTML_BASE_URL = 'hrm/'
HTML_BASE_URL = 'dhtml/hrm/'
router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee')

View File

@ -226,7 +226,7 @@ class MIOItemSerializer(CustomModelSerializer):
class MIODoSerializer(CustomModelSerializer):
belong_dept = serializers.PrimaryKeyRelatedField(
label="车间", queryset=Dept.objects.all(), required=True)
label="车间", queryset=Dept.objects.all(), required=False)
do_user = serializers.PrimaryKeyRelatedField(
label="执行人", queryset=User.objects.all(), required=True)
note = serializers.CharField(
@ -236,17 +236,18 @@ class MIODoSerializer(CustomModelSerializer):
model = MIO
fields = ['id', 'number', 'note', 'do_user',
'belong_dept', 'type', 'inout_date', 'mgroup', 'mio_user']
extra_kwargs = {'inout_date': {'required': True},
'do_user': {'required': True}, 'belong_dept': {'required': False}}
read_only_fields = ["number"]
extra_kwargs = {'inout_date': {'required': True}, 'do_user': {'required': True}, 'number': {"required": False, "allow_blank": True}}
def validate(self, attrs):
if 'mgroup' in attrs and attrs['mgroup']:
attrs['belong_dept'] = attrs['mgroup'].belong_dept
if not attrs.get("belong_dept", None):
raise ParseError('请选择车间或工段')
return attrs
def create(self, validated_data):
validated_data["number"] = MIO.get_a_number(validated_data["type"])
if not validated_data.get("number", None):
validated_data["number"] = MIO.get_a_number(validated_data["type"])
if validated_data['type'] not in [MIO.MIO_TYPE_DO_OUT, MIO.MIO_TYPE_DO_IN]:
raise ValidationError('出入库类型错误')
return super().create(validated_data)
@ -265,11 +266,13 @@ class MIOSaleSerializer(CustomModelSerializer):
class Meta:
model = MIO
fields = ['id', 'number', 'note', 'order', 'inout_date', 'customer', 'mio_user']
extra_kwargs = {'inout_date': {'required': True}}
extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}}
def create(self, validated_data):
validated_data['type'] = MIO.MIO_TYPE_SALE_OUT
order: Order = validated_data.get('order', None)
if not validated_data.get("number", None):
validated_data["number"] = MIO.get_a_number(validated_data["type"])
if order:
if order.state in [Order.ORDER_CREATE, Order.ORDER_DELIVERED]:
raise ValidationError('销售订单状态错误')
@ -292,12 +295,12 @@ class MIOPurSerializer(CustomModelSerializer):
class Meta:
model = MIO
fields = ['id', 'number', 'note', 'pu_order', 'inout_date', 'supplier', 'mio_user']
extra_kwargs = {'inout_date': {'required': True}}
read_only_fields = ["number"]
extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}}
def create(self, validated_data):
validated_data['type'] = MIO.MIO_TYPE_PUR_IN
validated_data['number'] = MIO.get_a_number(validated_data["type"])
if not validated_data.get("number", None):
validated_data["number"] = MIO.get_a_number(validated_data["type"])
pu_order: PuOrder = validated_data.get('pu_order', None)
if pu_order:
if pu_order.state in [PuOrder.PUORDER_CREATE, PuOrder.PUORDER_DONE]:
@ -320,11 +323,11 @@ class MIOOtherSerializer(CustomModelSerializer):
model = MIO
fields = ['id', 'number', 'note', 'supplier',
'customer', 'type', 'inout_date', 'mio_user']
extra_kwargs = {'inout_date': {'required': True}}
read_only_fields = ["number"]
extra_kwargs = {'inout_date': {'required': True}, 'number': {"required": False, "allow_blank": True}}
def create(self, validated_data):
validated_data['number'] = MIO.get_a_number(validated_data["type"])
if not validated_data.get("number", None):
validated_data["number"] = MIO.get_a_number(validated_data["type"])
if validated_data['type'] not in [MIO.MIO_TYPE_OTHER_OUT, MIO.MIO_TYPE_OTHER_IN]:
raise ValidationError('出入库类型错误')
return super().create(validated_data)

View File

@ -58,6 +58,8 @@ def do_out(item: MIOItem):
raise ParseError("组合件暂不支持追踪单件")
xbatches = []
if is_zhj:
xbatches = [item.batch]
for al in action_list:
xmaterial:Material = al[0]
xbatch:str = al[1]
@ -138,6 +140,8 @@ def do_in(item: MIOItem):
production_dept = None
xbatchs = []
if is_zhj:
xbatchs = [item.batch]
for al in action_list:
xmaterial, xbatch, xcount = al
xbatchs.append(xbatch)
@ -165,7 +169,7 @@ def do_in(item: MIOItem):
else:
raise ParseError(f'{str(xmaterial)}-{xbatch}车间物料不足')
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else None
wm_production_dept = wm.mgroup.belong_dept if wm.mgroup else wm.belong_dept
if production_dept is None:
production_dept = wm_production_dept
elif wm_production_dept and production_dept != wm_production_dept:
@ -185,6 +189,8 @@ def do_in(item: MIOItem):
"production_dept": production_dept
}
)
if mb.production_dept is None:
mb.production_dept = production_dept
mb.count = mb.count + xcount
mb.save()
@ -206,6 +212,10 @@ def do_in(item: MIOItem):
)
if not is_created:
raise ParseError("该批次组合件已存在")
if mb.production_dept is None:
mb.production_dept = production_dept
mb.count = mb.count + item.count
mb.save()
for mia in mias:
MaterialBatchA.objects.create(mb=mb, material=mia.material, batch=mia.batch, rate=mia.rate)
@ -222,8 +232,9 @@ class InmService:
更新物料数量
"""
# 统计物料数量
m_ids = MIOItem.objects.filter(mio=instance).values_list('material_id', flat=True)
cal_material_count(m_ids)
m_ids = list(MIOItem.objects.filter(mio=instance).values_list('material_id', flat=True))
m_ids2 = list(MIOItemA.objects.filter(mioitem__mio=instance).values_list('material_id', flat=True))
cal_material_count(m_ids+m_ids2)
@classmethod
def update_inm(cls, instance: MIO, is_reverse: bool = False):
@ -251,10 +262,16 @@ class InmService:
do_in(item)
elif instance.type == MIO.MIO_TYPE_SALE_OUT:
from apps.sam.services import SamService
cls.update_mb(instance, in_or_out)
if is_reverse:
cls.update_mb(instance, 1)
else:
cls.update_mb(instance, -1)
SamService.mio_saleout(instance, is_reverse)
elif instance.type == MIO.MIO_TYPE_OTHER_OUT:
cls.update_mb(instance, in_or_out)
if is_reverse:
cls.update_mb(instance, 1)
else:
cls.update_mb(instance, -1)
elif instance.type == MIO.MIO_TYPE_DO_OUT:
mioitems = MIOItem.objects.filter(mio=instance)
if is_reverse:
@ -319,7 +336,10 @@ class InmService:
if xcount > 0:
defect = defects_map[defect_id]
m_list.append((material, warehouse, i.batch, xcount, defect, i))
xbatchs = []
for material, warehouse, batch, change_count, defect, mioitem in m_list:
xbatchs.append(batch)
if change_count <= 0:
continue
state = WMaterial.WM_OK
@ -370,6 +390,10 @@ class InmService:
else:
raise ParseError("不支持的操作")
# 批次统计分析
xbatchs = list(set(xbatchs))
for xbatch in xbatchs:
MyThread(target=get_alldata_with_batch_and_store, args=(xbatch,)).start()
def daoru_mb(path: str):
"""

View File

@ -6,7 +6,7 @@ from apps.inm.views import (
MioPurViewSet, MioOtherViewSet, MIOItemwViewSet)
API_BASE_URL = 'api/inm/'
HTML_BASE_URL = 'inm/'
HTML_BASE_URL = 'dhtml/inm/'
router = DefaultRouter()
router.register('warehouse', WarehouseVIewSet, basename='warehouse')

View File

@ -254,6 +254,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
"test_date": ["isnull", "exact"]
}
ordering = ['create_time']
ordering_fields = ['create_time', 'test_date']
def add_info_for_list(self, data):
@ -286,6 +287,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
sr.save()
# 开始变动库存
InmService.update_mb_item(ins, -1, 'count_notok')
InmService.update_material_count(ins.mio)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=serializers.Serializer)
@ -303,6 +305,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
pass
ins.test_date = None
ins.save()
InmService.update_material_count(ins.mio)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemPurInTestSerializer)
@ -320,6 +323,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
sr = MIOItemPurInTestSerializer(instance=ins, data=request.data)
sr.is_valid(raise_exception=True)
sr.save()
InmService.update_material_count(ins.mio)
return Response()
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MioItemAnaSerializer)

View File

@ -2,7 +2,7 @@ from django.urls import path
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView, AuditlogViewSet, CeleryInfoView, RedisInfoView
API_BASE_URL = 'api/monitor/'
HTML_BASE_URL = 'monitor/'
HTML_BASE_URL = 'dhtml/monitor/'
urlpatterns = [
path(HTML_BASE_URL, index),
path(HTML_BASE_URL + 'index/', index),

View File

@ -4,7 +4,7 @@ from django.db.models.expressions import F
class MaterialFilter(filters.FilterSet):
tag = filters.CharFilter(method='filter_tag')
tag = filters.CharFilter(method='filter_tag', label="low_inm:库存不足")
class Meta:
model = Material
@ -27,7 +27,7 @@ class MaterialFilter(filters.FilterSet):
def filter_tag(self, queryset, name, value):
if value == 'low_inm':
queryset = queryset.exclude(count_safe=None).filter(
queryset = queryset.exclude(count_safe=None).exclude(count_safe__lte=0).filter(
count__lte=F('count_safe'))
return queryset

View File

@ -6,7 +6,7 @@ from apps.mtm.views import (MgroupViewSet, ShiftViewSet, TeamViewSet, MaterialVi
RoutePackViewSet, SruleViewSet, RouteMatViewSet)
API_BASE_URL = 'api/mtm/'
HTML_BASE_URL = 'mtm/'
HTML_BASE_URL = 'dhtml/mtm/'
router = DefaultRouter()
router.register('mgroup', MgroupViewSet, basename='mgroup')

View File

@ -265,6 +265,7 @@ class RouteViewSet(CustomModelViewSet):
serializer_class = RouteSerializer
filterset_class = RouteFilter
ordering = ['sort', 'process__sort', 'create_time']
ordering_fields = ['sort', 'process__sort', 'create_time', 'update_time']
select_related_fields = ['material',
'process', 'material_in', 'material_out', 'mgroup', 'routepack']

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.opm.views import GasCheckViewSet, OperationViewSet, OplCateViewSet, OplViewSet, OplWorkerViewSet
API_BASE_URL = 'api/opm/'
HTML_BASE_URL = 'opm/'
HTML_BASE_URL = 'dhtml/opm/'
router = DefaultRouter()
router.register('opl_cate', OplCateViewSet, basename='opl_cate')

View File

@ -24,10 +24,13 @@ class UtaskSerializer(CustomModelSerializer):
class Meta:
model = Utask
fields = '__all__'
read_only_fields = ["number"]
extra_kwargs = {
'number': {"required": False, "allow_blank": True}
}
def create(self, validated_data):
validated_data["number"] = Utask.get_a_number()
if not validated_data.get('number', None):
validated_data["number"] = Utask.get_a_number()
return super().create(validated_data)
def validate(self, attrs):

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.pm.views import (MtaskViewSet, UtaskViewSet, MtaskbViewSet)
API_BASE_URL = 'api/pm/'
HTML_BASE_URL = 'pm/'
HTML_BASE_URL = 'dhtml/pm/'
router = DefaultRouter()
router.register('mtask', MtaskViewSet, basename='mtask')

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet)
API_BASE_URL = 'api/pum/'
HTML_BASE_URL = 'pum/'
HTML_BASE_URL = 'dhtml/pum/'
router = DefaultRouter()
router.register('supplier', SupplierViewSet, basename='supplier')

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-04-11 00:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0048_auto_20250318_1342'),
]
operations = [
migrations.AlterField(
model_name='ptest',
name='sample_number',
field=models.TextField(verbose_name='样品编号'),
),
]

View File

@ -410,7 +410,7 @@ class Ptest(CommonAModel):
User, verbose_name='检验员', on_delete=models.CASCADE)
testitem = models.ForeignKey(
TestItem, verbose_name='测试项目', on_delete=models.CASCADE, null=True, blank=True)
sample_number = models.CharField('样品编号', max_length=20)
sample_number = models.TextField('样品编号')
sample_count = models.PositiveIntegerField('样品数量', null=True, blank=True)
sample_density = models.FloatField('样品密度', null=True, blank=True)
specification = models.CharField(

View File

@ -6,7 +6,7 @@ from apps.qm.views import (QuaStatViewSet, TestItemViewSet,
NotOkOptionView, DefectViewSet, QctViewSet, QctTestItemViewSet, QctDefectViewSet, QctMatViewSet)
API_BASE_URL = 'api/qm/'
HTML_BASE_URL = 'qm/'
HTML_BASE_URL = 'dhtml/qm/'
router = DefaultRouter()
router.register('quastat', QuaStatViewSet, basename='quastat')

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/rpm/'
HTML_BASE_URL = 'rpm/'
HTML_BASE_URL = 'dhtml/rpm/'
router = DefaultRouter()
router.register('rparty', RpartyViewSet, basename='rparty')

View File

@ -13,21 +13,25 @@ class SamService:
order = mio.order
if order is None:
return
for i in MIOItem.objects.filter(mio=mio):
orderitem = OrderItem.objects.get(order=order, material=i.material)
if is_reverse:
delivered_count = orderitem.delivered_count - i.count
else:
delivered_count = orderitem.delivered_count + i.count
orderitem_qs = OrderItem.objects.filter(order=order)
matIds = list(MIOItem.objects.filter(mio=mio).values_list('material__id', flat=True).distinct())
matIds2 = list(orderitem_qs.values_list('material__id', flat=True).distinct())
if set(matIds) != set(matIds2):
raise ValidationError('订单与发货单物料不一致')
for orderitem in orderitem_qs:
material = orderitem.material
delivered_count = MIOItem.objects.filter(
mio__order=order, material=material, mio__type='sale_out', mio__submit_time__isnull=False).values('count').aggregate(Sum('count'))['count__sum']
delivered_count = delivered_count if delivered_count else 0
if delivered_count > orderitem.count:
raise ValidationError((f'{i.material.name}-超出订单所需数量'))
raise ValidationError((f'{str(material)}-超出订单所需数量'))
elif delivered_count < 0:
raise ValidationError((f'{i.material.name}-数量小于0'))
raise ValidationError((f'{str(material)}-数量小于0'))
orderitem.delivered_count = delivered_count
orderitem.save()
orderitem.save(update_fields=['delivered_count'])
# 更新order的状态
qs = OrderItem.objects.filter(
order=order, count__lte=F('delivered_count'))
order=order, count__gt=F('delivered_count'))
order_state = Order.ORDER_DOING
if qs.exists():
pass

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.sam.views import (CustomerViewSet, ContractViewSet, OrderViewSet, OrderItemViewSet)
API_BASE_URL = 'api/sam/'
HTML_BASE_URL = 'sam/'
HTML_BASE_URL = 'dhtml/sam/'
router = DefaultRouter()
router.register('customer', CustomerViewSet, basename='customer')

View File

@ -57,8 +57,8 @@ class OrderViewSet(CustomModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
select_related_fields = ['contract', 'customer']
search_fields = ['number', 'contract__name', 'contract__number', 'customer__name', 'orderitem_order__material__name',
'orderitem_order__material__specification', 'orderitem_order__material__model']
search_fields = ['number', 'contract__name', 'contract__number', 'customer__name', 'item_order__material__name',
'item_order__material__specification', 'item_order__material__model']
filterset_fields = {
"contract": ["exact"],
"customer": ["exact"],

View File

@ -7,7 +7,7 @@ from .views import ApkViewSet, FileViewSet, PTaskViewSet, PTaskResultViewSet, Po
from rest_framework import routers
API_BASE_URL = 'api/system/'
HTML_BASE_URL = 'system/'
HTML_BASE_URL = 'dhtml/system/'
router = routers.DefaultRouter()
router.register('user', UserViewSet, basename="user")

View File

@ -767,6 +767,8 @@ class SysBaseConfigView(APIView):
config = get_sysconfig()
base_dict = {key: config[key]
for key in self.read_keys if key in config}
base_dict.get("base", {})["sys_version"] = settings.SYS_VERSION
base_dict.get("base", {})["sys_name"] = settings.SYS_NAME
return Response(base_dict)

View File

@ -5,7 +5,7 @@ from apps.third.views import DahuaTestView, DhCommonViewSet, SpTestView, Speaker
from apps.third.views_d import BltViewSet, TDeviceViewSet, TlogViewSet
API_BASE_URL = 'api/third/'
HTML_BASE_URL = 'third/'
HTML_BASE_URL = 'dhtml/third/'
router = routers.DefaultRouter()
router.register('xunxi', XxCommonViewSet, basename='api_xunxi')

View File

@ -1,5 +1,9 @@
from django.conf import settings
from rest_framework import serializers
from django.db.models import DecimalField
from django.core.validators import MinValueValidator
from django.utils.functional import cached_property
from decimal import Decimal
class MyFilePathField(serializers.CharField):
@ -8,3 +12,9 @@ class MyFilePathField(serializers.CharField):
if 'http' in value:
return str(value)
return settings.BASE_URL + str(value)
class PositiveDecimalField(DecimalField):
@cached_property
def validators(self):
return [MinValueValidator(Decimal('0.0'))] + super().validators

View File

@ -10,6 +10,14 @@ from io import BytesIO
from rest_framework.serializers import ValidationError
import ast
from typing import Dict
from django.core.serializers.json import DjangoJSONEncoder
from decimal import Decimal
class MyJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return super().default(obj)
class CodeAnalyzer(ast.NodeVisitor):
def __init__(self):

View File

@ -4,7 +4,7 @@ from rest_framework.routers import DefaultRouter
from apps.vm.views import VisitViewSet, VisitorViewSet, VpeopleViewSet
API_BASE_URL = 'api/vm/'
HTML_BASE_URL = 'vm/'
HTML_BASE_URL = 'dhtml/vm/'
router = DefaultRouter()
router.register('visit', VisitViewSet, basename='visit')

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/wf/'
HTML_BASE_URL = 'wf/'
HTML_BASE_URL = 'dhtml/wf/'
router = DefaultRouter()
router.register('workflow', WorkflowViewSet, basename='workflow')

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.12 on 2025-04-14 14:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pm', '0021_auto_20250317_1040'),
('wpm', '0107_alter_handoverb_handover'),
]
operations = [
migrations.AddField(
model_name='fmlog',
name='is_fix',
field=models.BooleanField(default=False, verbose_name='是否用于返修'),
),
migrations.AlterField(
model_name='fmlog',
name='mtask',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fmlog_mtask', to='pm.mtask', verbose_name='任务'),
),
]

View File

@ -128,7 +128,7 @@ class WMaterial(CommonBDModel):
@property
def count_working(self):
return Mlogb.objects.filter(wm_in=self, mlog__work_end_time__isnull=True).aggregate(count=Sum('count_use'))['count'] or 0
return Mlogb.objects.filter(wm_in=self, mlog__submit_time__isnull=True).aggregate(count=Sum('count_use'))['count'] or 0
@classmethod
def mat_in_qs(cls, mtask: Mtask, qs=None):
@ -153,9 +153,11 @@ class WMaterial(CommonBDModel):
class Fmlog(CommonADModel):
"""TN: 父级生产日志
"""
mtask = models.ForeignKey(Mtask, verbose_name='任务', on_delete=models.CASCADE, related_name='fmlog_mtask')
mtask = models.ForeignKey(Mtask, verbose_name='任务',
on_delete=models.CASCADE, related_name='fmlog_mtask', null=True, blank=True)
mgroup = models.ForeignKey(Mgroup, verbose_name='工段', on_delete=models.CASCADE, related_name='fmlog_mgroup')
note = models.TextField('备注', default='', blank=True)
is_fix = models.BooleanField('是否用于返修', default=False) # 返工/复检
enabled = models.BooleanField("是否启用", default=True)
class Mlog(CommonADModel):

View File

@ -212,6 +212,11 @@ class MlogbDefectSerializer(CustomModelSerializer):
model = MlogbDefect
fields = ["id", "defect_name", "count", "mlogb", "defect", "defect_okcate"]
read_only_fields = EXCLUDE_FIELDS_BASE + ["mlogb"]
def validate(self, attrs):
if attrs["count"] < 0:
raise serializers.ValidationError("存在负数!")
return attrs
class MlogbSerializer(CustomModelSerializer):
material_out_ = MaterialSimpleSerializer(
@ -541,7 +546,7 @@ class MlogSerializer(CustomModelSerializer):
attrs['mgroup'] = fmlog.mgroup
attrs['mtask'] = fmlog.mtask
attrs['mtype'] = fmlog.mgroup.mtype
if attrs['mtask'].route:
if attrs['mtask'] and attrs['mtask'].route:
attrs['route'] = attrs['mtask'].route
# if attrs['mtask'].mtaskb and mtaskb is None:
# raise ParseError('子任务不能为空')
@ -675,6 +680,8 @@ class MlogbInSerializer(CustomModelSerializer):
'wm_in': {'required': True, "allow_empty": False}}
def validate(self, attrs):
if attrs["count_use"] < 0 or attrs.get("count_pn_jgqbl", 0) < 0 or attrs.get("count_break", 0) < 0:
raise ParseError('存在负数!')
mlog:Mlog = attrs['mlog']
is_fix = mlog.is_fix
mtask: Mtask = attrs.get("mtask", None)
@ -739,6 +746,10 @@ class MlogbInUpdateSerializer(CustomModelSerializer):
model = Mlogb
fields = ['id', 'count_use', 'count_pn_jgqbl', 'note', 'mlogbdefect']
def validate(self, attrs):
if attrs["count_use"] < 0 or attrs.get("count_pn_jgqbl", 0) < 0:
raise ParseError('存在负数!')
return attrs
def update(self, instance, validated_data):
mlog: Mlog = instance.mlog
mlogbdefect = validated_data.pop("mlogbdefect", None)
@ -1153,6 +1164,7 @@ class AttLogSerializer(CustomModelSerializer):
class FmlogSerializer(CustomModelSerializer):
routepack_name = serializers.CharField(
source='mtask.route.routepack.name', read_only=True)
mgroup_name = serializers.CharField(source='mgroup.name', read_only=True)
mtask_number = serializers.CharField(source='mtask.number', read_only=True)
class Meta:
model = Fmlog
@ -1160,6 +1172,11 @@ class FmlogSerializer(CustomModelSerializer):
read_only_fields = EXCLUDE_FIELDS
def validate(self, attrs):
is_fix = attrs.get("is_fix", False)
if is_fix:
attrs["mtask"] = None
elif not attrs.get("mtask", None):
raise ParseError("请选择任务")
mtask: Mtask = attrs['mtask']
if mtask.state != Mtask.MTASK_ASSGINED:
raise ParseError('该任务非下达中不可选择')

View File

@ -5,13 +5,19 @@ from apps.inm.models import MIOItem
from apps.qm.models import FtestWork
from django.utils import timezone
from datetime import datetime
from server.conf import BASE_PROJECT_CODE
from django.conf import settings
import json
from apps.utils.tools import MyJSONEncoder
import decimal
import logging
myLogger = logging.getLogger('log')
def get_alldata_with_batch_and_store(batch: str):
def get_alldata_with_batch_and_store(batch: str, need_update_time=True):
"""
获取某个批次的整体生产数据并保存
"""
need_update = False
BASE_PROJECT_CODE = getattr(settings, "BASE_PROJECT_CODE", None)
if BASE_PROJECT_CODE == "gzerp":
need_update = True
last_time, data = get_alldata_with_batch(batch)
@ -23,8 +29,9 @@ def get_alldata_with_batch_and_store(batch: str):
bobj, _ = BatchSt.objects.get_or_create(batch=batch, defaults={
"last_time": last_time
})
bobj.last_time = last_time
bobj.data = data
if need_update_time:
bobj.last_time = last_time
bobj.data = json.loads(json.dumps(data, cls=MyJSONEncoder))
bobj.save()
def get_alldata_with_batch_gx(batch: str):
@ -78,6 +85,9 @@ def get_alldata_with_batch(batch: str):
data["棒料成型_合格率"] = round((data["棒料成型_count_ok"] * 100/ data["棒料成型_count_real"]), 1)
except ZeroDivisionError:
data["棒料成型_合格率"] = 0
except decimal.InvalidOperation:
myLogger.error(f"棒料成型_合格率计算错误decimal.InvalidOperation-{data}")
data["棒料成型_合格率"] = 0
# 管料成型数据
mgroup_glcx = Mgroup.objects.get(name="管料成型")
@ -118,11 +128,12 @@ def get_alldata_with_batch(batch: str):
if item.mio.do_user:
data["七车间入库_仓库执行人"].append(item.mio.mio_user)
for field in mioitem_count_fields:
if getattr(item, field) > 0 or field in ["count", "count_notok"]:
f_v = getattr(item, field)
if f_v is not None and (f_v > 0 or field in ["count", "count_notok"]):
if f'七车间入库_{field}' not in data:
data[f'七车间入库_{field}'] = int(getattr(item, field))
data[f'七车间入库_{field}'] = f_v
else:
data[f'七车间入库_{field}'] += int(getattr(item, field))
data[f'七车间入库_{field}'] += f_v
data["七车间入库_合格率"] = round((data["七车间入库_count"] - data["七车间入库_count_notok"]) * 100/ data["七车间入库_count"], 1)
data["七车间入库_日期"] = list(set(data["七车间入库_日期"]))
data["七车间入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["七车间入库_日期"]])
@ -150,11 +161,11 @@ def get_alldata_with_batch(batch: str):
if item.test_user:
data["十车间入库_抽检人"].append(item.test_user)
for field in mioitem_count_fields:
if getattr(item, field) > 0 or field in ["count", "count_notok", "count_sampling"]:
if getattr(item, field) is not None and (getattr(item, field) > 0 or field in ["count", "count_notok", "count_sampling"]):
if f'十车间入库_{field}' not in data:
data[f'十车间入库_{field}'] = int(getattr(item, field))
data[f'十车间入库_{field}'] = getattr(item, field)
else:
data[f'十车间入库_{field}'] += int(getattr(item, field))
data[f'十车间入库_{field}'] += getattr(item, field)
data["十车间入库_抽检人"] = list(set(data["十车间入库_抽检人"]))
data["十车间入库_抽检人"] = ";".join([item.name for item in data["十车间入库_抽检人"]])
if data["十车间入库_count_sampling"] > 0:
@ -185,11 +196,11 @@ def get_alldata_with_batch(batch: str):
if item.handle_user:
data["管料退火_操作人"].append(item.handle_user)
for field in mlog_count_fields:
if getattr(item, field) > 0 or field in ["count", "count_notok"]:
if getattr(item, field) is not None and (getattr(item, field) > 0 or field in ["count", "count_notok"]):
if f'管料退火_{field}' not in data:
data[f'管料退火_{field}'] = int(getattr(item, field))
data[f'管料退火_{field}'] = getattr(item, field)
else:
data[f'管料退火_{field}'] += int(getattr(item, field))
data[f'管料退火_{field}'] += getattr(item, field)
data["管料退火_日期"] = list(set(data["管料退火_日期"]))
data["管料退火_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["管料退火_日期"]])
data["管料退火_操作人"] = list(set(data["管料退火_操作人"]))
@ -216,11 +227,11 @@ def get_alldata_with_batch(batch: str):
if item.mio.mio_user:
data["六车间领料_车间执行人"].append(item.mio.mio_user)
for field in mioitem_count_fields:
if getattr(item, field) > 0:
if getattr(item, field) is not None and getattr(item, field) > 0:
if f'六车间领料_{field}' not in data:
data[f'六车间领料_{field}'] = int(getattr(item, field))
data[f'六车间领料_{field}'] = getattr(item, field)
else:
data[f'六车间领料_{field}'] += int(getattr(item, field))
data[f'六车间领料_{field}'] += getattr(item, field)
data["六车间领料_日期"] = list(set(data["六车间领料_日期"]))
data["六车间领料_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间领料_日期"]])
data["六车间领料_仓库执行人"] = list(set(data["六车间领料_仓库执行人"]))
@ -262,24 +273,11 @@ def get_alldata_with_batch(batch: str):
data[f'六车间_{mgroup_name}_日期'] = ";".join([item.strftime("%Y-%m-%d") for item in data[f'六车间_{mgroup_name}_日期']])
data[f'六车间_{mgroup_name}_操作人'] = list(set(data[f'六车间_{mgroup_name}_操作人']))
data[f'六车间_{mgroup_name}_操作人'] = ";".join([item.name for item in data[f'六车间_{mgroup_name}_操作人']])
data[f'六车间_{mgroup_name}_合格率'] = round(data[f'六车间_{mgroup_name}_count_ok'] * 100/ data[f'六车间_{mgroup_name}_count_real'], 1)
# 六车间入库/检验数据
# mioitem6_qs2 = MIOItem.objects.filter(mio__belong_dept=dept6, mio__type="do_in",
# batch=batch,
# mio__submit_time__isnull=False)
# if mioitem6_qs2.exists():
# data["六车间生产入库_日期"] = []
# for item in mioitem6_qs:
# data["六车间生产入库_日期"].append(item.mio.inout_date)
# for field in mioitem_count_fields:
# if getattr(item, field) > 0:
# if f'六车间生产入库_{field}' not in data:
# data[f'六车间生产入库_{field}'] = getattr(item, field)
# else:
# data[f'六车间生产入库_{field}'] += getattr(item, field)
# data["六车间生产入库_日期"] = list(set(data["六车间生产入库_日期"]))
# data["六车间生产入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_日期"]])
try:
data[f'六车间_{mgroup_name}_合格率'] = round(data[f'六车间_{mgroup_name}_count_ok'] * 100/ data[f'六车间_{mgroup_name}_count_real'], 1)
except decimal.InvalidOperation:
myLogger.error(f"六车间_{mgroup_name}_合格率decimal.InvalidOperation-{data}")
data[f'六车间_{mgroup_name}_合格率'] = 0
ftestwork_count_fields = FtestWork.count_fields()
# 六车间中检数据
@ -311,6 +309,34 @@ def get_alldata_with_batch(batch: str):
data['六车间中检_检验人'] = list(set(data['六车间中检_检验人']))
data['六车间中检_检验人'] = ";".join([item.name for item in data['六车间中检_检验人']])
# 六车间入库/检验数据
mioitem6_qs2 = MIOItem.objects.filter(mio__belong_dept=dept6, mio__type="do_in",
batch=batch,
mio__submit_time__isnull=False)
if mioitem6_qs2.exists():
data["六车间生产入库_日期"] = []
data["六车间生产入库_检验日期"] = []
data["六车间生产入库_检验人"] = []
for item in mioitem6_qs2:
data["六车间生产入库_日期"].append(item.mio.inout_date)
if item.test_date:
data["六车间生产入库_检验日期"].append(item.test_date)
for field in mioitem_count_fields:
if getattr(item, field) is not None and (getattr(item, field) > 0 or field in ["count", "count_notok"]):
if f'六车间生产入库_{field}' not in data:
data[f'六车间生产入库_{field}'] = getattr(item, field)
else:
data[f'六车间生产入库_{field}'] += getattr(item, field)
data["六车间生产入库_日期"] = list(set(data["六车间生产入库_日期"]))
data["六车间生产入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_日期"]])
data["六车间生产入库_检验日期"] = list(set(data["六车间生产入库_检验日期"]))
data["六车间生产入库_检验日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间生产入库_检验日期"]])
try:
data['六车间生产入库_合格率'] = round((data['六车间生产入库_count'] - data['六车间生产入库_count_notok']) * 100/ data['六车间生产入库_count'], 1)
except decimal.InvalidOperation:
myLogger.error("六车间生产入库_合格率decimal.InvalidOperation-{data}")
data['六车间生产入库_合格率'] = 0
# 成品检验数据
ftestwork_qs = FtestWork.objects.filter(batch=batch, type="prod")
if ftestwork_qs.exists():
@ -340,9 +366,29 @@ def get_alldata_with_batch(batch: str):
data['成品检验_检验人'] = list(set(data['成品检验_检验人']))
data['成品检验_检验人'] = ";".join([item.name for item in data['成品检验_检验人']])
data['成品检验_合格率'] = round(data['成品检验_count_ok'] * 100/ data['成品检验_count'], 1)
data["六车间_批次生产合格率"] = round(data["成品检验_count_ok"] * 100/ data["六车间领料_count"], 1)
if data["棒料成型_count_real"]:
if getattr(data, "六车间领料_count", 0) > 0:
data["六车间_批次生产合格率"] = round(data["成品检验_count_ok"] * 100/ data["六车间领料_count"], 1)
if getattr(data, "棒料成型_count_real", 0) > 0:
data["七车间_批次应出合格率"] = round(data["成品检验_count_ok"] * 100/ data["棒料成型_count_real"], 1)
# 销售发货数据
mioitem_qs = MIOItem.objects.filter(batch=batch, mio__type="sale_out", mio__submit_time__isnull=False)
if mioitem_qs.exists():
data["销售发货_日期"] = []
data['销售发货_仓库执行人'] = []
data['销售发货_count'] = 0
for item in mioitem_qs:
last_time = item.mio.update_time if item.mio.update_time > last_time else last_time
if item.mio.inout_date:
data["销售发货_日期"].append(item.mio.inout_date)
if item.mio.do_user:
data['销售发货_仓库执行人'].append(item.mio.do_user)
data['销售发货_count']+= item.count
if getattr(data, "棒料成型_count_real", 0) > 0:
data["七车间_批次发货合格率"] = round(data["销售发货_count"] * 100/ data["棒料成型_count_real"], 1)
if getattr(data, "六车间领料_count", 0) > 0:
data["六车间_批次发货合格率"] = round(data["销售发货_count"] * 100/ data["六车间领料_count"], 1)
data['销售发货_仓库执行人'] = ";".join([item.name for item in data['销售发货_仓库执行人']])
return last_time, data

View File

@ -10,7 +10,7 @@ from apps.wpm.datax import AnaViewSet
API_BASE_URL = 'api/wpm/'
HTML_BASE_URL = 'wpm/'
HTML_BASE_URL = 'dhtml/wpm/'
router = DefaultRouter()
router.register('sflog', SfLogViewSet, basename='sflog')

View File

@ -136,7 +136,7 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet):
serializer_class = WMaterialSerializer
select_related_fields = ['material', 'belong_dept', 'material__process', 'supplier']
search_fields = ['material__name',
'material__number', 'material__specification', 'batch', 'material__model', "defect__name", "notok_sign", 'material_ofrom']
'material__number', 'material__specification', 'batch', 'material__model', "defect__name", "notok_sign"]
filterset_class = WMaterialFilter
ordering_fields = ["update_time", "state", "count", "count_xtest"]

View File

@ -5,7 +5,7 @@ from apps.wpmw.views import WprViewSet
API_BASE_URL = 'api/wpmw/'
HTML_BASE_URL = 'wpmw/'
HTML_BASE_URL = 'dhtml/wpmw/'
router = DefaultRouter()
router.register('wpr', WprViewSet, basename='wpr')

13
changelog.md Normal file
View File

@ -0,0 +1,13 @@
## 2.6.2025033109
- feat: 功能新增
- 建立及完善批次号数据链 [caoqianming]
- 添加子工序以完善同一日志的记录 [caoqianming]
- wpr产品追溯上游链 [caoqianming]
- 工艺路线routepack支持子图逻辑及任务量算法 [caoqianming]
- 出入库及任务编号非必填及自动生成逻辑 [caoqianming]
- fix: 问题修复
- 车间库存检索bug [caoqianming]
- wpr产品创建oinfo的bug [caoqianming]
- 其他已知的bug [caoqianming]
## 2.6.2025031919
- 初始版本

12
config/e.conf.py Normal file
View File

@ -0,0 +1,12 @@
SECRET_KEY = 'xx'
DEBUG = False
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'xx',
'USER': 'postgres',
'PASSWORD': 'xx',
'HOST': 'xx',
'PORT': '5432',
}
}

View File

@ -1,5 +1,5 @@
import os
from . import conf
from config import conf
from celery import Celery
from celery.app.control import Control, Inspect

View File

@ -14,8 +14,10 @@ from datetime import datetime, timedelta
import os
import json
import sys
from .conf import *
from config.conf import *
from django.core.cache import cache
import logging
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
@ -33,8 +35,8 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
ALLOWED_HOSTS = ['*']
SYS_NAME = '星途工厂综合管理系统'
SYS_VERSION = '2.3.0'
SYS_VERSION = '2.6.2025033109'
X_FRAME_OPTIONS = 'SAMEORIGIN'
# Application definition
@ -270,6 +272,18 @@ LOG_PATH = os.path.join(BASE_DIR, 'log')
if not os.path.exists(LOG_PATH):
os.makedirs(LOG_PATH)
class TimedSizeRotatingHandler(logging.handlers.TimedRotatingFileHandler):
def __init__(self, filename, when='midnight', interval=1, backupCount=0,
maxBytes=0, encoding=None, delay=False, utc=False, atTime=None):
super().__init__(filename, when, interval, backupCount, encoding, delay, utc, atTime)
self.maxBytes = maxBytes
def shouldRollover(self, record):
if self.maxBytes > 0 and os.path.exists(self.baseFilename):
if os.stat(self.baseFilename).st_size >= self.maxBytes:
return True
return super().shouldRollover(record)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
@ -293,22 +307,28 @@ LOGGING = {
# 默认记录所有日志
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOG_PATH, 'all-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))),
'class': 'server.settings.TimedSizeRotatingHandler',
'filename': os.path.join(LOG_PATH, 'all.log'),
'when': 'midnight', # 每天午夜滚动
'interval': 1,
'maxBytes': 1024 * 1024 * 2, # 文件大小
'backupCount': 10, # 备份数
'backupCount': 30, # 备份数
'formatter': 'standard', # 输出格式
'encoding': 'utf-8', # 设置默认编码,否则打印出来汉字乱码
'delay': True, # 延迟打开文件,减少锁定冲突
},
# 输出错误日志
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOG_PATH, 'error-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))),
'class': 'server.settings.TimedSizeRotatingHandler',
'filename': os.path.join(LOG_PATH, 'error.log'),
'when': 'midnight',
'interval': 1,
'maxBytes': 1024 * 1024 * 2, # 文件大小
'backupCount': 10, # 备份数
'backupCount': 30, # 备份数
'formatter': 'standard', # 输出格式
'encoding': 'utf-8', # 设置默认编码
'delay': True, # 延迟打开文件,减少锁定冲突
},
# 控制台输出
'console': {
@ -320,12 +340,15 @@ LOGGING = {
# 输出info日志
'info': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOG_PATH, 'info-{}.log'.format(datetime.now().strftime('%Y-%m-%d'))),
'class': 'server.settings.TimedSizeRotatingHandler',
'filename': os.path.join(LOG_PATH, 'info.log'),
'when': 'midnight',
'interval': 1,
'maxBytes': 1024 * 1024 * 2,
'backupCount': 10,
'backupCount': 30,
'formatter': 'standard',
'encoding': 'utf-8', # 设置默认编码
'delay': True, # 延迟打开文件,减少锁定冲突
},
},
# 配置用哪几种 handlers 来处理日志
@ -346,7 +369,7 @@ LOGGING = {
}
##### 加载客户可自定义配置并提供操作方法 #####
SYS_JSON_PATH = os.path.join(BASE_DIR, 'server/conf.json')
SYS_JSON_PATH = os.path.join(BASE_DIR, 'config/conf.json')
def get_sysconfig(key='', default='raise_error', reload=False):
"""获取系统配置可指定key字符串

69
update_changelog.sh Normal file
View File

@ -0,0 +1,69 @@
#!/bin/bash
# 设置默认版本号 (格式: 2.6.YYYYMMDDHH)
DEFAULT_VERSION="2.6.$(date '+%Y%m%d%H')"
# 获取参数 (起始tag)
TARGET_TAG="$1"
# 获取最后一个tag
LAST_TAG=$(git describe --abbrev=0 --tags 2>/dev/null)
# 确定版本范围
if [ -z "$TARGET_TAG" ]; then
if [ -z "$LAST_TAG" ]; then
echo "没有找到任何tag将从头开始生成"
RANGE=""
else
RANGE="$LAST_TAG..HEAD"
fi
else
RANGE="$TARGET_TAG..HEAD"
fi
# 初始化临时文件
TEMP_FILE=$(mktemp)
# 生成变更日志头
echo "## $DEFAULT_VERSION" > "$TEMP_FILE"
echo >> "$TEMP_FILE"
# 按类型分类提交记录
process_commits() {
local type="$1"
local header="$2"
local pattern="$3"
# 查找匹配类型的提交
git log $RANGE --no-merges --pretty=format:"%s | %an | %ad" --date=short | grep -E "$pattern" | while read -r line; do
COMMIT_MSG=$(echo "$line" | cut -d'|' -f1 | sed 's/^ *//;s/ *$//')
AUTHOR=$(echo "$line" | cut -d'|' -f2 | sed 's/^ *//;s/ *$//')
DATE=$(echo "$line" | cut -d'|' -f3 | sed 's/^ *//;s/ *$//')
# 如果是第一次找到该类型,先打印标题
if [ $printed_header -eq 0 ]; then
echo "- $header" >> "$TEMP_FILE"
printed_header=1
fi
# 打印提交信息(移除类型前缀)
CLEAN_MSG=$(echo "$COMMIT_MSG" | sed -E "s/^$type:\s*//i")
echo " - $CLEAN_MSG [$AUTHOR]" >> "$TEMP_FILE"
done
}
# 处理各类型提交(按优先级排序)
printed_header=0; process_commits "feat" "feat: 新增功能" "^[fF]eat"
printed_header=0; process_commits "fix" "fix: 问题修复" "^[fF]ix"
printed_header=0; process_commits "" "other: 其他变更" "^((?![fF]eat|[fF]ix).)*$"
# 合并到原文件
if [ -f changelog.md ]; then
cat "$TEMP_FILE" changelog.md > changelog.md.tmp && mv changelog.md.tmp changelog.md
rm "$TEMP_FILE"
else
mv "$TEMP_FILE" changelog.md
fi
echo "变更日志已更新到 changelog.md"
echo "当前版本号: $DEFAULT_VERSION (请手动修改)"