泛微eteams OA对接金蝶云星空写入数据

需求:

公司需要先在OA上对准备生产的订单进行一次量产评审,所有相关人员评审通过后才可以进行生产,导致下工单的人员每次需要把OA上的信息复制到ERP进行审批。

为什么不直接在ERP上审批呢?
首先该节点涉及到很多不用ERP的用户,为了不增加用户数浪费资源。

其次泛微OA是以表单形式展现,相关人员有对该表单进行受控过,原先是需要线下进行签核表单信息,直接转为线上,支持手机电脑任意方式审批,加快效率。


总体设计思路:

1、开发一个泛微鉴权服务,保证获取的accesstoken是最新的,减少调用数量。

2、开发回调应用服务,对指定流程审核后进行触发,里面有一条判断审批节点的逻辑,原因是泛微的动作流需要收费,免费的只能获取所有回调,不支持指定审批节点回调,这样就导致任何一个节点都会触发一次回调,需要进行判断当前所属节点,如果是指定节点才进行下一步操作。

最终功能:

当OA流程到达指定审批节点后,自动把OA数据写入到云星空ERP的生产工单中。

开发语言采用python  ,涉及到FastAPI、json、K3CloudApiSdk等常用库

开发代码,为方便查看和阅读,先硬编码写入JSON模板,以及采用替换方式完成模板替换,没有进行异常处理,后续考虑异常处理添加一个通知到企业微信给指定用户。

代码仅供参考:

#泛微 estams OA
#定时获取accesstoken服务,其他程序通过get获取OA的最新accesstoken
from fastapi import FastAPI, HTTPException
import httpx
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from contextlib import asynccontextmanager

app = FastAPI()
scheduler = AsyncIOScheduler()

# 全局变量来存储 accessToken
access_token = ""

# 配置OA信息
CORP_ID = "公司ID"
APP_KEY = "自建应用key"
APP_SECRET = "自建应用secret"


async def fetch_access_token():
    global access_token

    # Step 1: 获取code
    authorize_url = "https://api.eteams.cn/oauth2/authorize"
    params = {
        "corpid": CORP_ID,
        "response_type": "code",
        "state": "lijinbin"
    }

    async with httpx.AsyncClient() as client:
        response = await client.get(authorize_url, params=params)
        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code, detail=response.text)

        data = response.json()
        if data["errcode"] != "0":
            raise HTTPException(status_code=400, detail=data["errmsg"])

        code = data["code"]

    # Step 2: 使用code获取accessToken
    token_url = "https://api.eteams.cn/oauth2/access_token"
    payload = {
        "app_key": APP_KEY,
        "app_secret": APP_SECRET,
        "grant_type": "authorization_code",
        "code": code
    }

    async with httpx.AsyncClient() as client:
        response = await client.post(token_url, data=payload)
        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code, detail=response.text)

        data = response.json()
        if data["errcode"] != "0":
            raise HTTPException(status_code=400, detail=data["errmsg"])

        access_token = data["accessToken"]


@asynccontextmanager
async def lifespan(app: FastAPI):
    # 在启动时获取一次 accessToken
    await fetch_access_token()
    # 定时任务每7100秒(略少于7200秒)更新一次 accessToken
    scheduler.add_job(fetch_access_token, 'interval', seconds=7100)
    scheduler.start()
    yield
    scheduler.shutdown()


app = FastAPI(lifespan=lifespan)


#通过get返回最新的accesstoken
@app.get("/get_access_token")
async def get_access_token():
    return {"accessToken": access_token}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=5002)
#读取OA流程数据,然后写入信息到金蝶ERP
from fastapi import FastAPI, Request, HTTPException
import httpx
from k3cloud_webapi_sdk.main import K3CloudApiSdk
import json, uuid
from datetime import datetime



app = FastAPI()

access_token = ""

api_sdk = K3CloudApiSdk()

api_sdk.InitConfig('填写acct_id', '用户名', '填写app_id',
                   '填写app_secret')
