Accept Merge Request #149: (feature/一键打印功能 -> develop)

Merge Request: sf更新代码

Created By: @龚启豪
Accepted By: @龚启豪
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/149?initial=true
This commit is contained in:
龚启豪
2023-03-14 14:58:10 +08:00
28 changed files with 544 additions and 35 deletions

32
Dockerfile Normal file
View File

@@ -0,0 +1,32 @@
FROM jikimo-hn-docker.pkg.coding.net/jikimo_sfs/odoo-sf/odoo-sf:1.0
USER root:root
ADD . /app
WORKDIR /app
COPY ./requirements.txt /app
RUN sed -i 's#http://deb.debian.org#http://mirrors.aliyun.com#g' /etc/apt/sources.list
# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf
# 系统更新
RUN apt-get update
# RUN apt-get do-release-upgrade
# 更新python的依赖
#RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
# 设置时区
RUN rm -rf /etc/localtime
RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 拷贝代码到工作目录
COPY . /mnt/extra-addons
# 拷贝配置文件到配置目录
COPY ./odoo.conf /etc/odoo
# 启动odoo
#COPY ./entrypoint.sh /

1
__init__.py Normal file
View File

@@ -0,0 +1 @@
# -*-coding:utf-8-*-

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,22 @@
{
"name": "Jikimo 3D模型展示模块",
"summary": "Jikimo 3D模型展示模块。",
"description": "Jikimo 3D模型展示模块限odoo16)",
"author": "Van",
"website": "https://jikimo.com",
"category": "Tutorials",
"version": "16.0.0.1",
"depends": ['web','sale','sale_management'],
"demo": [],
"data": [
'views/views.xml', #这是为了测试的效果,可以删除
],
'assets': {
'web.assets_qweb': [
'jikimo_model_viewer/static/src/js/3d_viewer.xml',
],
'web.assets_backend': [
'jikimo_model_viewer/static/src/js/*',
],
}
}

View File

@@ -0,0 +1 @@
from . import sale_order

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import models,fields
from odoo.tools import populate, groupby
_logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = "sale.order"
step_file = fields.Binary("Step File")

View File

@@ -0,0 +1,25 @@
# 演示DEMO
## 先给销售订单添加一个Binary字段
class SaleOrder(models.Model):
_inherit = "sale.order"
step_file = fields.Binary("Step File")
## 然后在销售订单详情的表单视中增加一个带有widget的标签
<record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- 以下仅用于演示效果 widget必需放在保存GLB文件内容的字段上 -->
<field name="payment_term_id" position="after">
<field name="step_file" widget="Viewer3D"/>
</field>
</field>
</record>
## 然后就可以到销售订单页面上查看效果
![img.png](static/src/images/img.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -0,0 +1,62 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";//注册
import { _lt } from "@web/core/l10n/translation";//翻译
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { FileUploader } from "@web/views/fields/file_handler";
import { session } from "@web/session";//登录会话
import { useService } from "@web/core/utils/hooks";
import { isBinarySize } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import utils from 'web.utils';
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component, onWillUpdateProps, useState, useRef, useEffect } from "@odoo/owl";
export class StepViewer extends Component {
setup() {
this.props.url = this.formatUrl();
}
formatUrl(){
var url = '';
if (this.props.value) {
if (utils.is_bin_size(this.props.value)) {
var url_props = {
base_url: session['web.base.url'],
model: this.props.record.resModel,
id: JSON.stringify(this.props.record.data['id']),
field: this.props.name}
url = url_props['base_url']+'/web/content/'+url_props['model']+'/'+url_props['id']+'/'+url_props['field']+'?download=true'
} else {
url = "data:model/gltf-binary;base64," + this.props.value;
}
}
return url
}
}
StepViewer.template = "jikimo_model_viewer.BinaryField3d";
StepViewer.displayName = _lt("3D File");
StepViewer.supportedTypes = ["binary"];
StepViewer.props = {
...standardFieldProps,
url: { type: String, optional: true },
};
StepViewer.extractProps = ({ attrs }) => {
return {
url: attrs.options.url,
};
};
registry.category("fields").add("Viewer3D", StepViewer);

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="jikimo_model_viewer.BinaryField3d" owl="1">
<model-viewer
t-att-src='props.url'
name="3D model"
alt="3D model"
auto-rotate="1"
camera-controls="1"
style="
background-color: #0b009d;
--poster-color: #ffffff00;"
>
<!-- <div class="text-center mt-4 mb-4 mr-4">-->
<!-- <span-->
<!-- id="model-viewer-fullscreen"-->
<!-- title="View fullscreen"-->
<!-- role="img"-->
<!-- aria-label="Fullscreen"-->
<!-- >-->
<!-- <i class="fa fa-arrows-alt fa-2x"/>-->
<!-- </span>-->
<!-- </div>-->
</model-viewer>
<!-- <model-viewer-->
<!-- src='/jikimo_model_viewer/static/src/js/3d_viewer/test.glb'-->
<!-- name="Test 3D model"-->
<!-- alt="Test 3D model"-->
<!-- auto-rotate="1"-->
<!-- camera-controls="1"-->
<!-- />-->
<script type="module"
src="/jikimo_model_viewer/static/src/lib/model-viewer.min.js">
</script>
</t>
</templates>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<field name="payment_term_id" position="after">
<!-- <field name="create_date" widget="show_units" options="{'units':'UTC'}" string="Create Date"/>-->
<!-- <group class="o_partner_order_summary" col="2"/>-->
<field name="step_file" widget="Viewer3D"/>
</field>
</field>
</record>
</odoo>

