# Conflicts:
#	sf_base/models/sf_base.py
#	sf_manufacturing_orders/__manifest__.py
#	sf_route_workcenter/models/workcenter.py
This commit is contained in:
jinling.yang
2022-11-16 09:43:10 +08:00
14 changed files with 1139 additions and 154 deletions

268
sf_base/models/sf_base.py Normal file
View File

@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
import logging
from datetime import datetime
import requests
import json
from odoo import fields, models, api
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.addons.sf_base.commons.common import Common
_logger = logging.getLogger(__name__)
class MachineBrandTags(models.Model):
_name = 'mrs.machine.brand.tags'
_description = '标签'
name = fields.Char('名称', size=50)
color = fields.Integer('颜色', default=0)
class MachineControlSystem(models.Model):
_name = 'mrs.machine.control_system'
_description = '控制系统'
code = fields.Char('编码', size=10)
name = fields.Char('名称', size=10)
brand_id = fields.Many2one('mrs.machine.brand', '品牌')
active = fields.Boolean('有效', default=True)
# 品牌标签
class MachineBrand(models.Model):
_name = 'mrs.machine.brand'
_description = '品牌'
name = fields.Char('名称')
tag_ids = fields.Many2many('mrs.machine.brand.tags', 'rel_machine_brand_tags', string='类别')
image_brand = fields.Image("品牌图片")
active = fields.Boolean('有效', default=True)
code = fields.Char('编码')
# 机床
class MachineTool(models.Model):
_name = 'mrs.machine_tool'
_description = '机床'
MTcode = fields.Char("编码")
code = fields.Char('行业编码')
name = fields.Char('名称')
knife_type = fields.Selection(
[("BT40", "BT40"), ("BT30", "BT30")],
default="", string="刀把类型")
number_of_knife_library = fields.Integer('刀库数量')
rotate_speed = fields.Integer('转速')
number_of_axles = fields.Selection(
[("三轴", "三轴"), ("四轴", "四轴"), ("五轴", "五轴")],
default="", string="轴数")
# 加工进程
x_axis = fields.Integer('X轴')
y_axis = fields.Integer('Y轴')
z_axis = fields.Integer('Z轴')
b_axis = fields.Integer('B轴')
c_axis = fields.Integer('C轴')
remark = fields.Text('备注')
precision = fields.Float('加工精度')
control_system_id = fields.Many2one('mrs.machine.control_system',
string="控制系统")
# 多个机床型号对应一个机床
type_id = fields.Many2one('mrs.machine_tool.type', '型号')
brand_id = fields.Many2one('mrs.machine.brand', string='品牌')
state = fields.Selection(
[("正常", "正常"), ("故障", "故障"), ("不可用", "不可用")],
default='正常', string="状态")
# 一个机床对应一個加工工厂,一个加工工厂对应多个机床
factory_id = fields.Many2one('res.partner', string='所属工厂',
domain="[('is_factory', '=', True)]")
# 一个机床对应一个供应商,一个供应商对应多个机床
supplier_id = fields.Many2one('res.partner', string='制造商',
domain="[('is_vendor', '=', True)]")
registration_date = fields.Date('注册日期')
active = fields.Boolean('有效', default=True)
@api.constrains('rotate_speed')
def _check_rotate_speed(self):
if self.rotate_speed <= 0:
raise ValidationError("转速不能为0")
@api.constrains('precision')
def _check_precision(self):
if self.precision <= 0.00:
raise ValidationError("加工精度不能为0")
@api.constrains('number_of_knife_library')
def _check_number_of_knife_library(self):
if self.number_of_knife_library <= 0:
raise ValidationError("刀库数量不能为0")
@api.constrains('x_axis')
def _check_x_axis(self):
if self.x_axis <= 0:
raise ValidationError("加工行程里x轴不能为0")
@api.constrains('y_axis')
def _check_y_axis(self):
if self.y_axis <= 0:
raise ValidationError("加工行程里y轴不能为0")
@api.constrains('z_axis')
def _check_z_axis(self):
if self.z_axis <= 0:
raise ValidationError("加工行程里z轴不能为0")
@api.constrains('b_axis')
def _check_b_axis(self):
if self.number_of_axles == '四轴':
print(self.number_of_axles)
if self.b_axis <= 0:
raise ValidationError("加工行程里b轴不能为0")
@api.constrains('c_axis')
def _check_c_axis(self):
if self.number_of_axles == '五轴':
if self.c_axis <= 0:
raise ValidationError("加工行程里c轴不能为0")
@api.onchange('type_id')
def get_type_info(self):
for item in self:
item.knife_type = item.type_id.knife_type
item.number_of_knife_library = item.type_id.number_of_knife_library
item.number_of_axles = item.type_id.number_of_axles
item.rotate_speed = item.type_id.rotate_speed
item.precision = item.type_id.precision
item.control_system_id = item.type_id.control_system_id
item.x_axis = item.type_id.x_axis
item.y_axis = item.type_id.y_axis
item.z_axis = item.type_id.z_axis
item.b_axis = item.type_id.b_axis
item.c_axis = item.type_id.c_axis
# 注册同步机床
def enroll_machine_tool(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
mrs_secret_key = sf_sync_config['mrs_secret_key']
headers = Common.get_headers(self, token, mrs_secret_key)
strurl = sf_sync_config['mrs_url'] + self.crea_url
objs_all = request.env['mrs.machine_tool'].sudo().search([])
machine_tool_list = []
if objs_all:
for item in objs_all:
val = {
'factory_token': token,
'id': item.id,
'name': item.name,
'code': item.code,
'precision': item.precision,
'knife_type': item.knife_type,
'number_of_knife_library': item.number_of_knife_library,
'rotate_speed': item.rotate_speed,
'number_of_axles': item.number_of_axles,
'control_system_id': self.env['mrs.machine.control_system'].search(
[('id', '=', item.control_system_id.id)]).code,
'type_id': self.env['mrs.machine_tool.type'].search([('id', '=', item.type_id.id)]).code,
'brand_id': self.env['mrs.machine.brand'].search([('id', '=', item.brand_id.id)]).code,
'supplier_id': item.supplier_id.id,
'x_axis': item.x_axis,
'y_axis': item.y_axis,
'z_axis': item.z_axis,
'b_axis': item.b_axis,
'c_axis': item.c_axis,
'state': item.state,
'active': item.active,
}
machine_tool_list.append(val)
# kw = machine_tool_list
kw = json.dumps(machine_tool_list, ensure_ascii=False)
r = requests.post(strurl, json={}, data={'kw': kw}, headers=headers)
print(r)
if r == 200:
raise ValidationError("机床注册成功")
else:
raise ValidationError("没有注册机床信息")
class MachineToolType(models.Model):
_name = 'mrs.machine_tool.type'
_description = '机床型号'
# _order = 'priority desc, code, name, id'
name = fields.Char('名称')
brand_id = fields.Many2one('mrs.machine.brand', string='品牌')
knife_type = fields.Selection(
[("BT40", "BT40"), ("BT30", "BT30")],
default="", string="刀把类型")
number_of_knife_library = fields.Integer('刀库数量')
rotate_speed = fields.Integer('转速')
# 多个型号对应一个机床
machine_tool_id = fields.Many2one('mrs.machine_tool', '机床')
number_of_axles = fields.Selection(
[("三轴", "三轴"), ("四轴", "四轴"), ("五轴", "五轴")],
default="", string="轴数")
# 加工进程
x_axis = fields.Integer('X轴')
y_axis = fields.Integer('Y轴')
z_axis = fields.Integer('Z轴')
b_axis = fields.Integer('B轴')
c_axis = fields.Integer('C轴')
remark = fields.Text('备注')
precision = fields.Float('加工精度')
control_system_id = fields.Many2one('mrs.machine.control_system',
string="控制系统")
active = fields.Boolean('有效', default=True)
code = fields.Char('编码')
# 刀具
class CuttingTool(models.Model):
_name = 'mrs.cutting_tool.category'
_description = '刀具类别'
code = fields.Char('编码')
name = fields.Char('名称')
remark = fields.Text('备注')
active = fields.Boolean('有效', default=True)
class CuttingToolType(models.Model):
_name = 'mrs.cutting_tool.type'
_description = '刀具型号'
code = fields.Char('编码')
name = fields.Char('名称')
diameter = fields.Integer('直径')
long_blade = fields.Integer('避空长/刃长')
cone_angle_pitch = fields.Integer('锥角/节距')
shank_diameter = fields.Integer('柄径')
taper_shank_length = fields.Integer('锥柄长')
tool_length = fields.Integer('刀具总长')
blade_number = fields.Integer('刃数')
category_id = fields.Many2one('mrs.cutting_tool.category', string='刀具类别')
brand_id = fields.Many2one('mrs.machine.brand', string='品牌')
remark = fields.Text('备注')
active = fields.Boolean('有效', default=True)
class CNCprocessing(models.Model):
_name = 'cnc.processing'
_description = "CNC加工"
cnc_id = fields.Many2one('ir.attachment')
FNo = fields.Char(string="序号")
FPGName = fields.Char(string="程序名")
FKnifeName = fields.Char(string="刀具名称")
FDNo = fields.Char(string="刀号")
FWorkType = fields.Char(string="加工类型")
FXY = fields.Char(string="余量_X/Y")
FZ = fields.Char(string="余量_Z")
FJGSD = fields.Char(string="加工深度(Z)")
FSCCD = fields.Char(string="刀具伸出长度")
FDJSpec = fields.Char(string="刀柄型号")
FJGDate = fields.Char(string="预计加工时间")
FComment = fields.Char(string="备注")

View File

@@ -296,9 +296,8 @@
<search string="托盘">
<field name="name" string="名称" filter_domain="[('name','ilike',self)]"/>
<field name="code" string="编码" filter_domain="[('code','ilike',self)]"/>
<!-- <field name="state" string="状态" filter_domain="[('state','ilike',self)]"/>-->
<group string="分组">
<filter name="state" string="状态" domain="[]" context="{'group_by': 'state'}"/>
<filter name="state" string="状态" domain="[]" context="{'group_by': 'state'}"/>
</group>
</search>
@@ -338,5 +337,6 @@
</record>
</data>
</odoo>

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫智能工厂 制造订单',
'version': '1.0',
'summary': '智能工厂制造订单',
'sequence': 1,
'description': """
""",
'category': '',
'website': 'https://www.sf.jikimo.com',
'depends': ['mrp'],
'data': [
'views/sf_production.xml',
],
'demo': [
],
'qweb': [
],
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -203,115 +203,59 @@ class StockRule(models.Model):
return True
# @api.model
# def _run_manufacture(self, procurements):
# productions_values_by_company = defaultdict(list)
# errors = []
# for procurement, rule in procurements:
# if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0:
# # If procurement contains negative quantity, don't create a MO that would be for a negative value.
# continue
# bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)
#
# product = self.env['product.template'].search(
# ["&", ("id", '=', procurement.product_id.id), ('single_manufacturing', '!=', False)])
# if not product:
# productions_values_by_company[procurement.company_id.id].append(
# rule._prepare_mo_vals1(*procurement, bom))
# else:
# orders_vals = rule._prepare_mo_vals(*procurement, bom)
#
# if isinstance(orders_vals, list):
# for vals in orders_vals:
# productions_values_by_company[procurement.company_id.id].append(vals)
# else:
# productions_values_by_company[procurement.company_id.id].append(orders_vals)
#
# if errors:
# raise ProcurementException(errors)
#
# for company_id, productions_values in productions_values_by_company.items():
# # create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
# productions_values)
# a = self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
# b = self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
# c = productions._create_workorder()
# productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
# (
# p.move_dest_ids.procure_method != 'make_to_order' and not p.move_raw_ids and not p.workorder_ids)).action_confirm()
#
# for production in productions:
# origin_production = production.move_dest_ids and production.move_dest_ids[
# 0].raw_material_production_id or False
# orderpoint = production.orderpoint_id
# if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
# production.message_post(
# body=_('This production order has been created from Replenishment Report.'),
# message_type='comment',
# subtype_xmlid='mail.mt_note')
# elif orderpoint:
# production.message_post_with_view(
# 'mail.message_origin_link',
# values={'self': production, 'origin': orderpoint},
# subtype_id=self.env.ref('mail.mt_note').id)
# elif origin_production:
# production.message_post_with_view(
# 'mail.message_origin_link',
# values={'self': production, 'origin': origin_production},
# subtype_id=self.env.ref('mail.mt_note').id)
# return True
#
# def _prepare_mo_vals1(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
# bom):
# date_planned = self._get_date_planned(product_id, company_id, values)
# date_deadline = values.get('date_deadline') or date_planned + relativedelta(
# days=company_id.manufacturing_lead) + relativedelta(days=product_id.produce_delay)
# mo_values = {
# 'origin': origin,
# 'product_id': product_id.id,
# 'product_description_variants': values.get('product_description_variants'),
# 'product_qty': product_qty,
# 'product_uom_id': product_uom.id,
# 'location_src_id': self.location_src_id.id or self.picking_type_id.default_location_src_id.id or location_id.id,
# 'location_dest_id': location_id.id,
# 'bom_id': bom.id,
# 'date_deadline': date_deadline,
# 'date_planned_start': date_planned,
# 'date_planned_finished': fields.Datetime.from_string(values['date_planned']),
# 'procurement_group_id': False,
# 'propagate_cancel': self.propagate_cancel,
# 'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
# 'picking_type_id': self.picking_type_id.id or values['warehouse_id'].manu_type_id.id,
# 'company_id': company_id.id,
# 'move_dest_ids': values.get('move_dest_ids') and [(4, x.id) for x in values['move_dest_ids']] or False,
# 'user_id': False,
# }
# # Use the procurement group created in _run_pull mrp override
# # Preserve the origin from the original stock move, if available
# if location_id.warehouse_id.manufacture_steps == 'pbm_sam' and values.get('move_dest_ids') and values.get(
# 'group_id') and values['move_dest_ids'][0].origin != values['group_id'].name:
# origin = values['move_dest_ids'][0].origin
# mo_values.update({
# 'name': values['group_id'].name,
# 'procurement_group_id': values['group_id'].id,
# 'origin': origin,
# })
# return mo_values
#
# def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
# bom):
#
# mo_lists = []
# for num in range(1, int(product_qty) + 1):
# vals = super(StockRule, self)._prepare_mo_vals(product_id, 1.0, product_uom, location_id, name, origin,
# company_id, values,
# bom)
# vals.update({
# # 'name': values['group_id'].name,
# 'procurement_group_id': values['group_id'].id,
# # 'origin': origin,
# })
# mo_lists.append(vals)
#
# return mo_lists
@api.model
def _run_manufacture(self, procurements):
productions_values_by_company = defaultdict(list)
errors = []
for procurement, rule in procurements:
if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0:
# If procurement contains negative quantity, don't create a MO that would be for a negative value.
continue
bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)
productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom))
if errors:
raise ProcurementException(errors)
for company_id, productions_values in productions_values_by_company.items():
# create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
'''创建制造订单'''
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
productions_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_workorder()
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
(
p.move_dest_ids.procure_method != 'make_to_order' and not p.move_raw_ids and not p.workorder_ids)).action_confirm()
for production in productions:
'''
创建制造订单时生成序列号
'''
production.lot_producing_id._get_next_serial(production.company_id, production.product_id)
production.action_generate_serial()
origin_production = production.move_dest_ids and production.move_dest_ids[
0].raw_material_production_id or False
orderpoint = production.orderpoint_id
if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
production.message_post(
body=_('This production order has been created from Replenishment Report.'),
message_type='comment',
subtype_xmlid='mail.mt_note')
elif orderpoint:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': orderpoint},
subtype_id=self.env.ref('mail.mt_note').id)
elif origin_production:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': origin_production},
subtype_id=self.env.ref('mail.mt_note').id)
return True

