Merge branch 'master' of https://e.coding.net/ctcdevteam/ehs/ehs_server
This commit is contained in:
commit
6a5ec34ef8
|
@ -5,7 +5,7 @@ from apps.bi.models import Dataset
|
||||||
import concurrent
|
import concurrent
|
||||||
from apps.utils.sql import execute_raw_sql, format_sqldata
|
from apps.utils.sql import execute_raw_sql, format_sqldata
|
||||||
|
|
||||||
forbidden_keywords = ["UPDATE", "DELETE", "DROP", "TRUNCATE"]
|
forbidden_keywords = ["UPDATE", "DELETE", "DROP", "TRUNCATE", "INSERT", "CREATE", "ALTER", "GRANT", "REVOKE", "EXEC", "EXECUTE"]
|
||||||
|
|
||||||
|
|
||||||
def check_sql_safe(sql: str):
|
def check_sql_safe(sql: str):
|
||||||
|
|
192
apps/em/cd.py
192
apps/em/cd.py
|
@ -4,9 +4,49 @@ import json
|
||||||
import time
|
import time
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from apps.utils.thread import MyThread
|
from apps.utils.thread import MyThread
|
||||||
|
import struct
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
def get_tyy_data_t(host, port):
|
myLogger = logging.getLogger('log')
|
||||||
cd_thread_key = f"cd_thread_{host}_{port}"
|
|
||||||
|
def get_checksum(body_msg):
|
||||||
|
return sum(body_msg) & 0xFF
|
||||||
|
|
||||||
|
def handle_bytes(arr):
|
||||||
|
if len(arr) < 8:
|
||||||
|
return "返回数据长度错误"
|
||||||
|
|
||||||
|
if arr[0] != 0xEB or arr[1] != 0x90:
|
||||||
|
return "数据头不正确"
|
||||||
|
|
||||||
|
|
||||||
|
# 读取长度信息
|
||||||
|
length_arr = arr[2:4][::-1] # 反转字节
|
||||||
|
length = int.from_bytes(length_arr, byteorder='little', signed=True) # 小端格式
|
||||||
|
|
||||||
|
# 提取内容
|
||||||
|
body = arr[4:4 + length - 3]
|
||||||
|
|
||||||
|
# 校验和检查
|
||||||
|
check_sum = get_checksum(body)
|
||||||
|
if check_sum != arr[length + 1]:
|
||||||
|
return "校验错误"
|
||||||
|
|
||||||
|
|
||||||
|
# 尾部标识检查
|
||||||
|
if arr[length + 2] != 0xFF or arr[length + 3] != 0xFE:
|
||||||
|
return "尾错误"
|
||||||
|
|
||||||
|
content = body.decode('utf-8')
|
||||||
|
|
||||||
|
res = json.loads(content)
|
||||||
|
|
||||||
|
return res[0]
|
||||||
|
|
||||||
|
def get_tyy_data_t(host, port, tid):
|
||||||
|
cd_thread_key_id = f"cd_thread_{host}_{port}_id"
|
||||||
|
cd_thread_key_val = f"cd_thread_{host}_{port}_val"
|
||||||
sc = None
|
sc = None
|
||||||
def connect_and_send(retry=1):
|
def connect_and_send(retry=1):
|
||||||
nonlocal sc
|
nonlocal sc
|
||||||
|
@ -16,71 +56,141 @@ def get_tyy_data_t(host, port):
|
||||||
sc.connect((host, int(port)))
|
sc.connect((host, int(port)))
|
||||||
sc.sendall(b"R")
|
sc.sendall(b"R")
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
sc = None
|
|
||||||
if retry > 0:
|
|
||||||
connect_and_send(retry-1)
|
|
||||||
except OSError as e:
|
|
||||||
sc = None
|
|
||||||
if retry > 0:
|
if retry > 0:
|
||||||
connect_and_send(retry-1)
|
connect_and_send(retry-1)
|
||||||
else:
|
else:
|
||||||
cache.set(cd_thread_key, {"err_msg": f"采集器连接失败-{str(e)}"})
|
|
||||||
except ConnectionResetError:
|
|
||||||
sc = None
|
|
||||||
if retry > 0:
|
|
||||||
connect_and_send(retry-1)
|
|
||||||
else:
|
|
||||||
cache.set(cd_thread_key, {"err_msg": "采集器重置了连接"})
|
|
||||||
except socket.timeout:
|
|
||||||
cache.set(cd_thread_key, {"err_msg": "采集器连接超时"})
|
|
||||||
while True:
|
|
||||||
cd_thread_val = cache.get(cd_thread_key, default=None)
|
|
||||||
if cd_thread_val is None:
|
|
||||||
if sc:
|
if sc:
|
||||||
try:
|
try:
|
||||||
sc.close()
|
sc.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
break
|
sc = None
|
||||||
elif cd_thread_val == "get":
|
except OSError as e:
|
||||||
|
sc = None
|
||||||
|
cache.set(cd_thread_key_val, {"err_msg": f"采集器连接失败-{str(e)}"})
|
||||||
|
except ConnectionResetError:
|
||||||
|
sc = None
|
||||||
|
cache.set(cd_thread_key_val, {"err_msg": "采集器重置了连接"})
|
||||||
|
except socket.timeout:
|
||||||
|
sc = None
|
||||||
|
cache.set(cd_thread_key_val, {"err_msg": "采集器连接超时"})
|
||||||
|
except Exception as e:
|
||||||
|
sc = None
|
||||||
|
cache.set(cd_thread_key_val, {"err_msg": f"采集器连接失败-{str(e)}"})
|
||||||
|
|
||||||
|
while cache.get(cd_thread_key_id) == tid:
|
||||||
|
if cache.get(cd_thread_key_val) == "get":
|
||||||
|
cache.set(cd_thread_key_val, "working")
|
||||||
connect_and_send()
|
connect_and_send()
|
||||||
if sc is None:
|
if sc is None:
|
||||||
cache.set(cd_thread_key, {"err_msg": "采集器连接失败"})
|
|
||||||
continue
|
continue
|
||||||
resp = sc.recv(1024)
|
resp = sc.recv(1024)
|
||||||
if not resp:
|
res = handle_bytes(resp)
|
||||||
cache.set(cd_thread_key, {"err_msg": f"采集器返回空数据-{str(resp)}"})
|
if isinstance(res, str):
|
||||||
elif len(resp) < 8:
|
cache.set(cd_thread_key_val, {"err_msg": f'采集器返回数据错误-{res}'})
|
||||||
cache.set(cd_thread_key, {"err_msg": f"设备未启动-{str(resp)}"})
|
elif not res:
|
||||||
|
cache.set(cd_thread_key_val, {"err_msg": f"采集器返回数据为空-{str(res)}"})
|
||||||
else:
|
else:
|
||||||
json_data = resp[5:-4]
|
myLogger.info(f"采集器返回数据-{res}")
|
||||||
json_str = json_data.decode('utf-8')
|
cache.set(cd_thread_key_val, res)
|
||||||
res = json.loads(json_str)
|
|
||||||
cache.set(cd_thread_key, res)
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
def get_tyy_data(*args):
|
if sc:
|
||||||
|
try:
|
||||||
|
sc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_tyy_data_2(*args, retry=1):
|
||||||
host, port = args[0], int(args[1])
|
host, port = args[0], int(args[1])
|
||||||
cd_thread_key = f"cd_thread_{host}_{port}"
|
cd_thread_key_id = f"cd_thread_{host}_{port}_id"
|
||||||
cd_thread_val = cache.get(cd_thread_key, default=None)
|
cd_thread_key_val = f"cd_thread_{host}_{port}_val"
|
||||||
if cd_thread_val is None:
|
cd_thread_val_id = cache.get(cd_thread_key_id, default=None)
|
||||||
cache.set(cd_thread_key, "start")
|
if cd_thread_val_id is None:
|
||||||
cd_thread = MyThread(target=get_tyy_data_t, args=(host, port), daemon=True)
|
tid = uuid.uuid4()
|
||||||
|
cache.set(cd_thread_key_id, tid, timeout=10800)
|
||||||
|
cd_thread = MyThread(target=get_tyy_data_t, args=(host, port, tid), daemon=True)
|
||||||
cd_thread.start()
|
cd_thread.start()
|
||||||
cache.set(cd_thread_key, "get")
|
cache.set(cd_thread_key_val, "get")
|
||||||
num = 0
|
num = 0
|
||||||
|
get_val = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
num += 1
|
num += 1
|
||||||
if num > 8:
|
if num > 8:
|
||||||
break
|
break
|
||||||
val = cache.get(cd_thread_key)
|
val = cache.get(cd_thread_key_val)
|
||||||
if isinstance(val, dict):
|
if isinstance(val, dict):
|
||||||
|
get_val = True
|
||||||
if "err_msg" in val:
|
if "err_msg" in val:
|
||||||
raise ParseError(val["err_msg"])
|
raise ParseError(val["err_msg"])
|
||||||
return val
|
return val
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
raise ParseError("获取数据超时")
|
|
||||||
|
if not get_val and retry > 0:
|
||||||
|
cache.set(cd_thread_key_id, None)
|
||||||
|
get_tyy_data_2(*args, retry=retry-1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
sc_all = {}
|
||||||
print(get_tyy_data())
|
|
||||||
|
def get_tyy_data(*args):
|
||||||
|
host, port = args[0], int(args[1])
|
||||||
|
global sc_all
|
||||||
|
sc = None
|
||||||
|
|
||||||
|
def connect_and_send(retry=1):
|
||||||
|
nonlocal sc
|
||||||
|
sc = sc_all.get(f"{host}_{port}", None)
|
||||||
|
try:
|
||||||
|
if sc is None:
|
||||||
|
sc = socket.socket()
|
||||||
|
sc.settimeout(5) # 设置超时
|
||||||
|
sc.connect((host, port))
|
||||||
|
sc_all[f"{host}_{port}"] = sc
|
||||||
|
sc.sendall(b"R")
|
||||||
|
except BrokenPipeError:
|
||||||
|
if retry > 0:
|
||||||
|
if sc:
|
||||||
|
try:
|
||||||
|
sc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sc_all.pop(f"{host}_{port}", None)
|
||||||
|
return connect_and_send(retry-1)
|
||||||
|
else:
|
||||||
|
if sc:
|
||||||
|
try:
|
||||||
|
sc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sc_all.pop(f"{host}_{port}", None)
|
||||||
|
sc = None
|
||||||
|
raise ParseError("采集器连接失败-管道重置")
|
||||||
|
except OSError as e:
|
||||||
|
if sc:
|
||||||
|
try:
|
||||||
|
sc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sc_all.pop(f"{host}_{port}", None)
|
||||||
|
sc = None
|
||||||
|
raise ParseError(f"采集器连接失败-{str(e)}")
|
||||||
|
except TimeoutError as e:
|
||||||
|
if sc:
|
||||||
|
try:
|
||||||
|
sc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sc_all.pop(f"{host}_{port}", None)
|
||||||
|
sc = None
|
||||||
|
raise ParseError(f"采集器连接超时-{str(e)}")
|
||||||
|
|
||||||
|
connect_and_send()
|
||||||
|
resp = sc.recv(1024)
|
||||||
|
res = handle_bytes(resp)
|
||||||
|
# myLogger.error(res)
|
||||||
|
if isinstance(res, str):
|
||||||
|
raise ParseError(f'采集器返回数据错误-{res}')
|
||||||
|
else:
|
||||||
|
return res
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# 角色
|
||||||
|
你是一位数据分析专家和前端程序员,具备深厚的专业知识和丰富的实践经验。你能够精准理解用户的文本描述, 并形成报告。
|
||||||
|
# 技能
|
||||||
|
1. 仔细分析用户提供的JSON格式数据,分析用户需求。
|
||||||
|
2. 依据得到的需求, 分别获取JSON数据中的关键信息。
|
||||||
|
3. 根据2中的关键信息最优化选择表格/饼图/柱状图/折线图等格式绘制报告。
|
||||||
|
# 回答要求
|
||||||
|
1. 仅生成完整的HTML代码,所有功能都需要实现,支持响应式,不要输出任何解释或说明。
|
||||||
|
2. 代码中如需要Echarts等js库,请直接使用中国大陆的CDN链接例如bootcdn的链接。
|
||||||
|
3. 标题为 数据分析报告。
|
||||||
|
3. 在开始部分,请以表格形式简略展示获取的JSON数据。
|
||||||
|
4. 之后选择最合适的图表方式生成相应的图。
|
||||||
|
5. 在最后提供可下载该报告的完整PDF的按钮和功能。
|
||||||
|
6. 在最后提供可下载含有JSON数据的EXCEL文件的按钮和功能。
|
|
@ -0,0 +1,53 @@
|
||||||
|
# 角色
|
||||||
|
你是一位资深的Postgresql数据库SQL专家,具备深厚的专业知识和丰富的实践经验。你能够精准理解用户的文本描述,并生成准确可执行的SQL语句。
|
||||||
|
# 技能
|
||||||
|
1. 仔细分析用户提供的文本描述,明确用户需求。
|
||||||
|
2. 根据对用户需求的理解,生成符合Postgresql数据库语法的准确可执行的SQL语句。
|
||||||
|
# 回答要求
|
||||||
|
1. 如果用户的询问未以 查询 开头,请直接回复 "请以 查询 开头,重新描述你的需求"。
|
||||||
|
2. 生成的SQL语句必须符合Postgresql数据库的语法规范。
|
||||||
|
3. 不要使用 Markerdown 和 SQL 语法格式输出,禁止添加语法标准、备注、说明等信息。
|
||||||
|
4. 直接输出符合Postgresql标准的SQL语句,用txt纯文本格式展示即可。
|
||||||
|
5. 如果无法生成符合要求的SQL语句,请直接回复 "无法生成"。
|
||||||
|
# 示例
|
||||||
|
1. 问:查询 外协白片抛 工段在2025年6月1日到2025年6月15日之间的生产合格数以及合格率等
|
||||||
|
答:select
|
||||||
|
sum(mlog.count_use) as 领用数,
|
||||||
|
sum(mlog.count_real) as 生产数,
|
||||||
|
sum(mlog.count_ok) as 合格数,
|
||||||
|
sum(mlog.count_notok) as 不合格数,
|
||||||
|
CAST ( SUM ( mlog.count_ok ) AS FLOAT ) / NULLIF ( SUM ( mlog.count_real ), 0 ) * 100 AS 合格率
|
||||||
|
from wpm_mlog mlog
|
||||||
|
left join mtm_mgroup mgroup on mgroup.id = mlog.mgroup_id
|
||||||
|
where mlog.submit_time is not null
|
||||||
|
and mgroup.name = '外协白片抛'
|
||||||
|
and mlog.handle_date >= '2025-06-01'
|
||||||
|
and mlog.handle_date <= '2025-06-15'
|
||||||
|
2. 问:查询 黑化 工段在2025年6月的生产合格数以及合格率等
|
||||||
|
答: select
|
||||||
|
sum(mlog.count_use) as 领用数,
|
||||||
|
sum(mlog.count_real) as 生产数,
|
||||||
|
sum(mlog.count_ok) as 合格数,
|
||||||
|
sum(mlog.count_notok) as 不合格数,
|
||||||
|
CAST ( SUM ( mlog.count_ok ) AS FLOAT ) / NULLIF ( SUM ( mlog.count_real ), 0 ) * 100 AS 合格率
|
||||||
|
from wpm_mlog mlog
|
||||||
|
left join mtm_mgroup mgroup on mgroup.id = mlog.mgroup_id
|
||||||
|
where mlog.submit_time is not null
|
||||||
|
and mgroup.name = '黑化'
|
||||||
|
and mlog.handle_date >= '2025-06-01'
|
||||||
|
and mlog.handle_date <= '2025-06-30'
|
||||||
|
3. 问:查询 各工段 在2025年6月的生产合格数以及合格率等
|
||||||
|
答: select
|
||||||
|
mgroup.name as 工段,
|
||||||
|
sum(mlog.count_use) as 领用数,
|
||||||
|
sum(mlog.count_real) as 生产数,
|
||||||
|
sum(mlog.count_ok) as 合格数,
|
||||||
|
sum(mlog.count_notok) as 不合格数,
|
||||||
|
CAST ( SUM ( mlog.count_ok ) AS FLOAT ) / NULLIF ( SUM ( mlog.count_real ), 0 ) * 100 AS 合格率
|
||||||
|
from wpm_mlog mlog
|
||||||
|
left join mtm_mgroup mgroup on mgroup.id = mlog.mgroup_id
|
||||||
|
where mlog.submit_time is not null
|
||||||
|
and mlog.handle_date >= '2025-06-01'
|
||||||
|
and mlog.handle_date <= '2025-06-30'
|
||||||
|
group by mgroup.id
|
||||||
|
order by mgroup.sort
|
|
@ -2,6 +2,7 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from apps.ichat.views import QueryLLMviewSet, ConversationViewSet
|
from apps.ichat.views import QueryLLMviewSet, ConversationViewSet
|
||||||
|
from apps.ichat.views2 import WorkChain
|
||||||
|
|
||||||
API_BASE_URL = 'api/ichat/'
|
API_BASE_URL = 'api/ichat/'
|
||||||
|
|
||||||
|
@ -11,4 +12,5 @@ router.register('conversation', ConversationViewSet, basename='conversation')
|
||||||
router.register('message', QueryLLMviewSet, basename='message')
|
router.register('message', QueryLLMviewSet, basename='message')
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(API_BASE_URL, include(router.urls)),
|
path(API_BASE_URL, include(router.urls)),
|
||||||
|
path(API_BASE_URL + 'workchain/ask/', WorkChain.as_view(), name='workchain')
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
from apps.utils.sql import execute_raw_sql
|
||||||
|
import json
|
||||||
|
from apps.utils.tools import MyJSONEncoder
|
||||||
|
from .utils import is_safe_sql
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.conf import settings
|
||||||
|
from apps.utils.mixins import MyLoggingMixin
|
||||||
|
from django.core.cache import cache
|
||||||
|
import uuid
|
||||||
|
from apps.utils.thread import MyThread
|
||||||
|
|
||||||
|
LLM_URL = getattr(settings, "LLM_URL", "")
|
||||||
|
API_KEY = getattr(settings, "LLM_API_KEY", "")
|
||||||
|
MODEL = "qwen14b"
|
||||||
|
HEADERS = {
|
||||||
|
"Authorization": f"Bearer {API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
def load_promot(name):
|
||||||
|
with open(os.path.join(CUR_DIR, f'promot/{name}.md'), 'r') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def ask(input:str, p_name:str, stream=False):
|
||||||
|
his = [{"role":"system", "content": load_promot(p_name)}]
|
||||||
|
his.append({"role":"user", "content": input})
|
||||||
|
payload = {
|
||||||
|
"model": MODEL,
|
||||||
|
"messages": his,
|
||||||
|
"temperature": 0,
|
||||||
|
"stream": stream
|
||||||
|
}
|
||||||
|
response = requests.post(LLM_URL, headers=HEADERS, json=payload, stream=stream)
|
||||||
|
if not stream:
|
||||||
|
return response.json()["choices"][0]["message"]["content"]
|
||||||
|
else:
|
||||||
|
# 处理流式响应
|
||||||
|
full_content = ""
|
||||||
|
for chunk in response.iter_lines():
|
||||||
|
if chunk:
|
||||||
|
# 通常流式响应是SSE格式(data: {...})
|
||||||
|
decoded_chunk = chunk.decode('utf-8')
|
||||||
|
if decoded_chunk.startswith("data:"):
|
||||||
|
json_str = decoded_chunk[5:].strip()
|
||||||
|
if json_str == "[DONE]":
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
chunk_data = json.loads(json_str)
|
||||||
|
if "choices" in chunk_data and chunk_data["choices"]:
|
||||||
|
delta = chunk_data["choices"][0].get("delta", {})
|
||||||
|
if "content" in delta:
|
||||||
|
print(delta["content"])
|
||||||
|
full_content += delta["content"]
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
return full_content
|
||||||
|
|
||||||
|
def work_chain(input:str, t_key:str):
|
||||||
|
pdict = {"state": "progress", "steps": [{"state":"ok", "msg":"正在生成查询语句"}]}
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
res_text = ask(input, 'w_sql')
|
||||||
|
if res_text == '请以 查询 开头,重新描述你的需求':
|
||||||
|
pdict["state"] = "error"
|
||||||
|
pdict["steps"].append({"state":"error", "msg":res_text})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
pdict["steps"].append({"state":"ok", "msg":"查询语句生成成功", "content":res_text})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
if not is_safe_sql(res_text):
|
||||||
|
pdict["state"] = "error"
|
||||||
|
pdict["steps"].append({"state":"error", "msg":"当前查询存在风险,请重新描述你的需求"})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
return
|
||||||
|
pdict["steps"].append({"state":"ok", "msg":"正在执行查询语句"})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
res = execute_raw_sql(res_text)
|
||||||
|
pdict["steps"].append({"state":"ok", "msg":"查询语句执行成功", "content":res})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
pdict["steps"].append({"state":"ok", "msg":"正在生成报告"})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
res2 = ask(json.dumps(res, cls=MyJSONEncoder, ensure_ascii=False), 'w_ana')
|
||||||
|
content = res2.lstrip('```html ').rstrip('```')
|
||||||
|
pdict["state"] = "done"
|
||||||
|
pdict["content"] = content
|
||||||
|
pdict["steps"].append({"state":"ok", "msg":"报告生成成功", "content": content})
|
||||||
|
cache.set(t_key, pdict)
|
||||||
|
return
|
||||||
|
|
||||||
|
class InputSerializer(serializers.Serializer):
|
||||||
|
input = serializers.CharField(label="查询需求")
|
||||||
|
|
||||||
|
class WorkChain(MyLoggingMixin, APIView):
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_summary="提交查询需求",
|
||||||
|
request_body=InputSerializer)
|
||||||
|
def post(self, request):
|
||||||
|
llm_enabled = getattr(settings, "LLM_ENABLED", False)
|
||||||
|
if not llm_enabled:
|
||||||
|
raise ParseError('LLM功能未启用')
|
||||||
|
input = request.data.get('input')
|
||||||
|
t_key = f'ichat_{uuid.uuid4()}'
|
||||||
|
MyThread(target=work_chain, args=(input, t_key)).start()
|
||||||
|
return Response({'ichat_tid': t_key})
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_summary="获取查询进度")
|
||||||
|
def get(self, request):
|
||||||
|
llm_enabled = getattr(settings, "LLM_ENABLED", False)
|
||||||
|
if not llm_enabled:
|
||||||
|
raise ParseError('LLM功能未启用')
|
||||||
|
ichat_tid = request.GET.get('ichat_tid')
|
||||||
|
if ichat_tid:
|
||||||
|
return Response(cache.get(ichat_tid))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(work_chain("查询 一次超洗 工段在2025年6月的生产合格数等并形成报告"))
|
||||||
|
|
||||||
|
from apps.ichat.views2 import work_chain
|
||||||
|
print(work_chain('查询外观检验工段在2025年6月的生产合格数等并形成报告'))
|
|
@ -35,6 +35,7 @@ class MioFilter(filters.FilterSet):
|
||||||
"order": ["exact"],
|
"order": ["exact"],
|
||||||
"item_mio__test_date": ["isnull"],
|
"item_mio__test_date": ["isnull"],
|
||||||
"item_mio__test_user": ["isnull"],
|
"item_mio__test_user": ["isnull"],
|
||||||
|
"item_mio__w_mioitem__number": ["exact"],
|
||||||
"mgroup": ["exact"],
|
"mgroup": ["exact"],
|
||||||
"item_mio__batch": ["exact"]
|
"item_mio__batch": ["exact"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-06-19 02:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inm', '0030_auto_20250523_0922'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mioitem',
|
||||||
|
name='unit_price',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, max_digits=14, null=True, verbose_name='单价'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -139,6 +139,7 @@ class MIOItem(BaseModel):
|
||||||
material = models.ForeignKey(
|
material = models.ForeignKey(
|
||||||
Material, verbose_name='物料', on_delete=models.CASCADE)
|
Material, verbose_name='物料', on_delete=models.CASCADE)
|
||||||
batch = models.TextField('批次号', db_index=True)
|
batch = models.TextField('批次号', db_index=True)
|
||||||
|
unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
|
||||||
count = models.DecimalField('出入数量', max_digits=12, decimal_places=3)
|
count = models.DecimalField('出入数量', max_digits=12, decimal_places=3)
|
||||||
count_tested = models.PositiveIntegerField('已检数', null=True, blank=True)
|
count_tested = models.PositiveIntegerField('已检数', null=True, blank=True)
|
||||||
test_date = models.DateField('检验日期', null=True, blank=True)
|
test_date = models.DateField('检验日期', null=True, blank=True)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBa
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from server.settings import get_sysconfig
|
from server.settings import get_sysconfig
|
||||||
from apps.wpmw.models import Wpr
|
from apps.wpmw.models import Wpr
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
class WareHourseSerializer(CustomModelSerializer):
|
class WareHourseSerializer(CustomModelSerializer):
|
||||||
|
@ -109,7 +110,7 @@ class MIOItemCreateSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MIOItem
|
model = MIOItem
|
||||||
fields = ['mio', 'warehouse', 'material',
|
fields = ['mio', 'warehouse', 'material',
|
||||||
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm']
|
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price']
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'mio': {'required': True}, 'warehouse': {'required': False},
|
'mio': {'required': True}, 'warehouse': {'required': False},
|
||||||
'material': {'required': False}, 'batch': {'required': False}}
|
'material': {'required': False}, 'batch': {'required': False}}
|
||||||
|
@ -207,8 +208,7 @@ class MIOItemAListSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class MIOItemSerializer(CustomModelSerializer):
|
class MIOItemSerializer(CustomModelSerializer):
|
||||||
warehouse_name = serializers.CharField(
|
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||||
source='warehouse.name', read_only=True)
|
|
||||||
material_ = MaterialSerializer(source='material', read_only=True)
|
material_ = MaterialSerializer(source='material', read_only=True)
|
||||||
assemb = MIOItemAListSerializer(
|
assemb = MIOItemAListSerializer(
|
||||||
source='a_mioitem', read_only=True, many=True)
|
source='a_mioitem', read_only=True, many=True)
|
||||||
|
@ -222,6 +222,15 @@ class MIOItemSerializer(CustomModelSerializer):
|
||||||
model = MIOItem
|
model = MIOItem
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
ret["price"] = None
|
||||||
|
if ret["unit_price"] is not None:
|
||||||
|
ret["price"] = Decimal(ret["count"]) * Decimal(ret["unit_price"])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class MioItemDetailSerializer(MIOItemSerializer):
|
||||||
|
mio_ = MIOListSerializer(source='mio', read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class MIODoSerializer(CustomModelSerializer):
|
class MIODoSerializer(CustomModelSerializer):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from apps.inm.serializers import (
|
||||||
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
|
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
|
||||||
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
|
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
|
||||||
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
|
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
|
||||||
MIOItemwSerializer)
|
MIOItemwSerializer, MioItemDetailSerializer)
|
||||||
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
|
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
|
||||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||||
from apps.inm.services import InmService
|
from apps.inm.services import InmService
|
||||||
|
@ -25,6 +25,8 @@ from apps.utils.permission import has_perm
|
||||||
from .filters import MaterialBatchFilter, MioFilter
|
from .filters import MaterialBatchFilter, MioFilter
|
||||||
from apps.qm.serializers import FtestProcessSerializer
|
from apps.qm.serializers import FtestProcessSerializer
|
||||||
from apps.mtm.models import Material
|
from apps.mtm.models import Material
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from drf_yasg import openapi
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
@ -243,6 +245,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
|
||||||
perms_map = {'get': '*', 'post': '*', 'delete': '*'}
|
perms_map = {'get': '*', 'post': '*', 'delete': '*'}
|
||||||
queryset = MIOItem.objects.all()
|
queryset = MIOItem.objects.all()
|
||||||
serializer_class = MIOItemSerializer
|
serializer_class = MIOItemSerializer
|
||||||
|
retrieve_serializer_class = MioItemDetailSerializer
|
||||||
create_serializer_class = MIOItemCreateSerializer
|
create_serializer_class = MIOItemCreateSerializer
|
||||||
select_related_fields = ['warehouse', 'mio', 'material', 'test_user']
|
select_related_fields = ['warehouse', 'mio', 'material', 'test_user']
|
||||||
filterset_fields = {
|
filterset_fields = {
|
||||||
|
@ -258,9 +261,28 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode
|
||||||
ordering_fields = ['create_time', 'test_date']
|
ordering_fields = ['create_time', 'test_date']
|
||||||
|
|
||||||
def add_info_for_list(self, data):
|
def add_info_for_list(self, data):
|
||||||
|
with_mio = self.request.query_params.get('with_mio', "no")
|
||||||
|
if with_mio == "yes" and isinstance(data, list):
|
||||||
|
mio_ids = [item['mio'] for item in data]
|
||||||
|
mio_qs = MIO.objects.filter(id__in=mio_ids)
|
||||||
|
mio_qs_= MIOListSerializer(mio_qs, many=True).data
|
||||||
|
mio_dict = {mio['id']: mio for mio in mio_qs_}
|
||||||
|
for item in data:
|
||||||
|
mioId = item['mio']
|
||||||
|
item['mio_'] = mio_dict[mioId]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@swagger_auto_schema(manual_parameters=[
|
||||||
|
openapi.Parameter(name="with_mio", in_=openapi.IN_QUERY, description="是否返回出入库记录信息",
|
||||||
|
type=openapi.TYPE_STRING, required=False),
|
||||||
|
openapi.Parameter(name="query", in_=openapi.IN_QUERY, description="定制返回数据",
|
||||||
|
type=openapi.TYPE_STRING, required=False),
|
||||||
|
openapi.Parameter(name="with_children", in_=openapi.IN_QUERY, description="带有children(yes/no/count)",
|
||||||
|
type=openapi.TYPE_STRING, required=False)
|
||||||
|
])
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.mio.state != MIO.MIO_CREATE:
|
if instance.mio.state != MIO.MIO_CREATE:
|
||||||
raise ParseError('出入库记录非创建中不可删除')
|
raise ParseError('出入库记录非创建中不可删除')
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-06-18 08:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mtm', '0058_process_wpr_number_rule'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='material',
|
||||||
|
name='bin_number_main',
|
||||||
|
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='主库位号'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -101,6 +101,7 @@ class Material(CommonAModel):
|
||||||
brothers = models.JSONField('兄弟件', default=list, null=False, blank=True)
|
brothers = models.JSONField('兄弟件', default=list, null=False, blank=True)
|
||||||
unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
|
unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
|
||||||
into_wm = models.BooleanField('是否进入车间库存', default=True)
|
into_wm = models.BooleanField('是否进入车间库存', default=True)
|
||||||
|
bin_number_main = models.CharField('主库位号', max_length=50, null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = '物料表'
|
verbose_name = '物料表'
|
||||||
|
|
|
@ -24,7 +24,7 @@ class MaterialSimpleSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Material
|
model = Material
|
||||||
fields = ['id', 'name', 'number', 'model',
|
fields = ['id', 'name', 'number', 'model',
|
||||||
'specification', 'type', 'cate', 'brothers', 'process_name', 'full_name', "tracking"]
|
'specification', 'type', 'cate', 'brothers', 'process_name', 'full_name', "tracking", "bin_number_main"]
|
||||||
|
|
||||||
def get_full_name(self, obj):
|
def get_full_name(self, obj):
|
||||||
return f'{obj.name}|{obj.specification if obj.specification else ""}|{obj.model if obj.model else ""}|{obj.process.name if obj.process else ""}'
|
return f'{obj.name}|{obj.specification if obj.specification else ""}|{obj.model if obj.model else ""}|{obj.process.name if obj.process else ""}'
|
||||||
|
|
|
@ -50,7 +50,7 @@ class MtaskFilter(filters.FilterSet):
|
||||||
"is_count_utask": ["exact"],
|
"is_count_utask": ["exact"],
|
||||||
"start_date": ["exact", "gte", "lte"],
|
"start_date": ["exact", "gte", "lte"],
|
||||||
"end_date": ["exact", "gte", "lte"],
|
"end_date": ["exact", "gte", "lte"],
|
||||||
"mgroup": ["exact"],
|
"mgroup": ["exact", "in"],
|
||||||
"mgroup__name": ["exact"],
|
"mgroup__name": ["exact"],
|
||||||
"mgroup__cate": ["exact"],
|
"mgroup__cate": ["exact"],
|
||||||
"mgroup__process": ["exact"],
|
"mgroup__process": ["exact"],
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-06-11 03:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pm', '0021_auto_20250317_1040'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='utask',
|
||||||
|
name='priority',
|
||||||
|
field=models.PositiveIntegerField(default=20, help_text='10:低;20:中;30:高', verbose_name='优先级'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -37,6 +37,7 @@ class Utask(CommonBDModel):
|
||||||
type = models.CharField('任务类型', max_length=10,
|
type = models.CharField('任务类型', max_length=10,
|
||||||
help_text=str(TASK_TYPE), default='mass')
|
help_text=str(TASK_TYPE), default='mass')
|
||||||
routepack = models.ForeignKey(RoutePack, verbose_name='关联工艺包', on_delete=models.SET_NULL, null=True, blank=True)
|
routepack = models.ForeignKey(RoutePack, verbose_name='关联工艺包', on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
priority = models.PositiveIntegerField('优先级', default=20, help_text="10:低;20:中;30:高")
|
||||||
state = models.PositiveIntegerField(
|
state = models.PositiveIntegerField(
|
||||||
'状态', choices=UTASK_STATES, default=UTASK_CREATED, help_text=str(UTASK_STATES))
|
'状态', choices=UTASK_STATES, default=UTASK_CREATED, help_text=str(UTASK_STATES))
|
||||||
number = models.CharField('编号', max_length=50, unique=True)
|
number = models.CharField('编号', max_length=50, unique=True)
|
||||||
|
|
|
@ -27,7 +27,8 @@ class UtaskSerializer(CustomModelSerializer):
|
||||||
model = Utask
|
model = Utask
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'number': {"required": False, "allow_blank": True}
|
'number': {"required": False, "allow_blank": True},
|
||||||
|
"priority": {"required": False, "allow_null": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
@ -52,6 +53,7 @@ class UtaskSerializer(CustomModelSerializer):
|
||||||
attrs['count_day'] = math.ceil(attrs['count']/rela_days)
|
attrs['count_day'] = math.ceil(attrs['count']/rela_days)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ParseError('日均任务数计划失败')
|
raise ParseError('日均任务数计划失败')
|
||||||
|
attrs["priority"] = attrs.get("priority", 20)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
|
|
@ -29,7 +29,8 @@ class UtaskViewSet(CustomModelViewSet):
|
||||||
serializer_class = UtaskSerializer
|
serializer_class = UtaskSerializer
|
||||||
filterset_class = UtaskFilter
|
filterset_class = UtaskFilter
|
||||||
select_related_fields = ['material']
|
select_related_fields = ['material']
|
||||||
ordering = ['-start_date']
|
ordering_fields = ['priority', 'start_date']
|
||||||
|
ordering = ["priority", '-start_date']
|
||||||
|
|
||||||
def perform_destroy(self, instance):
|
def perform_destroy(self, instance):
|
||||||
if instance.state >= Utask.UTASK_WORKING:
|
if instance.state >= Utask.UTASK_WORKING:
|
||||||
|
@ -143,8 +144,8 @@ class MtaskViewSet(CustomModelViewSet):
|
||||||
filterset_class = MtaskFilter
|
filterset_class = MtaskFilter
|
||||||
select_related_fields = ['material_in', 'material_out', 'mgroup']
|
select_related_fields = ['material_in', 'material_out', 'mgroup']
|
||||||
prefetch_related_fields = ['mlog_mtask', 'b_mtask']
|
prefetch_related_fields = ['mlog_mtask', 'b_mtask']
|
||||||
ordering_fields = ['start_date', 'mgroup__process__sort', 'create_time']
|
ordering_fields = ["utask__priority", 'start_date', 'mgroup__process__sort', 'create_time']
|
||||||
ordering = ['-start_date', 'route__sort', 'mgroup__process__sort', '-create_time']
|
ordering = ["utask__priority", '-start_date', 'route__sort', 'mgroup__process__sort', '-create_time']
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MtaskDaySerializer)
|
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=MtaskDaySerializer)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
|
|
@ -110,7 +110,8 @@ class MlogFilter(filters.FilterSet):
|
||||||
"mgroup__name": ["exact", "in", "contains"],
|
"mgroup__name": ["exact", "in", "contains"],
|
||||||
"submit_time": ["isnull"],
|
"submit_time": ["isnull"],
|
||||||
"fmlog": ["exact"],
|
"fmlog": ["exact"],
|
||||||
"b_mlog__batch": ["exact"]
|
"b_mlog__batch": ["exact"],
|
||||||
|
"b_mlog__w_mlogb__number": ["exact"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def filter_cbatch(self, queryset, name, value):
|
def filter_cbatch(self, queryset, name, value):
|
||||||
|
@ -158,7 +159,8 @@ class HandoverFilter(filters.FilterSet):
|
||||||
"recive_mgroup__name": ["exact"],
|
"recive_mgroup__name": ["exact"],
|
||||||
"type": ["exact", "in"],
|
"type": ["exact", "in"],
|
||||||
"mtype": ["exact", "in"],
|
"mtype": ["exact", "in"],
|
||||||
"b_handover__batch": ["exact"]
|
"b_handover__batch": ["exact"],
|
||||||
|
"b_handover__w_handoverb__number": ["exact"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 3.2.12 on 2025-06-09 05:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wpm', '0116_auto_20250523_0922'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='handoverbw',
|
||||||
|
name='handoverb',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='w_handoverb', to='wpm.handoverb', verbose_name='关联交接记录'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -125,6 +125,10 @@ class WMaterial(CommonBDModel):
|
||||||
def count_working(self):
|
def count_working(self):
|
||||||
return Mlogb.objects.filter(wm_in=self, mlog__submit_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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def count_handovering(self):
|
||||||
|
return Handoverb.objects.filter(wm=self, handover__submit_time__isnull=True).aggregate(count=Sum('count'))['count'] or 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mat_in_qs(cls, mtask: Mtask, qs=None):
|
def mat_in_qs(cls, mtask: Mtask, qs=None):
|
||||||
"""
|
"""
|
||||||
|
@ -316,7 +320,7 @@ class Mlog(CommonADModel):
|
||||||
通过mlogb计算mlog count 合计
|
通过mlogb计算mlog count 合计
|
||||||
"""
|
"""
|
||||||
mlog = self
|
mlog = self
|
||||||
if mlog.fill_way == Mlog.MLOG_23:
|
if mlog.fill_way in [Mlog.MLOG_23, Mlog.MLOG_12]:
|
||||||
a_dict = {
|
a_dict = {
|
||||||
"total_count_use": Sum('count_use'),
|
"total_count_use": Sum('count_use'),
|
||||||
"total_count_break": Sum('count_break'),
|
"total_count_break": Sum('count_break'),
|
||||||
|
@ -431,16 +435,16 @@ class Mlogb(BaseModel):
|
||||||
def mlogbdefect(self):
|
def mlogbdefect(self):
|
||||||
return MlogbDefect.objects.filter(mlogb=self)
|
return MlogbDefect.objects.filter(mlogb=self)
|
||||||
|
|
||||||
def cal_count_pn_jgqbl(self):
|
def cal_count_pn_jgqbl(self, cal_mlog=False):
|
||||||
mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="in")
|
mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="in")
|
||||||
count_pn_jgqbl = mqs.aggregate(total=Sum("count"))["total"] or 0
|
count_pn_jgqbl = mqs.aggregate(total=Sum("count"))["total"] or 0
|
||||||
self.count_pn_jgqbl = count_pn_jgqbl
|
self.count_pn_jgqbl = count_pn_jgqbl
|
||||||
self.save(update_fields=["count_pn_jgqbl"])
|
self.save(update_fields=["count_pn_jgqbl"])
|
||||||
mlog = self.mlog
|
mlog = self.mlog
|
||||||
if mlog:
|
if mlog and cal_mlog:
|
||||||
mlog.cal_mlog_count_from_mlogb()
|
mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
def cal_count_notok(self):
|
def cal_count_notok(self, cal_mlog=True):
|
||||||
mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="out")
|
mqs = MlogbDefect.get_defect_qs_from_mlogb(self, ftype="out")
|
||||||
count_notok = mqs.filter(defect__okcate=30).aggregate(total=Sum("count"))["total"] or 0
|
count_notok = mqs.filter(defect__okcate=30).aggregate(total=Sum("count"))["total"] or 0
|
||||||
count_notok_full = mqs.exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0
|
count_notok_full = mqs.exclude(defect__okcate=10).aggregate(total=Sum("count"))["total"] or 0
|
||||||
|
@ -448,10 +452,10 @@ class Mlogb(BaseModel):
|
||||||
self.count_ok = self.count_real - count_notok
|
self.count_ok = self.count_real - count_notok
|
||||||
self.count_ok_full = self.count_real - count_notok_full
|
self.count_ok_full = self.count_real - count_notok_full
|
||||||
if self.count_ok_full < 0:
|
if self.count_ok_full < 0:
|
||||||
raise ParseError("完全合格数不能小于0")
|
raise ParseError(f"完全合格数不能小于0:{self.count_real}-{self.count_ok}-{self.count_notok}-{self.count_ok_full}")
|
||||||
self.save(update_fields=["count_ok", "count_notok", "count_ok_full"])
|
self.save(update_fields=["count_ok", "count_notok", "count_ok_full"])
|
||||||
mlog = self.mlog
|
mlog = self.mlog
|
||||||
if mlog:
|
if mlog and cal_mlog:
|
||||||
mlog.cal_mlog_count_from_mlogb()
|
mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
class MlogbDefect(BaseModel):
|
class MlogbDefect(BaseModel):
|
||||||
|
@ -518,7 +522,7 @@ class Mlogbw(BaseModel):
|
||||||
mlogb.count_notok = count_notok
|
mlogb.count_notok = count_notok
|
||||||
mlogb.count_ok = count - mlogb.count_notok
|
mlogb.count_ok = count - mlogb.count_notok
|
||||||
mlogb.save()
|
mlogb.save()
|
||||||
mlogb.cal_count_notok()
|
mlogb.cal_count_notok(cal_mlog=True)
|
||||||
|
|
||||||
class Handover(CommonADModel):
|
class Handover(CommonADModel):
|
||||||
"""
|
"""
|
||||||
|
@ -592,7 +596,7 @@ class Handoverb(BaseModel):
|
||||||
class Handoverbw(BaseModel):
|
class Handoverbw(BaseModel):
|
||||||
"""TN: 单个产品交接记录
|
"""TN: 单个产品交接记录
|
||||||
"""
|
"""
|
||||||
handoverb = models.ForeignKey(Handoverb, verbose_name='关联交接记录', on_delete=models.CASCADE)
|
handoverb = models.ForeignKey(Handoverb, verbose_name='关联交接记录', on_delete=models.CASCADE, related_name="w_handoverb")
|
||||||
number = models.TextField('单个编号')
|
number = models.TextField('单个编号')
|
||||||
wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.CASCADE
|
wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.CASCADE
|
||||||
, related_name='wpr_handoverbw', null=True, blank=True)
|
, related_name='wpr_handoverbw', null=True, blank=True)
|
||||||
|
@ -673,15 +677,14 @@ class BatchSt(BaseModel):
|
||||||
cls_qs = cls.objects.filter(batch=batch)
|
cls_qs = cls.objects.filter(batch=batch)
|
||||||
if cls_qs.exists():
|
if cls_qs.exists():
|
||||||
if reuse_node:
|
if reuse_node:
|
||||||
node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(
|
node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(handover=None, mio=None, mlog=None)).order_by('-version').first()
|
||||||
material_start__isnull=True)).order_by('-version').first()
|
|
||||||
if node is None:
|
if node is None:
|
||||||
raise ParseError(f"{batch}-该批次号因物料不同不可引用")
|
raise ParseError(f"{batch}-该批次号因物料不同不可引用")
|
||||||
elif node.material_start is None:
|
elif node.material_start is None:
|
||||||
node.material_start = material_start
|
node.material_start = material_start
|
||||||
node.save(update_fields = ["material_start"])
|
node.save(update_fields = ["material_start"])
|
||||||
elif node.material_start is not None and node.material_start != material_start:
|
# elif node.material_start is not None and node.material_start != material_start:
|
||||||
raise ParseError(f"{batch}-该批次号因物料不同不可引用-{str(node.material_start)} vs {str(material_start)}")
|
# raise ParseError(f"{batch}-该批次号因物料不同不可引用-{str(node.material_start)} vs {str(material_start)}")
|
||||||
return node, False
|
return node, False
|
||||||
else:
|
else:
|
||||||
latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"]
|
latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"]
|
||||||
|
|
|
@ -25,10 +25,17 @@ def main(batch: str):
|
||||||
if mlogb1_qs.exists():
|
if mlogb1_qs.exists():
|
||||||
data[f"{mgroup_name}_日期"] = []
|
data[f"{mgroup_name}_日期"] = []
|
||||||
data[f"{mgroup_name}_操作人"] = []
|
data[f"{mgroup_name}_操作人"] = []
|
||||||
|
data[f"{mgroup_name}_count_use"] = 0
|
||||||
data[f"{mgroup_name}_count_real"] = 0
|
data[f"{mgroup_name}_count_real"] = 0
|
||||||
data[f"{mgroup_name}_count_ok"] = 0
|
data[f"{mgroup_name}_count_ok"] = 0
|
||||||
data[f"{mgroup_name}_count_ok_full"] = 0
|
data[f"{mgroup_name}_count_ok_full"] = 0
|
||||||
|
mlogb_q_ids = []
|
||||||
for item in mlogb1_qs:
|
for item in mlogb1_qs:
|
||||||
|
# 找到对应的输入
|
||||||
|
mlogb_from:Mlogb = item.mlogb_from
|
||||||
|
if mlogb_from:
|
||||||
|
mlogb_q_ids.append(mlogb_from.id)
|
||||||
|
data[f"{mgroup_name}_count_use"] += mlogb_from.count_use
|
||||||
if item.mlog.handle_user:
|
if item.mlog.handle_user:
|
||||||
data[f"{mgroup_name}_操作人"].append(item.mlog.handle_user)
|
data[f"{mgroup_name}_操作人"].append(item.mlog.handle_user)
|
||||||
if item.mlog.handle_date:
|
if item.mlog.handle_date:
|
||||||
|
@ -48,6 +55,11 @@ def main(batch: str):
|
||||||
data[f"{mgroup_name}_合格率"] = 0
|
data[f"{mgroup_name}_合格率"] = 0
|
||||||
|
|
||||||
mlogbd1_qs = MlogbDefect.objects.filter(mlogb__in=mlogb1_qs, count__gt=0).values("defect__name").annotate(total=Sum("count"))
|
mlogbd1_qs = MlogbDefect.objects.filter(mlogb__in=mlogb1_qs, count__gt=0).values("defect__name").annotate(total=Sum("count"))
|
||||||
|
mlogbd1_q_qs = MlogbDefect.objects.filter(mlogb__id__in=mlogb_q_ids, count__gt=0).values("defect__name").annotate(total=Sum("count"))
|
||||||
|
|
||||||
|
for item in mlogbd1_q_qs:
|
||||||
|
data[f"{mgroup_name}_加工前_缺陷_{item['defect__name']}"] = item["total"]
|
||||||
|
data[f"{mgroup_name}_加工前_缺陷_{item['defect__name']}_比例"] = round((item["total"] / data[f"{mgroup_name}_count_use"])*100, 2)
|
||||||
|
|
||||||
for item in mlogbd1_qs:
|
for item in mlogbd1_qs:
|
||||||
data[f"{mgroup_name}_缺陷_{item['defect__name']}"] = item["total"]
|
data[f"{mgroup_name}_缺陷_{item['defect__name']}"] = item["total"]
|
||||||
|
@ -58,6 +70,7 @@ def main(batch: str):
|
||||||
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}_操作人"]])
|
||||||
|
|
||||||
|
|
||||||
mlogb2_qs = Mlogb.objects.filter(mlog__submit_time__isnull=False, material_out__isnull=False, mlog__mgroup__name="外观检验", mlog__is_fix=True, batch=batch)
|
mlogb2_qs = Mlogb.objects.filter(mlog__submit_time__isnull=False, material_out__isnull=False, mlog__mgroup__name="外观检验", mlog__is_fix=True, batch=batch)
|
||||||
if mlogb2_qs.exists():
|
if mlogb2_qs.exists():
|
||||||
data["外观检验_返修_日期"] = []
|
data["外观检验_返修_日期"] = []
|
||||||
|
@ -72,7 +85,7 @@ def main(batch: str):
|
||||||
data["外观检验_返修_日期"].append(item.mlog.handle_date)
|
data["外观检验_返修_日期"].append(item.mlog.handle_date)
|
||||||
data["外观检验_返修_count_real"] += item.count_real
|
data["外观检验_返修_count_real"] += item.count_real
|
||||||
data["外观检验_返修_count_ok"] += item.count_ok
|
data["外观检验_返修_count_ok"] += item.count_ok
|
||||||
data["外观检验_返修_count_ok_full"] += item.count_ok_full
|
data["外观检验_返修_count_ok_full"] += item.count_ok_full if item.count_ok_full else 0
|
||||||
|
|
||||||
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["外观检验_返修_日期"]])
|
||||||
|
|
|
@ -192,6 +192,7 @@ class WMaterialSerializer(CustomModelSerializer):
|
||||||
notok_sign_name = serializers.SerializerMethodField()
|
notok_sign_name = serializers.SerializerMethodField()
|
||||||
defect_name = serializers.CharField(source="defect.name", read_only=True)
|
defect_name = serializers.CharField(source="defect.name", read_only=True)
|
||||||
count_working = serializers.IntegerField(read_only=True, label='在制数量')
|
count_working = serializers.IntegerField(read_only=True, label='在制数量')
|
||||||
|
count_handovering = serializers.IntegerField(read_only=True, label='正在交送的数量')
|
||||||
|
|
||||||
def get_notok_sign_name(self, obj):
|
def get_notok_sign_name(self, obj):
|
||||||
return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None
|
return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None
|
||||||
|
@ -202,7 +203,10 @@ class WMaterialSerializer(CustomModelSerializer):
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
ret = super().to_representation(instance)
|
ret = super().to_representation(instance)
|
||||||
|
if 'count' in ret and 'count_working' in ret:
|
||||||
ret['count_cando'] = str(Decimal(ret['count']) - Decimal(ret['count_working']))
|
ret['count_cando'] = str(Decimal(ret['count']) - Decimal(ret['count_working']))
|
||||||
|
if 'count' in ret and 'count_handovering' in ret:
|
||||||
|
ret['count_canhandover'] = str(Decimal(ret['count']) - Decimal(ret['count_handovering']))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class MlogbDefectSerializer(CustomModelSerializer):
|
class MlogbDefectSerializer(CustomModelSerializer):
|
||||||
|
@ -388,7 +392,7 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogbin_defect_objects:
|
if mlogbin_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
|
||||||
mlogbin.cal_count_pn_jgqbl()
|
mlogbin.cal_count_pn_jgqbl(cal_mlog=False)
|
||||||
|
|
||||||
# mlogb只用于组合件输出物填写
|
# mlogb只用于组合件输出物填写
|
||||||
brotherId_should_list = material_out.brothers
|
brotherId_should_list = material_out.brothers
|
||||||
|
@ -444,7 +448,8 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogb_defect_objects:
|
if mlogb_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
||||||
mlogb.cal_count_notok()
|
mlogb.cal_count_notok(cal_mlog=False)
|
||||||
|
instance.cal_mlog_count_from_mlogb()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
@ -494,7 +499,7 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogbin_defect_objects:
|
if mlogbin_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogbin_defect_objects)
|
||||||
minx.cal_count_pn_jgqbl()
|
minx.cal_count_pn_jgqbl(cal_mlog=False)
|
||||||
|
|
||||||
# 修改产出
|
# 修改产出
|
||||||
if instance.fill_way == Mlog.MLOG_2 and instance.material_out.brothers:
|
if instance.fill_way == Mlog.MLOG_2 and instance.material_out.brothers:
|
||||||
|
@ -516,6 +521,8 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
batch_out = generate_new_batch(batch_in, instance)
|
batch_out = generate_new_batch(batch_in, instance)
|
||||||
mox, _ = Mlogb.objects.get_or_create(mlog=instance, batch=batch_out,
|
mox, _ = Mlogb.objects.get_or_create(mlog=instance, batch=batch_out,
|
||||||
mtask=instance.mtask, material_out=instance.material_out)
|
mtask=instance.mtask, material_out=instance.material_out)
|
||||||
|
# 需要同步更新数量
|
||||||
|
mox.count_real = instance.count_real
|
||||||
mox.count_ok = instance.count_ok
|
mox.count_ok = instance.count_ok
|
||||||
mox.count_notok = instance.count_notok
|
mox.count_notok = instance.count_notok
|
||||||
mox.count_break_t = instance.count_break_t
|
mox.count_break_t = instance.count_break_t
|
||||||
|
@ -539,7 +546,8 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogb_defect_objects:
|
if mlogb_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
||||||
mox.cal_count_notok()
|
mox.cal_count_notok(cal_mlog=False)
|
||||||
|
instance.cal_mlog_count_from_mlogb()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
@ -578,7 +586,10 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
supplier = attrs.get('supplier', None)
|
supplier = attrs.get('supplier', None)
|
||||||
if not supplier:
|
if not supplier:
|
||||||
raise ParseError('外协必须选择外协单位')
|
raise ParseError('外协必须选择外协单位')
|
||||||
|
|
||||||
mtask = attrs.get('mtask', None)
|
mtask = attrs.get('mtask', None)
|
||||||
|
mlogdefect = attrs.get('mlogdefect', None)
|
||||||
|
if mlogdefect is None:
|
||||||
count_notok = 0
|
count_notok = 0
|
||||||
for i in attrs:
|
for i in attrs:
|
||||||
if 'count_n_' in i:
|
if 'count_n_' in i:
|
||||||
|
@ -588,10 +599,8 @@ class MlogSerializer(CustomModelSerializer):
|
||||||
attrs['count_notok'] = count_notok
|
attrs['count_notok'] = count_notok
|
||||||
if attrs['count_ok'] < 0:
|
if attrs['count_ok'] < 0:
|
||||||
raise ParseError('合格数量不能小于0')
|
raise ParseError('合格数量不能小于0')
|
||||||
if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']:
|
if attrs['count_real'] != attrs['count_ok'] + attrs['count_notok']:
|
||||||
pass
|
raise ParseError('生产数量需等于合格数量+不合格数量')
|
||||||
else:
|
|
||||||
raise ParseError('生产数量不能小于合格数量')
|
|
||||||
if mtask:
|
if mtask:
|
||||||
if mtask.start_date == mtask.end_date:
|
if mtask.start_date == mtask.end_date:
|
||||||
attrs['handle_date'] = mtask.start_date
|
attrs['handle_date'] = mtask.start_date
|
||||||
|
@ -755,7 +764,7 @@ class MlogbInSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogb_defect_objects:
|
if mlogb_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
||||||
ins.cal_count_pn_jgqbl()
|
ins.cal_count_pn_jgqbl(cal_mlog=False)
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
class MlogbInUpdateSerializer(CustomModelSerializer):
|
class MlogbInUpdateSerializer(CustomModelSerializer):
|
||||||
|
@ -773,7 +782,7 @@ class MlogbInUpdateSerializer(CustomModelSerializer):
|
||||||
mlogbdefect = validated_data.pop("mlogbdefect", None)
|
mlogbdefect = validated_data.pop("mlogbdefect", None)
|
||||||
if mlog.submit_time is not None:
|
if mlog.submit_time is not None:
|
||||||
raise ParseError('生产日志已提交不可编辑')
|
raise ParseError('生产日志已提交不可编辑')
|
||||||
ins = super().update(instance, validated_data)
|
ins:Mlogb = super().update(instance, validated_data)
|
||||||
if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
|
if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH:
|
||||||
MlogbDefect.objects.filter(mlogb=ins).delete()
|
MlogbDefect.objects.filter(mlogb=ins).delete()
|
||||||
mlogb_defect_objects = [
|
mlogb_defect_objects = [
|
||||||
|
@ -782,7 +791,21 @@ class MlogbInUpdateSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogb_defect_objects:
|
if mlogb_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
||||||
ins.cal_count_notok()
|
ins.cal_count_pn_jgqbl(cal_mlog=False)
|
||||||
|
# 只有普通工序的才可联动
|
||||||
|
material_out:Material = ins.mlog.material_out
|
||||||
|
route:Route = mlog.route
|
||||||
|
if material_out.tracking == Material.MA_TRACKING_BATCH:
|
||||||
|
if route and route.process and route.process.mtype == Process.PRO_NORMAL:
|
||||||
|
mlogbout_qs = Mlogb.objects.filter(mlog=ins.mlog, mlogb_from=ins)
|
||||||
|
if mlogbout_qs.count() == 1:
|
||||||
|
mlogbout = mlogbout_qs.first()
|
||||||
|
mlogbout.count_real = ins.count_use - ins.count_pn_jgqbl - ins.count_break
|
||||||
|
mlogbout.count_ok = mlogbout.count_real - mlogbout.count_notok
|
||||||
|
if mlogbout.count_real < 0 or mlogbout.count_ok < 0:
|
||||||
|
raise ParseError("对应的产出数异常")
|
||||||
|
mlogbout.save(update_fields=["count_real", "count_ok"])
|
||||||
|
mlogbout.cal_count_notok(cal_mlog=False)
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
class MlogbwCreateUpdateSerializer(CustomModelSerializer):
|
class MlogbwCreateUpdateSerializer(CustomModelSerializer):
|
||||||
|
@ -898,7 +921,7 @@ class MlogbOutUpdateSerializer(CustomModelSerializer):
|
||||||
]
|
]
|
||||||
if mlogb_defect_objects:
|
if mlogb_defect_objects:
|
||||||
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
MlogbDefect.objects.bulk_create(mlogb_defect_objects)
|
||||||
ins.cal_count_notok()
|
ins.cal_count_notok(cal_mlog=False)
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
|
|
|
@ -150,6 +150,8 @@ def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
|
||||||
"""
|
"""
|
||||||
生产日志提交后需要执行的操作
|
生产日志提交后需要执行的操作
|
||||||
"""
|
"""
|
||||||
|
if mlog.count_real == 0:
|
||||||
|
raise ParseError('产出数量不能为0')
|
||||||
if mlog.submit_time is not None:
|
if mlog.submit_time is not None:
|
||||||
return
|
return
|
||||||
if now is None:
|
if now is None:
|
||||||
|
@ -914,8 +916,39 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime,
|
||||||
|
|
||||||
|
|
||||||
def handover_revert(handover:Handover):
|
def handover_revert(handover:Handover):
|
||||||
|
if handover.submit_time is None:
|
||||||
|
raise ParseError('该交接单未提交!')
|
||||||
|
handover_type = handover.type
|
||||||
|
handover_mtype = handover.mtype
|
||||||
|
if handover_type in [Handover.H_NORMAL, Handover.H_REPAIR] and handover_mtype == Handover.H_NORMAL:
|
||||||
|
raise ParseError('该交接单不支持撤销!')
|
||||||
|
handoverb_qs = Handoverb.objects.filter(handover=handover)
|
||||||
|
material = handover.material
|
||||||
|
for item in handoverb_qs:
|
||||||
|
wm = item.wm
|
||||||
|
wm_to = item.wm_to
|
||||||
|
if wm is None or wm_to is None:
|
||||||
|
raise ParseError('该交接单不支持撤销2!')
|
||||||
|
wm.count = wm.count + item.count
|
||||||
|
wm.save()
|
||||||
|
wm_to.count = wm_to.count - item.count
|
||||||
|
if wm_to.count < 0:
|
||||||
|
raise ParseError('库存不足无法撤回!')
|
||||||
|
wm_to.save()
|
||||||
|
if material.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
|
handoverbws = Handoverbw.objects.filter(handoverb=item)
|
||||||
|
if handoverbws.count() != item.count:
|
||||||
|
raise ParseError("交接与明细数量不一致,操作失败")
|
||||||
|
for item in handoverbws:
|
||||||
|
wpr:Wpr = item.wpr
|
||||||
|
Wpr.change_or_new(wpr=wpr, wm=wm, old_wm=wpr.wm, old_mb=wpr.mb, add_version=False)
|
||||||
|
|
||||||
|
handover.submit_time = None
|
||||||
|
handover.submit_user = None
|
||||||
|
handover.save()
|
||||||
|
# 删除追踪链
|
||||||
BatchLog.clear(handover=handover)
|
BatchLog.clear(handover=handover)
|
||||||
pass
|
|
||||||
|
|
||||||
def mlog_submit_validate(ins: Mlog):
|
def mlog_submit_validate(ins: Mlog):
|
||||||
if ins.submit_time:
|
if ins.submit_time:
|
||||||
|
|
|
@ -27,7 +27,7 @@ from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer,
|
||||||
MlogbOutUpdateSerializer, FmlogSerializer, FmlogUpdateSerializer, BatchStSerializer,
|
MlogbOutUpdateSerializer, FmlogSerializer, FmlogUpdateSerializer, BatchStSerializer,
|
||||||
MlogbwCreateUpdateSerializer, HandoverMgroupSerializer, MlogListSerializer,
|
MlogbwCreateUpdateSerializer, HandoverMgroupSerializer, MlogListSerializer,
|
||||||
MlogbSerializer, MlogUserSerializer, BatchLogSerializer)
|
MlogbSerializer, MlogUserSerializer, BatchLogSerializer)
|
||||||
from .services import mlog_submit, handover_submit, mlog_revert, get_batch_dag
|
from .services import mlog_submit, handover_submit, mlog_revert, get_batch_dag, handover_revert
|
||||||
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
from apps.wpm.services import mlog_submit_validate, generate_new_batch
|
||||||
from apps.wf.models import State
|
from apps.wf.models import State
|
||||||
from apps.wpmw.models import Wpr
|
from apps.wpmw.models import Wpr
|
||||||
|
@ -428,6 +428,18 @@ class HandoverViewSet(CustomModelViewSet):
|
||||||
handover_submit(ins, user, None)
|
handover_submit(ins, user, None)
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
@action(methods=['post'], detail=True, perms_map={'post': 'handover.submit'}, serializer_class=Serializer)
|
||||||
|
@transaction.atomic
|
||||||
|
def revert(self, request, *args, **kwargs):
|
||||||
|
"""交接记录撤回(变动车间库存)
|
||||||
|
|
||||||
|
交接记录撤回
|
||||||
|
"""
|
||||||
|
ins: Handover = self.get_object()
|
||||||
|
if ins.submit_time:
|
||||||
|
handover_revert(ins)
|
||||||
|
return Response()
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=HandoverMgroupSerializer)
|
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=HandoverMgroupSerializer)
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def mgroups(self, request, *args, **kwargs):
|
def mgroups(self, request, *args, **kwargs):
|
||||||
|
@ -563,6 +575,12 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
if ins.mlog.submit_time is not None:
|
if ins.mlog.submit_time is not None:
|
||||||
raise ParseError('生产日志已提交不可编辑')
|
raise ParseError('生产日志已提交不可编辑')
|
||||||
ins.delete()
|
ins.delete()
|
||||||
|
ins.mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
ins = serializer.save()
|
||||||
|
ins.mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
|
@ -611,7 +629,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
d_count_real = mlogbin.count_use - mlogbin.count_pn_jgqbl
|
d_count_real = mlogbin.count_use - mlogbin.count_pn_jgqbl
|
||||||
d_count_ok = d_count_real
|
d_count_ok = d_count_real
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=
|
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=
|
||||||
update_dict(m_dict, {"count_real": d_count_real, "count_ok": d_count_ok}))
|
update_dict(m_dict, {"count_real": d_count_real, "count_ok": d_count_ok, "count_ok_full": d_count_ok}))
|
||||||
mlogbout.count_json_from = mlogbin.count_json_from
|
mlogbout.count_json_from = mlogbin.count_json_from
|
||||||
mlogbout.save(update_fields=["count_json_from"])
|
mlogbout.save(update_fields=["count_json_from"])
|
||||||
if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
|
@ -627,34 +645,42 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
if process and process.number_to_batch:
|
if process and process.number_to_batch:
|
||||||
m_dict["batch"] = mlogbwin.number
|
m_dict["batch"] = mlogbwin.number
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(
|
mlogbout, _ = Mlogb.objects.get_or_create(
|
||||||
mlogbw_from=mlogbwin, defaults=update_dict(m_dict, {"count_real": div_number, "count_ok": div_number}))
|
mlogbw_from=mlogbwin, defaults=update_dict(m_dict, {"count_real": div_number, "count_ok": div_number, "count_ok_full": div_number}))
|
||||||
if lenx == 1:
|
if lenx == 1:
|
||||||
mlogbout.mlogb_from = mlogbin
|
mlogbout.mlogb_from = mlogbin
|
||||||
mlogbout.number_from = mlogbwin.number
|
mlogbout.number_from = mlogbwin.number
|
||||||
mlogbout.save()
|
mlogbout.save()
|
||||||
elif material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
elif material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
d_count_real = (mlogbin.count_use-mlogbin.count_pn_jgqbl) * div_number
|
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=m_dict)
|
||||||
d_count_ok = d_count_real
|
d_count_real = 0
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,
|
|
||||||
{"count_real": d_count_real, "count_ok": d_count_ok}))
|
|
||||||
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
|
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
|
||||||
wpr_ = mlogbwin.wpr
|
wpr_ = mlogbwin.wpr
|
||||||
|
for key, val in wpr_.oinfo.items():
|
||||||
|
if val['name'] == "切片数":
|
||||||
|
div_number = int(val["val"])
|
||||||
|
d_count_real = d_count_real + div_number
|
||||||
if div_number == 1:
|
if div_number == 1:
|
||||||
Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin})
|
Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin})
|
||||||
else:
|
else:
|
||||||
for i in range(div_number):
|
for i in range(div_number):
|
||||||
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f'{wpr_.number}-{i+1}', defaults={"mlogbw_from": mlogbwin})
|
Mlogbw.objects.get_or_create(mlogb=mlogbout, number=f'{wpr_.number}-{i+1}', defaults={"mlogbw_from": mlogbwin})
|
||||||
|
d_count_ok = d_count_real
|
||||||
|
mlogbout.count_real = d_count_real
|
||||||
|
mlogbout.count_ok = d_count_ok
|
||||||
|
mlogbout.count_ok_full = d_count_ok
|
||||||
|
mlogbout.save(update_fields=["count_real", "count_ok", "count_ok_full"])
|
||||||
elif material_in.tracking == Material.MA_TRACKING_BATCH and material_out.tracking == Material.MA_TRACKING_BATCH:
|
elif material_in.tracking == Material.MA_TRACKING_BATCH and material_out.tracking == Material.MA_TRACKING_BATCH:
|
||||||
d_count_real = (mlogbin.count_use-mlogbin.count_pn_jgqbl) * div_number
|
d_count_real = (mlogbin.count_use-mlogbin.count_pn_jgqbl) * div_number
|
||||||
d_count_ok = d_count_real
|
d_count_ok = d_count_real
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,{"count_real": d_count_real, "count_ok": d_count_ok}))
|
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,{"count_real": d_count_real, "count_ok": d_count_ok, "count_ok_full": d_count_ok}))
|
||||||
mlogbout.count_json_from = mlogbin.count_json_from
|
mlogbout.count_json_from = mlogbin.count_json_from
|
||||||
mlogbout.save(update_fields=["count_json_from"])
|
mlogbout.save(update_fields=["count_json_from"])
|
||||||
elif mtype == Process.PRO_MERGE: # 支持批到批,批到个
|
elif mtype == Process.PRO_MERGE: # 支持批到批,批到个
|
||||||
xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / route.div_number)
|
div_number = route.div_number
|
||||||
|
xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / div_number)
|
||||||
d_count_real = xcount
|
d_count_real = xcount
|
||||||
d_count_ok = xcount
|
d_count_ok = xcount
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict, {"count_real": d_count_real, "count_ok": d_count_ok}))
|
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict, {"count_real": d_count_real, "count_ok": d_count_ok, "count_ok_full": d_count_ok}))
|
||||||
mlogbout.count_json_from = mlogbin.count_json_from
|
mlogbout.count_json_from = mlogbin.count_json_from
|
||||||
mlogbout.save(update_fields=["count_json_from"])
|
mlogbout.save(update_fields=["count_json_from"])
|
||||||
wpr_number_rule = process.wpr_number_rule
|
wpr_number_rule = process.wpr_number_rule
|
||||||
|
@ -678,13 +704,14 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
elif is_fix:# 支持批到批,个到个
|
elif is_fix:# 支持批到批,个到个
|
||||||
d_count_real = mlogbin.count_use-mlogbin.count_pn_jgqbl
|
d_count_real = mlogbin.count_use-mlogbin.count_pn_jgqbl
|
||||||
d_count_ok = d_count_real
|
d_count_ok = d_count_real
|
||||||
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,{"count_real": d_count_real, "count_ok": d_count_ok}))
|
mlogbout, _ = Mlogb.objects.get_or_create(mlogb_from=mlogbin, defaults=update_dict(m_dict,{"count_real": d_count_real, "count_ok": d_count_ok, "count_ok_full": d_count_ok}))
|
||||||
if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE:
|
||||||
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
|
for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"):
|
||||||
wpr_ = mlogbwin.wpr
|
wpr_ = mlogbwin.wpr
|
||||||
Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin})
|
Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin})
|
||||||
else:
|
else:
|
||||||
raise ParseError("不支持生成产出物料!")
|
raise ParseError("不支持生成产出物料!")
|
||||||
|
mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_number_with_rule(cls, rule, material_out:Material, gen_count=1):
|
def gen_number_with_rule(cls, rule, material_out:Material, gen_count=1):
|
||||||
|
@ -698,6 +725,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
cq_w = 4
|
cq_w = 4
|
||||||
if '02d' in rule:
|
if '02d' in rule:
|
||||||
cq_w = 2
|
cq_w = 2
|
||||||
|
n_count = 0
|
||||||
if wpr:
|
if wpr:
|
||||||
try:
|
try:
|
||||||
n_count = int(wpr.number[-cq_w:].lstrip('0'))
|
n_count = int(wpr.number[-cq_w:].lstrip('0'))
|
||||||
|
@ -705,9 +733,9 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust
|
||||||
raise ParseError(f"获取该类产品最后编号错误: {str(e)}")
|
raise ParseError(f"获取该类产品最后编号错误: {str(e)}")
|
||||||
try:
|
try:
|
||||||
if gen_count == 1:
|
if gen_count == 1:
|
||||||
return rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count, c_year2=c_year2)
|
return rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count+1, c_year2=c_year2)
|
||||||
else:
|
else:
|
||||||
return [rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count+i, c_year2=c_year2) for i in range(gen_count)]
|
return [rule.format(c_year=c_year, c_month=c_month, m_model=m_model, n_count=n_count+i+1, c_year2=c_year2) for i in range(gen_count)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ParseError(f"个号生成错误: {e}")
|
raise ParseError(f"个号生成错误: {e}")
|
||||||
|
|
||||||
|
@ -716,6 +744,11 @@ class MlogbOutViewSet(UpdateModelMixin, CustomGenericViewSet):
|
||||||
queryset = Mlogb.objects.filter(material_out__isnull=False)
|
queryset = Mlogb.objects.filter(material_out__isnull=False)
|
||||||
serializer_class = MlogbOutUpdateSerializer
|
serializer_class = MlogbOutUpdateSerializer
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def perform_update(self, serializer):
|
||||||
|
ins:Mlogb = serializer.save()
|
||||||
|
ins.mlog.cal_mlog_count_from_mlogb()
|
||||||
|
|
||||||
|
|
||||||
class FmlogViewSet(CustomModelViewSet):
|
class FmlogViewSet(CustomModelViewSet):
|
||||||
perms_map = {'get': '*', 'post': 'mlog.create', 'put': 'mlog.update', 'delete': 'mlog.delete'}
|
perms_map = {'get': '*', 'post': 'mlog.create', 'put': 'mlog.update', 'delete': 'mlog.delete'}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Wpr(BaseModel):
|
||||||
wpr_from = models.ForeignKey("self", verbose_name="来源于", on_delete=models.CASCADE, null=True, blank=True)
|
wpr_from = models.ForeignKey("self", verbose_name="来源于", on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def change_or_new(cls, wpr=None, number=None, mb=None, wm=None, old_mb=None, old_wm=None, ftest=None, wpr_from=None):
|
def change_or_new(cls, wpr=None, number=None, mb=None, wm=None, old_mb=None, old_wm=None, ftest=None, wpr_from=None, add_version=True):
|
||||||
is_create = False
|
is_create = False
|
||||||
if wpr is None and number is None:
|
if wpr is None and number is None:
|
||||||
raise ParseError("id和number不能同时为空")
|
raise ParseError("id和number不能同时为空")
|
||||||
|
@ -105,6 +105,7 @@ class Wpr(BaseModel):
|
||||||
WprDefect.objects.filter(wpr=ins).delete()
|
WprDefect.objects.filter(wpr=ins).delete()
|
||||||
if wm.defect:
|
if wm.defect:
|
||||||
WprDefect.objects.create(wpr=ins, defect=wm.defect, is_main=True)
|
WprDefect.objects.create(wpr=ins, defect=wm.defect, is_main=True)
|
||||||
|
if add_version:
|
||||||
ins.version = ins.version + 1
|
ins.version = ins.version + 1
|
||||||
ins.save()
|
ins.save()
|
||||||
if ftest:
|
if ftest:
|
||||||
|
|
24
changelog.md
24
changelog.md
|
@ -1,3 +1,27 @@
|
||||||
|
## 2.6.2025061715
|
||||||
|
- feat: 新增功能
|
||||||
|
- mlogbin的编辑与mlogbout的联动 [caoqianming]
|
||||||
|
- mlogb首次创建需要更新count_ok_full [caoqianming]
|
||||||
|
- 支持从wpr处获取切片数 [caoqianming]
|
||||||
|
- workchain通过线程执行 [caoqianming]
|
||||||
|
- mlogbin 和 update时cal_count_notok cal_mlog传False [caoqianming]
|
||||||
|
- ichat 添加workchain接口 [caoqianming]
|
||||||
|
- 优化get_tyy_data [caoqianming]
|
||||||
|
- batch_gxerp添加加工前缺陷 [caoqianming]
|
||||||
|
- 增加check_sql_safe 稳定性 [caoqianming]
|
||||||
|
- 放开出入库的物料匹配 [caoqianming]
|
||||||
|
- 采购入库节点可复用 [caoqianming]
|
||||||
|
- fix: 问题修复
|
||||||
|
- mlogbinupdate时的bug [caoqianming]
|
||||||
|
- mlogserializer需要同步count_real [caoqianming]
|
||||||
|
- cal_mlog_count_from_mlogb 需要传参触发 [caoqianming]
|
||||||
|
- mlog 数值计算bug [caoqianming]
|
||||||
|
- wmaterial get在传入query参数时完善can_do的逻辑 [caoqianming]
|
||||||
|
## 2.6.2025060913
|
||||||
|
- feat: 新增功能
|
||||||
|
- inm 和 wpm添加wpr number的查询条件 [caoqianming]
|
||||||
|
- 优化设备采集cd [caoqianming]
|
||||||
|
- mlogb cal_count_pn_jgqbl [caoqianming]
|
||||||
## 2.6.2025060617
|
## 2.6.2025060617
|
||||||
- feat: 新增功能
|
- feat: 新增功能
|
||||||
- mlogbw也进行mlogb.cal_count_notok() [caoqianming]
|
- mlogbw也进行mlogb.cal_count_notok() [caoqianming]
|
||||||
|
|
|
@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
|
||||||
ALLOWED_HOSTS = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
SYS_NAME = '星途工厂综合管理系统'
|
SYS_NAME = '星途工厂综合管理系统'
|
||||||
SYS_VERSION = '2.6.2025060617'
|
SYS_VERSION = '2.6.2025061715'
|
||||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
Loading…
Reference in New Issue