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*.py
server/conf.ini server/conf.ini
server/conf*.json server/conf*.json
config/conf*.py
config/conf*.json
sh/* sh/*
temp/* temp/*
nohup.out nohup.out

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/am/' API_BASE_URL = 'api/am/'
HTML_BASE_URL = 'am/' HTML_BASE_URL = 'dhtml/am/'
router = DefaultRouter() router = DefaultRouter()
router.register('area', AreaViewSet, basename='area') 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.models import Dataset, DatasetRecord
from apps.bi.services import exec_dataset from apps.bi.services import exec_dataset
import json import json
from apps.utils.tools import MyJSONEncoder
@shared_task() @shared_task()
def exec_dataset_and_store(code: str, query: str = ''): def exec_dataset_and_store(code: str, query: str = ''):
dt = Dataset.objects.get(code=code) dt = Dataset.objects.get(code=code)
@ -16,4 +16,5 @@ def exec_dataset_and_store(code: str, query: str = ''):
if query: if query:
squery = json.loads(query) squery = json.loads(query)
dtr.full_sql, dtr.result = exec_dataset(dt, squery) dtr.full_sql, dtr.result = exec_dataset(dt, squery)
dtr.result = json.loads(json.dumps(dtr.result, cls=MyJSONEncoder))
dtr.save() dtr.save()

View File

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

View File

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

View File

@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/cms/' API_BASE_URL = 'api/cms/'
HTML_BASE_URL = 'cms/' HTML_BASE_URL = 'dhtml/cms/'
router = DefaultRouter() router = DefaultRouter()
router.register('article', ArticleViewSet, basename='article') 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 from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/develop/' API_BASE_URL = 'api/develop/'
HTML_BASE_URL = 'develop/' HTML_BASE_URL = 'dhtml/develop/'
router = DefaultRouter() router = DefaultRouter()
router.register('test', TestViewSet, basename='api_test') router.register('test', TestViewSet, basename='api_test')
router.register('correct', CorrectViewSet, basename='correct') router.register('correct', CorrectViewSet, basename='correct')

View File

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

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/ecm/' API_BASE_URL = 'api/ecm/'
HTML_BASE_URL = 'ecm/' HTML_BASE_URL = 'dhtml/ecm/'
router = DefaultRouter() router = DefaultRouter()
router.register('event_cate', EventCateViewSet, basename='event_cate') 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 from apps.edu.views import QuestioncatViewSet, QuestionViewSet, PaperViewSet, ExamViewSet, ExamRecordViewSet, TrainRecordViewSet
API_BASE_URL = 'api/edu/' API_BASE_URL = 'api/edu/'
HTML_BASE_URL = 'edu/' HTML_BASE_URL = 'dhtml/edu/'
router = DefaultRouter() router = DefaultRouter()
router.register('questioncat', QuestioncatViewSet, basename='questioncat') 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 from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView
API_BASE_URL = 'api/em/' API_BASE_URL = 'api/em/'
HTML_BASE_URL = 'em/' HTML_BASE_URL = 'dhtml/em/'
router = DefaultRouter() router = DefaultRouter()
router.register('equipment', EquipmentViewSet, basename='equipment') router.register('equipment', EquipmentViewSet, basename='equipment')

View File

@ -4,7 +4,7 @@ from apps.enm.views import (MpointViewSet, MpLogxViewSet, MpointStatViewSet,
EnStatViewSet, EnStat2ViewSet, XscriptViewSet) EnStatViewSet, EnStat2ViewSet, XscriptViewSet)
API_BASE_URL = 'api/enm/' API_BASE_URL = 'api/enm/'
HTML_BASE_URL = 'enm/' HTML_BASE_URL = 'dhtml/enm/'
router = DefaultRouter() router = DefaultRouter()
router.register('mpoint', MpointViewSet, basename='mpoint') 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 from .views import DrainViewSet, DrainEquipViewSet, VehicleAccessViewSet, EnvDataViewSet, CarWashViewSet
API_BASE_URL = 'api/enp/' API_BASE_URL = 'api/enp/'
HTML_BASE_URL = 'enp/' HTML_BASE_URL = 'dhtml/enp/'
router = DefaultRouter() router = DefaultRouter()
router.register('drain', DrainViewSet, basename='drain') 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) from apps.fim.views import (PriceSetViewSet, FeeSetViewSet, FeeViewSet)
API_BASE_URL = 'api/fim/' API_BASE_URL = 'api/fim/'
HTML_BASE_URL = 'fim/' HTML_BASE_URL = 'dhtml/fim/'
router = DefaultRouter() router = DefaultRouter()
router.register('priceset', PriceSetViewSet, basename='priceset') router.register('priceset', PriceSetViewSet, basename='priceset')

View File

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

View File

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

View File

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

View File

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

View File

@ -254,6 +254,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
"test_date": ["isnull", "exact"] "test_date": ["isnull", "exact"]
} }
ordering = ['create_time'] ordering = ['create_time']
ordering_fields = ['create_time', 'test_date']
def add_info_for_list(self, data): def add_info_for_list(self, data):
@ -286,6 +287,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
sr.save() sr.save()
# 开始变动库存 # 开始变动库存
InmService.update_mb_item(ins, -1, 'count_notok') InmService.update_mb_item(ins, -1, 'count_notok')
InmService.update_material_count(ins.mio)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=serializers.Serializer) @action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=serializers.Serializer)
@ -303,6 +305,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
pass pass
ins.test_date = None ins.test_date = None
ins.save() ins.save()
InmService.update_material_count(ins.mio)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'mioitem.test'}, serializer_class=MIOItemPurInTestSerializer) @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 = MIOItemPurInTestSerializer(instance=ins, data=request.data)
sr.is_valid(raise_exception=True) sr.is_valid(raise_exception=True)
sr.save() sr.save()
InmService.update_material_count(ins.mio)
return Response() return Response()
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MioItemAnaSerializer) @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 from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView, AuditlogViewSet, CeleryInfoView, RedisInfoView
API_BASE_URL = 'api/monitor/' API_BASE_URL = 'api/monitor/'
HTML_BASE_URL = 'monitor/' HTML_BASE_URL = 'dhtml/monitor/'
urlpatterns = [ urlpatterns = [
path(HTML_BASE_URL, index), path(HTML_BASE_URL, index),
path(HTML_BASE_URL + 'index/', index), path(HTML_BASE_URL + 'index/', index),

View File

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

View File

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

View File

@ -265,6 +265,7 @@ class RouteViewSet(CustomModelViewSet):
serializer_class = RouteSerializer serializer_class = RouteSerializer
filterset_class = RouteFilter filterset_class = RouteFilter
ordering = ['sort', 'process__sort', 'create_time'] ordering = ['sort', 'process__sort', 'create_time']
ordering_fields = ['sort', 'process__sort', 'create_time', 'update_time']
select_related_fields = ['material', select_related_fields = ['material',
'process', 'material_in', 'material_out', 'mgroup', 'routepack'] '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 from apps.opm.views import GasCheckViewSet, OperationViewSet, OplCateViewSet, OplViewSet, OplWorkerViewSet
API_BASE_URL = 'api/opm/' API_BASE_URL = 'api/opm/'
HTML_BASE_URL = 'opm/' HTML_BASE_URL = 'dhtml/opm/'
router = DefaultRouter() router = DefaultRouter()
router.register('opl_cate', OplCateViewSet, basename='opl_cate') router.register('opl_cate', OplCateViewSet, basename='opl_cate')

View File

@ -24,10 +24,13 @@ class UtaskSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Utask model = Utask
fields = '__all__' fields = '__all__'
read_only_fields = ["number"] extra_kwargs = {
'number': {"required": False, "allow_blank": True}
}
def create(self, validated_data): 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) return super().create(validated_data)
def validate(self, attrs): def validate(self, attrs):

View File

@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter
from apps.pm.views import (MtaskViewSet, UtaskViewSet, MtaskbViewSet) from apps.pm.views import (MtaskViewSet, UtaskViewSet, MtaskbViewSet)
API_BASE_URL = 'api/pm/' API_BASE_URL = 'api/pm/'
HTML_BASE_URL = 'pm/' HTML_BASE_URL = 'dhtml/pm/'
router = DefaultRouter() router = DefaultRouter()
router.register('mtask', MtaskViewSet, basename='mtask') 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) from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet)
API_BASE_URL = 'api/pum/' API_BASE_URL = 'api/pum/'
HTML_BASE_URL = 'pum/' HTML_BASE_URL = 'dhtml/pum/'
router = DefaultRouter() router = DefaultRouter()
router.register('supplier', SupplierViewSet, basename='supplier') 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) User, verbose_name='检验员', on_delete=models.CASCADE)
testitem = models.ForeignKey( testitem = models.ForeignKey(
TestItem, verbose_name='测试项目', on_delete=models.CASCADE, null=True, blank=True) 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_count = models.PositiveIntegerField('样品数量', null=True, blank=True)
sample_density = models.FloatField('样品密度', null=True, blank=True) sample_density = models.FloatField('样品密度', null=True, blank=True)
specification = models.CharField( specification = models.CharField(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -767,6 +767,8 @@ class SysBaseConfigView(APIView):
config = get_sysconfig() config = get_sysconfig()
base_dict = {key: config[key] base_dict = {key: config[key]
for key in self.read_keys if key in config} 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) 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 from apps.third.views_d import BltViewSet, TDeviceViewSet, TlogViewSet
API_BASE_URL = 'api/third/' API_BASE_URL = 'api/third/'
HTML_BASE_URL = 'third/' HTML_BASE_URL = 'dhtml/third/'
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register('xunxi', XxCommonViewSet, basename='api_xunxi') router.register('xunxi', XxCommonViewSet, basename='api_xunxi')

View File

@ -1,5 +1,9 @@
from django.conf import settings from django.conf import settings
from rest_framework import serializers 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): class MyFilePathField(serializers.CharField):
@ -8,3 +12,9 @@ class MyFilePathField(serializers.CharField):
if 'http' in value: if 'http' in value:
return str(value) return str(value)
return settings.BASE_URL + 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 from rest_framework.serializers import ValidationError
import ast import ast
from typing import Dict 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): class CodeAnalyzer(ast.NodeVisitor):
def __init__(self): def __init__(self):

View File

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

View File

@ -4,7 +4,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
API_BASE_URL = 'api/wf/' API_BASE_URL = 'api/wf/'
HTML_BASE_URL = 'wf/' HTML_BASE_URL = 'dhtml/wf/'
router = DefaultRouter() router = DefaultRouter()
router.register('workflow', WorkflowViewSet, basename='workflow') 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 @property
def count_working(self): 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 @classmethod
def mat_in_qs(cls, mtask: Mtask, qs=None): def mat_in_qs(cls, mtask: Mtask, qs=None):
@ -153,9 +153,11 @@ class WMaterial(CommonBDModel):
class Fmlog(CommonADModel): class Fmlog(CommonADModel):
"""TN: 父级生产日志 """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') mgroup = models.ForeignKey(Mgroup, verbose_name='工段', on_delete=models.CASCADE, related_name='fmlog_mgroup')
note = models.TextField('备注', default='', blank=True) note = models.TextField('备注', default='', blank=True)
is_fix = models.BooleanField('是否用于返修', default=False) # 返工/复检
enabled = models.BooleanField("是否启用", default=True) enabled = models.BooleanField("是否启用", default=True)
class Mlog(CommonADModel): class Mlog(CommonADModel):

View File

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

View File

@ -5,13 +5,19 @@ from apps.inm.models import MIOItem
from apps.qm.models import FtestWork from apps.qm.models import FtestWork
from django.utils import timezone from django.utils import timezone
from datetime import datetime 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 need_update = False
BASE_PROJECT_CODE = getattr(settings, "BASE_PROJECT_CODE", None)
if BASE_PROJECT_CODE == "gzerp": if BASE_PROJECT_CODE == "gzerp":
need_update = True need_update = True
last_time, data = get_alldata_with_batch(batch) 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={ bobj, _ = BatchSt.objects.get_or_create(batch=batch, defaults={
"last_time": last_time "last_time": last_time
}) })
bobj.last_time = last_time if need_update_time:
bobj.data = data bobj.last_time = last_time
bobj.data = json.loads(json.dumps(data, cls=MyJSONEncoder))
bobj.save() bobj.save()
def get_alldata_with_batch_gx(batch: str): 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) data["棒料成型_合格率"] = round((data["棒料成型_count_ok"] * 100/ data["棒料成型_count_real"]), 1)
except ZeroDivisionError: except ZeroDivisionError:
data["棒料成型_合格率"] = 0 data["棒料成型_合格率"] = 0
except decimal.InvalidOperation:
myLogger.error(f"棒料成型_合格率计算错误decimal.InvalidOperation-{data}")
data["棒料成型_合格率"] = 0
# 管料成型数据 # 管料成型数据
mgroup_glcx = Mgroup.objects.get(name="管料成型") mgroup_glcx = Mgroup.objects.get(name="管料成型")
@ -118,11 +128,12 @@ def get_alldata_with_batch(batch: str):
if item.mio.do_user: if item.mio.do_user:
data["七车间入库_仓库执行人"].append(item.mio.mio_user) data["七车间入库_仓库执行人"].append(item.mio.mio_user)
for field in mioitem_count_fields: 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: if f'七车间入库_{field}' not in data:
data[f'七车间入库_{field}'] = int(getattr(item, field)) data[f'七车间入库_{field}'] = f_v
else: 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["七车间入库_合格率"] = round((data["七车间入库_count"] - data["七车间入库_count_notok"]) * 100/ data["七车间入库_count"], 1)
data["七车间入库_日期"] = list(set(data["七车间入库_日期"])) data["七车间入库_日期"] = list(set(data["七车间入库_日期"]))
data["七车间入库_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in 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: if item.test_user:
data["十车间入库_抽检人"].append(item.test_user) data["十车间入库_抽检人"].append(item.test_user)
for field in mioitem_count_fields: 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: if f'十车间入库_{field}' not in data:
data[f'十车间入库_{field}'] = int(getattr(item, field)) data[f'十车间入库_{field}'] = getattr(item, field)
else: else:
data[f'十车间入库_{field}'] += int(getattr(item, field)) data[f'十车间入库_{field}'] += getattr(item, field)
data["十车间入库_抽检人"] = list(set(data["十车间入库_抽检人"])) data["十车间入库_抽检人"] = list(set(data["十车间入库_抽检人"]))
data["十车间入库_抽检人"] = ";".join([item.name for item in data["十车间入库_抽检人"]]) data["十车间入库_抽检人"] = ";".join([item.name for item in data["十车间入库_抽检人"]])
if data["十车间入库_count_sampling"] > 0: if data["十车间入库_count_sampling"] > 0:
@ -185,11 +196,11 @@ def get_alldata_with_batch(batch: str):
if item.handle_user: if item.handle_user:
data["管料退火_操作人"].append(item.handle_user) data["管料退火_操作人"].append(item.handle_user)
for field in mlog_count_fields: 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: if f'管料退火_{field}' not in data:
data[f'管料退火_{field}'] = int(getattr(item, field)) data[f'管料退火_{field}'] = getattr(item, field)
else: else:
data[f'管料退火_{field}'] += int(getattr(item, field)) data[f'管料退火_{field}'] += getattr(item, field)
data["管料退火_日期"] = list(set(data["管料退火_日期"])) data["管料退火_日期"] = list(set(data["管料退火_日期"]))
data["管料退火_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["管料退火_日期"]]) data["管料退火_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["管料退火_日期"]])
data["管料退火_操作人"] = list(set(data["管料退火_操作人"])) data["管料退火_操作人"] = list(set(data["管料退火_操作人"]))
@ -216,11 +227,11 @@ def get_alldata_with_batch(batch: str):
if item.mio.mio_user: if item.mio.mio_user:
data["六车间领料_车间执行人"].append(item.mio.mio_user) data["六车间领料_车间执行人"].append(item.mio.mio_user)
for field in mioitem_count_fields: 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: if f'六车间领料_{field}' not in data:
data[f'六车间领料_{field}'] = int(getattr(item, field)) data[f'六车间领料_{field}'] = getattr(item, field)
else: else:
data[f'六车间领料_{field}'] += int(getattr(item, field)) data[f'六车间领料_{field}'] += getattr(item, field)
data["六车间领料_日期"] = list(set(data["六车间领料_日期"])) data["六车间领料_日期"] = list(set(data["六车间领料_日期"]))
data["六车间领料_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间领料_日期"]]) data["六车间领料_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["六车间领料_日期"]])
data["六车间领料_仓库执行人"] = list(set(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}_日期'] = ";".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}_操作人'] = list(set(data[f'六车间_{mgroup_name}_操作人']))
data[f'六车间_{mgroup_name}_操作人'] = ";".join([item.name for item in 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) try:
data[f'六车间_{mgroup_name}_合格率'] = round(data[f'六车间_{mgroup_name}_count_ok'] * 100/ data[f'六车间_{mgroup_name}_count_real'], 1)
# 六车间入库/检验数据 except decimal.InvalidOperation:
# mioitem6_qs2 = MIOItem.objects.filter(mio__belong_dept=dept6, mio__type="do_in", myLogger.error(f"六车间_{mgroup_name}_合格率decimal.InvalidOperation-{data}")
# batch=batch, data[f'六车间_{mgroup_name}_合格率'] = 0
# 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["六车间生产入库_日期"]])
ftestwork_count_fields = FtestWork.count_fields() ftestwork_count_fields = FtestWork.count_fields()
# 六车间中检数据 # 六车间中检数据
@ -311,6 +309,34 @@ def get_alldata_with_batch(batch: str):
data['六车间中检_检验人'] = list(set(data['六车间中检_检验人'])) data['六车间中检_检验人'] = list(set(data['六车间中检_检验人']))
data['六车间中检_检验人'] = ";".join([item.name for item in 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") ftestwork_qs = FtestWork.objects.filter(batch=batch, type="prod")
if ftestwork_qs.exists(): if ftestwork_qs.exists():
@ -340,9 +366,29 @@ def get_alldata_with_batch(batch: str):
data['成品检验_检验人'] = list(set(data['成品检验_检验人'])) data['成品检验_检验人'] = list(set(data['成品检验_检验人']))
data['成品检验_检验人'] = ";".join([item.name for item in 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)
data["六车间_批次生产合格率"] = round(data["成品检验_count_ok"] * 100/ data["六车间领料_count"], 1) if getattr(data, "六车间领料_count", 0) > 0:
if data["棒料成型_count_real"]: 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) 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 return last_time, data

View File

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

View File

@ -136,7 +136,7 @@ class WMaterialViewSet(ListModelMixin, CustomGenericViewSet):
serializer_class = WMaterialSerializer serializer_class = WMaterialSerializer
select_related_fields = ['material', 'belong_dept', 'material__process', 'supplier'] select_related_fields = ['material', 'belong_dept', 'material__process', 'supplier']
search_fields = ['material__name', 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 filterset_class = WMaterialFilter
ordering_fields = ["update_time", "state", "count", "count_xtest"] 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/' API_BASE_URL = 'api/wpmw/'
HTML_BASE_URL = 'wpmw/' HTML_BASE_URL = 'dhtml/wpmw/'
router = DefaultRouter() router = DefaultRouter()
router.register('wpr', WprViewSet, basename='wpr') 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 import os
from . import conf from config import conf
from celery import Celery from celery import Celery
from celery.app.control import Control, Inspect from celery.app.control import Control, Inspect

View File

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