43
requirements.txt Normal file
View File

@@ -0,0 +1,43 @@
Babel==2.9.1 # min version = 2.6.0 (Focal with security backports)
chardet==3.0.4
cryptography==3.4.8
decorator==4.4.2
docutils==0.16
ebaysdk==2.1.5
freezegun==0.3.15; python_version >= '3.8'
gevent==1.5.0 ; python_version == '3.7'
gevent==21.8.0 ; python_version > '3.9' # (Jammy)
greenlet==1.1.2 ; python_version > '3.9' # (Jammy)
idna==2.8
Jinja2==2.11.3 # min version = 2.10.1 (Focal - with security backports)
libsass==0.18.0
lxml==4.6.5 # min version = 4.5.0 (Focal - with security backports)
MarkupSafe==1.1.0
num2words==0.5.6
ofxparse==0.21; python_version > '3.9' # (Jammy)
passlib==1.7.3 # min version = 1.7.2 (Focal with security backports)
Pillow==9.0.1 # min version = 7.0.0 (Focal with security backports)
polib==1.1.0
psutil==5.6.7 # min version = 5.5.1 (Focal with security backports)
psycopg2==2.8.6; sys_platform == 'win32' or python_version >= '3.8'
pydot==1.4.1
pyopenssl==19.0.0
PyPDF2==1.26.0
pypiwin32 ; sys_platform == 'win32'
pyserial==3.4
python-dateutil==2.7.3
python-ldap==3.4.0 ; sys_platform != 'win32' # min version = 3.2.0 (Focal with security backports)
python-stdnum==1.13
pytz==2019.3
pyusb==1.0.2
qrcode==6.1
reportlab==3.5.59 # version < 3.5.54 are not compatible with Pillow 8.1.2 and 3.5.59 is bullseye
requests==2.25.1 # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal)
urllib3==1.26.5 # indirect / min version = 1.25.8 (Focal with security backports)
vobject==0.9.6.1
Werkzeug==0.16.1 ; python_version <= '3.9'
Werkzeug==2.0.2 ; python_version > '3.9' # (Jammy)
xlrd==1.2.0; python_version >= '3.8'
XlsxWriter==1.1.2
xlwt==1.3.*
zeep==3.4.0

View File

@@ -49,6 +49,7 @@ class MrsProductionProcess(models.Model):
processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序')
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
active = fields.Boolean('有效', default=True)
parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数')
class MrsProcessingTechnology(models.Model):
@@ -96,3 +97,12 @@ class SupplierSort(models.Model):
('supplier_sort_uniq', 'unique (partner_id,materials_model_id)', '排序不能重复!')
]
class MrsProductionProcessParameter(models.Model):
_name = 'sf.production.process.parameter'
_description = '可选参数'
name = fields.Char('参数名')
active = fields.Boolean('有效', default=True)
price = fields.Float('单价')
process_id = fields.Many2one('sf.production.process', string='表面工艺')
materials_model_ids = fields.Many2many('sf.materials.model', 'applicable_material', string='适用材料')
code = fields.Char("编码")

View File

@@ -14,7 +14,7 @@ access_sf_processing_technology,sf_processing_technology,model_sf_processing_tec
access_sf_tray,sf_tray,model_sf_tray,base.group_user,1,1,1,1
access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,1
access_sf_production_process_parameter,sf_production_process_parameter,model_sf_production_process_parameter,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
14 access_sf_tray sf_tray model_sf_tray base.group_user 1 1 1 1
15 access_sf_supplier_sort sf_supplier_sort model_sf_supplier_sort base.group_user 1 1 1 1
16 access_sf_production_process_parameter sf_production_process_parameter model_sf_production_process_parameter base.group_user 1 1 1 1
17
18
19
20

View File

@@ -74,6 +74,32 @@
</form>
</field>
</page>
<page string="可选参数">
<field name="parameter_ids">
<tree force_save="1">
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
<field name='process_id' default="default" invisible="1"/>
</tree>
<form>
<sheet>
<group>
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</page>
</notebook>
<group>
<field name="remark"/>