View File

@@ -10,10 +10,13 @@
""",
'category': 'YZ',
'website': 'https://www.sf.cs.jikimo.com',
'depends': ['mrp', 'sf_base','hr_holidays'],
'depends': ['mrp', 'sf_base', 'hr_holidays', 'maintenance'],
'data': [
'views/sf_tray_view.xml',
'views/sf_workorder.xml',
'views/maintenance_views.xml',
'views/mrp_views.xml',
'views/mrp_workcenter_views.xml',
'report/sf_tray_report.xml'
],
'demo': [

View File

@@ -1,2 +1,3 @@
# -*-coding:utf-8-*-
from . import workcenter
from . import workcenter
from . import mrp_maintenance

View File

@@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil.relativedelta import relativedelta
from datetime import timedelta, datetime
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.addons.resource.models.resource import Intervals
class MrpWorkcenter(models.Model):
_inherit = "mrp.workcenter"
equipment_ids = fields.One2many(
'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment",
check_company=True)
def action_work_order(self):
if not self.env.context.get('desktop_list_view', False):
action = self.env["ir.actions.actions"]._for_xml_id("sf_route_workcenter.mrp_workorder_action_tablet")
return action
else:
return super(MrpWorkcenter, self).action_work_order()
def _get_unavailability_intervals(self, start_datetime, end_datetime):
res = super(MrpWorkcenter, self)._get_unavailability_intervals(start_datetime, end_datetime)
if not self:
return res
sql = """
SELECT workcenter_id, ARRAY_AGG((schedule_date || '|' || schedule_date + INTERVAL '1h' * duration)) as date_intervals
FROM maintenance_request
LEFT JOIN maintenance_equipment
ON maintenance_request.equipment_id = maintenance_equipment.id
WHERE
schedule_date IS NOT NULL
AND duration IS NOT NULL
AND equipment_id IS NOT NULL
AND maintenance_equipment.workcenter_id IS NOT NULL
AND maintenance_equipment.workcenter_id IN %s
AND (schedule_date, schedule_date + INTERVAL '1h' * duration) OVERLAPS (%s, %s)
GROUP BY maintenance_equipment.workcenter_id;
"""
self.env.cr.execute(sql, [tuple(self.ids), fields.Datetime.to_string(start_datetime.astimezone()), fields.Datetime.to_string(end_datetime.astimezone())])
res_maintenance = defaultdict(list)
for wc_row in self.env.cr.dictfetchall():
res_maintenance[wc_row.get('workcenter_id')] = [
[fields.Datetime.to_datetime(i) for i in intervals.split('|')]
for intervals in wc_row.get('date_intervals')
]
for wc_id in self.ids:
intervals_previous_list = [(s.timestamp(), e.timestamp(), self.env['maintenance.request']) for s, e in res[wc_id]]
intervals_maintenances_list = [(m[0].timestamp(), m[1].timestamp(), self.env['maintenance.request']) for m in res_maintenance[wc_id]]
final_intervals_wc = Intervals(intervals_previous_list + intervals_maintenances_list)
res[wc_id] = [(datetime.fromtimestamp(s), datetime.fromtimestamp(e)) for s, e, _ in final_intervals_wc]
return res
class MaintenanceEquipment(models.Model):
_inherit = "maintenance.equipment"
_check_company_auto = True
expected_mtbf = fields.Integer(string='Expected MTBF', help='Expected Mean Time Between Failure')
mtbf = fields.Integer(compute='_compute_maintenance_request', string='MTBF', help='Mean Time Between Failure, computed based on done corrective maintenances.')
mttr = fields.Integer(compute='_compute_maintenance_request', string='MTTR', help='Mean Time To Repair')
estimated_next_failure = fields.Date(compute='_compute_maintenance_request', string='Estimated time before next failure (in days)', help='Computed as Latest Failure Date + MTBF')
latest_failure_date = fields.Date(compute='_compute_maintenance_request', string='Latest Failure Date')
workcenter_id = fields.Many2one(
'mrp.workcenter', string='Work Center', check_company=True)
@api.depends('effective_date', 'maintenance_ids.stage_id', 'maintenance_ids.close_date', 'maintenance_ids.request_date')
def _compute_maintenance_request(self):
for equipment in self:
maintenance_requests = equipment.maintenance_ids.filtered(lambda x: x.maintenance_type == 'corrective' and x.stage_id.done)
mttr_days = 0
for maintenance in maintenance_requests:
if maintenance.stage_id.done and maintenance.close_date:
mttr_days += (maintenance.close_date - maintenance.request_date).days
equipment.mttr = len(maintenance_requests) and (mttr_days / len(maintenance_requests)) or 0
maintenance = maintenance_requests.sorted(lambda x: x.request_date)
if len(maintenance) >= 1:
equipment.mtbf = (maintenance[-1].request_date - equipment.effective_date).days / len(maintenance)
equipment.latest_failure_date = maintenance and maintenance[-1].request_date or False
if equipment.mtbf:
equipment.estimated_next_failure = equipment.latest_failure_date + relativedelta(days=equipment.mtbf)
else:
equipment.estimated_next_failure = False
def button_mrp_workcenter(self):
self.ensure_one()
return {
'name': _('work centers'),
'view_mode': 'form',
'res_model': 'mrp.workcenter',
'view_id': self.env.ref('mrp.mrp_workcenter_view').id,
'type': 'ir.actions.act_window',
'res_id': self.workcenter_id.id,
'context': {
'default_company_id': self.company_id.id
}
}
class MaintenanceRequest(models.Model):
_inherit = "maintenance.request"
_check_company_auto = True
production_id = fields.Many2one(
'mrp.production', string='Manufacturing Order', check_company=True)
workorder_id = fields.Many2one(
'mrp.workorder', string='Work Order', check_company=True)
production_company_id = fields.Many2one(string='Production Company', related='production_id.company_id')
company_id = fields.Many2one(domain="[('id', '=?', production_company_id)]")
class MrpProduction(models.Model):
_inherit = "mrp.production"
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
request_ids = fields.One2many('maintenance.request', 'production_id')
@api.depends('request_ids')
def _compute_maintenance_count(self):
for production in self:
production.maintenance_count = len(production.request_ids)
def button_maintenance_req(self):
self.ensure_one()
return {
'name': _('New Maintenance Request'),
'view_mode': 'form',
'res_model': 'maintenance.request',
'type': 'ir.actions.act_window',
'context': {
'default_company_id': self.company_id.id,
'default_production_id': self.id,
},
'domain': [('production_id', '=', self.id)],
}
def open_maintenance_request_mo(self):
self.ensure_one()
action = {
'name': _('Maintenance Requests'),
'view_mode': 'kanban,tree,form,pivot,graph,calendar',
'res_model': 'maintenance.request',
'type': 'ir.actions.act_window',
'context': {
'default_company_id': self.company_id.id,
'default_production_id': self.id,
},
'domain': [('production_id', '=', self.id)],
}
if self.maintenance_count == 1:
production = self.env['maintenance.request'].search([('production_id', '=', self.id)])
action['view_mode'] = 'form'
action['res_id'] = production.id
return action
class MrpProductionWorkcenterLine(models.Model):
_inherit = "mrp.workorder"
def button_maintenance_req(self):
self.ensure_one()
return {
'name': _('New Maintenance Request'),
'view_mode': 'form',
'views': [(self.env.ref('mrp_maintenance.maintenance_request_view_form_inherit_mrp').id, 'form')],
'res_model': 'maintenance.request',
'type': 'ir.actions.act_window',
'context': {
'default_company_id': self.company_id.id,
'default_workorder_id': self.id,
'default_production_id': self.production_id.id,
'discard_on_footer_button': True,
},
'target': 'new',
'domain': [('workorder_id', '=', self.id)]
}

View File

@@ -0,0 +1,339 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import base64
import logging
import math
from io import BytesIO
from odoo import api, fields, models, SUPERUSER_ID
from pystrich.code128 import Code128Encoder
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class CNCprocessing(models.Model):
_inherit = 'cnc.processing'
_description = "CNC加工"
workorder_id = fields.Many2one('mrp.workorder', string="工单")
class Tray(models.Model):
_inherit = 'sf.tray'
_description = '托盘'
qr_image = fields.Binary(string="托盘二维码", compute='compute_qr_image')
production_id = fields.Many2one('mrp.production', string='制造订单',
related='workorder_id.production_id'
)
workorder_id = fields.Many2one('mrp.workorder', string="工单"
)
@api.onchange('production_id')
def updateTrayState(self):
if self.workorder_id != False:
self.state = '占用'
else:
self.state = '空闲'
def unclamp(self):
self.workorder_id = False
self.production_id = False
self.state = '空闲'
@api.depends('code')
def compute_qr_image(self):
for item in self:
if not item.code:
item.qr_image = False
continue
# 根据code动态生成二维码图片
# qr = qrcode.QRCode(
# version=1,
# error_correction=qrcode.constants.ERROR_CORRECT_L,
# box_size=10,
# border=4,
# )
# qr.add_data(item.code)
# qr.make(fit=True)
# img = qr.make_image()
# 生成条形码文件
# bar = barcode.get("ean13", "123456789102", writer=ImageWriter())
# a = bar.get_fullcode()
# b = bar.save('occ')
# 生成条形码图片
partner_encoder = Code128Encoder(item.code)
# 转换bytes流
temp = BytesIO()
partner_encoder.save(temp)
# img.save(temp, format='PNG')
qr_image = base64.b64encode(temp.getvalue())
item.qr_image = qr_image
'''
工单绑定托盘信息
'''
class MrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
_description = '工单'
tray_ids = fields.One2many('sf.tray', 'workorder_id', string='托盘')
# def get_tray_info(self):
# @api.onchange('X_axis', 'Y_axis', 'Z_axis')
# def get_center_point(self):
# return 'X:%s,Y:%s,Z:%s' % (self.X_axis, self.Y_axis, self.Z_axis)
# 加工面
# surface = fields.Selection([("前面", "前面"), ("后面", "后面"), ("左面", "左面"), ("右面", "右面"),
# ("上面", "上面")], string="加工面1")
material_center_point = fields.Char(string='配料中心点')
X1_axis = fields.Float(string='Lx1', default=0)
Y1_axis = fields.Float(string='Ly1', default=0)
Z1_axis = fields.Float(string='Lz1', default=0)
X2_axis = fields.Float(string='Lx2', default=0)
Y2_axis = fields.Float(string='Ly2', default=0)
Z2_axis = fields.Float(string='Lz2', default=0)
X3_axis = fields.Float(string='Fx3', default=0)
Y3_axis = fields.Float(string='Fy3', default=0)
Z3_axis = fields.Float(string='Fz3', default=0)
X4_axis = fields.Float(string='Fx4', default=0)
Y4_axis = fields.Float(string='Fy4', default=0)
Z4_axis = fields.Float(string='Fz4', default=0)
X5_axis = fields.Float(string='Rx5', default=0)
Y5_axis = fields.Float(string='Ry5', default=0)
Z5_axis = fields.Float(string='Rz5', default=0)
X6_axis = fields.Float(string='Rx6', default=0)
Y6_axis = fields.Float(string='Ry6', default=0)
Z6_axis = fields.Float(string='Rz6', default=0)
X7_axis = fields.Float(string='Bx7', default=0)
Y7_axis = fields.Float(string='By7', default=0)
Z7_axis = fields.Float(string='Bz7', default=0)
X8_axis = fields.Float(string='Bx8', default=0)
Y8_axis = fields.Float(string='By8', default=0)
Z8_axis = fields.Float(string='Bz8', default=0)
X9_axis = fields.Float(string='Uz9', default=0)
Y9_axis = fields.Float(string='Uz9', default=0)
Z9_axis = fields.Float(string='Uz9', default=0)
X10_axis = fields.Float(string='Uz10', default=0)
Y10_axis = fields.Float(string='Uz10', default=0)
Z10_axis = fields.Float(string='Uz10', default=0)
# 计算配料中心点和与x轴倾斜度方法
def getcenter(self):
x1 = self.X1_axis
x2 = self.X2_axis
x3 = self.X3_axis
x4 = self.X4_axis
x5 = self.X5_axis
x6 = self.X6_axis
x7 = self.X7_axis
x8 = self.X8_axis
y1 = self.Y1_axis
y2 = self.Y2_axis
y3 = self.Y3_axis
y4 = self.Y4_axis
y5 = self.Y5_axis
y6 = self.Y6_axis
y7 = self.Y7_axis
y8 = self.Y8_axis
z1 = self.Z9_axis
x0 = ((x3 - x4) * (x2 * y1 - x1 * y2) - (x1 - x2) * (x4 * y3 - x3 * y4)) / (
(x3 - x4) * (y1 - y2) - (x1 - x2) * (y3 - y4))
y0 = ((y3 - y4) * (y2 * x1 - y1 * x2) - (y1 - y2) * (y4 * x3 - y3 * x4)) / (
(y3 - y4) * (x1 - x2) - (y1 - y2) * (x3 - x4))
x1 = ((x7 - x8) * (x6 * y5 - x5 * y7) - (x5 - x6) * (x8 * y7 - x7 * y8)) / (
(x7 - x8) * (y5 - y6) - (x5 - x6) * (y7 - y8));
y1 = ((y7 - y8) * (y6 * x5 - y5 * x7) - (y5 - y6) * (y8 * x7 - y7 * x8)) / (
(y7 - y8) * (x5 - x6) - (y5 - y6) * (x7 - x8))
x = (x0 + x1) / 2
y = (y0 + y1) / 2
z = z1 / 2
jd = math.atan2((x7 - x8), (y7 - y8))
jdz = jd * 180 / math.pi
print("(%s,%s)" % (x, y))
self.material_center_point = ("(%s,%s,%s)" % (x, y, z))
self.X_deviation_angle = jdz
X_deviation_angle = fields.Integer(string="X轴偏差度", default=0)
test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], string="检测结果")
cnc_ids = fields.One2many("cnc.processing", 'workorder_id', string="CNC加工")
tray_code = fields.Char(string="托盘")
# 扫码绑定托盘方法
def gettray(self):
if self.tray_code != False:
values = self.env['sf.tray'].search([("code", "=", self.tray_code)])
if values:
if values.state == "占用":
raise ValidationError('该托盘已占用')
if values.state == "报损":
raise ValidationError('该托盘已损坏')
else:
values.update({
'workorder_id': self,
'production_id': self.production_id,
'state': '占用',
})
else:
raise ValidationError('该托盘编码已失效')
else:return ""
# 解除托盘绑定
def unbindtray(self):
tray = self.env['sf.tray'].search([("production_id", "=", self.production_id.id)])
if tray:
tray.unclamp()
return ""
def recreateManufacturing(self):
"""
重新生成制造订单
"""
values = self.env['mrp.production'].create_production1_values(self.production_id)
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
self.production_id.company_id).create(
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_workorder()
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
(
p.move_dest_ids.procure_method != 'make_to_order' and not p.move_raw_ids and not p.workorder_ids)).action_confirm()
for production in productions:
origin_production = production.move_dest_ids and production.move_dest_ids[
0].raw_material_production_id or False
orderpoint = production.orderpoint_id
if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
production.message_post(
body=_('This production order has been created from Replenishment Report.'),
message_type='comment',
subtype_xmlid='mail.mt_note')
elif orderpoint:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': orderpoint},
subtype_id=self.env.ref('mail.mt_note').id)
elif origin_production:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': origin_production},
subtype_id=self.env.ref('mail.mt_note').id)
# print(productions)
return ""
def recreateWorkerOrder(self):
"""
返工重新生成工单
"""
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())
productions.create_workorder1(self.processing_panel)
return ""
def fetchCNC(self):
return ""
'''
制造订单绑定托盘信息
'''
class MrpProduction(models.Model):
_inherit = 'mrp.production'
_description = "制造订单"
tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
def create_production1_values(self, production):
production_values_str = {'origin': production.origin,
'product_id': production.product_id.id,
'product_description_variants': production.product_description_variants,
'product_qty': production.product_qty,
'product_uom_id': production.product_uom_id.id,
'location_src_id': production.location_src_id.id,
'location_dest_id': production.location_dest_id.id,
'bom_id': production.bom_id.id,
'date_deadline': production.date_deadline,
'date_planned_start': production.date_planned_start,
'date_planned_finished': production.date_planned_finished,
'procurement_group_id': False,
'propagate_cancel': production.propagate_cancel,
'orderpoint_id': production.orderpoint_id.id,
'picking_type_id': production.picking_type_id.id,
'company_id': production.company_id.id,
'move_dest_ids': production.move_dest_ids.ids,
'user_id': production.user_id.id}
return production_values_str
def create_workorder1(self, k):
for production in self:
if not production.bom_id or not production.product_id:
continue
workorders_values = []
product_qty = production.product_uom_id._compute_quantity(production.product_qty,
production.bom_id.product_uom_id)
exploded_boms, dummy = production.bom_id.explode(production.product_id,
product_qty / production.bom_id.product_qty,
picking_type=production.bom_id.picking_type_id)
for bom, bom_data in exploded_boms:
# If the operations of the parent BoM and phantom BoM are the same, don't recreate work orders.
if not (bom.operation_ids and (not bom_data['parent_line'] or bom_data[
'parent_line'].bom_id.operation_ids != bom.operation_ids)):
continue
for operation in bom.operation_ids:
if operation._skip_operation_line(bom_data['product']):
continue
workorders_values += [{
'name': operation.name,
'production_id': production.id,
'workcenter_id': operation.workcenter_id.id,
'product_uom_id': production.product_uom_id.id,
'operation_id': operation.id,
'state': 'pending',
}]
# 根据加工面板的面数及对应的工序模板生成工单
i = 0
production.product_id.model_processing_panel = k
processing_panel_len = len(k)
for k in (production.product_id.model_processing_panel.split(',')):
routingworkcenter = self.env['sf.model.type.routing.sort'].search(
[('model_type_id', '=', production.product_id.model_type_id.id)],
order='sequence asc'
)
i += 1
for route in routingworkcenter:
if route.routing_type == 'CNC加工':
workorders_values.append(
self.env['mrp.workorder'].json_workorder_str(k, production, route))
if route.routing_type == '后置三元质量检测':
workorders_values.append(
self.env['mrp.workorder'].json_workorder_str(k, production, route))
production.workorder_ids = workorders_values
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
class Attachment(models.Model):
_inherit = 'ir.attachment'
cnc_model = fields.Binary('cnc文件', attachment=False)
model_name = fields.Char('模型名称')

View File

@@ -1,6 +1,6 @@
<odoo>
<data>
<!-- 打印尺寸-->
<record id="sf_tray1" model="report.paperformat">
<field name="name">Dymo Label Sheet</field>
<field name="default" eval="True"/>
@@ -16,6 +16,7 @@
<field name="dpi">96</field>
</record>
<!-- 打印动作-->
<record id="label_sf_tray_code" model="ir.actions.report">
<field name="name">打印条形码</field>
<field name="model">sf.tray</field>
@@ -27,7 +28,7 @@
<field name="paperformat_id" ref="sf_route_workcenter.sf_tray1"/>
</record>
<!-- 打印模板-->
<template id="sf_tray_template">
<t t-call="web.html_container">
<t t-call="web.external_layout">

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="maintenance_equipment_view_form_inherit_mrp" model="ir.ui.view">
<field name="name">maintenance.equipment.view.form.inherit.mrp</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="maintenance.hr_equipment_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="button_mrp_workcenter" type="object" class="oe_stat_button"
icon="fa-cogs" string="Work Center" attrs="{'invisible': [('workcenter_id', '=', False)]}" groups="mrp.group_mrp_routings">
</button>
</xpath>
<xpath expr="//field[@name='location']" position="after">
<field name="workcenter_id" context="{'default_company_id':company_id}" groups="mrp.group_mrp_routings"/>
</xpath>
<xpath expr="//group[@name='maintenance']" position="after">
<group name="statistics">
<label for="expected_mtbf" string="Expected Mean Time Between Failure"/>
<div class="o_row">
<field name="expected_mtbf"/> days
</div>
<label for="mtbf" string="Mean Time Between Failure"/>
<div class="o_row">
<field name="mtbf" /> days
</div>
<label for="estimated_next_failure" string="Estimated Next Failure"/>
<div class="o_row">
<field name="estimated_next_failure" />
</div>
<field name="latest_failure_date" string="Latest Failure" />
<label for="mttr" string="Mean Time To Repair"/>
<div class="o_row">
<field name="mttr" /> days
</div>
</group>
</xpath>
</field>
</record>
<record id="maintenance_request_view_form_inherit_mrp" model="ir.ui.view">
<field name="name">maintenance.request.view.form.inherit.mrp</field>
<field name="model">maintenance.request</field>
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='maintenance_type']" position="after">
<field name="production_company_id" invisible="1"/>
<field name="workorder_id" invisible="1"/>
<field name="production_id" options="{'no_create': True, 'no_open': True}"/>
<field name="workorder_id" attrs="{'invisible': [('production_id', '=', False)]}" options="{'no_create': True, 'no_open': True}" domain="[('production_id', '=', production_id)]" groups="mrp.group_mrp_routings"/>
<!-- <field name="repair_id"/> -->
</xpath>
<xpath expr="//div[hasclass('oe_chatter')]" position="after">
<div invisible="not context.get('discard_on_footer_button', False)">
<footer class="oe_edit_only">
<button special="save" data-hotkey="v" string="Save" class="oe_highlight"/>
<button string="Discard" special="cancel" data-hotkey="z"/>
</footer>
</div>
</xpath>
<field name="equipment_id" position="attributes">
<attribute name="domain">['|', (not workorder_id and 1 or 0, '=', 1), '|', ('workcenter_id', '=', False), ('workcenter_id.order_ids', 'in', workorder_id)]</attribute>
</field>
</field>
</record>
<record id="maintenance_request_view_search_inherit_mrp" model="ir.ui.view">
<field name="name">maintenence.request.view.search.inherit.mrp</field>
<field name="model">maintenance.request</field>
<field name="inherit_id" ref="maintenance.hr_equipment_request_view_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='maintenance_team_id']" position="after">
<field name="production_id" string="Operation" filter_domain="['|', ('production_id', 'ilike', self), ('workorder_id', 'ilike', self)]"/>
</xpath>
</field>
</record>
<menuitem
id="maintenance.menu_equipment_form"
name="Equipments"
parent="maintenance.menu_maintenance_title"
groups="maintenance.group_equipment_manager,base.group_user"
sequence="2"/>
<menuitem id="menu_workcenter_tree"
action="mrp.mrp_workcenter_action"
groups="mrp.group_mrp_routings"
parent="maintenance.menu_equipment_form"
sequence="1"/>
<menuitem
id="menu_equipment_dashboard"
name="Machines &amp; Tools"
parent="maintenance.menu_equipment_form"
action="maintenance.hr_equipment_action"
sequence="2"/>
</odoo>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MRP.WORKCENTER -->
<record id="mrp_workcenter_view_form_inherit_maintenance" model="ir.ui.view">
<field name="name">mrp.workcenter.form.inherit.maintenance</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Equipment" name="equipment">
<field name="equipment_ids" widget="many2many">
<tree string="Equipments">
<field name="name"/>
<field name="technician_user_id"/>
<field name="category_id"/>
<field name="mtbf"/>
<field name="mttr"/>
<field name="estimated_next_failure" string="Est. Next Failure"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<record id="mrp_workcenter_view_kanban_inherit_maintenance" model="ir.ui.view">
<field name="name">mrp.workcenter.view.kanban.inherit.maintenance</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='plan_order']" position="after">
<div>
<a name="%(maintenance.hr_equipment_request_action)d" type="action">Maintenance</a>
</div>
</xpath>
</field>
</record>
<!-- MRP.PRODUCTION -->
<record id="mrp_production_view_form_inherit_maintenance" model="ir.ui.view">
<field name="name">mrp.production.view.form.inherit.maintenance</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<button name="action_cancel" position="before">
<button name="button_maintenance_req" type="object" string="Maintenance Request"/>
</button>
<div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench" attrs="{'invisible': [('maintenance_count', '=', 0)]}" context="{'search_default_production_id': active_id}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="maintenance_count"/></span>
<span class="o_stat_text">Maintenance</span>
</div>
</button>
</div>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<odoo>
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
<field name="arch" type="xml">
<!-- Desktop view -->
<xpath expr="//div[@name='o_wo']" position="inside">
<button class="btn btn-secondary fa fa-desktop" name="action_work_order" type="object" context="{'search_default_ready': 1, 'search_default_progress': 1, 'search_default_pending': 1, 'desktop_list_view': 1, 'search_default_workcenter_id': active_id}" title="Work orders" aria-label="Work orders"/>
</xpath>
</field>
</record>
<!-- override to change the no content image -->
<record id="mrp.action_work_orders" model="ir.actions.act_window">
<field name="help" type="html">
<p class="o_view_nocontent_workorder">
No work orders to do!
</p><p>
Work orders are operations to do as part of a manufacturing order.
Operations are defined in the bill of materials or added in the manufacturing order directly.
</p><p>
Use the table work center control panel to register operations in the shop floor directly.
The tablet provides worksheets for your workers and allow them to scrap products, track time,
launch a maintenance request, perform quality tests, etc.
</p>
</field>
</record>
<menuitem id="menu_mrp_dashboard"
name="工作中心概述"
action="mrp.mrp_workcenter_kanban_action"
groups="mrp.group_mrp_routings"
parent="mrp.menu_mrp_root"
sequence="5"/>
</odoo>

View File

@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sf_tray_form_inherit" model="ir.ui.view">

View File

@@ -1,6 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.actions.act_window" id="mrp_workorder_action_tablet">
<field name="name">Work Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.workorder</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('mrp.workcenter_line_kanban')}),
(0, 0, {'view_mode': 'tree', 'view_id': ref('mrp.mrp_production_workorder_tree_editable_view')}) ]"/>
<field name="target">fullscreen</field>
<field name="domain">[('state', 'not in', ['done', 'cancel'])]</field>
<field name="context">{'search_default_workcenter_id': active_id}</field>
<field name="help" type="html">
<p class="o_view_nocontent_workorder">
No work orders to do!
</p><p>
Work orders are operations to do as part of a manufacturing order.
Operations are defined in the bill of materials or added in the manufacturing order directly.
</p><p>
Use the table work center control panel to register operations in the shop floor directly.
The tablet provides worksheets for your workers and allow them to scrap products, track time,
launch a maintenance request, perform quality tests, etc.
</p>
</field>
</record>
<record id="sf_install_the_tray_workorder_form_view" model="ir.ui.view">
<field name="name">装夹工序工单</field>
<field name="model">mrp.workorder</field>
@@ -22,8 +48,9 @@
<field name="routing_type" invisible="1"/>
<field name="processing_panel" readonly = "1" />
<field name="tray_code"/>
<div class="col-12 col-lg-6 o_setting_box">
<button type="object" class="oe_highlight" name="gettray" string="扫描托盘"
<button type="object" class="oe_highlight" name="gettray" string="绑定托盘"
attrs='{"invisible": [("production_id","=",False)]}'
/>
</div>
@@ -46,97 +73,97 @@
<div>左面:</div>
<div></div>
<div class="o_address_city">
<label for="X1_axis" string="Lx1"/>
<label for="X1_axis" string="x1"/>
<field name='X1_axis' class="o_address_city"/>
<label for="Y1_axis" string="Ly1"/>
<label for="Y1_axis" string="y1"/>
<field name='Y1_axis' class="o_address_city"/>
<label for="Z1_axis" string="Lz1"/>
<label for="Z1_axis" string="z1"/>
<field name='Z1_axis' class="o_address_city"/>
</div>
<div class="o_address_city">
<label for="X2_axis" string="Lx2"/>
<label for="X2_axis" string="x2"/>
<field name='X2_axis' class="o_address_city"/>
<label for="Y2_axis" string="Ly2"/>
<label for="Y2_axis" string="y2"/>
<field name='Y2_axis' class="o_address_city"/>
<label for="Z2_axis" string="Lz2"/>
<label for="Z2_axis" string="z2"/>
<field name='Z2_axis' class="o_address_city"/>
</div>
<div>前面:</div>
<div></div>
<div class="o_address_city">
<label for="X3_axis" string="Fx1"/>
<label for="X3_axis" string="x1"/>
<field name='X3_axis' class="o_address_city"/>
<label for="Y3_axis" string="Fy1"/>
<label for="Y3_axis" string="y1"/>
<field name='Y3_axis' class="o_address_city"/>
<label for="Z3_axis" string="Fz1"/>
<label for="Z3_axis" string="z1"/>
<field name='Z3_axis' class="o_address_city"/>
</div>
<div class="o_address_city">
<label for="X4_axis" string="Fx2"/>
<label for="X4_axis" string="x2"/>
<field name='X4_axis' class="o_address_city"/>
<label for="Y4_axis" string="Fy2"/>
<label for="Y4_axis" string="y2"/>
<field name='Y4_axis' class="o_address_city"/>
<label for="Z4_axis" string="Fz2"/>
<label for="Z4_axis" string="z2"/>
<field name='Z4_axis' class="o_address_city"/>
</div>
<div>右面:</div>
<div></div>
<div class="o_address_city">
<label for="X5_axis" string="Rx1"/>
<label for="X5_axis" string="x1"/>
<field name='X5_axis' class="o_address_city"/>
<label for="Y5_axis" string="Ry1"/>
<label for="Y5_axis" string="y1"/>
<field name='Y5_axis' class="o_address_city"/>
<label for="Z5_axis" string="Rz1"/>
<label for="Z5_axis" string="z1"/>
<field name='Z5_axis' class="o_address_city"/>
</div>
<div class="o_address_city">
<label for="X6_axis" string="Rx2"/>
<label for="X6_axis" string="x2"/>
<field name='X6_axis' class="o_address_city"/>
<label for="Y6_axis" string="Ry2"/>
<label for="Y6_axis" string="y2"/>
<field name='Y6_axis' class="o_address_city"/>
<label for="Z6_axis" string="Rz2"/>
<label for="Z6_axis" string="z2"/>
<field name='Z6_axis' class="o_address_city"/>
</div>
<div>下面:</div>
<div></div>
<div class="o_address_city">
<label for="X7_axis" string="Bx1"/>
<label for="X7_axis" string="x1"/>
<field name='X7_axis' class="o_address_city"/>
<label for="Y7_axis" string="By1"/>
<label for="Y7_axis" string="y1"/>
<field name='Y7_axis' class="o_address_city"/>
<label for="Z7_axis" string="Bz1"/>
<label for="Z7_axis" string="z1"/>
<field name='Z7_axis' class="o_address_city"/>
</div>
<div class="o_address_city">
<label for="X8_axis" string="Bx2"/>
<label for="X8_axis" string="x2"/>
<field name='X8_axis' class="o_address_city"/>
<label for="Y8_axis" string="By2"/>
<label for="Y8_axis" string="y2"/>
<field name='Y8_axis' class="o_address_city"/>
<label for="Z8_axis" string="Bz2"/>
<label for="Z8_axis" string="z2"/>
<field name='Z8_axis' class="o_address_city"/>
</div>
<div>上面:</div>
<div></div>
<div class="o_address_city">
<label for="X9_axis" string="Ux1"/>
<label for="X9_axis" string="x1"/>
<field name='X9_axis' class="o_address_city"/>
<label for="Y9_axis" string="Uy1"/>
<label for="Y9_axis" string="y1"/>
<field name='Y9_axis' class="o_address_city"/>
<label for="Z9_axis" string="Uz1"/>
<label for="Z9_axis" string="z1"/>
<field name='Z9_axis' class="o_address_city"/>
</div>
<div class="o_address_city">
<label for="X10_axis" string="Ux2"/>
<label for="X10_axis" string="x2"/>
<field name='X10_axis' class="o_address_city"/>
<label for="Y10_axis" string="Uy2"/>
<label for="Y10_axis" string="y2"/>
<field name='Y10_axis' class="o_address_city"/>
<label for="Z10_axis" string="Uz2"/>
<label for="Z10_axis" string="z2"/>
<field name='Z10_axis' class="o_address_city"/>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<button type="object" class="oe_highlight" name="getcenter" string="中心定位"
<button type="object" class="oe_highlight" name="getcenter" string="计算定位"
/>
</div>
@@ -153,11 +180,12 @@
</xpath>
<xpath expr="//page[last()]" position="after">
<page string="CNC加工" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<group>
<page string="CNC程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<field name="cnc_ids" widget="one2many">
<tree>
<field name="cnc_id"/>
<field name="FNo" />
<field name="FPGName" />
<field name="FKnifeName" />
@@ -174,8 +202,6 @@
</field>
</group>
</page>
</xpath>