服务器动作生成png小票图片 - xiaohao0576/odoo-doc GitHub Wiki

# Available variables:
#  - env: environment on which the action is triggered
#  - model: model of the record on which the action is triggered; is a void recordset
#  - record: record on which the action is triggered; may be void
#  - records: recordset of all records on which the action is triggered in multi-mode; may be void
#  - time, datetime, dateutil, timezone: useful Python libraries
#  - float_compare: utility function to compare floats based on specific precision
#  - b64encode, b64decode: functions to encode/decode binary data
#  - log: log(message, level='info'): logging function to record debug information in ir.logging table
#  - _logger: _logger.info(message): logger to emit messages in server logs
#  - UserError: exception class for raising user-facing warning messages
#  - Command: x2many commands namespace
# To return an action, assign: action = {...}

html_body = '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多语言黑白表格 (576px 宽度)</title>

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:[email protected]&display=swap" rel="stylesheet">

    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Khmer:[email protected]&display=swap" rel="stylesheet">

    <style>
        /* CSS 样式 */
        body {
            /* Prioritize Noto Sans Khmer first for broad support, then Noto Sans SC, then a generic sans-serif */
            font-family: 'Noto Sans Khmer', 'Noto Sans SC', sans-serif;
            background-color: #FFFFFF;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            min-height: 100vh;
            margin: 0;
            padding: 0px;
            box-sizing: border-box;
        }

        .page-container {
            max-width: 576px;
            width: 100%;
            background-color: #FFFFFF;
            padding: 5px;
            box-sizing: border-box;
        }

        table {
            border-collapse: collapse;
            width: 100%;
            background-color: #FFFFFF;
            color: #000000;
            font-size: 24px;
        }

        th, td {
            border: 1px solid #000000;
            padding: 1px 3px;
            text-align: left;
        }

        th {
            background-color: #000000;
            font-weight: bold;
            color: #FFFFFF;
        }

        .col-qty {
            width: 30px;
            text-align: center;
        }

        .col-product {
            width: auto;
            /* No specific font rule here, it inherits from body */
        }
    </style>
</head>
<body>
    
    <div><h2 style="text-align: center; font-size: 28px;">下单菜品(Table <TABLE_NUMBER/>)</h2></div>
    <div class="page-container">
        <table>
            <thead>
                <tr>
                    <th class="col-qty">QTY</th>
                    <th class="col-product">Product</th>
                </tr>
            </thead>
            <tbody>
<TABLE_BODY/>
            </tbody>
        </table>
    </div>
    <div style="padding-left: 15px; font-size: 24px;"><p><ORDER_TIME/></p></div>

</body>
</html>'''


order_number = env.context.get("order_number","")
pos_id = env.context.get("pos_id", 0)
order = env['pos.order'].search(domain=[("session_id.config_id", "=", pos_id),("tracking_number", "=", order_number)], order='id desc', limit=1)

if order:
    tbody = ''
    for line in order.lines:
        tbody += f'''<tr>
                        <td class="col-qty">{int(line.qty)}</td>
                        <td class="col-product">{line.full_product_name}</td>
                </tr>'''
    
    html_body = html_body.replace('<TABLE_BODY/>', tbody)
    
    now = datetime.datetime.now() + datetime.timedelta(hours=7)
    formatted_time = now.strftime("%Y-%m-%d %H:%M:%S")
    html_body = html_body.replace('<ORDER_TIME/>', formatted_time)
    html_body = html_body.replace('<TABLE_NUMBER/>', str(order.table_id.table_number))
   
    
    toimage = env['ir.actions.report']._run_wkhtmltoimage
    
    images = toimage(bodies=[html_body], width=576, height=0, image_format='png')
    
    img = b64encode(images[0]).decode("utf-8")
    
    action = {'img': img}

使用python脚本,调用以上服务器动作,获得png文件,并在本地打印

import sys
import os
import time
import requests
import base64
import json

def main():
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <三位订单编号>")
        sys.exit(1)

    order_number = sys.argv[1]
    output_image = "/tmp/output_image.png"

    # 删除旧的图片文件
    if os.path.exists(output_image):
        os.remove(output_image)

    # 构造POST数据
    payload = {
        "jsonrpc": "2.0",
        "method": "call",
        "params": {
            "service": "object",
            "method": "execute_kw",
            "args": [
                "datang-erp-test26",
                2,
                "e343a__*****___yourapikey___*****___d64923",
                "ir.actions.server",
                "run",
                [[1036]],
                {"context": {"order_number": order_number, "pos_id": 2}}
            ],
            "kwargs": {}
        },
        "id": int(time.time()) 
    }

    headers = {'Content-Type': 'application/json'}
    url = "https://datang-erp-test26.odoo.com/jsonrpc"

    # 发送POST请求
    response = requests.post(url, headers=headers, data=json.dumps(payload))
    response.raise_for_status()
    result = response.json()

    # 提取图片并解码
    img_base64 = result.get('result', {}).get('img')
    if not img_base64:
        print("未获取到图片数据")
        sys.exit(1)

    with open(output_image, "wb") as f:
        f.write(base64.b64decode(img_base64))

    # 第二个POST请求
    print_url = "https://localhost.ip.hogantech.net:1443/eprint/png"
    print_payload = {
        "x_printer": "usb",
        "x_url": f"file:{output_image}"
    }

    print_headers = {'Content-Type': 'application/json'}
    requests.post(print_url, headers=print_headers, data=json.dumps(print_payload), verify=True)

if __name__ == "__main__":
    main()
⚠️ **GitHub.com Fallback** ⚠️