View File

@@ -164,14 +164,20 @@ class MrpProduction(models.Model):
#工单排序
def _reset_work_order_sequence1(self, k):
sequen = 0
for rec in self:
current_sequence = 1
current_sequence = 10
for work in rec.workorder_ids:
work.sequence = current_sequence
current_sequence += 1
sfa = rec
for a in sfa:
print(a)
current_sequence += 10
if work.name == '后置三元质量检测' and work.processing_panel == k:
sequen = work.sequence
for work in rec.workorder_ids:
if work.name == '后置三元质量检测(返工)' and work.processing_panel == k:
work.sequence = sequen + 2
if work.name == 'CNC加工(返工)' and work.processing_panel == k:
work.sequence = sequen + 1
#在制造订单上新增工单
def _create_workorder1(self, k):
@@ -211,6 +217,7 @@ class MrpProduction(models.Model):
order='sequence asc'
)
i += 1
for route in routingworkcenter:
if route.routing_type == '后置三元质量检测':
@@ -222,8 +229,10 @@ class MrpProduction(models.Model):
self.env['mrp.workorder'].json_workorder_str1(k, production, route))
production.workorder_ids = workorders_values
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
workorder = self.env['mrp.workorder'].browse(production.workorder_ids.ids)
print(workorder)
# for item in workorder:
# workorder.duration_expected = workorder._get_duration_expected()
def _create_workorder2(self, k):
res = self._create_workorder1(k)

View File

@@ -314,8 +314,8 @@ class ResMrpWorkOrder(models.Model):
subtype_id=self.env.ref('mail.mt_note').id)
if self.test_results == '返工':
productions = self.production_id
self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
# self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
productions._create_workorder2(self.processing_panel)
else:
self.results = '合格'
@@ -379,7 +379,7 @@ class ResMrpWorkOrder(models.Model):
'product_uom_id': production.product_uom_id.id,
'qty_producing': 0,
'operation_id': False,
'name': route.route_workcenter_id.name,
'name': '%s(返工)' % route.route_workcenter_id.name,
'processing_panel': k,
'routing_type': route.routing_type,
'work_state': '' if not route.routing_type == '获取CNC加工程序' else '待发起',

View File

@@ -131,4 +131,15 @@
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
<record model="ir.cron" id="sf_cron13">
<field name="name">同步表面工艺可选参数</field>
<field name="model_id" ref="model_sf_production_process_parameter"/>
<field name="state">code</field>
<field name="code">model.sync_production_process_parameter()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
</odoo>

View File

@@ -2,7 +2,7 @@
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
@@ -19,29 +19,33 @@ class ResConfigSettings(models.TransientModel):
ftp_password = fields.Char(string='FTP密码')
def sf_all_sync(self):
self.env['sf.production.materials'].sync_all_production_materials()
_logger.info("同步资源库材料")
self.env['sf.materials.model'].sync_all_materials_model()
_logger.info("同步资源库材料型号")
self.env['sf.production.process'].sync_all_production_process()
_logger.info("同步资源库表面工艺")
self.env['sf.processing.technology'].sync_all_processing_technology()
_logger.info("同步资源库加工工艺")
self.env['sf.machine.brand.tags'].sync_all_machine_brand_tags()
_logger.info("同步资源库品牌类别")
self.env['sf.machine.brand'].sync_all_machine_brand()
_logger.info("同步资源库品牌")
self.env['sf.machine.control_system'].sync_all_machine_tool_type_control_system()
_logger.info("同步资源库控制系统")
# self.env['sf.machine_tool'].sync_all_machine_tool()
# _logger.info("同步机床行业编码")
self.env['sf.machine_tool.type'].sync_all_machine_tool_type()
_logger.info("同步资源库机床型号")
self.env['sf.cutting_tool.category'].sync_all_cutting_tool_category()
_logger.info("同步资源库刀具类别")
self.env['sf.cutting_tool.type'].sync_all_cutting_tool_type()
_logger.info("同步资源库刀具")
# self.env['sf.processing.order'].sync_all_processing_order()
try:
self.env['sf.production.materials'].sync_all_production_materials()
_logger.info("同步资源库材料")
self.env['sf.materials.model'].sync_all_materials_model()
_logger.info("同步资源库材料型号")
self.env['sf.production.process'].sync_all_production_process()
_logger.info("同步资源库表面工艺")
self.env['sf.processing.technology'].sync_all_processing_technology()
_logger.info("同步资源库加工工艺")
self.env['sf.machine.brand.tags'].sync_all_machine_brand_tags()
_logger.info("同步资源库品牌类别")
self.env['sf.machine.brand'].sync_all_machine_brand()
_logger.info("同步资源库品牌")
self.env['sf.machine.control_system'].sync_all_machine_tool_type_control_system()
_logger.info("同步资源库控制系统")
self.env['sf.machine_tool.type'].sync_all_machine_tool_type()
_logger.info("同步资源库机床型号")
self.env['sf.cutting_tool.category'].sync_all_cutting_tool_category()
_logger.info("同步资源库刀具类别")
self.env['sf.cutting_tool.type'].sync_all_cutting_tool_type()
_logger.info("同步资源库刀具")
self.env['sf.production.process.parameter'].sync_all_production_process_parameter()
_logger.info("同步表面工艺参数")
except Exception as e:
_logger.info("捕获错误信息:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@api.model
def get_values(self):

View File

@@ -948,3 +948,75 @@ class sfProcessingOrder(models.Model):
})
else:
raise ValidationError("认证未通过")
class sfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter'
_description = '表面工艺可选参数'
url = '/api/production_process_parameter/list'
# 定时同步每日表面工艺
def sync_production_process_parameter(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['mrs_production_process_parameter_yesterday_list']:
if item:
brand = self.env['sf.production.process.parameter'].search(
[("code", '=', item['code'])])
if brand:
brand.name = item['name'],
brand.code = item['code'],
brand.active = item['active'],
brand.price = item['price'],
else:
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"active": item['active'],
"price" : item['price'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
})
else:
raise ValidationError("认证未通过") # 定时同步表面工艺
# 同步所有表面工艺
def sync_all_production_process_parameter(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['mrs_production_process_parameter_all_list']:
if item:
brand = self.env['sf.production.process.parameter'].search(
[("code", '=', item['code'])])
if not brand:
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"price": item['price'],
"active": item['active'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
})
else:
raise ValidationError("认证未通过")

