From 11f06c36f78786e4738f500bf17f50ec11b7ea1b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 13:03:03 +0800 Subject: [PATCH 01/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E9=87=87=E9=9B=86cd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/em/cd.py | 74 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/apps/em/cd.py b/apps/em/cd.py index 70a4ec77..ce544053 100644 --- a/apps/em/cd.py +++ b/apps/em/cd.py @@ -4,6 +4,41 @@ import json import time from django.core.cache import cache from apps.utils.thread import MyThread +import struct + +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 = struct.unpack(' 0: - connect_and_send(retry-1) - else: - cache.set(cd_thread_key, {"err_msg": f"采集器连接失败-{str(e)}"}) + 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": "采集器重置了连接"}) + cache.set(cd_thread_key, {"err_msg": "采集器重置了连接"}) except socket.timeout: + sc = None cache.set(cd_thread_key, {"err_msg": "采集器连接超时"}) + except Exception as e: + sc = None + cache.set(cd_thread_key, {"err_msg": f"采集器连接失败-{str(e)}"}) + while True: cd_thread_val = cache.get(cd_thread_key, default=None) if cd_thread_val is None: @@ -43,23 +77,21 @@ def get_tyy_data_t(host, port): pass break elif cd_thread_val == "get": + cache.set(cd_thread_key, "working") connect_and_send() if sc is None: - cache.set(cd_thread_key, {"err_msg": "采集器连接失败"}) continue resp = sc.recv(1024) - if not resp: - cache.set(cd_thread_key, {"err_msg": f"采集器返回空数据-{str(resp)}"}) - elif len(resp) < 8: - cache.set(cd_thread_key, {"err_msg": f"设备未启动-{str(resp)}"}) + res = handle_bytes(resp) + if isinstance(res, str): + cache.set(cd_thread_key, {"err_msg": f'采集器返回数据错误-{res}'}) else: - json_data = resp[5:-4] - json_str = json_data.decode('utf-8') - res = json.loads(json_str) cache.set(cd_thread_key, res) time.sleep(0.3) -def get_tyy_data(*args): +def get_tyy_data(*args, sleep=0): + if sleep > 0: + time.sleep(sleep) host, port = args[0], int(args[1]) cd_thread_key = f"cd_thread_{host}_{port}" cd_thread_val = cache.get(cd_thread_key, default=None) @@ -69,17 +101,23 @@ def get_tyy_data(*args): cd_thread.start() cache.set(cd_thread_key, "get") num = 0 + get_val = False + while True: num += 1 if num > 8: break val = cache.get(cd_thread_key) if isinstance(val, dict): + get_val = True if "err_msg" in val: raise ParseError(val["err_msg"]) return val time.sleep(0.3) - raise ParseError("获取数据超时") + + if not get_val: + cache.set(cd_thread_key, None) + get_tyy_data(*args, sleep=2) if __name__ == '__main__': From 77c4c54eb1e473f784fd40583ed24152899aaec1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 13:31:46 +0800 Subject: [PATCH 02/39] =?UTF-8?q?feat:=20inm=20=E5=92=8C=20wpm=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0wpr=20number=E7=9A=84=E6=9F=A5=E8=AF=A2=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/inm/filters.py | 1 + apps/wpm/filters.py | 6 ++++-- .../0117_alter_handoverbw_handoverb.py | 19 +++++++++++++++++++ apps/wpm/models.py | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 apps/wpm/migrations/0117_alter_handoverbw_handoverb.py diff --git a/apps/inm/filters.py b/apps/inm/filters.py index dbbc470a..b641be55 100644 --- a/apps/inm/filters.py +++ b/apps/inm/filters.py @@ -35,6 +35,7 @@ class MioFilter(filters.FilterSet): "order": ["exact"], "item_mio__test_date": ["isnull"], "item_mio__test_user": ["isnull"], + "item_mio__w_mioitem__number": ["exact"], "mgroup": ["exact"], "item_mio__batch": ["exact"] } diff --git a/apps/wpm/filters.py b/apps/wpm/filters.py index 858f4e06..e5a0e8b2 100644 --- a/apps/wpm/filters.py +++ b/apps/wpm/filters.py @@ -110,7 +110,8 @@ class MlogFilter(filters.FilterSet): "mgroup__name": ["exact", "in", "contains"], "submit_time": ["isnull"], "fmlog": ["exact"], - "b_mlog__batch": ["exact"] + "b_mlog__batch": ["exact"], + "b_mlog__w_mlogb__number": ["exact"], } def filter_cbatch(self, queryset, name, value): @@ -158,7 +159,8 @@ class HandoverFilter(filters.FilterSet): "recive_mgroup__name": ["exact"], "type": ["exact", "in"], "mtype": ["exact", "in"], - "b_handover__batch": ["exact"] + "b_handover__batch": ["exact"], + "b_handover__w_handoverb__number": ["exact"] } diff --git a/apps/wpm/migrations/0117_alter_handoverbw_handoverb.py b/apps/wpm/migrations/0117_alter_handoverbw_handoverb.py new file mode 100644 index 00000000..71e37275 --- /dev/null +++ b/apps/wpm/migrations/0117_alter_handoverbw_handoverb.py @@ -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='关联交接记录'), + ), + ] diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 4c06fad8..8f0c2609 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -592,7 +592,7 @@ class Handoverb(BaseModel): class Handoverbw(BaseModel): """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('单个编号') wpr = models.ForeignKey("wpmw.wpr", verbose_name='关联产品', on_delete=models.CASCADE , related_name='wpr_handoverbw', null=True, blank=True) From 016ef5351725ae1204659deefe38bc44b5b2a398 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 13:33:13 +0800 Subject: [PATCH 03/39] release: 2.6.2025060913 --- changelog.md | 5 +++++ server/settings.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f5ac508e..957ab8a8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +## 2.6.2025060913 +- feat: 新增功能 + - inm 和 wpm添加wpr number的查询条件 [caoqianming] + - 优化设备采集cd [caoqianming] + - mlogb cal_count_pn_jgqbl [caoqianming] ## 2.6.2025060617 - feat: 新增功能 - mlogbw也进行mlogb.cal_count_notok() [caoqianming] diff --git a/server/settings.py b/server/settings.py index fdab6542..2678e14a 100755 --- a/server/settings.py +++ b/server/settings.py @@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) ALLOWED_HOSTS = ['*'] SYS_NAME = '星途工厂综合管理系统' -SYS_VERSION = '2.6.2025060617' +SYS_VERSION = '2.6.2025060913' X_FRAME_OPTIONS = 'SAMEORIGIN' # Application definition From 7d29b9c4eb4893b37d316b46db539d70a76bee8d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 15:37:17 +0800 Subject: [PATCH 04/39] =?UTF-8?q?feat:=20=E9=87=87=E8=B4=AD=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E8=8A=82=E7=82=B9=E5=8F=AF=E5=A4=8D=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 8f0c2609..33b44d1c 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -673,8 +673,7 @@ class BatchSt(BaseModel): cls_qs = cls.objects.filter(batch=batch) if cls_qs.exists(): if reuse_node: - node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter( - material_start__isnull=True)).order_by('-version').first() + node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(hanover=None, mio=None, mlog=None)).order_by('-version').first() if node is None: raise ParseError(f"{batch}-该批次号因物料不同不可引用") elif node.material_start is None: From 2e3e8f997c799de19dceaa8703e2805e9c854024 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 15:44:31 +0800 Subject: [PATCH 05/39] =?UTF-8?q?feat:=20=E9=87=87=E8=B4=AD=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E8=8A=82=E7=82=B9=E5=8F=AF=E5=A4=8D=E7=94=A82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 33b44d1c..2d7b5a87 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -673,7 +673,7 @@ class BatchSt(BaseModel): cls_qs = cls.objects.filter(batch=batch) if cls_qs.exists(): if reuse_node: - node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(hanover=None, mio=None, mlog=None)).order_by('-version').first() + node:BatchSt = (cls_qs.filter(mio__isnull=False)|cls_qs.filter(handover=None, mio=None, mlog=None)).order_by('-version').first() if node is None: raise ParseError(f"{batch}-该批次号因物料不同不可引用") elif node.material_start is None: From 84690e7b542b7189ab39e1d02a3a76bba49c7a6b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 9 Jun 2025 15:50:08 +0800 Subject: [PATCH 06/39] =?UTF-8?q?feat:=20=E6=94=BE=E5=BC=80=E5=87=BA?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E7=9A=84=E7=89=A9=E6=96=99=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 2d7b5a87..fdbe51d9 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -679,8 +679,8 @@ class BatchSt(BaseModel): elif node.material_start is None: node.material_start = material_start node.save(update_fields = ["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)}") + # 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)}") return node, False else: latest_version = BatchSt.objects.filter(batch=batch).aggregate(Max("version"))["version__max"] From 438d06af2fce94d26f8d61da456e406a8cc2e62b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 10 Jun 2025 08:48:13 +0800 Subject: [PATCH 07/39] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0check=5Fsql=5Fs?= =?UTF-8?q?afe=20=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/bi/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bi/services.py b/apps/bi/services.py index 4c44ccfa..29cc91e4 100644 --- a/apps/bi/services.py +++ b/apps/bi/services.py @@ -5,7 +5,7 @@ from apps.bi.models import Dataset import concurrent 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): From 5e0c515929cbd557db3c89effc5274d1418e01ab Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 10 Jun 2025 10:05:53 +0800 Subject: [PATCH 08/39] =?UTF-8?q?feat:=20batch=5Fgxerp=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8A=A0=E5=B7=A5=E5=89=8D=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/scripts/batch_gxerp.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/wpm/scripts/batch_gxerp.py b/apps/wpm/scripts/batch_gxerp.py index 8bff0645..34065099 100644 --- a/apps/wpm/scripts/batch_gxerp.py +++ b/apps/wpm/scripts/batch_gxerp.py @@ -25,10 +25,17 @@ def main(batch: str): if mlogb1_qs.exists(): 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_ok"] = 0 data[f"{mgroup_name}_count_ok_full"] = 0 + mlogb_q_ids = [] 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: data[f"{mgroup_name}_操作人"].append(item.mlog.handle_user) if item.mlog.handle_date: @@ -48,6 +55,11 @@ def main(batch: str): 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_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: 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}_操作人"] = ";".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) if mlogb2_qs.exists(): data["外观检验_返修_日期"] = [] @@ -72,7 +85,7 @@ def main(batch: str): data["外观检验_返修_日期"].append(item.mlog.handle_date) data["外观检验_返修_count_real"] += item.count_real 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["外观检验_返修_日期"] = ";".join([item.strftime("%Y-%m-%d") for item in data["外观检验_返修_日期"]]) From 8efd28633ca94d179b2a56382036a5cb4530fe7a Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 10 Jun 2025 12:42:02 +0800 Subject: [PATCH 09/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96get=5Ftyy=5Fdat?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/em/cd.py | 144 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 36 deletions(-) diff --git a/apps/em/cd.py b/apps/em/cd.py index ce544053..905ad23a 100644 --- a/apps/em/cd.py +++ b/apps/em/cd.py @@ -5,6 +5,10 @@ import time from django.core.cache import cache from apps.utils.thread import MyThread import struct +import uuid +import logging + +myLogger = logging.getLogger('log') def get_checksum(body_msg): return sum(body_msg) & 0xFF @@ -19,7 +23,7 @@ def handle_bytes(arr): # 读取长度信息 length_arr = arr[2:4][::-1] # 反转字节 - length = struct.unpack(' 0: connect_and_send(retry-1) + else: + if sc: + try: + sc.close() + except Exception: + pass + sc = None except OSError as e: sc = None - cache.set(cd_thread_key, {"err_msg": f"采集器连接失败-{str(e)}"}) + cache.set(cd_thread_key_val, {"err_msg": f"采集器连接失败-{str(e)}"}) except ConnectionResetError: sc = None - cache.set(cd_thread_key, {"err_msg": "采集器重置了连接"}) + cache.set(cd_thread_key_val, {"err_msg": "采集器重置了连接"}) except socket.timeout: sc = None - cache.set(cd_thread_key, {"err_msg": "采集器连接超时"}) + cache.set(cd_thread_key_val, {"err_msg": "采集器连接超时"}) except Exception as e: sc = None - cache.set(cd_thread_key, {"err_msg": f"采集器连接失败-{str(e)}"}) + cache.set(cd_thread_key_val, {"err_msg": f"采集器连接失败-{str(e)}"}) - while True: - cd_thread_val = cache.get(cd_thread_key, default=None) - if cd_thread_val is None: - if sc: - try: - sc.close() - except Exception: - pass - break - elif cd_thread_val == "get": - cache.set(cd_thread_key, "working") + 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() if sc is None: continue resp = sc.recv(1024) res = handle_bytes(resp) if isinstance(res, str): - cache.set(cd_thread_key, {"err_msg": f'采集器返回数据错误-{res}'}) + cache.set(cd_thread_key_val, {"err_msg": f'采集器返回数据错误-{res}'}) + elif not res: + cache.set(cd_thread_key_val, {"err_msg": f"采集器返回数据为空-{str(res)}"}) else: - cache.set(cd_thread_key, res) + myLogger.info(f"采集器返回数据-{res}") + cache.set(cd_thread_key_val, res) time.sleep(0.3) -def get_tyy_data(*args, sleep=0): - if sleep > 0: - time.sleep(sleep) + if sc: + try: + sc.close() + except Exception: + pass + +def get_tyy_data_2(*args, retry=1): host, port = args[0], int(args[1]) - cd_thread_key = f"cd_thread_{host}_{port}" - cd_thread_val = cache.get(cd_thread_key, default=None) - if cd_thread_val is None: - cache.set(cd_thread_key, "start") - cd_thread = MyThread(target=get_tyy_data_t, args=(host, port), daemon=True) + cd_thread_key_id = f"cd_thread_{host}_{port}_id" + cd_thread_key_val = f"cd_thread_{host}_{port}_val" + cd_thread_val_id = cache.get(cd_thread_key_id, default=None) + if cd_thread_val_id is None: + 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() - cache.set(cd_thread_key, "get") + cache.set(cd_thread_key_val, "get") num = 0 get_val = False @@ -107,7 +119,7 @@ def get_tyy_data(*args, sleep=0): num += 1 if num > 8: break - val = cache.get(cd_thread_key) + val = cache.get(cd_thread_key_val) if isinstance(val, dict): get_val = True if "err_msg" in val: @@ -115,10 +127,70 @@ def get_tyy_data(*args, sleep=0): return val time.sleep(0.3) - if not get_val: - cache.set(cd_thread_key, None) - get_tyy_data(*args, sleep=2) + 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__': - print(get_tyy_data()) \ No newline at end of file +sc_all = {} + +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 Exception 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 From bfc37f69af48fa340562c29532562a2e1b999eee Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 10 Jun 2025 13:35:38 +0800 Subject: [PATCH 10/39] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96get=5Ftyy=5Fdat?= =?UTF-8?q?a=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/em/cd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/em/cd.py b/apps/em/cd.py index 905ad23a..cfa76a31 100644 --- a/apps/em/cd.py +++ b/apps/em/cd.py @@ -176,7 +176,7 @@ def get_tyy_data(*args): sc_all.pop(f"{host}_{port}", None) sc = None raise ParseError(f"采集器连接失败-{str(e)}") - except Exception as e: + except TimeoutError as e: if sc: try: sc.close() @@ -184,7 +184,7 @@ def get_tyy_data(*args): pass sc_all.pop(f"{host}_{port}", None) sc = None - raise ParseError(f"采集器连接失败-{str(e)}") + raise ParseError(f"采集器连接超时-{str(e)}") connect_and_send() resp = sc.recv(1024) From fea3c320a99730d17102398af984d044d3343439 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 11 Jun 2025 10:58:31 +0800 Subject: [PATCH 11/39] feat: gen_number_with_rule bug --- apps/wpm/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index eaececeb..2d69ea39 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -698,6 +698,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust cq_w = 4 if '02d' in rule: cq_w = 2 + n_count = 0 if wpr: try: n_count = int(wpr.number[-cq_w:].lstrip('0')) From 0719ef8f4234239b91a21cdb605fbdbad1ecdd7f Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 11 Jun 2025 11:02:50 +0800 Subject: [PATCH 12/39] feat: gen_number_with_rule bug2 --- apps/wpm/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 2d69ea39..6d9a1636 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -706,9 +706,9 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust raise ParseError(f"获取该类产品最后编号错误: {str(e)}") try: 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: - 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: raise ParseError(f"个号生成错误: {e}") From b3000b013fc4d2162100506786b5bb35c6f54cf1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 11 Jun 2025 11:32:07 +0800 Subject: [PATCH 13/39] =?UTF-8?q?feat:=20utask=E6=B7=BB=E5=8A=A0=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pm/filters.py | 2 +- apps/pm/migrations/0022_utask_priority.py | 18 ++++++++++++++++++ apps/pm/models.py | 1 + apps/pm/serializers.py | 4 +++- apps/pm/views.py | 7 ++++--- 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 apps/pm/migrations/0022_utask_priority.py diff --git a/apps/pm/filters.py b/apps/pm/filters.py index 29e96d01..3e19565b 100644 --- a/apps/pm/filters.py +++ b/apps/pm/filters.py @@ -50,7 +50,7 @@ class MtaskFilter(filters.FilterSet): "is_count_utask": ["exact"], "start_date": ["exact", "gte", "lte"], "end_date": ["exact", "gte", "lte"], - "mgroup": ["exact"], + "mgroup": ["exact", "in"], "mgroup__name": ["exact"], "mgroup__cate": ["exact"], "mgroup__process": ["exact"], diff --git a/apps/pm/migrations/0022_utask_priority.py b/apps/pm/migrations/0022_utask_priority.py new file mode 100644 index 00000000..02828d56 --- /dev/null +++ b/apps/pm/migrations/0022_utask_priority.py @@ -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='优先级'), + ), + ] diff --git a/apps/pm/models.py b/apps/pm/models.py index 59e4a1fa..b8b2994c 100644 --- a/apps/pm/models.py +++ b/apps/pm/models.py @@ -37,6 +37,7 @@ class Utask(CommonBDModel): type = models.CharField('任务类型', max_length=10, help_text=str(TASK_TYPE), default='mass') 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( '状态', choices=UTASK_STATES, default=UTASK_CREATED, help_text=str(UTASK_STATES)) number = models.CharField('编号', max_length=50, unique=True) diff --git a/apps/pm/serializers.py b/apps/pm/serializers.py index fb0b9b68..667bf56c 100644 --- a/apps/pm/serializers.py +++ b/apps/pm/serializers.py @@ -27,7 +27,8 @@ class UtaskSerializer(CustomModelSerializer): model = Utask fields = '__all__' extra_kwargs = { - 'number': {"required": False, "allow_blank": True} + 'number': {"required": False, "allow_blank": True}, + "priority": {"required": False, "allow_null": True}, } @transaction.atomic @@ -52,6 +53,7 @@ class UtaskSerializer(CustomModelSerializer): attrs['count_day'] = math.ceil(attrs['count']/rela_days) except Exception: raise ParseError('日均任务数计划失败') + attrs["priority"] = attrs.get("priority", 20) return attrs def update(self, instance, validated_data): diff --git a/apps/pm/views.py b/apps/pm/views.py index 2c76fce5..c13f2aa9 100644 --- a/apps/pm/views.py +++ b/apps/pm/views.py @@ -29,7 +29,8 @@ class UtaskViewSet(CustomModelViewSet): serializer_class = UtaskSerializer filterset_class = UtaskFilter select_related_fields = ['material'] - ordering = ['-start_date'] + ordering_fields = ['priority', 'start_date'] + ordering = ["priority", '-start_date'] def perform_destroy(self, instance): if instance.state >= Utask.UTASK_WORKING: @@ -143,8 +144,8 @@ class MtaskViewSet(CustomModelViewSet): filterset_class = MtaskFilter select_related_fields = ['material_in', 'material_out', 'mgroup'] prefetch_related_fields = ['mlog_mtask', 'b_mtask'] - ordering_fields = ['start_date', 'mgroup__process__sort', 'create_time'] - ordering = ['-start_date', 'route__sort', 'mgroup__process__sort', '-create_time'] + ordering_fields = ["utask__priority", 'start_date', '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) @transaction.atomic From bb56becb5dbbfb98ddc965369f1b41e2d7378fdd Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 12 Jun 2025 09:07:43 +0800 Subject: [PATCH 14/39] =?UTF-8?q?feat:=20ichat=20=E6=B7=BB=E5=8A=A0workcha?= =?UTF-8?q?in=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ichat/promot/w_ana.md | 14 ++++++++ apps/ichat/promot/w_sql.md | 53 ++++++++++++++++++++++++++++ apps/ichat/urls.py | 2 ++ apps/ichat/views2.py | 71 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 apps/ichat/promot/w_ana.md create mode 100644 apps/ichat/promot/w_sql.md create mode 100644 apps/ichat/views2.py diff --git a/apps/ichat/promot/w_ana.md b/apps/ichat/promot/w_ana.md new file mode 100644 index 00000000..0751e9a1 --- /dev/null +++ b/apps/ichat/promot/w_ana.md @@ -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文件的按钮和功能。 \ No newline at end of file diff --git a/apps/ichat/promot/w_sql.md b/apps/ichat/promot/w_sql.md new file mode 100644 index 00000000..c987e161 --- /dev/null +++ b/apps/ichat/promot/w_sql.md @@ -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 \ No newline at end of file diff --git a/apps/ichat/urls.py b/apps/ichat/urls.py index fcc50def..88a41b81 100644 --- a/apps/ichat/urls.py +++ b/apps/ichat/urls.py @@ -2,6 +2,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter from apps.ichat.views import QueryLLMviewSet, ConversationViewSet +from apps.ichat.views2 import WorkChain API_BASE_URL = 'api/ichat/' @@ -11,4 +12,5 @@ router.register('conversation', ConversationViewSet, basename='conversation') router.register('message', QueryLLMviewSet, basename='message') urlpatterns = [ path(API_BASE_URL, include(router.urls)), + path(API_BASE_URL + 'workchain/ask/', WorkChain.as_view(), name='workchain') ] diff --git a/apps/ichat/views2.py b/apps/ichat/views2.py new file mode 100644 index 00000000..1f0b2397 --- /dev/null +++ b/apps/ichat/views2.py @@ -0,0 +1,71 @@ +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 + +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): + his = [{"role":"system", "content": load_promot(p_name)}] + his.append({"role":"user", "content": input}) + payload = { + "model": MODEL, + "messages": his, + "temperature": 0 + } + response = requests.post(LLM_URL, headers=HEADERS, json=payload) + return response.json()["choices"][0]["message"]["content"] + +def work_chain(input:str): + res_text = ask(input, 'w_sql') + if res_text == '请以 查询 开头,重新描述你的需求': + return '请以 查询 开头,重新描述你的需求' + else: + if not is_safe_sql(res_text): + return '当前查询存在风险,请重新描述你的需求' + res = execute_raw_sql(res_text) + res2 = ask(json.dumps(res, cls=MyJSONEncoder, ensure_ascii=False), 'w_ana') + return res2 + +class InputSerializer(serializers.Serializer): + input = serializers.CharField(label="查询需求") + +class WorkChain(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') + res_text = work_chain(input) + res_text = res_text.lstrip('```html ').rstrip('```') + return Response({'content': res_text}) + +if __name__ == "__main__": + print(work_chain("查询外观检验工段在2025年6月的生产合格数等并形成报告")) + + from apps.ichat.views2 import work_chain + print(work_chain('查询外观检验工段在2025年6月的生产合格数等并形成报告')) \ No newline at end of file From dbad1522184c5e24dc1fc65acc8077b81b6df82b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 12 Jun 2025 10:37:19 +0800 Subject: [PATCH 15/39] =?UTF-8?q?fix:=20wmaterial=20get=E5=9C=A8=E4=BC=A0?= =?UTF-8?q?=E5=85=A5query=E5=8F=82=E6=95=B0=E6=97=B6=E5=AE=8C=E5=96=84can?= =?UTF-8?q?=5Fdo=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 19e4c697..0873e902 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -202,7 +202,8 @@ class WMaterialSerializer(CustomModelSerializer): def to_representation(self, instance): ret = super().to_representation(instance) - ret['count_cando'] = str(Decimal(ret['count']) - Decimal(ret['count_working'])) + if 'count' in ret: + ret['count_cando'] = str(Decimal(ret['count']) - Decimal(ret['count_working'])) return ret class MlogbDefectSerializer(CustomModelSerializer): From c02bb6d3db301a63cbbd1a7aa8ec2b361a6d2480 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 09:03:43 +0800 Subject: [PATCH 16/39] =?UTF-8?q?fix:=20mlog=20=E6=95=B0=E5=80=BC=E8=AE=A1?= =?UTF-8?q?=E7=AE=97bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 2 +- apps/wpm/serializers.py | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index fdbe51d9..a1920d26 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -316,7 +316,7 @@ class Mlog(CommonADModel): 通过mlogb计算mlog count 合计 """ mlog = self - if mlog.fill_way == Mlog.MLOG_23: + if mlog.fill_way in [Mlog.MLOG_23, Mlog.MLOG_12]: a_dict = { "total_count_use": Sum('count_use'), "total_count_break": Sum('count_break'), diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 0873e902..ab7eb67a 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -579,20 +579,21 @@ class MlogSerializer(CustomModelSerializer): supplier = attrs.get('supplier', None) if not supplier: raise ParseError('外协必须选择外协单位') + mtask = attrs.get('mtask', None) - count_notok = 0 - for i in attrs: - if 'count_n_' in i: - if attrs[i] < 0: - raise ParseError(f'{attrs[i]}不能小于0') - count_notok = count_notok + attrs[i] - attrs['count_notok'] = count_notok + mlogdefect = attrs.get('mlogdefect', None) + if mlogdefect is None: + count_notok = 0 + for i in attrs: + if 'count_n_' in i: + if attrs[i] < 0: + raise ParseError(f'{attrs[i]}不能小于0') + count_notok = count_notok + attrs[i] + attrs['count_notok'] = count_notok if attrs['count_ok'] < 0: raise ParseError('合格数量不能小于0') - if attrs['count_real'] >= attrs['count_ok'] + attrs['count_notok']: - pass - else: - raise ParseError('生产数量不能小于合格数量') + if attrs['count_real'] != attrs['count_ok'] + attrs['count_notok']: + raise ParseError('生产数量需等于合格数量+不合格数量') if mtask: if mtask.start_date == mtask.end_date: attrs['handle_date'] = mtask.start_date From 6d87a69d8d30b8578a85320f4315fecd37bb78d3 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 11:23:02 +0800 Subject: [PATCH 17/39] =?UTF-8?q?fix:=20cal=5Fmlog=5Fcount=5Ffrom=5Fmlogb?= =?UTF-8?q?=20=E9=9C=80=E8=A6=81=E4=BC=A0=E5=8F=82=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 12 ++++++------ apps/wpm/serializers.py | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index a1920d26..600a52ad 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -431,16 +431,16 @@ class Mlogb(BaseModel): def mlogbdefect(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") count_pn_jgqbl = mqs.aggregate(total=Sum("count"))["total"] or 0 self.count_pn_jgqbl = count_pn_jgqbl self.save(update_fields=["count_pn_jgqbl"]) mlog = self.mlog - if mlog: + if mlog and cal_mlog: 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") 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 @@ -448,10 +448,10 @@ class Mlogb(BaseModel): self.count_ok = self.count_real - count_notok self.count_ok_full = self.count_real - count_notok_full 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"]) mlog = self.mlog - if mlog: + if mlog and cal_mlog: mlog.cal_mlog_count_from_mlogb() class MlogbDefect(BaseModel): @@ -518,7 +518,7 @@ class Mlogbw(BaseModel): mlogb.count_notok = count_notok mlogb.count_ok = count - mlogb.count_notok mlogb.save() - mlogb.cal_count_notok() + mlogb.cal_count_notok(cal_mlog=True) class Handover(CommonADModel): """ diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index ab7eb67a..292584f0 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -389,7 +389,7 @@ class MlogSerializer(CustomModelSerializer): ] if mlogbin_defect_objects: MlogbDefect.objects.bulk_create(mlogbin_defect_objects) - mlogbin.cal_count_pn_jgqbl() + mlogbin.cal_count_pn_jgqbl(cal_mlog=False) # mlogb只用于组合件输出物填写 brotherId_should_list = material_out.brothers @@ -416,7 +416,7 @@ class MlogSerializer(CustomModelSerializer): pass else: batch_out = generate_new_batch(batch_in, instance) - + add_dict_2 = { 'mlog': instance, 'batch': batch_out, 'mtask': instance.mtask, 'material_out': instance.material_out, @@ -445,7 +445,8 @@ class MlogSerializer(CustomModelSerializer): ] if 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 def update(self, instance, validated_data): @@ -495,7 +496,7 @@ class MlogSerializer(CustomModelSerializer): ] if 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: @@ -540,7 +541,8 @@ class MlogSerializer(CustomModelSerializer): ] if 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 def validate(self, attrs): @@ -757,7 +759,7 @@ class MlogbInSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_pn_jgqbl() + ins.cal_count_pn_jgqbl(cal_mlog=True) return ins class MlogbInUpdateSerializer(CustomModelSerializer): @@ -775,7 +777,7 @@ class MlogbInUpdateSerializer(CustomModelSerializer): mlogbdefect = validated_data.pop("mlogbdefect", None) if mlog.submit_time is not None: 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: MlogbDefect.objects.filter(mlogb=ins).delete() mlogb_defect_objects = [ @@ -784,7 +786,7 @@ class MlogbInUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok() + ins.cal_count_notok(cal_mlog=True) return ins class MlogbwCreateUpdateSerializer(CustomModelSerializer): @@ -900,7 +902,7 @@ class MlogbOutUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok() + ins.cal_count_notok(cal_mlog=True) return ins def validate(self, attrs): From 680608e16fba96e1137a9e4f7c1b51ba77b4e8c5 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 14:29:31 +0800 Subject: [PATCH 18/39] =?UTF-8?q?fix:=20mlogserializer=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E5=90=8C=E6=AD=A5count=5Freal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 2 ++ apps/wpm/services.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 292584f0..38d8d5da 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -518,6 +518,8 @@ class MlogSerializer(CustomModelSerializer): batch_out = generate_new_batch(batch_in, instance) mox, _ = Mlogb.objects.get_or_create(mlog=instance, batch=batch_out, mtask=instance.mtask, material_out=instance.material_out) + # 需要同步更新数量 + mox.count_real = instance.count_real mox.count_ok = instance.count_ok mox.count_notok = instance.count_notok mox.count_break_t = instance.count_break_t diff --git a/apps/wpm/services.py b/apps/wpm/services.py index 1dbf5e96..3e800675 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -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: return if now is None: From cf7673cd981ac7b43c68cd08e4b63528891cb5b9 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 14:31:48 +0800 Subject: [PATCH 19/39] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BB=8Ewpr?= =?UTF-8?q?=E5=A4=84=E8=8E=B7=E5=8F=96=E5=88=87=E7=89=87=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 6d9a1636..9d98ba17 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -639,6 +639,8 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust {"count_real": d_count_real, "count_ok": d_count_ok})) for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): wpr_ = mlogbwin.wpr + if wpr_.oinfo.get("切片数", None) is not None: + div_number = wpr_.oinfo["切片数"] if div_number == 1: Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin}) else: @@ -651,7 +653,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust mlogbout.count_json_from = mlogbin.count_json_from mlogbout.save(update_fields=["count_json_from"]) elif mtype == Process.PRO_MERGE: # 支持批到批,批到个 - xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / route.div_number) + xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / div_number) d_count_real = 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})) From b9a7138e72f93449f1f3b2bbc72556e01fe4fca5 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 15:24:53 +0800 Subject: [PATCH 20/39] =?UTF-8?q?feat:=20mlogbin=20=E5=92=8C=20update?= =?UTF-8?q?=E6=97=B6cal=5Fcount=5Fnotok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 38d8d5da..a602c575 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -761,7 +761,7 @@ class MlogbInSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_pn_jgqbl(cal_mlog=True) + ins.cal_count_pn_jgqbl(cal_mlog=True) return ins class MlogbInUpdateSerializer(CustomModelSerializer): @@ -788,7 +788,9 @@ class MlogbInUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok(cal_mlog=True) + ins.cal_count_notok(cal_mlog=True) + # 找到对应的产出 + return ins class MlogbwCreateUpdateSerializer(CustomModelSerializer): From 4bd6eab9f4516bad181e08e272782ae3cb7bc018 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 15:56:43 +0800 Subject: [PATCH 21/39] =?UTF-8?q?feat:=20mlogbin=20=E5=92=8C=20update?= =?UTF-8?q?=E6=97=B6cal=5Fcount=5Fnotok=20cal=5Fmlog=E4=BC=A0False?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 30 ++++++++++++++++++------------ apps/wpm/views.py | 12 ++++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index a602c575..5eafc17f 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -761,7 +761,7 @@ class MlogbInSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_pn_jgqbl(cal_mlog=True) + ins.cal_count_pn_jgqbl(cal_mlog=False) return ins class MlogbInUpdateSerializer(CustomModelSerializer): @@ -781,16 +781,22 @@ class MlogbInUpdateSerializer(CustomModelSerializer): raise ParseError('生产日志已提交不可编辑') ins:Mlogb = super().update(instance, validated_data) if mlogbdefect is not None and ins.material_in.tracking == Material.MA_TRACKING_BATCH: - MlogbDefect.objects.filter(mlogb=ins).delete() - mlogb_defect_objects = [ - MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()}) - for item in mlogbdefect if item["count"] > 0 - ] - if mlogb_defect_objects: - MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok(cal_mlog=True) - # 找到对应的产出 - + MlogbDefect.objects.filter(mlogb=ins).delete() + mlogb_defect_objects = [ + MlogbDefect(**{**item, "mlogb": ins, "id": idWorker.get_id()}) + for item in mlogbdefect if item["count"] > 0 + ] + if mlogb_defect_objects: + MlogbDefect.objects.bulk_create(mlogb_defect_objects) + ins.cal_count_notok(cal_mlog=False) + # 只有普通工序的才可联动 + # route:Route = mlog.route + # 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 + return ins class MlogbwCreateUpdateSerializer(CustomModelSerializer): @@ -906,7 +912,7 @@ class MlogbOutUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok(cal_mlog=True) + ins.cal_count_notok(cal_mlog=False) return ins def validate(self, attrs): diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 9d98ba17..034aeca6 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -563,7 +563,13 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust if ins.mlog.submit_time is not None: raise ParseError('生产日志已提交不可编辑') 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 def perform_create(self, serializer): mlogbin: Mlogb = serializer.save() @@ -687,6 +693,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin}) else: raise ParseError("不支持生成产出物料!") + mlog.cal_mlog_count_from_mlogb() @classmethod def gen_number_with_rule(cls, rule, material_out:Material, gen_count=1): @@ -719,6 +726,11 @@ class MlogbOutViewSet(UpdateModelMixin, CustomGenericViewSet): queryset = Mlogb.objects.filter(material_out__isnull=False) serializer_class = MlogbOutUpdateSerializer + @transaction.atomic + def perform_update(self, serializer): + ins:Mlogb = serializer.save() + ins.mlog.cal_mlog_count_from_mlogb() + class FmlogViewSet(CustomModelViewSet): perms_map = {'get': '*', 'post': 'mlog.create', 'put': 'mlog.update', 'delete': 'mlog.delete'} From ab0c962053dd9ce124c8b7c27a80ece8413be93b Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 16:22:50 +0800 Subject: [PATCH 22/39] =?UTF-8?q?feat:=20mlogbin=E8=81=94=E5=8A=A8mlogbout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 5eafc17f..0406caeb 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -788,14 +788,20 @@ class MlogbInUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) - ins.cal_count_notok(cal_mlog=False) # 只有普通工序的才可联动 - # route:Route = mlog.route - # 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 + route:Route = mlog.route + 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) + ins.cal_count_notok(cal_mlog=False) + return ins From 4955ad1f11baab0eb74154eeca76b9bf8f5b2ffd Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 16:23:07 +0800 Subject: [PATCH 23/39] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BB=8Ewpr?= =?UTF-8?q?=E5=A4=84=E8=8E=B7=E5=8F=96=E5=88=87=E7=89=87=E6=95=B02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 034aeca6..2f8d5818 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -645,8 +645,9 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust {"count_real": d_count_real, "count_ok": d_count_ok})) for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): wpr_ = mlogbwin.wpr - if wpr_.oinfo.get("切片数", None) is not None: - div_number = wpr_.oinfo["切片数"] + for key, val in wpr_.oinfo.items(): + if key == "切片数": + div_number = int(val["val"]) if div_number == 1: Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin}) else: From e774992d3a20fa897f953a14b136e691c3038900 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 16:23:35 +0800 Subject: [PATCH 24/39] =?UTF-8?q?feat:=20workchain=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ichat/views2.py | 86 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/apps/ichat/views2.py b/apps/ichat/views2.py index 1f0b2397..9b2766bb 100644 --- a/apps/ichat/views2.py +++ b/apps/ichat/views2.py @@ -10,6 +10,10 @@ 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", "") @@ -25,47 +29,101 @@ def load_promot(name): return f.read() -def ask(input:str, p_name:str): +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 + "temperature": 0, + "stream": stream } - response = requests.post(LLM_URL, headers=HEADERS, json=payload) - return response.json()["choices"][0]["message"]["content"] + 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): +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 == '请以 查询 开头,重新描述你的需求': - return '请以 查询 开头,重新描述你的需求' + 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): - return '当前查询存在风险,请重新描述你的需求' + 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') - return res2 + 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(APIView): +class WorkChain(MyLoggingMixin, APIView): @swagger_auto_schema( - operation_summary="查询工作", + 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') - res_text = work_chain(input) - res_text = res_text.lstrip('```html ').rstrip('```') - return Response({'content': res_text}) + 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月的生产合格数等并形成报告")) + print(work_chain("查询 一次超洗 工段在2025年6月的生产合格数等并形成报告")) from apps.ichat.views2 import work_chain print(work_chain('查询外观检验工段在2025年6月的生产合格数等并形成报告')) \ No newline at end of file From a820d6803ee0785b1fdc4df49fa1d41cb81e3239 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 16:25:06 +0800 Subject: [PATCH 25/39] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BB=8Ewpr?= =?UTF-8?q?=E5=A4=84=E8=8E=B7=E5=8F=96=E5=88=87=E7=89=87=E6=95=B03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 2f8d5818..dea4d749 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -646,7 +646,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): wpr_ = mlogbwin.wpr for key, val in wpr_.oinfo.items(): - if key == "切片数": + if val['name'] == "切片数": div_number = int(val["val"]) if div_number == 1: Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin}) From 95b10371b9afbb95cc820159fda5353d45d15e73 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Fri, 13 Jun 2025 16:36:51 +0800 Subject: [PATCH 26/39] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BB=8Ewpr?= =?UTF-8?q?=E5=A4=84=E8=8E=B7=E5=8F=96=E5=88=87=E7=89=87=E6=95=B04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index dea4d749..7829d768 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -639,20 +639,23 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust mlogbout.number_from = mlogbwin.number mlogbout.save() 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 - 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=m_dict) + d_count_real = 0 for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): 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: Mlogbw.objects.get_or_create(wpr=wpr_, mlogb=mlogbout, defaults={"number": wpr_.number, "mlogbw_from": mlogbwin}) else: for i in range(div_number): 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.save(update_fields=["count_real", "count_ok"]) 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_ok = d_count_real From 31421ca0aaea48ba45800e5cac44e2ba13fda23a Mon Sep 17 00:00:00 2001 From: caoqianming Date: Sun, 15 Jun 2025 22:19:52 +0800 Subject: [PATCH 27/39] =?UTF-8?q?feat:=20mlogb=E9=A6=96=E6=AC=A1=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E9=9C=80=E8=A6=81=E6=9B=B4=E6=96=B0count=5Fok=5Ffull?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index 7829d768..d226cb34 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -617,7 +617,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust d_count_real = mlogbin.count_use - mlogbin.count_pn_jgqbl 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})) + 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.save(update_fields=["count_json_from"]) if material_in.tracking == Material.MA_TRACKING_SINGLE and material_out.tracking == Material.MA_TRACKING_SINGLE: @@ -633,7 +633,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust if process and process.number_to_batch: m_dict["batch"] = mlogbwin.number 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: mlogbout.mlogb_from = mlogbin mlogbout.number_from = mlogbwin.number @@ -655,18 +655,19 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust d_count_ok = d_count_real mlogbout.count_real = d_count_real mlogbout.count_ok = d_count_ok - mlogbout.save(update_fields=["count_real", "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: d_count_real = (mlogbin.count_use-mlogbin.count_pn_jgqbl) * div_number 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.save(update_fields=["count_json_from"]) elif mtype == Process.PRO_MERGE: # 支持批到批,批到个 xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / div_number) d_count_real = 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.save(update_fields=["count_json_from"]) wpr_number_rule = process.wpr_number_rule @@ -690,7 +691,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust elif is_fix:# 支持批到批,个到个 d_count_real = mlogbin.count_use-mlogbin.count_pn_jgqbl 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: for mlogbwin in Mlogbw.objects.filter(mlogb=mlogbin).order_by("number"): wpr_ = mlogbwin.wpr From 1366f432eefd7090923ac616938d13bf34366bce Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 16 Jun 2025 17:42:43 +0800 Subject: [PATCH 28/39] =?UTF-8?q?feat:=20mlogbin=E7=9A=84=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E4=B8=8Emlogbout=E7=9A=84=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 0406caeb..2b11c751 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -789,17 +789,19 @@ class MlogbInUpdateSerializer(CustomModelSerializer): if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) # 只有普通工序的才可联动 + material_out:Material = ins.mlog.material_out route:Route = mlog.route - 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) + 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) ins.cal_count_notok(cal_mlog=False) From 8d6f2bfd20b5e129c77f585faae60be5736b4f68 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 17 Jun 2025 08:36:48 +0800 Subject: [PATCH 29/39] =?UTF-8?q?fix:=20mlogbinupdate=E6=97=B6=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/serializers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index 2b11c751..e1f79d87 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -788,6 +788,7 @@ class MlogbInUpdateSerializer(CustomModelSerializer): ] if mlogb_defect_objects: MlogbDefect.objects.bulk_create(mlogb_defect_objects) + ins.cal_count_pn_jgqbl(cal_mlog=False) # 只有普通工序的才可联动 material_out:Material = ins.mlog.material_out route:Route = mlog.route @@ -802,9 +803,6 @@ class MlogbInUpdateSerializer(CustomModelSerializer): raise ParseError("对应的产出数异常") mlogbout.save(update_fields=["count_real", "count_ok"]) mlogbout.cal_count_notok(cal_mlog=False) - ins.cal_count_notok(cal_mlog=False) - - return ins class MlogbwCreateUpdateSerializer(CustomModelSerializer): From 39c86a14d5d540bdccea04fcb8e13691b5b12f12 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Tue, 17 Jun 2025 15:39:39 +0800 Subject: [PATCH 30/39] release: 2.6.2025061715 --- changelog.md | 19 +++++++++++++++++++ server/settings.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 957ab8a8..cba9228c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,22 @@ +## 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] diff --git a/server/settings.py b/server/settings.py index 2678e14a..d28aed0b 100755 --- a/server/settings.py +++ b/server/settings.py @@ -35,7 +35,7 @@ sys.path.insert(0, os.path.join(BASE_DIR, 'apps')) ALLOWED_HOSTS = ['*'] SYS_NAME = '星途工厂综合管理系统' -SYS_VERSION = '2.6.2025060913' +SYS_VERSION = '2.6.2025061715' X_FRAME_OPTIONS = 'SAMEORIGIN' # Application definition From 6af708d4f01d57d3bd1964fb7a167cebe9b99839 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 18 Jun 2025 10:31:35 +0800 Subject: [PATCH 31/39] =?UTF-8?q?feat:=20wmaterial=E6=B7=BB=E5=8A=A0can=5F?= =?UTF-8?q?handover=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 4 ++++ apps/wpm/serializers.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 600a52ad..02195c10 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -125,6 +125,10 @@ class WMaterial(CommonBDModel): def count_working(self): 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, submit_time__isnull=True).aggregate(count=Sum('count'))['count'] or 0 + @classmethod def mat_in_qs(cls, mtask: Mtask, qs=None): """ diff --git a/apps/wpm/serializers.py b/apps/wpm/serializers.py index e1f79d87..c0928944 100644 --- a/apps/wpm/serializers.py +++ b/apps/wpm/serializers.py @@ -192,6 +192,7 @@ class WMaterialSerializer(CustomModelSerializer): notok_sign_name = serializers.SerializerMethodField() defect_name = serializers.CharField(source="defect.name", read_only=True) count_working = serializers.IntegerField(read_only=True, label='在制数量') + count_handovering = serializers.IntegerField(read_only=True, label='正在交送的数量') def get_notok_sign_name(self, obj): return getattr(NotOkOption, obj.notok_sign, NotOkOption.qt).label if obj.notok_sign else None @@ -202,8 +203,10 @@ class WMaterialSerializer(CustomModelSerializer): def to_representation(self, instance): ret = super().to_representation(instance) - if 'count' in ret: + if 'count' in ret and 'count_working' in ret: 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 class MlogbDefectSerializer(CustomModelSerializer): From a819566c230f2235635ccc4ae881c4bec47f58af Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 18 Jun 2025 10:56:39 +0800 Subject: [PATCH 32/39] =?UTF-8?q?fix:=20wmaterial=E6=B7=BB=E5=8A=A0can=5Fh?= =?UTF-8?q?andover=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wpm/models.py b/apps/wpm/models.py index 02195c10..051a27ae 100644 --- a/apps/wpm/models.py +++ b/apps/wpm/models.py @@ -127,7 +127,7 @@ class WMaterial(CommonBDModel): @property def count_handovering(self): - return Handoverb.objects.filter(wm=self, submit_time__isnull=True).aggregate(count=Sum('count'))['count'] or 0 + return Handoverb.objects.filter(wm=self, handover__submit_time__isnull=True).aggregate(count=Sum('count'))['count'] or 0 @classmethod def mat_in_qs(cls, mtask: Mtask, qs=None): From 8805237a44aea0a4600f67a0f2e354106082d74f Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 18 Jun 2025 16:29:38 +0800 Subject: [PATCH 33/39] =?UTF-8?q?feat:=20=E7=89=A9=E6=96=99=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=BB=E5=BA=93=E4=BD=8D=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0059_material_bin_number_main.py | 18 ++++++++++++++++++ apps/mtm/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 apps/mtm/migrations/0059_material_bin_number_main.py diff --git a/apps/mtm/migrations/0059_material_bin_number_main.py b/apps/mtm/migrations/0059_material_bin_number_main.py new file mode 100644 index 00000000..80574563 --- /dev/null +++ b/apps/mtm/migrations/0059_material_bin_number_main.py @@ -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='主库位号'), + ), + ] diff --git a/apps/mtm/models.py b/apps/mtm/models.py index 2c3fa008..8e941d93 100644 --- a/apps/mtm/models.py +++ b/apps/mtm/models.py @@ -101,6 +101,7 @@ class Material(CommonAModel): brothers = models.JSONField('兄弟件', default=list, null=False, blank=True) unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True) into_wm = models.BooleanField('是否进入车间库存', default=True) + bin_number_main = models.CharField('主库位号', max_length=50, null=True, blank=True) class Meta: verbose_name = '物料表' From 25c1cce41ad0ab403a99a8aa84d7ec1343dc62a1 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Wed, 18 Jun 2025 16:30:41 +0800 Subject: [PATCH 34/39] =?UTF-8?q?feat:=20materialsimpleserializer=E8=BF=94?= =?UTF-8?q?=E5=9B=9Ebin=5Fnumber=5Fmain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mtm/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 86e6039a..049c0fa3 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -24,7 +24,7 @@ class MaterialSimpleSerializer(CustomModelSerializer): class Meta: model = Material 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): return f'{obj.name}|{obj.specification if obj.specification else ""}|{obj.model if obj.model else ""}|{obj.process.name if obj.process else ""}' From 741b64142b7afdd48da145208fd06242f19c1b65 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 19 Jun 2025 10:37:09 +0800 Subject: [PATCH 35/39] =?UTF-8?q?feat:=20mioitem=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8D=95=E4=BB=B7=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/inm/migrations/0031_mioitem_unit_price.py | 18 ++++++++++++++++++ apps/inm/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 apps/inm/migrations/0031_mioitem_unit_price.py diff --git a/apps/inm/migrations/0031_mioitem_unit_price.py b/apps/inm/migrations/0031_mioitem_unit_price.py new file mode 100644 index 00000000..6c6d1b67 --- /dev/null +++ b/apps/inm/migrations/0031_mioitem_unit_price.py @@ -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='单价'), + ), + ] diff --git a/apps/inm/models.py b/apps/inm/models.py index 2428fd06..a0c931b9 100644 --- a/apps/inm/models.py +++ b/apps/inm/models.py @@ -139,6 +139,7 @@ class MIOItem(BaseModel): material = models.ForeignKey( Material, verbose_name='物料', on_delete=models.CASCADE) 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_tested = models.PositiveIntegerField('已检数', null=True, blank=True) test_date = models.DateField('检验日期', null=True, blank=True) From 3b04bdf0677ddfa7b0120d4f9e528083c841c4da Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 19 Jun 2025 14:11:24 +0800 Subject: [PATCH 36/39] =?UTF-8?q?feat:=20mioitem=E8=BF=94=E5=9B=9Emio?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/inm/serializers.py | 14 +++++++++++--- apps/inm/views.py | 26 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/apps/inm/serializers.py b/apps/inm/serializers.py index 83152be0..cb24b878 100644 --- a/apps/inm/serializers.py +++ b/apps/inm/serializers.py @@ -109,7 +109,7 @@ class MIOItemCreateSerializer(CustomModelSerializer): class Meta: model = MIOItem fields = ['mio', 'warehouse', 'material', - 'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm'] + 'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price'] extra_kwargs = { 'mio': {'required': True}, 'warehouse': {'required': False}, 'material': {'required': False}, 'batch': {'required': False}} @@ -207,8 +207,7 @@ class MIOItemAListSerializer(CustomModelSerializer): class MIOItemSerializer(CustomModelSerializer): - warehouse_name = serializers.CharField( - source='warehouse.name', read_only=True) + warehouse_name = serializers.CharField(source='warehouse.name', read_only=True) material_ = MaterialSerializer(source='material', read_only=True) assemb = MIOItemAListSerializer( source='a_mioitem', read_only=True, many=True) @@ -222,6 +221,15 @@ class MIOItemSerializer(CustomModelSerializer): model = MIOItem fields = '__all__' + def to_representation(self, instance): + ret = super().to_representation(instance) + ret["price"] = None + if ret["unit_price"] is not None: + ret["price"] = ret["count"] * ret["unit_price"] + return ret + +class MioItemDetailSerializer(MIOItemSerializer): + mio_ = MIOListSerializer(source='mio', read_only=True) class MIODoSerializer(CustomModelSerializer): diff --git a/apps/inm/views.py b/apps/inm/views.py index 0b57f514..2a1b8085 100644 --- a/apps/inm/views.py +++ b/apps/inm/views.py @@ -14,7 +14,7 @@ from apps.inm.serializers import ( MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer, MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer, MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer, - MIOItemwSerializer) + MIOItemwSerializer, MioItemDetailSerializer) from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.inm.services import InmService @@ -25,6 +25,8 @@ from apps.utils.permission import has_perm from .filters import MaterialBatchFilter, MioFilter from apps.qm.serializers import FtestProcessSerializer from apps.mtm.models import Material +from drf_yasg.utils import swagger_auto_schema +from drf_yasg import openapi # Create your views here. @@ -243,6 +245,7 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode perms_map = {'get': '*', 'post': '*', 'delete': '*'} queryset = MIOItem.objects.all() serializer_class = MIOItemSerializer + retrieve_serializer_class = MioItemDetailSerializer create_serializer_class = MIOItemCreateSerializer select_related_fields = ['warehouse', 'mio', 'material', 'test_user'] filterset_fields = { @@ -258,9 +261,28 @@ class MIOItemViewSet(CustomListModelMixin, BulkCreateModelMixin, BulkDestroyMode ordering_fields = ['create_time', 'test_date'] 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 + @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): if instance.mio.state != MIO.MIO_CREATE: raise ParseError('出入库记录非创建中不可删除') From dce573f892f3af3bf9d9b49a44bbee67648338d9 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 19 Jun 2025 14:23:49 +0800 Subject: [PATCH 37/39] =?UTF-8?q?fix:=20mioitem=20price=20=E8=AE=A1?= =?UTF-8?q?=E7=AE=97bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/inm/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/inm/serializers.py b/apps/inm/serializers.py index cb24b878..76a6586c 100644 --- a/apps/inm/serializers.py +++ b/apps/inm/serializers.py @@ -12,6 +12,7 @@ from .models import MIO, MaterialBatch, MIOItem, WareHouse, MIOItemA, MaterialBa from django.db import transaction from server.settings import get_sysconfig from apps.wpmw.models import Wpr +from decimal import Decimal class WareHourseSerializer(CustomModelSerializer): @@ -225,7 +226,7 @@ class MIOItemSerializer(CustomModelSerializer): ret = super().to_representation(instance) ret["price"] = None if ret["unit_price"] is not None: - ret["price"] = ret["count"] * ret["unit_price"] + ret["price"] = Decimal(ret["count"]) * Decimal(ret["unit_price"]) return ret class MioItemDetailSerializer(MIOItemSerializer): From 4f08374ca6fa9ba2ccd8db8b8c8d211569f09b3d Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 19 Jun 2025 15:43:02 +0800 Subject: [PATCH 38/39] =?UTF-8?q?feat:=20mlogbin=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/wpm/views.py b/apps/wpm/views.py index d226cb34..c4afe65e 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -664,6 +664,7 @@ class MlogbInViewSet(CreateModelMixin, UpdateModelMixin, DestroyModelMixin, Cust mlogbout.count_json_from = mlogbin.count_json_from mlogbout.save(update_fields=["count_json_from"]) elif mtype == Process.PRO_MERGE: # 支持批到批,批到个 + div_number = route.div_number xcount = math.floor( (mlogbin.count_use-mlogbin.count_pn_jgqbl) / div_number) d_count_real = xcount d_count_ok = xcount From db2e8934ba6ddf210beede6115a3658ab1f6e7d2 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 19 Jun 2025 17:25:33 +0800 Subject: [PATCH 39/39] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BA=A4?= =?UTF-8?q?=E6=8E=A5=E6=92=A4=E5=9B=9E=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wpm/services.py | 33 ++++++++++++++++++++++++++++++++- apps/wpm/views.py | 14 +++++++++++++- apps/wpmw/models.py | 5 +++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/apps/wpm/services.py b/apps/wpm/services.py index 3e800675..ff59693a 100644 --- a/apps/wpm/services.py +++ b/apps/wpm/services.py @@ -916,8 +916,39 @@ def handover_submit(handover:Handover, user: User, now: Union[datetime.datetime, 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) - pass + def mlog_submit_validate(ins: Mlog): if ins.submit_time: diff --git a/apps/wpm/views.py b/apps/wpm/views.py index c4afe65e..750c0904 100644 --- a/apps/wpm/views.py +++ b/apps/wpm/views.py @@ -27,7 +27,7 @@ from .serializers import (SflogExpSerializer, SfLogSerializer, StLogSerializer, MlogbOutUpdateSerializer, FmlogSerializer, FmlogUpdateSerializer, BatchStSerializer, MlogbwCreateUpdateSerializer, HandoverMgroupSerializer, MlogListSerializer, 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.wf.models import State from apps.wpmw.models import Wpr @@ -427,6 +427,18 @@ class HandoverViewSet(CustomModelViewSet): if ins.submit_time is None: handover_submit(ins, user, None) 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) @transaction.atomic diff --git a/apps/wpmw/models.py b/apps/wpmw/models.py index 21932de2..99678377 100644 --- a/apps/wpmw/models.py +++ b/apps/wpmw/models.py @@ -29,7 +29,7 @@ class Wpr(BaseModel): wpr_from = models.ForeignKey("self", verbose_name="来源于", on_delete=models.CASCADE, null=True, blank=True) @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 if wpr is None and number is None: raise ParseError("id和number不能同时为空") @@ -105,7 +105,8 @@ class Wpr(BaseModel): WprDefect.objects.filter(wpr=ins).delete() if wm.defect: WprDefect.objects.create(wpr=ins, defect=wm.defect, is_main=True) - ins.version = ins.version + 1 + if add_version: + ins.version = ins.version + 1 ins.save() if ftest: # 通过检验变更其缺陷项