# 订单量产评审的json模板
ddlcps_json = '''{
    "NeedUpDateFields": [],
    "NeedReturnFields": [],
    "IsDeleteEntry": "true",
    "SubSystemId": "",
    "IsVerifyBaseDataField": "false",
    "IsEntryBatchFill": "true",
    "ValidateFlag": "true",
    "NumberSearch": "true",
    "IsAutoAdjustField": "false",
    "InterationFlags": "",
    "IgnoreInterationFlag": "",
    "IsControlPrecision": "false",
    "ValidateRepeatJson": "false",
    "Model": {
        "FID": 0,
        "FBillType": {
            "FNUMBER": "SCDD05_SYS"
        },
        "FDate": "file_单据日期",
        "FPrdOrgId": {
            "FNumber": "100"
        },
        "FOwnerTypeId": "BD_OwnerOrg",
        "FIsRework": false,
        "FBusinessType": "1",
        "FTrustteed": false,
        "FIsEntrust": false,
        "FPPBOMType": "1",
        "FIssueMtrl": false,
		"FDescription": "file_备注",
		"F_SLJH_Text_qtr": "file_OA流程",
        "FIsQCMO": false,
        "FTreeEntity": [
            {
                "FProductType": "1",
                "FMaterialId": {
                    "FNumber": "file_物料编码"
                },
                "FWorkShopID": {
                    "FNumber": "file_生产车间"
                },
                "FUnitId": {
                    "FNumber": "Pcs"
                },
                "FQty": file_数量,
                "FYieldQty": file_数量,
                "FPlanStartDate": "file_计划开始时间",
                "FPlanFinishDate": "file_计划完成时间",
                "FRequestOrgId": {
                    "FNumber": "100"
                },
                "FISBACKFLUSH": true,
                "FStockInOrgId": {
                    "FNumber": "100"
                },
                "FBaseYieldQty": file_数量,
                "FReqType": "1",
                "FPriority": 0,
                "FSrcBillType": "",
                "FSTOCKREADY": 0.0,
                "FBaseStockReady": 0.0,
                "FBaseRepairQty": 0.0,
                "FRepairQty": 0.0,
                "FBaseStockInScrapSelQty": 0.0,
                "FStockInScrapSelQty": 0.0,
                "FBaseStockInScrapQty": 0.0,
                "FStockInScrapQty": 0.0,
                "FBaseRptFinishQty": 0.0,
                "FRptFinishQty": 0.0,
                "FStockInFailSelAuxQty": 0.0,
                "FStockInUlRatio": 0.0,
                "FInStockOwnerTypeId": "BD_OwnerOrg",
                "FBaseStockInLimitH": file_数量,
                "FInStockOwnerId": {
                    "FNumber": "100"
                },
                "FSrcBillNo": "",
                "FStockInLlRatio": 0.0,
                "FCheckProduct": true,
                "FBaseStockInLimitL": file_数量,
                "FBaseUnitQty": file_数量,
                "FRepQuaSelAuxQty": 0.0,
                "FRepQuaAuxQty": 0.0,
                "FRepFailSelAuxQty": 0.0,
                "FRoutingId": {
                    "FNumber": "file_工艺路线"
                },
                "FRepFailAuxQty": 0.0,
                "FStockInQuaAuxQty": 0.0,
                "FStockInQuaSelAuxQty": 0.0,
                "FStockInReMadeSelQty": 0.0,
                "FStockInFailAuxQty": 0.0,
                "FStockInQuaSelQty": 0.0,
                "FStockInQuaQty": 0.0,
                "FBaseUnitId": {
                    "FNumber": "Pcs"
                },
                "FStockInFailSelQty": 0.0,
                "FStockInFailQty": 0.0,
                "FRepQuaSelQty": 0.0,
                "FRepQuaQty": 0.0,
                "FStockInLimitH": file_数量,
                "FRepFailSelQty": 0.0,
                "FRepFailQty": 0.0,
                "FStockInLimitL": file_数量,
                "FOperId": 0,
                "FCostRate": 100.0,
                "FCreateType": "1",
                "FYieldRate": 100.0,
                "FGroup": 1,
                "FNoStockInQty": file_数量,
                "FRowExpandType": 0,
                "FBaseNoStockInQty": file_数量,
                "FRowId": "e3ec95fb-e4c0-94d4-11ef-635a7638364c",
                "FScheduleSeq": 0.0,
                "FSNQty": 0.0,
                "FScheduleProcSplit": 0,
                "FReStkQuaQty": 0.0,
                "FBaseReStkQuaQty": 0.0,
                "FReStkFailQty": 0.0,
                "FBaseReStkFailQty": 0.0,
                "FReStkScrapQty": 0.0,
                "FBaseReStkScrapQty": 0.0,
                "FReStkReMadeQty": 0.0,
                "FStockInReMadeQty": 0.0,
                "FBaseReStkReMadeQty": 0.0,
                "FScheduleStatus": "1",
                "FPickMtrlStatus": "1",
                "FISNEWLC": 0,
                "FSrcSplitSeq": 0,
                "FSrcSplitEntryId": 0,
                "FSrcSplitId": 0,
                "FSRCBOMENTRYID": 0,
                "FMOChangeFlag": false,
                "FBaseStockInReMadeSelQty": 0.0,
                "FIsFirstInspect": false,
                "FFirstInspectStatus": "A",
                "FBaseSampleDamageQty": 0.0,
                "FSampleDamageQty": 0.0,
                "FISENABLESCHEDULE": false,
                "FPPBOMENTRYID": 0,
                "FBOMENTRYID": 0,
                "FISMRPCAL": false,
                "F_ora_Text_re5": "file_客户订单号",
                "FIsMRP": false,
                "F_SLJH_Text_83g": "file_客户物料号",
                "FFirstQCControlType": "0",
                "FIsGenerateOrder": false,
                "FBaseDirectStockInQuaQty": 0.0,
                "FDirectStockInQuaQty": 0.0,
                "FBaseDirectPickMtrlSelQty": 0.0,
                "FDirectPickMtrlSelQty": 0.0
            }
        ]
    }
}'''