3
zpl_print/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
# -*-coding:utf-8-*-
from .import models
from .import controllers

23
zpl_print/__manifest__.py Normal file
View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '一键打印功能',
'version': '1.0',
'summary': '一件打印',
'sequence': 1,
'description': """
在本模块,实现了一键打印功能
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['base', 'web'],
'data': [
],
'demo': [
],
'qweb': [
],
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -0,0 +1,2 @@
# -*-coding:utf-8-*-
from .import report

View File

@@ -0,0 +1,60 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import logging
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
import werkzeug.wsgi
from werkzeug.urls import url_parse
from odoo import http
from odoo.http import content_disposition, request
from odoo.tools.safe_eval import safe_eval, time
from odoo.addons.web.controllers.report import ReportController
from ..models.common import Common
_logger = logging.getLogger(__name__)
class ZplReportController(ReportController):
#------------------------------------------------------
# Report controllers
#------------------------------------------------------
@http.route([
'/report/<converter>/<reportname>',
'/report/<converter>/<reportname>/<docids>',
], type='http', auth='user', website=True)
def report_routes(self, reportname, docids=None, converter=None, **data):
report = request.env['ir.actions.report']
context = dict(request.env.context)
if docids:
docids = [int(i) for i in docids.split(',')]
if data.get('options'):
data.update(json.loads(data.pop('options')))
if data.get('context'):
data['context'] = json.loads(data['context'])
context.update(data['context'])
if converter == 'html':
html = report.with_context(context)._render_qweb_html(reportname, docids, data=data)[0]
return request.make_response(html)
elif converter == 'pdf':
pdf = report.with_context(context)._render_qweb_pdf(reportname, docids, data=data)[0]
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
return request.make_response(pdf, headers=pdfhttpheaders)
elif converter == 'text':
text = report.with_context(context)._render_qweb_text(reportname, docids, data=data)[0]
text_str = text.decode()
Common.print_zpl(self, text_str)
texthttpheaders = [('Content-Type', 'text/plain'), ('Content-Length', len(text))]
return request.make_response(text, headers=texthttpheaders)
else:
raise werkzeug.exceptions.HTTPException(description='Converter %s not implemented.' % converter)

View File

View File

@@ -0,0 +1,2 @@
# -*-coding:utf-8-*-
from .import common

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from odoo import models
import ctypes
class Common(models.Model):
_name = 'sf.sync.common'
_description = u'公用类'
def print_zpl(self, zpl_str):
WinDll_path = "D://桌面//pythonZPL//tsc_python_sdk_example//TSC_Python_SDK_Example//tsc_sample//libs//TSCLIB.dll"
try:
tsclibrary = ctypes.WinDLL(WinDll_path)
tsclibrary.openportW("USB")
print(zpl_str)
tsclibrary.sendcommandW(zpl_str)
print('111222')
tsclibrary.printlabelW("0", "1");
tsclibrary.closeport();
except Exception as e:
raise UserWarning("错误警告")