mirror of
https://github.com/rnvm9wjdtj-bot/myaps_api.git
synced 2026-06-02 05:54:40 +00:00
优化项目结构
This commit is contained in:
+3
-1
@@ -76,4 +76,6 @@ jspm_packages/
|
||||
.env
|
||||
|
||||
test/
|
||||
*.ipynb
|
||||
|
||||
# 忽略project_files文件夹下以_ed为结尾的py文件
|
||||
project_files/*_ed.py
|
||||
@@ -12,6 +12,7 @@ MyapsDbActions
|
||||
|
||||
import os, importlib
|
||||
|
||||
from config.settings import MYAPS_MAIN_DB, TURN_ON_SCHEDULE_TASK
|
||||
from ..utils.scheduler import cron_task
|
||||
from apps.io_api.common import dict_to_lower_keys
|
||||
|
||||
@@ -28,12 +29,11 @@ except:
|
||||
#################################################################################
|
||||
# ⬇️定时任务HOOK
|
||||
#################################################################################
|
||||
schedule_task_hour = current_project.DefaultParams.SCHEDULE_TASK_HOUR
|
||||
schedule_task_minute = current_project.DefaultParams.SCHEDULE_TASK_MINUTE
|
||||
turn_on_schedule_task = os.getenv('TURN_ON_SCHEDULE_TASK', 'True').lower() == 'true'
|
||||
schedule_task_hour = "6,8,10,12,14,16"
|
||||
schedule_task_minute = "55"
|
||||
|
||||
|
||||
if turn_on_schedule_task:
|
||||
if TURN_ON_SCHEDULE_TASK:
|
||||
@cron_task(hour=schedule_task_hour, minute=schedule_task_minute)
|
||||
async def refresh_stock(db_name: str | None = None):
|
||||
return await current_project.ScheduleTasks.refresh_stock(db_name)
|
||||
@@ -45,10 +45,8 @@ if turn_on_schedule_task:
|
||||
#################################################################################
|
||||
from apps.data_opt.utils.mysqlmonitor import mysql_monitor
|
||||
|
||||
main_db = os.getenv('MYAPS_MAIN_DB')
|
||||
|
||||
|
||||
@mysql_monitor.on_update_for_table("t_supply", database=main_db)
|
||||
@mysql_monitor.on_update_for_table("t_supply", database=MYAPS_MAIN_DB)
|
||||
async def handle_update_supply(database: str, table: str, data: dict, data_diff: dict):
|
||||
"""处理t_supply表的更新事件"""
|
||||
supply_type = data['new']['Type']
|
||||
|
||||
@@ -4,21 +4,22 @@
|
||||
"""
|
||||
|
||||
# import threading
|
||||
import os
|
||||
# import os
|
||||
import logging
|
||||
from typing import Literal
|
||||
from abc import ABC#, abstractmethod
|
||||
|
||||
# from tortoise import Tortoise
|
||||
|
||||
from config.settings import MYAPS_MAIN_DB, THIS_SERVER_PORT, THIS_PROTOCOL#, MYAPS_BASE_URL
|
||||
from config.settings import MYAPS_MAIN_DB, THIS_SERVER_PORT, THIS_PROTOCOL, SCHEDULED_DBS#, MYAPS_BASE_URL
|
||||
from apps.data_opt.utils.common import get_session
|
||||
|
||||
# ❗⬇️不要删掉,便于各项目文件引用
|
||||
from globalobjects import file_timed_logger
|
||||
from apps.io_api.common import standard_response
|
||||
from apps.data_opt.components.hap import HapConnection
|
||||
|
||||
from ..utils.scheduler import cron_task
|
||||
from ..utils.common import add_basic_auth_requests
|
||||
|
||||
|
||||
# 配置日志
|
||||
@@ -31,7 +32,7 @@ console_log = logging.getLogger(__name__)
|
||||
|
||||
class ScheduleTasksAbc(ABC):
|
||||
this_base_url = f'{THIS_PROTOCOL}localhost:{THIS_SERVER_PORT}'
|
||||
scheduled_dbs = os.getenv('SCHEDULED_DBS').split(',')
|
||||
scheduled_dbs = SCHEDULED_DBS
|
||||
_session = get_session()
|
||||
|
||||
@classmethod
|
||||
@@ -106,13 +107,6 @@ class MyapsDbActionsAbc(ABC):
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class DefaultParamsAbc:
|
||||
SCHEDULE_TASK_HOUR = "6,8,10,12,14,16"
|
||||
SCHEDULE_TASK_MINUTE = "55"
|
||||
|
||||
|
||||
|
||||
class DefaultValueAbc:
|
||||
myaps_is_pro = 1 # 1 / 0 MyAPS是否专业版
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from datetime import datetime
|
||||
from fastapi import status
|
||||
|
||||
from ._base import (
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc, DefaultParamsAbc,
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc,
|
||||
file_log, console_log, standard_response, get_session, HapConnection
|
||||
)
|
||||
|
||||
@@ -23,8 +23,6 @@ hap_conn = HapConnection(
|
||||
sign='...'
|
||||
)
|
||||
|
||||
class DefaultParams(DefaultParamsAbc):
|
||||
pass
|
||||
|
||||
class DefaultValue(DefaultValueAbc):
|
||||
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"""江阴海达橡塑"""
|
||||
|
||||
import requests, logging#, os, atexit
|
||||
import requests, uuid#, logging#, os, atexit
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import status
|
||||
|
||||
from config.settings import MYAPS_MAIN_DB, SCHEDULED_DBS, THIS_BASE_URL, TURN_ON_SCHEDULE_TASK
|
||||
from ._base import (
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc, DefaultParamsAbc,
|
||||
file_log, console_log, standard_response, get_session, HapConnection
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc,
|
||||
file_log, console_log, standard_response, get_session, HapConnection,
|
||||
cron_task, add_basic_auth_requests
|
||||
)
|
||||
|
||||
|
||||
|
||||
#################################################################################
|
||||
# ⬇️对象及项目参数
|
||||
#################################################################################
|
||||
@@ -22,22 +25,17 @@ hap_conn = HapConnection(
|
||||
)
|
||||
|
||||
|
||||
class DefaultParams(DefaultParamsAbc):
|
||||
pass
|
||||
|
||||
class DefaultValue(DefaultValueAbc):
|
||||
MAT_PLANT = "1600" # 默认工厂
|
||||
MAT_PLANNER = "haida" # 默认计划员
|
||||
MAT_LOCATION = "1600" # 默认车间
|
||||
|
||||
main_db = MyapsDbActionsAbc.main_db
|
||||
|
||||
werks = "1600"
|
||||
|
||||
#################################################################################
|
||||
# ⬇️项目可复用逻辑
|
||||
#################################################################################
|
||||
from apps.data_opt.utils.common import add_basic_auth_requests
|
||||
|
||||
sap_url1 = 'http://192.168.201.2:8000/zrestful_test2?sap-client=800' # 库存
|
||||
sap_url2 = 'http://192.168.201.2:8000/zrestful_plan?sap-client=800' # 计划
|
||||
|
||||
@@ -45,15 +43,9 @@ sap_username = 'T058'
|
||||
sap_password = '123456'
|
||||
# 创建requests会话
|
||||
sap_session = get_session(allowed_methods=["GET", "POST"])
|
||||
# sap_session2 = get_session(allowed_methods=["GET", "POST"])
|
||||
|
||||
# 添加Basic认证
|
||||
add_basic_auth_requests(sap_session, sap_username, sap_password)
|
||||
# add_basic_auth_requests(sap_session2, sap_username, sap_password)
|
||||
|
||||
# import json
|
||||
import uuid
|
||||
# from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
async def sap_post(url: str, session: requests.Session, interface_id: str, data: dict):
|
||||
@@ -92,76 +84,69 @@ async def sap_post(url: str, session: requests.Session, interface_id: str, data:
|
||||
#################################################################################
|
||||
# ⬇️定时任务设置
|
||||
#################################################################################
|
||||
class ScheduleTasks(ScheduleTasksAbc):
|
||||
schedule_task_hour = '6,8,10,12,14,16'
|
||||
schedule_task_minute = '55'
|
||||
|
||||
@classmethod
|
||||
async def refresh_stock(cls, db_name: str | None = None):
|
||||
"""
|
||||
刷新库存,先清空supply中类型为ST的数据,再从ERP同步1600厂全部库存数据
|
||||
db_name: 账套名称,默认刷新所有账套
|
||||
"""
|
||||
console_log.info("开始执行刷新库存任务")
|
||||
response = None
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
try:
|
||||
response = sap_session.get(url=f"{sap_url1}", headers={'interface': 'stock', 'werks': werks})
|
||||
data = response.json()['data']
|
||||
stock = pd.DataFrame(data)
|
||||
stock = stock.astype({
|
||||
'werks': 'str',
|
||||
'matnr': 'str',
|
||||
'lgort': 'str',
|
||||
'labst': 'int32',
|
||||
'labst2': 'int32',
|
||||
'charg': 'str'
|
||||
})
|
||||
stock['avail_qty'] = stock['labst'] + stock['labst2']
|
||||
stock['supplyno'] = stock['werks'] + '-' + stock['matnr'] # 注意不要用f string,否则supplyno会变成所有料号的超长字符串
|
||||
stock['type'] = 'ST'
|
||||
stock['priority'] = 0
|
||||
stock['avail_date'] = now
|
||||
stock['dt_req'] = now
|
||||
stock['status'] = 'NEW'
|
||||
stock['category'] = ''
|
||||
stock['create_date'] = now
|
||||
stock = (stock
|
||||
.groupby(['supplyno'], as_index=False)
|
||||
.agg({
|
||||
'matnr': 'first',
|
||||
'avail_qty': 'sum',
|
||||
'type': 'first',
|
||||
'avail_date': 'first',
|
||||
'dt_req': 'first',
|
||||
'priority': 'first',
|
||||
'status': 'first',
|
||||
'category': 'first',
|
||||
'create_date': 'first',
|
||||
}))
|
||||
stock = stock.rename(columns={
|
||||
'matnr': 'materialno',
|
||||
})
|
||||
stock_data = stock.to_dict(orient='records')
|
||||
|
||||
dbs = db_name or ','.join(cls.scheduled_dbs)
|
||||
cls._session.delete(f"{cls.this_base_url}/api/t_supply?db_name={dbs}&type=ST")
|
||||
cls._session.post(f"{cls.this_base_url}/api/t_supply?db_name={dbs}", json=stock_data)
|
||||
console_log.info(f"刷新库存任务执行完成,账套:{dbs}")
|
||||
response = standard_response(message=f"刷新库存任务执行完成,账套:{dbs}")
|
||||
|
||||
except Exception as e:
|
||||
console_log.error(f"刷新库存任务执行失败: {str(e)}")
|
||||
response = standard_response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, success=0, message=f"刷新库存任务执行失败: {str(e)}")
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
async def get_bom(cls):
|
||||
"""
|
||||
从SAP获取BOM数据
|
||||
"""
|
||||
response = sap_session.get(url=sap_url1, headers={'interface': 'bom', 'werks': "1600"})
|
||||
bom_json_data = response.json()['data']
|
||||
return bom_json_data
|
||||
@cron_task(hour=schedule_task_hour, minute=schedule_task_minute)
|
||||
def refresh_stock(db_name: str | None = None):
|
||||
"""
|
||||
刷新库存,先清空supply中类型为ST的数据,再从ERP同步1600厂全部库存数据
|
||||
db_name: 账套名称,默认刷新所有账套
|
||||
"""
|
||||
console_log.info("开始执行刷新库存任务")
|
||||
response = None
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
try:
|
||||
response = sap_session.get(url=f"{sap_url1}", headers={'interface': 'stock', 'werks': werks})
|
||||
data = response.json()['data']
|
||||
stock = pd.DataFrame(data)
|
||||
stock = stock.astype({
|
||||
'werks': 'str',
|
||||
'matnr': 'str',
|
||||
'lgort': 'str',
|
||||
'labst': 'int32',
|
||||
'labst2': 'int32',
|
||||
'charg': 'str'
|
||||
})
|
||||
stock['avail_qty'] = stock['labst'] + stock['labst2']
|
||||
stock['supplyno'] = stock['werks'] + '-' + stock['matnr'] # 注意不要用f string,否则supplyno会变成所有料号的超长字符串
|
||||
stock['type'] = 'ST'
|
||||
stock['priority'] = 0
|
||||
stock['avail_date'] = now
|
||||
stock['dt_req'] = now
|
||||
stock['status'] = 'NEW'
|
||||
stock['category'] = ''
|
||||
stock['create_date'] = now
|
||||
stock = (stock
|
||||
.groupby(['supplyno'], as_index=False)
|
||||
.agg({
|
||||
'matnr': 'first',
|
||||
'avail_qty': 'sum',
|
||||
'type': 'first',
|
||||
'avail_date': 'first',
|
||||
'dt_req': 'first',
|
||||
'priority': 'first',
|
||||
'status': 'first',
|
||||
'category': 'first',
|
||||
'create_date': 'first',
|
||||
}))
|
||||
stock = stock.rename(columns={
|
||||
'matnr': 'materialno',
|
||||
})
|
||||
stock_data = stock.to_dict(orient='records')
|
||||
|
||||
dbs = db_name or SCHEDULED_DBS
|
||||
sap_session.delete(f"{THIS_BASE_URL}/api/t_supply?db_name={dbs}&type=ST")
|
||||
sap_session.post(f"{THIS_BASE_URL}/api/t_supply?db_name={dbs}", json=stock_data)
|
||||
console_log.info(f"刷新库存任务执行完成,账套:{dbs}")
|
||||
response = standard_response(message=f"刷新库存任务执行完成,账套:{dbs}")
|
||||
|
||||
except Exception as e:
|
||||
console_log.error(f"刷新库存任务执行失败: {str(e)}")
|
||||
response = standard_response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, success=0, message=f"刷新库存任务执行失败: {str(e)}")
|
||||
return response
|
||||
|
||||
#################################################################################
|
||||
# ⬇️数据库事件处理
|
||||
#################################################################################
|
||||
@@ -173,7 +158,7 @@ class MyapsDbActions(MyapsDbActionsAbc):
|
||||
确认计划任务,将主账套中需要转MO的PL推送到SAP,将计划任务状态更新为已确认
|
||||
"""
|
||||
try:
|
||||
supply_response = cls._session.get(f"{cls.this_base_url}/api/v_supply_mo?db_name={main_db}&supplyno={pl_data['supplyno']}")
|
||||
supply_response = sap_session.get(f"{THIS_BASE_URL}/api/v_supply_mo?db_name={MYAPS_MAIN_DB}&supplyno={pl_data['supplyno']}")
|
||||
supply_response_json = supply_response.json()
|
||||
supply_data = supply_response_json['data'][0]
|
||||
start_datetime = supply_data['dt_ordstart']#.strftime('%Y%m%d %H:%M:%S')
|
||||
@@ -188,7 +173,6 @@ class MyapsDbActions(MyapsDbActionsAbc):
|
||||
"GSTRP": start_datetime.split('T')[0], # 基本开始日期
|
||||
"GLTRP": end_datetime.split('T')[0], # 基本完成日期
|
||||
"GAMNG": supply_data['avail_qty'], # 总订单数量
|
||||
# "FEVOR": "SAP", # 生产主管
|
||||
"WEMPF": "SAP", # 产线代码
|
||||
"BACKUP1": ','.join([i['workcenter'] for i in orderwc])
|
||||
}
|
||||
@@ -200,7 +184,7 @@ class MyapsDbActions(MyapsDbActionsAbc):
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if sap_mo_data['STATUS'] == 'S':
|
||||
log_msg = f"✅推送计划任务执行成功,账套:{main_db},MO单号:{sap_mo_data['AUFNR']}"
|
||||
log_msg = f"✅推送计划任务执行成功,账套:{MYAPS_MAIN_DB},MO单号:{sap_mo_data['AUFNR']}"
|
||||
console_log.info(log_msg)
|
||||
file_log.info(log_msg)
|
||||
pl_data['mono'] = sap_mo_data['AUFNR']
|
||||
@@ -208,7 +192,7 @@ class MyapsDbActions(MyapsDbActionsAbc):
|
||||
pl_data['memo'] = f'✅{now} @ERP【{sap_mo_data['MESSAGE']}】'
|
||||
pl_data['is_execute_updates'] = True
|
||||
else:
|
||||
log_msg = f"🚫推送计划任务执行失败,账套:{main_db},错误信息:{sap_mo_data['MESSAGE']}"
|
||||
log_msg = f"🚫推送计划任务执行失败,账套:{MYAPS_MAIN_DB},错误信息:{sap_mo_data['MESSAGE']}"
|
||||
console_log.error(log_msg)
|
||||
file_log.error(log_msg)
|
||||
pl_data['mono'] = ''
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
"""江阴海达橡塑"""
|
||||
|
||||
import requests#, logging#, os, atexit
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import status
|
||||
|
||||
from ._base import (
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc,
|
||||
file_log, console_log, standard_response, get_session, HapConnection
|
||||
)
|
||||
|
||||
|
||||
#################################################################################
|
||||
# ⬇️对象及项目参数
|
||||
#################################################################################
|
||||
hap_conn = HapConnection(
|
||||
base_url='https://api.mingdao.com',
|
||||
app_key='d519a8ea60f9efa6',
|
||||
sign='NjAwYzI5OWJlMTNhNTcwODM5ZTEwOWE2YjE3ZDZiNWRmYzk4NTJjNTZmODQ4N2EzNGNjNWM2ZGMzNTBlYjY0Ng=='
|
||||
)
|
||||
|
||||
|
||||
class DefaultValue(DefaultValueAbc):
|
||||
MAT_PLANT = "1600" # 默认工厂
|
||||
MAT_PLANNER = "haida" # 默认计划员
|
||||
MAT_LOCATION = "1600" # 默认车间
|
||||
|
||||
main_db = MyapsDbActionsAbc.main_db
|
||||
werks = "1600"
|
||||
|
||||
#################################################################################
|
||||
# ⬇️项目可复用逻辑
|
||||
#################################################################################
|
||||
from apps.data_opt.utils.common import add_basic_auth_requests
|
||||
|
||||
sap_url1 = 'http://192.168.201.2:8000/zrestful_test2?sap-client=800' # 库存
|
||||
sap_url2 = 'http://192.168.201.2:8000/zrestful_plan?sap-client=800' # 计划
|
||||
|
||||
sap_username = 'T058'
|
||||
sap_password = '123456'
|
||||
# 创建requests会话
|
||||
sap_session = get_session(allowed_methods=["GET", "POST"])
|
||||
# sap_session2 = get_session(allowed_methods=["GET", "POST"])
|
||||
|
||||
# 添加Basic认证
|
||||
add_basic_auth_requests(sap_session, sap_username, sap_password)
|
||||
# add_basic_auth_requests(sap_session2, sap_username, sap_password)
|
||||
|
||||
# import json
|
||||
import uuid
|
||||
# from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
async def sap_post(url: str, session: requests.Session, interface_id: str, data: dict):
|
||||
"""
|
||||
向SAP系统发送POST请求
|
||||
url: 请求URL
|
||||
session: requests会话
|
||||
data: 请求数据
|
||||
"""
|
||||
headers = {
|
||||
"INTF_ID": interface_id,
|
||||
"SRC_SYSTEM": "APS",
|
||||
"DEST_SYSTEM": "SAP",
|
||||
"SRC_MSGID": str(uuid.uuid4()).replace("-", ""),
|
||||
"BACKUP1": "",
|
||||
"BACKUP2": ""
|
||||
}
|
||||
response: requests.Response = session.post(url, headers=headers, json={
|
||||
"HEAD": headers,
|
||||
"BODY": [data]
|
||||
})
|
||||
|
||||
response_json = {}
|
||||
if response.status_code == status.HTTP_200_OK:
|
||||
response_json = response.json()
|
||||
console_log.info(f"POST请求成功,状态码:{response.status_code},响应内容:{response_json}")
|
||||
else:
|
||||
console_log.error(f"POST请求失败,状态码:{response.status_code},响应内容:{response.text}")
|
||||
return {
|
||||
'status_code': response.status_code,
|
||||
'response_text': response.text,
|
||||
'response_json': response_json
|
||||
}
|
||||
|
||||
|
||||
#################################################################################
|
||||
# ⬇️定时任务设置
|
||||
#################################################################################
|
||||
class ScheduleTasks(ScheduleTasksAbc):
|
||||
|
||||
@classmethod
|
||||
async def refresh_stock(cls, db_name: str | None = None):
|
||||
"""
|
||||
刷新库存,先清空supply中类型为ST的数据,再从ERP同步1600厂全部库存数据
|
||||
db_name: 账套名称,默认刷新所有账套
|
||||
"""
|
||||
console_log.info("开始执行刷新库存任务")
|
||||
response = None
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
try:
|
||||
response = sap_session.get(url=f"{sap_url1}", headers={'interface': 'stock', 'werks': werks})
|
||||
data = response.json()['data']
|
||||
stock = pd.DataFrame(data)
|
||||
stock = stock.astype({
|
||||
'werks': 'str',
|
||||
'matnr': 'str',
|
||||
'lgort': 'str',
|
||||
'labst': 'int32',
|
||||
'labst2': 'int32',
|
||||
'charg': 'str'
|
||||
})
|
||||
stock['avail_qty'] = stock['labst'] + stock['labst2']
|
||||
stock['supplyno'] = stock['werks'] + '-' + stock['matnr'] # 注意不要用f string,否则supplyno会变成所有料号的超长字符串
|
||||
stock['type'] = 'ST'
|
||||
stock['priority'] = 0
|
||||
stock['avail_date'] = now
|
||||
stock['dt_req'] = now
|
||||
stock['status'] = 'NEW'
|
||||
stock['category'] = ''
|
||||
stock['create_date'] = now
|
||||
stock = (stock
|
||||
.groupby(['supplyno'], as_index=False)
|
||||
.agg({
|
||||
'matnr': 'first',
|
||||
'avail_qty': 'sum',
|
||||
'type': 'first',
|
||||
'avail_date': 'first',
|
||||
'dt_req': 'first',
|
||||
'priority': 'first',
|
||||
'status': 'first',
|
||||
'category': 'first',
|
||||
'create_date': 'first',
|
||||
}))
|
||||
stock = stock.rename(columns={
|
||||
'matnr': 'materialno',
|
||||
})
|
||||
stock_data = stock.to_dict(orient='records')
|
||||
|
||||
dbs = db_name or ','.join(cls.scheduled_dbs)
|
||||
cls._session.delete(f"{cls.this_base_url}/api/t_supply?db_name={dbs}&type=ST")
|
||||
cls._session.post(f"{cls.this_base_url}/api/t_supply?db_name={dbs}", json=stock_data)
|
||||
console_log.info(f"刷新库存任务执行完成,账套:{dbs}")
|
||||
response = standard_response(message=f"刷新库存任务执行完成,账套:{dbs}")
|
||||
|
||||
except Exception as e:
|
||||
console_log.error(f"刷新库存任务执行失败: {str(e)}")
|
||||
response = standard_response(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, success=0, message=f"刷新库存任务执行失败: {str(e)}")
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
async def get_bom(cls):
|
||||
"""
|
||||
从SAP获取BOM数据
|
||||
"""
|
||||
response = sap_session.get(url=sap_url1, headers={'interface': 'bom', 'werks': "1600"})
|
||||
bom_json_data = response.json()['data']
|
||||
return bom_json_data
|
||||
|
||||
#################################################################################
|
||||
# ⬇️数据库事件处理
|
||||
#################################################################################
|
||||
class MyapsDbActions(MyapsDbActionsAbc):
|
||||
|
||||
@classmethod
|
||||
async def confirm_pl(cls, pl_data: dict):
|
||||
"""
|
||||
确认计划任务,将主账套中需要转MO的PL推送到SAP,将计划任务状态更新为已确认
|
||||
"""
|
||||
try:
|
||||
supply_response = cls._session.get(f"{cls.this_base_url}/api/v_supply_mo?db_name={main_db}&supplyno={pl_data['supplyno']}")
|
||||
supply_response_json = supply_response.json()
|
||||
supply_data = supply_response_json['data'][0]
|
||||
start_datetime = supply_data['dt_ordstart']#.strftime('%Y%m%d %H:%M:%S')
|
||||
end_datetime = supply_data['dt_ordend']#.strftime('%Y%m%d %H:%M:%S')
|
||||
orderwc = supply_data['orderwc']
|
||||
data = {
|
||||
# "CY_SEQNR": supply_data['supplyno'], # APS单号
|
||||
"WERKS": werks, # 工厂
|
||||
"MATNR": supply_data['materialno'],
|
||||
"AUART": "ZP01", # 订单类型
|
||||
"VERID": "SAP", # 生产版本
|
||||
"GSTRP": start_datetime.split('T')[0], # 基本开始日期
|
||||
"GLTRP": end_datetime.split('T')[0], # 基本完成日期
|
||||
"GAMNG": supply_data['avail_qty'], # 总订单数量
|
||||
# "FEVOR": "SAP", # 生产主管
|
||||
"WEMPF": "SAP", # 产线代码
|
||||
"BACKUP1": ','.join([i['workcenter'] for i in orderwc])
|
||||
}
|
||||
|
||||
# sap_response = await sap_post(url=sap_url2, session=sap_session2, interface_id="ZPP_PLAN_ORD_CREATE", data=data)
|
||||
sap_response = await sap_post(url=sap_url2, session=sap_session, interface_id="ZPP_PLAN_ORD_CREATE", data=data)
|
||||
sap_response_json = sap_response['response_json']
|
||||
sap_mo_data = sap_response_json['BODY'][0]
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if sap_mo_data['STATUS'] == 'S':
|
||||
log_msg = f"✅推送计划任务执行成功,账套:{main_db},MO单号:{sap_mo_data['AUFNR']}"
|
||||
console_log.info(log_msg)
|
||||
file_log.info(log_msg)
|
||||
pl_data['mono'] = sap_mo_data['AUFNR']
|
||||
pl_data['status'] = 'E2A'
|
||||
pl_data['memo'] = f'✅{now} @ERP【{sap_mo_data['MESSAGE']}】'
|
||||
pl_data['is_execute_updates'] = True
|
||||
else:
|
||||
log_msg = f"🚫推送计划任务执行失败,账套:{main_db},错误信息:{sap_mo_data['MESSAGE']}"
|
||||
console_log.error(log_msg)
|
||||
file_log.error(log_msg)
|
||||
pl_data['mono'] = ''
|
||||
pl_data['status'] = 'CRE' # ❗❗失败情况下,状态务必回撤为 CRE ,否则后续无法再次下达
|
||||
pl_data['memo'] = f'🚫{now} @ERP【{sap_mo_data['MESSAGE']}】'
|
||||
pl_data['is_execute_updates'] = False
|
||||
except Exception as e:
|
||||
log_msg = f"🚫推送计划任务执行失败: {str(e)}"
|
||||
console_log.error(log_msg)
|
||||
file_log.error(log_msg)
|
||||
pl_data['mono'] = ''
|
||||
pl_data['status'] = 'CRE'
|
||||
pl_data['memo'] = f'🚫{now} @APS【{str(e)}】'
|
||||
pl_data['is_execute_updates'] = False
|
||||
|
||||
await super().confirm_pl(pl_data)
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from datetime import datetime
|
||||
from fastapi import status
|
||||
|
||||
from ._base import (
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc, DefaultParamsAbc,
|
||||
ScheduleTasksAbc, MyapsDbActionsAbc, DefaultValueAbc,
|
||||
file_log, console_log, standard_response, get_session, HapConnection
|
||||
)
|
||||
|
||||
@@ -26,8 +26,6 @@ hap_conn = HapConnection(
|
||||
sign='NjAwYzI5OWJlMTNhNTcwODM5ZTEwOWE2YjE3ZDZiNWRmYzk4NTJjNTZmODQ4N2EzNGNjNWM2ZGMzNTBlYjY0Ng=='
|
||||
)
|
||||
|
||||
class DefaultParams(DefaultParamsAbc):
|
||||
pass
|
||||
|
||||
class DefaultValue(DefaultValueAbc):
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ from typing import Dict, Optional, Union, Tuple, Any
|
||||
import qrcode
|
||||
from qrcode.image.styledpil import StyledPilImage
|
||||
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer
|
||||
from qrcode.image.styles.colormasks import SquareGradiantColorMask
|
||||
from qrcode.image.svg import SvgImage, SvgFragmentImage
|
||||
# from qrcode.image.styles.colormasks import SquareGradiantColorMask
|
||||
from qrcode.image.svg import SvgImage#, SvgFragmentImage
|
||||
import barcode
|
||||
from barcode.writer import ImageWriter, SVGWriter
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import os, base64, requests, json, ast, re
|
||||
import base64, requests, json, ast, re#,os,
|
||||
from urllib3.util.retry import Retry
|
||||
from requests.adapters import HTTPAdapter
|
||||
from typing import Optional, Dict, Union
|
||||
|
||||
@@ -8,6 +8,8 @@ from apscheduler.executors.pool import ThreadPoolExecutor
|
||||
from apscheduler.jobstores.memory import MemoryJobStore
|
||||
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_MISSED
|
||||
|
||||
from config.settings import TURN_ON_SCHEDULE_TASK
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -248,7 +250,7 @@ def weekly_task(day_of_week: str = 'mon', hour: int = 0, minute: int = 0):
|
||||
|
||||
def initialize_scheduler():
|
||||
"""初始化并启动调度器"""
|
||||
if os.getenv("TURN_ON_SCHEDULE_TASK", "False").lower() == "true":
|
||||
if TURN_ON_SCHEDULE_TASK:
|
||||
if scheduler_manager.init_scheduler():
|
||||
scheduler_manager.start()
|
||||
atexit.register(scheduler_manager.shutdown)
|
||||
@@ -268,7 +270,7 @@ def get_scheduler_status() -> Dict:
|
||||
}
|
||||
|
||||
# 应用启动时自动初始化
|
||||
# if os.getenv("TURN_ON_SCHEDULE_TASK", "False").lower() == "true":
|
||||
# if TURN_ON_SCHEDULE_TASK:
|
||||
# # initialize_scheduler()
|
||||
# # 或者在模块导入后合适的时机调用
|
||||
# pass
|
||||
|
||||
@@ -8,7 +8,7 @@ import functools
|
||||
from fastapi import APIRouter, Query, Body, status#, Request, Path
|
||||
# from tortoise import Tortoise
|
||||
|
||||
from config import settings
|
||||
from config.settings import MYAPS_DB_SET, MYAPS_MAIN_DB
|
||||
from globalobjects import globalconst as gc
|
||||
from .models import TMaterial, TWorkcenter, TMatWc, TMatVer, TMatWcBom, TSupply, TDemand, TMold, TMatWcMold, TConfirm#,TortoiseBaseModel
|
||||
from .schemas import (
|
||||
@@ -144,9 +144,9 @@ async def get_meta():
|
||||
success=1,
|
||||
message="获取元数据成功",
|
||||
meta={
|
||||
"db_set": settings.MYAPS_DB_SET,
|
||||
"dbs_str": ",".join(settings.MYAPS_DB_SET),
|
||||
"main_db": settings.MYAPS_MAIN_DB,
|
||||
"db_set": MYAPS_DB_SET,
|
||||
"dbs_str": ",".join(MYAPS_DB_SET),
|
||||
"main_db": MYAPS_MAIN_DB,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -18,11 +18,14 @@ MYAPS_DB_USER = os.getenv("MYAPS_DB_USER")
|
||||
MYAPS_DB_PASSWORD = os.getenv("MYAPS_DB_PASSWORD")
|
||||
MYAPS_DB_SET = os.getenv("MYAPS_DB_SET").split(",")
|
||||
MYAPS_MAIN_DB = os.getenv("MYAPS_MAIN_DB")
|
||||
SCHEDULED_DBS = os.getenv('SCHEDULED_DBS')
|
||||
TEST_DB = os.getenv("TEST_DB", "testdb")
|
||||
|
||||
THIS_PROTOCOL = os.getenv("THIS_PROTOCOL", "http://")
|
||||
THIS_SERVER_HOST = os.getenv("THIS_SERVER_HOST")
|
||||
THIS_SERVER_PORT = int(os.getenv("THIS_SERVER_PORT"))
|
||||
THIS_BASE_URL = f"{THIS_PROTOCOL}localhost:{THIS_SERVER_PORT}"
|
||||
|
||||
# 本API数据库配置<postgreSQL>
|
||||
THIS_DB_HOST = os.getenv("THIS_DB_HOST")
|
||||
THIS_DB_PORT = int(os.getenv("THIS_DB_PORT"))
|
||||
@@ -30,6 +33,8 @@ THIS_DB_USER = os.getenv("THIS_DB_USER")
|
||||
THIS_DB_PASSWORD = os.getenv("THIS_DB_PASSWORD")
|
||||
THIS_DB_NAME = os.getenv("THIS_DB_NAME")
|
||||
|
||||
TURN_ON_SCHEDULE_TASK = os.getenv('TURN_ON_SCHEDULE_TASK', 'true').lower() == 'true'
|
||||
|
||||
######################################################################################
|
||||
# 数据库配置
|
||||
connections = {}
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi.responses import JSONResponse
|
||||
from fastapi.openapi.docs import get_swagger_ui_html
|
||||
from tortoise.contrib.fastapi import register_tortoise
|
||||
|
||||
from config.settings import TORTOISE_ORM_CONFIG, THIS_SERVER_PORT, BASE_DIR
|
||||
from config.settings import TORTOISE_ORM_CONFIG, THIS_SERVER_PORT, BASE_DIR, TURN_ON_SCHEDULE_TASK
|
||||
from globalobjects import file_timed_logger
|
||||
from apps.io_api.routers import rt as io_rt
|
||||
from apps.io_api.common import register_exception_handlers
|
||||
@@ -200,7 +200,7 @@ register_tortoise(
|
||||
)
|
||||
|
||||
# 检查是否开启定时任务
|
||||
if os.getenv("TURN_ON_SCHEDULE_TASK", False).lower() == "true":
|
||||
if TURN_ON_SCHEDULE_TASK:
|
||||
# 初始化定时任务管理器
|
||||
from apps.data_opt.utils.scheduler import initialize_scheduler, get_scheduler_status
|
||||
initialize_scheduler()
|
||||
|
||||
+7
-80
@@ -205,33 +205,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 本地访问提示样式 */
|
||||
.local-access-notice {
|
||||
background-color: #e3f2fd;
|
||||
border: 1px solid #2196f3;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.local-access-notice .icon {
|
||||
margin-right: 8px;
|
||||
color: #2196f3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>数据校验工具</h1>
|
||||
|
||||
<!-- 本地访问提示 -->
|
||||
<div class="local-access-notice" id="localAccessNotice" style="display: none;">
|
||||
<span class="icon">🏠</span>
|
||||
<strong>本地访问检测到</strong>:API密钥输入框已自动隐藏,无需输入API密钥即可使用。
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<div class="tab-nav">
|
||||
<button class="tab-button active" data-tab="bom">BOM校验</button>
|
||||
@@ -248,8 +228,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="bomApiKeyGroup">
|
||||
<label for="bomApiKey">API密钥 <span class="required">*</span></label>
|
||||
<input type="text" id="bomApiKey" placeholder="请输入API密钥" required>
|
||||
<label for="bomApiKey">API密钥 <span class="optional">(可选)</span></label>
|
||||
<input type="text" id="bomApiKey" placeholder="本地访问可留空,远程访问请输入API密钥">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -308,8 +288,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="routeApiKeyGroup">
|
||||
<label for="routeApiKey">API密钥 <span class="required">*</span></label>
|
||||
<input type="text" id="routeApiKey" placeholder="请输入API密钥" required>
|
||||
<label for="routeApiKey">API密钥 <span class="optional">(可选)</span></label>
|
||||
<input type="text" id="routeApiKey" placeholder="本地访问可留空,远程访问请输入API密钥">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -351,43 +331,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 检测是否为本地访问
|
||||
function isLocalAccess() {
|
||||
const hostname = window.location.hostname;
|
||||
const protocol = window.location.protocol;
|
||||
|
||||
// 本地访问的特征:
|
||||
// 1. hostname为 localhost 或 127.0.0.1
|
||||
// 2. hostname以 .local 或 .localhost 结尾
|
||||
// 3. 私有IP地址范围 (192.168.x.x, 10.x.x.x, 172.16.x.x-172.31.x.x)
|
||||
return hostname === 'localhost' ||
|
||||
hostname === '127.0.0.1' ||
|
||||
hostname.endsWith('.local') ||
|
||||
hostname.endsWith('.localhost') ||
|
||||
hostname.startsWith('192.168.') ||
|
||||
hostname.startsWith('10.') ||
|
||||
(hostname.startsWith('172.16.') || hostname.startsWith('172.17.') ||
|
||||
hostname.startsWith('172.18.') || hostname.startsWith('172.19.') ||
|
||||
hostname.startsWith('172.20.') || hostname.startsWith('172.21.') ||
|
||||
hostname.startsWith('172.22.') || hostname.startsWith('172.23.') ||
|
||||
hostname.startsWith('172.24.') || hostname.startsWith('172.25.') ||
|
||||
hostname.startsWith('172.26.') || hostname.startsWith('172.27.') ||
|
||||
hostname.startsWith('172.28.') || hostname.startsWith('172.29.') ||
|
||||
hostname.startsWith('172.30.') || hostname.startsWith('172.31.'));
|
||||
}
|
||||
|
||||
// TAB切换功能
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 检测本地访问并隐藏API密钥输入框
|
||||
if (isLocalAccess()) {
|
||||
// 隐藏API密钥输入框
|
||||
document.getElementById('bomApiKeyGroup').style.display = 'none';
|
||||
document.getElementById('routeApiKeyGroup').style.display = 'none';
|
||||
|
||||
// 显示本地访问提示
|
||||
document.getElementById('localAccessNotice').style.display = 'block';
|
||||
}
|
||||
|
||||
const tabButtons = document.querySelectorAll('.tab-button');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
@@ -452,16 +397,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为本地访问,使用不同的API密钥获取方式
|
||||
const isLocal = isLocalAccess();
|
||||
const apiKey = isLocal ? 'local_access' : document.getElementById('bomApiKey').value;
|
||||
|
||||
// 如果不是本地访问且没有输入API密钥,则提示输入
|
||||
if (!isLocal && !apiKey) {
|
||||
alert('请输入API密钥');
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = document.getElementById('bomApiKey').value || 'local_access';
|
||||
const parentCol = document.getElementById('bomParentCol').value;
|
||||
const childCol = document.getElementById('bomChildCol').value;
|
||||
const numeratorCol = document.getElementById('bomNumeratorCol').value;
|
||||
@@ -590,16 +526,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为本地访问,使用不同的API密钥获取方式
|
||||
const isLocal = isLocalAccess();
|
||||
const apiKey = isLocal ? 'local_access' : document.getElementById('routeApiKey').value;
|
||||
|
||||
// 如果不是本地访问且没有输入API密钥,则提示输入
|
||||
if (!isLocal && !apiKey) {
|
||||
alert('请输入API密钥');
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = document.getElementById('routeApiKey').value || 'local_access';
|
||||
const productCol = document.getElementById('routeProductCol').value;
|
||||
const productVersionCol = document.getElementById('routeProductVersionCol').value;
|
||||
const sortNoCol = document.getElementById('routeSortNoCol').value;
|
||||
@@ -684,4 +611,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user