#泛微OA回调服务,只要流程节点审批后就回调信息
@app.get("/callback")
async def receive_callback(request: Request):
    params = request.query_params
    id_value = params.get("id")
    if not id_value:
        raise HTTPException(status_code=400, detail="Missing 'id' parameter")

    print(id_value)  # 流程ID
    await get_acc_token()
    await read_lc_info(id_value)

    return {"message": "Callback received successfully", "params": dict(params)}


async def get_acc_token():
    global access_token
    print('准备读取token')
    #这个地址就是我挂accesstoken服务的地址,使用get获取最新的accesstoken
    url = "http://192.168.0.121:5002/get_access_token"

    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code, detail=response.text)

        data = response.json()
        access_token = data["accessToken"]
        print('读取到token:' + access_token)


#读取流程信息json信息
async def read_lc_info(id):
    url = "https://api.eteams.cn/workflow/v2/getInfoByID"
    params = {
        "access_token": access_token,
        "userid": "OA的用户ID",
        "id": id
    }

    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        if response.status_code != 200:
            raise HTTPException(status_code=response.status_code, detail=response.text)

        data = response.json()
        print(data)

        # 读取当前节点
        data_details = str(data['flowRequest']['currentNode'])
        print(data_details)
        # 如果当前节点等于供应链管理部,就执行插入工单到金蝶
        if data_details == '供应链管理部':
            print('节点正确,执行入到金蝶生产工单')
            await read_lc_file(data)


#通过物料读取上一次的历史信息,方便判断这个物料的生产车间和工艺路线
async def select_FWorkShopID(FMaterial_Id):
    # FBillNo 订单号
    # FMaterialId.FNumber 物料编码
    # FWorkShopID.FNumber 生产车间编码
    # FRoutingId.FName 工艺路线
    #FRoutingId.FNumber 工艺路线编码
    # "FieldKeys": "FBillNo,FMaterialId.FNumber,FWorkShopID.FNumber,FRoutingId.FNumber",
    para1 = {
        "FormId": "PRD_MO",
        "FieldKeys": "FBillNo,FMaterialId.FNumber,FWorkShopID.FNumber,FRoutingId.FNumber",
        "FilterString": f"FMaterialId.FNumber='{FMaterial_Id}' and FDocumentStatus='C' O",
        "OrderString": "FDate desc",
        "TopRowCount": 0,
        "StartRow": 0,
        "Limit": 10,
        "SubSystemId": ""
    }
    response = api_sdk.ExecuteBillQuery(para1)
    #如果没有物料数据的情况下,默认生产车间和工艺路线
    if not response:
        #生产车间、工艺路线
        return 'BM000013', 'RT000006'

    data = json.loads(response)

    gylx_id = data[0][2]
    cj_id = data[0][3]
    # 工艺路线、生产车间
    return gylx_id,cj_id

# 读取流程里面的json字段信息
async def read_lc_file(lc_file):
    # 读取流程明细字段
    lc_mx = lc_file['formData']['dataDetails']
    lc_name = str(lc_file['flowRequest']['name'])
    # 读取插入金蝶的模板
    template = ddlcps_json
    current_date = datetime.now().strftime('%Y-%m-%d')
    template = template.replace('file_单据日期', current_date)
    erp_number=str(lc_mx.get('ERP编号', None))
    #读取该物料上一次的工艺路线,车间和工艺路线
    cj,gylx=await select_FWorkShopID(erp_number)
    
    #一步步替换
    template = template.replace('file_生产车间', str(cj))  # 默认半自动
    template = template.replace('file_计划开始时间', current_date)
    template = template.replace('file_计划完成时间', lc_mx.get('要求交货日期', None))
    template = template.replace('file_物料编码', erp_number)
    template = template.replace('file_客户订单号', lc_mx.get('客户订单编号', None))
    template = template.replace('file_备注', lc_mx.get('客户', None) + "___" + lc_mx.get('软件型号', None))
    template = template.replace('file_数量', lc_mx.get('订单数量', None))
    template = template.replace('file_工艺路线', str(gylx))
    template = template.replace('file_OA流程', lc_name)
    rowid = uuid.uuid4()
    template = template.replace('file_uuid', str(rowid))
    # 判断客户的物料号是否存在,如果存在才进行插入,否者留空
    if '客户物料编码' in lc_mx:
        template = template.replace('file_客户物料号', lc_mx.get('客户物料编码', None))
    else:
        template = template.replace('file_客户物料号', '')
    print('这是预备插入到金蝶的数据:' + template)
    
    #插入到金蝶的生产订单中暂存
    await insert_h_po_order(template)


# 创建金蝶生产工单并暂存
async def insert_h_po_order(gd_info):
    formId = "PRD_MO"
    # 调用接口
    response = api_sdk.Draft(formId, gd_info)

    print("接口返回结果:" + response)
    # 对返回结果进行解析和校验
    res = json.loads(response)
    is_success = res["Result"]["ResponseStatus"]["IsSuccess"]
    print(type(is_success))  # 打印出值和类型以便进一步分析


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="192.168.0.121", port=18809)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值