Merge branch refs/heads/develop into refs/heads/hotfix/刀具预调仪
This commit is contained in:
@@ -504,3 +504,26 @@ div:has(.o_required_modifier) > label::before {
|
||||
background-color: #4A4F59;
|
||||
border-color: #4A4F59;
|
||||
}
|
||||
|
||||
// 功能刀具组装单 弹窗样式
|
||||
.o_horizontal_separator.mt-4.mb-3.text-uppercase.fw-bolder.small ~ div.col-lg-6 .o_inner_group.col-lg-6 {
|
||||
width: 100%;
|
||||
}
|
||||
.o_horizontal_separator.mt-4.mb-3.text-uppercase.fw-bolder.small ~ div .o_inner_group .o_wrap_field.d-flex.d-sm-contents.flex-column{
|
||||
display: flex!important;
|
||||
flex-direction: row!important;
|
||||
input {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表格横向滚动
|
||||
.o_list_renderer.o_renderer {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
// 设置表单页面label文本不换行
|
||||
.o_form_view .o_group .o_wrap_label .o_form_label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
.o_data_row .w-100 {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
display: block !important;
|
||||
//display: block !important;
|
||||
}
|
||||
|
||||
.o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove) {
|
||||
|
||||
@@ -215,7 +215,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
if workorder.state != 'progress':
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单未开始'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
workorder.button_finish()
|
||||
workorder.write({'date_finished': datetime.now()})
|
||||
# workorder.process_state = '待解除装夹'
|
||||
# workorder.sudo().production_id.process_state = '待解除装夹'
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
import requests
|
||||
from itertools import groupby
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError,ValidationError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||
|
||||
@@ -77,6 +77,7 @@ class MrpProduction(models.Model):
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
rework_production = fields.Many2one('mrp.production', string='返工的制造订单')
|
||||
|
||||
@api.depends(
|
||||
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state',
|
||||
@@ -154,8 +155,30 @@ class MrpProduction(models.Model):
|
||||
for production in self:
|
||||
production.maintenance_count = len(production.request_ids)
|
||||
|
||||
# 制造订单报废:编程单更新
|
||||
def updateCNC(self):
|
||||
try:
|
||||
res = {'production_no': self.name, 'programming_no': self.programming_no,
|
||||
'order_no': self.origin}
|
||||
logging.info('res=%s:' % res)
|
||||
configsettings = self.env['res.config.settings'].get_values()
|
||||
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
|
||||
url = '/api/intelligent_programming/update_intelligent_programmings'
|
||||
config_url = configsettings['sf_url'] + url
|
||||
res['token'] = configsettings['token']
|
||||
ret = requests.post(config_url, json={}, data=res, headers=config_header)
|
||||
ret = ret.json()
|
||||
logging.info('updateCNC-ret:%s' % ret)
|
||||
if ret['status'] == 1:
|
||||
self.write({'work_state': '已编程'})
|
||||
else:
|
||||
raise UserError(ret['message'])
|
||||
except Exception as e:
|
||||
logging.info('updateCNC error:%s' % e)
|
||||
raise UserError("更新程单失败,请联系管理员")
|
||||
|
||||
# cnc程序获取
|
||||
def fetchCNC(self, production_names):
|
||||
def fetchCNC(self, production_names, scrap_production):
|
||||
cnc = self.env['mrp.production'].search([('id', '=', self.id)])
|
||||
quick_order = self.env['quick.easy.order'].search(
|
||||
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
|
||||
@@ -171,6 +194,8 @@ class MrpProduction(models.Model):
|
||||
'production_no': production_names,
|
||||
'machine_tool_code': '',
|
||||
'product_name': cnc.product_id.name,
|
||||
'remanufacture_type': '' if not scrap_production else scrap_production.workorder_ids.filtered(
|
||||
lambda b: b.routing_type == "CNC加工").test_results,
|
||||
'model_code': cnc.product_id.model_code,
|
||||
'material_code': self.env['sf.production.materials'].search(
|
||||
[('id', '=', cnc.product_id.materials_id.id)]).materials_no,
|
||||
@@ -268,7 +293,7 @@ class MrpProduction(models.Model):
|
||||
# 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制;
|
||||
# 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心;
|
||||
|
||||
def _create_workorder3(self):
|
||||
def _create_workorder3(self, is_fetchcnc=False, scrap_production=False):
|
||||
# 根据product_id对self进行分组
|
||||
grouped_product_ids = {k: list(g) for k, g in groupby(self, key=lambda x: x.product_id.id)}
|
||||
# 初始化一个字典来存储每个product_id对应的生产订单名称列表
|
||||
@@ -311,11 +336,16 @@ class MrpProduction(models.Model):
|
||||
production_programming = self.search(
|
||||
[('product_id.id', '=', production.product_id.id), ('origin', '=', production.origin)],
|
||||
limit=1, order='id asc')
|
||||
if not production_programming.programming_no:
|
||||
production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id]))
|
||||
if not production_programming.programming_no or (is_fetchcnc is True and scrap_production):
|
||||
# 制造订单报废/返工也需重新编程
|
||||
if (is_fetchcnc is True and scrap_production) or (
|
||||
is_fetchcnc is False and scrap_production):
|
||||
production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id]),
|
||||
scrap_production)
|
||||
else:
|
||||
production.write({'programming_no': production_programming.programming_no,
|
||||
'programming_state': '编程中'})
|
||||
|
||||
# # 根据加工面板的面数及对应的工序模板生成工单
|
||||
i = 0
|
||||
processing_panel_len = len(production.product_id.model_processing_panel.split(','))
|
||||
@@ -379,6 +409,12 @@ class MrpProduction(models.Model):
|
||||
workorders_values.append(
|
||||
self.env['mrp.workorder'].json_workorder_str('', production, route))
|
||||
production.workorder_ids = workorders_values
|
||||
if production_programming.programming_state == '已编程':
|
||||
logging.info("production_programming: %s" % production_programming.name)
|
||||
production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').write({
|
||||
'cnc_ids': production_programming.workorder_ids.filtered(
|
||||
lambda
|
||||
t1: t1.routing_type == 'CNC加工').cnc_ids})
|
||||
for workorder in production.workorder_ids:
|
||||
workorder.duration_expected = workorder._get_duration_expected()
|
||||
|
||||
@@ -406,20 +442,15 @@ class MrpProduction(models.Model):
|
||||
|
||||
# 工单排序
|
||||
def _reset_work_order_sequence1(self, k):
|
||||
sequen = 0
|
||||
for rec in self:
|
||||
current_sequence = 10
|
||||
cnc_workorder = rec.workorder_ids.filtered(lambda wo: wo.name == "CNC加工")
|
||||
cnc_back_workorder = rec.workorder_ids.filtered(lambda wo: wo.name == "CNC加工(返工)")
|
||||
for work in rec.workorder_ids:
|
||||
work.sequence = current_sequence
|
||||
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
|
||||
if work.name == cnc_workorder.name and work.processing_panel == k:
|
||||
cnc_back_workorder.write({'sequence': work.sequence + 1})
|
||||
print(cnc_back_workorder.sequence)
|
||||
elif work.routing_type not in ['装夹预调'] and work != cnc_back_workorder:
|
||||
work.sequence += 1
|
||||
|
||||
# 在制造订单上新增工单
|
||||
def _create_workorder1(self, k):
|
||||
@@ -459,13 +490,7 @@ class MrpProduction(models.Model):
|
||||
order='sequence asc'
|
||||
)
|
||||
i += 1
|
||||
|
||||
for route in routingworkcenter:
|
||||
|
||||
# if route.routing_type == '后置三元质量检测':
|
||||
# workorders_values.append(
|
||||
# self.env['mrp.workorder'].json_workorder_str1(k, production, route)
|
||||
# )
|
||||
if route.routing_type == 'CNC加工':
|
||||
workorders_values.append(
|
||||
self.env['mrp.workorder'].json_workorder_str1(k, production, route))
|
||||
@@ -532,8 +557,8 @@ class MrpProduction(models.Model):
|
||||
# work.button_finish()
|
||||
|
||||
# 创建工单并进行排序
|
||||
def _create_workorder(self):
|
||||
self._create_workorder3()
|
||||
def _create_workorder(self, is_fetchcnc=False, scrap_production=False):
|
||||
self._create_workorder3(is_fetchcnc=is_fetchcnc, scrap_production=scrap_production)
|
||||
self._reset_work_order_sequence()
|
||||
return True
|
||||
|
||||
|
||||
@@ -199,7 +199,12 @@ class ResMrpWorkOrder(models.Model):
|
||||
production_line_state = fields.Selection(related='production_id.production_line_state',
|
||||
string='上/下产线', store=True)
|
||||
detection_report = fields.Binary('检测报告', readonly=True)
|
||||
is_remanufacture = fields.Boolean(string='是否重新生成制造订单', default=True)
|
||||
is_remanufacture = fields.Boolean(string='重新生成制造订单', default=False)
|
||||
is_fetchcnc = fields.Boolean(string='重新获取NC程序', default=False)
|
||||
reason = fields.Selection(
|
||||
[("programming", "编程"), ("clamping", "返工"), ("cutter", "刀具"), ("operate computer", "操机"),
|
||||
("technology", "工艺"), ("customer redrawing", "客户改图"), ("other", "其他"), ], string="原因")
|
||||
detailed_reason = fields.Text('详细原因')
|
||||
|
||||
@api.onchange('rfid_code')
|
||||
def _onchange(self):
|
||||
@@ -672,18 +677,64 @@ class ResMrpWorkOrder(models.Model):
|
||||
"""
|
||||
重新生成制造订单或者重新生成工单
|
||||
"""
|
||||
if self.test_results == '报废':
|
||||
if self.test_results in ['返工', '报废']:
|
||||
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_raw_values())
|
||||
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
productions._create_workorder()
|
||||
productions._create_workorder(is_fetchcnc=self.is_fetchcnc, scrap_production=self.production_id)
|
||||
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_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production_item)
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production_item)
|
||||
|
||||
for production in productions:
|
||||
origin_production = production.move_dest_ids and production.move_dest_ids[
|
||||
@@ -704,13 +755,36 @@ class ResMrpWorkOrder(models.Model):
|
||||
'mail.message_origin_link',
|
||||
values={'self': production, 'origin': origin_production},
|
||||
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())
|
||||
productions._create_workorder2(self.processing_panel)
|
||||
else:
|
||||
self.results = '合格'
|
||||
|
||||
'''
|
||||
创建生产计划
|
||||
'''
|
||||
# 工单耗时
|
||||
workorder_duration = 0
|
||||
for workorder in productions.workorder_ids:
|
||||
workorder_duration += workorder.duration_expected
|
||||
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', productions.origin)])
|
||||
if sale_order:
|
||||
sale_order.mrp_production_ids |= productions
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(self.production_id.company_id).create({
|
||||
'name': productions.name,
|
||||
'order_deadline': sale_order.deadline_of_delivery,
|
||||
'production_id': productions.id,
|
||||
'date_planned_start': productions.date_planned_start,
|
||||
'origin': productions.origin,
|
||||
'product_qty': productions.product_qty,
|
||||
'product_id': productions.product_id.id,
|
||||
'state': 'draft',
|
||||
})
|
||||
# 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())
|
||||
# productions._create_workorder2(self.processing_panel)
|
||||
# else:
|
||||
# self.results = '合格'
|
||||
|
||||
def json_workorder_str1(self, k, production, route):
|
||||
workorders_values_str = [0, '', {
|
||||
@@ -720,7 +794,6 @@ class ResMrpWorkOrder(models.Model):
|
||||
'name': '%s(返工)' % route.route_workcenter_id.name,
|
||||
'processing_panel': k,
|
||||
'routing_type': route.routing_type,
|
||||
'work_state': '' if not route.routing_type == '获取CNC加工程序' else '待发起',
|
||||
'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids,
|
||||
route.routing_type,
|
||||
production.product_id),
|
||||
@@ -728,10 +801,37 @@ class ResMrpWorkOrder(models.Model):
|
||||
'date_planned_finished': datetime.now() + timedelta(days=1),
|
||||
'duration_expected': 60,
|
||||
'duration': 0,
|
||||
|
||||
'manual_quotation': production.workorder_ids.filtered(
|
||||
lambda t: t.routing_type == 'CNC加工').manual_quotation,
|
||||
'rfid_code': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').rfid_code,
|
||||
'cnc_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cnc_ids,
|
||||
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
|
||||
}]
|
||||
return workorders_values_str
|
||||
|
||||
# @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state')
|
||||
# def _compute_state(self):
|
||||
# super(ResMrpWorkOrder, self)._compute_state()
|
||||
# for item in self:
|
||||
# print(item.name)
|
||||
# print(item.state)
|
||||
# print(item.is_remanufacture)
|
||||
# scrap_workorder = self.env['mrp.workorder'].search(
|
||||
# [('production_id', '=', item.production_id.id), ('routing_type', '=', 'CNC加工'),
|
||||
# ('state', '=', 'done'), ('test_results', 'in', ['返工', '报废'])])
|
||||
# print(scrap_workorder)
|
||||
# # if item.routing_type == 'CNC加工' and item.state in ['done'] and item.test_results in ['返工', '报废']:
|
||||
# if item.routing_type == '解除装夹':
|
||||
# if scrap_workorder and item.state not in ['cancel']:
|
||||
# item.state = 'cancel'
|
||||
# elif item.routing_type == '表面工艺':
|
||||
# if scrap_workorder:
|
||||
# stock_move = self.env['stock.move'].search(
|
||||
# [('origin', '=', item.production_id.name)])
|
||||
# stock_move.write({'state': 'cancel'})
|
||||
# item.picking_ids.write({'state': 'cancel'})
|
||||
# item.state = 'cancel'
|
||||
|
||||
# 重写工单开始按钮方法
|
||||
def button_start(self):
|
||||
if self.routing_type == '装夹预调':
|
||||
@@ -887,6 +987,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
raise UserError(
|
||||
'请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
|
||||
tem_date_planned_finished = record.date_planned_finished
|
||||
tem_date_finished = record.date_finished
|
||||
logging.info('routing_type:%s' % record.routing_type)
|
||||
super().button_finish()
|
||||
logging.info('date_planned_finished:%s' % record.date_planned_finished)
|
||||
@@ -895,6 +996,15 @@ class ResMrpWorkOrder(models.Model):
|
||||
record.write({
|
||||
'date_planned_finished': tem_date_planned_finished # 保持原值
|
||||
})
|
||||
if record.routing_type == 'CNC加工':
|
||||
record.write({
|
||||
'date_finished': tem_date_finished # 保持原值
|
||||
})
|
||||
if record.routing_type == 'CNC加工' and record.test_results in ['返工', '报废']:
|
||||
record.production_id.action_cancel()
|
||||
record.production_id.workorder_ids.write({'rfid_code': False, 'rfid_code_old': record.rfid_code})
|
||||
if record.is_remanufacture is True:
|
||||
record.recreateManufacturingOrWorkerOrder()
|
||||
is_production_id = True
|
||||
for workorder in record.production_id.workorder_ids:
|
||||
if workorder.state != 'done':
|
||||
@@ -1035,19 +1145,27 @@ class CNCprocessing(models.Model):
|
||||
# 根据程序名和加工面匹配到ftp里对应的Nc程序名,可优化为根据cnc_processing.program_path进行匹配
|
||||
def get_cnc_processing_file(self, serverdir, cnc_processing, program_path):
|
||||
logging.info('serverdir:%s' % serverdir)
|
||||
logging.info('cnc_processing:%s' % cnc_processing)
|
||||
for root, dirs, files in os.walk(serverdir):
|
||||
for f in files:
|
||||
logging.info('splitext(f):%s' % os.path.splitext(f)[1])
|
||||
if os.path.splitext(f)[1] == ".pdf":
|
||||
full_path = os.path.join(serverdir, root, f)
|
||||
if full_path is not False:
|
||||
if not cnc_processing.workorder_id.cnc_worksheet:
|
||||
cnc_processing.workorder_id.cnc_worksheet = base64.b64encode(
|
||||
open(full_path, 'rb').read())
|
||||
else:
|
||||
if f in program_path:
|
||||
# if cnc_processing.program_name == f.split('.')[0]:
|
||||
cnc_file_path = os.path.join(serverdir, root, f)
|
||||
self.write_file(cnc_file_path, cnc_processing)
|
||||
logging.info('full_path:%s' % full_path)
|
||||
logging.info('routing_type:%s' % cnc_processing.workorder_id.routing_type)
|
||||
logging.info('cnc_worksheet:%s' % cnc_processing.workorder_id.cnc_worksheet)
|
||||
# with open(full_path, 'rb') as pdf_file:
|
||||
# file_content = pdf_file.read()
|
||||
# cnc_processing.workorder_id.cnc_worksheet = base64.b64encode(file_content)
|
||||
if not cnc_processing.workorder_id.cnc_worksheet:
|
||||
logging.info('full_path111555:%s' % full_path)
|
||||
cnc_processing.workorder_id.cnc_worksheet = base64.b64encode(
|
||||
open(full_path, 'rb').read())
|
||||
else:
|
||||
if f in program_path:
|
||||
# if cnc_processing.program_name == f.split('.')[0]:
|
||||
cnc_file_path = os.path.join(serverdir, root, f)
|
||||
self.write_file(cnc_file_path, cnc_processing)
|
||||
|
||||
# 创建附件(nc文件)
|
||||
def attachment_create(self, name, data):
|
||||
|
||||
@@ -207,7 +207,7 @@ class StockRule(models.Model):
|
||||
'''
|
||||
创建工单
|
||||
'''
|
||||
productions._create_workorder()
|
||||
productions._create_workorder(is_fetchcnc=False, scrap_production=False)
|
||||
|
||||
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
|
||||
(
|
||||
@@ -631,8 +631,8 @@ class ReStockMove(models.Model):
|
||||
'reserved_uom_qty': 1.0,
|
||||
'lot_id': purchase.picking_ids.move_line_ids.lot_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
'workorder_id': '' if not sorted_workorders else sorted_workorders.id,
|
||||
'production_id': '' if not sorted_workorders else sorted_workorders.production_id.id,
|
||||
# 'workorder_id': '' if not sorted_workorders else sorted_workorders.id,
|
||||
# 'production_id': '' if not sorted_workorders else sorted_workorders.production_id.id,
|
||||
'state': 'assigned',
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<tree editable="bottom">
|
||||
<field name="name" required="1"/>
|
||||
<field name="type" readonly="1" string="任务类型"/>
|
||||
<field name="route_type" string="类型"/>
|
||||
<field name="route_type" string="类型" required="1"/>
|
||||
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"/>
|
||||
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"/>
|
||||
<field name="destination_production_line_id" required="1" options="{'no_create': True}"/>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('mrp.mrp_production_workorder_tree_editable_view')}) ]"/>
|
||||
<!-- (0, 0, {'view_mode': 'kanban', 'view_id': ref('mrp.workcenter_line_kanban')})-->
|
||||
<!-- (0, 0, {'view_mode': 'kanban', 'view_id': ref('mrp.workcenter_line_kanban')})-->
|
||||
<!-- <field name="target">fullscreen</field>-->
|
||||
<field name="target">current</field>
|
||||
<field name="domain">[('state', '!=', 'cancel'),('schedule_state', '=', '已排')]</field>
|
||||
@@ -214,7 +214,7 @@
|
||||
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||
<field name="functional_fixture_type_id"
|
||||
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||
<field name="rfid_code" force_save="1" readonly="1" cache="True"
|
||||
<field name="rfid_code" cache="True"
|
||||
attrs="{'invisible': [('rfid_code_old', '!=', False)]}"/>
|
||||
<field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/>
|
||||
</group>
|
||||
@@ -477,7 +477,12 @@
|
||||
<page string="后置三元检测" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
|
||||
<group>
|
||||
<field name="test_results" attrs='{"invisible":[("results","!=",False)]}'/>
|
||||
<field name="is_remanufacture" attrs='{"invisible":[("test_results","=","合格")]}'/>
|
||||
<field name="is_remanufacture" attrs='{"invisible":[("test_results","!=","报废")]}'/>
|
||||
<field name="is_fetchcnc"
|
||||
attrs='{"invisible":["|",("test_results","=","合格"),("is_remanufacture","=",False)]}'/>
|
||||
<field name="reason"
|
||||
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")]}'/>
|
||||
<field name="detailed_reason" attrs='{"invisible":[("test_results","=","合格")]}'/>
|
||||
<field name="results" readonly="1" attrs='{"invisible":[("results","!=","合格")]}'/>
|
||||
<field name="detection_report" attrs='{"invisible":[("results","!=",False)]}'
|
||||
widget="pdf_viewer"/>
|
||||
@@ -618,7 +623,6 @@
|
||||
<field name="route_id" options="{'no_create': True}"
|
||||
domain="[('route_type','in',['上产线','下产线'])]"/>
|
||||
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
|
||||
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
|
||||
<field name="feeder_station_destination_id" readonly="1" force_save="1"/>
|
||||
<field name="is_cnc_program_down" readonly="1"/>
|
||||
<!-- <field name="rfid_code"/>-->
|
||||
|
||||
@@ -32,6 +32,9 @@ class FtpController():
|
||||
logging.error(f"Error checking file: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
# # 检测字符串的编码
|
||||
# def detect_encoding(self, s):
|
||||
# result = chardet.detect(s)
|
||||
@@ -43,6 +46,8 @@ class FtpController():
|
||||
os.makedirs(serverdir)
|
||||
try:
|
||||
logging.info("进入FTP目录 ")
|
||||
self.ftp.pwd()
|
||||
logging.info('当前目录:%s' % self.ftp.pwd())
|
||||
logging.info('目录:%s' % target_dir)
|
||||
target_dir1 = target_dir.split('/')
|
||||
logging.info('目录1:%s' % target_dir1[1])
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<field name="model_id" ref="model_sf_tool_datasync"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_tool_datasync_all()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="interval_number">12</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import base64
|
||||
import requests
|
||||
import logging
|
||||
from odoo import models, api
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo.exceptions import UserError
|
||||
@@ -10,30 +11,36 @@ class StockLot(models.Model):
|
||||
_inherit = 'stock.lot'
|
||||
_description = '夹具物料序列号注册'
|
||||
|
||||
def enroll_fixture_material_stock(self):
|
||||
def sync_enroll_fixture_material_stock_all(self):
|
||||
logging.info("调用 sync_enroll_fixture_material_stock_all 同步接口")
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + "/api/fixture_material_stock/create"
|
||||
objs_all = self.env['stock.lot'].search([('id', '=', self.id)])
|
||||
product_ids = self.env['product.product'].sudo().search([('categ_type', '=', '夹具')]).ids
|
||||
objs_all = self.env['stock.lot'].search([('rfid', '!=', False), ('product_id', 'in', product_ids)])
|
||||
fixture_material_stock_list = []
|
||||
if objs_all:
|
||||
for item in objs_all:
|
||||
val = {
|
||||
'name': item.name,
|
||||
'tool_material_status': item.tool_material_status,
|
||||
'location': [] if not item.quant_ids else item.quant_ids[-1].location_id.name,
|
||||
'fixture_material_search_id': item.fixture_material_search_id.id,
|
||||
}
|
||||
fixture_material_stock_list.append(val)
|
||||
kw = json.dumps(fixture_material_stock_list, ensure_ascii=False)
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
return '夹具物料序列号注册成功'
|
||||
else:
|
||||
raise UserError("没有注册夹具物料序列号信息")
|
||||
try:
|
||||
if objs_all:
|
||||
for item in objs_all:
|
||||
val = {
|
||||
'name': item.name,
|
||||
'tool_material_status': item.tool_material_status,
|
||||
'location': [] if not item.quant_ids else item.quant_ids[-1].location_id.name,
|
||||
'fixture_material_search_id': item.fixture_material_search_id.id,
|
||||
}
|
||||
fixture_material_stock_list.append(val)
|
||||
kw = json.dumps(fixture_material_stock_list, ensure_ascii=False)
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
logging.info("夹具物料序列号每日同步成功")
|
||||
return '夹具物料序列号注册成功'
|
||||
else:
|
||||
logging.info("没有注册夹具物料序列号信息")
|
||||
except Exception as e:
|
||||
logging.info("夹具物料序列号同步失败:%s" % e)
|
||||
|
||||
|
||||
class FixtureMaterialSearch(models.Model):
|
||||
@@ -42,41 +49,46 @@ class FixtureMaterialSearch(models.Model):
|
||||
|
||||
crea_url = "/api/fixture_material/create"
|
||||
|
||||
def enroll_fixture_material(self):
|
||||
def sync_enroll_fixture_material_all(self):
|
||||
logging.info("调用 sync_enroll_fixture_material_all 同步接口")
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + self.crea_url
|
||||
objs_all = self.search([('id', '=', self.id)])
|
||||
objs_all = self.search([])
|
||||
fixture_material_list = []
|
||||
if objs_all:
|
||||
for obj in objs_all:
|
||||
val = {
|
||||
'name': obj.name,
|
||||
'id': obj.id,
|
||||
'fixture_material_code': obj.fixture_material_id.code,
|
||||
'fixture_model_code': obj.fixture_model_id.code,
|
||||
'specification_name': obj.specification_fixture_id.name,
|
||||
'image': '' if not obj.image else base64.b64encode(obj.image).decode('utf-8'),
|
||||
'number': obj.number,
|
||||
'usable_num': obj.usable_num,
|
||||
'have_been_used_num': obj.have_been_used_num,
|
||||
'scrap_num': obj.scrap_num
|
||||
}
|
||||
fixture_material_list.append(val)
|
||||
kw = json.dumps(fixture_material_list, ensure_ascii=False)
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
return '夹具物料注册成功'
|
||||
else:
|
||||
raise UserError("没有注册夹具物料信息")
|
||||
try:
|
||||
if objs_all:
|
||||
for obj in objs_all:
|
||||
val = {
|
||||
'name': obj.name,
|
||||
'id': obj.id,
|
||||
'fixture_material_code': obj.fixture_material_id.code,
|
||||
'fixture_model_code': obj.fixture_model_id.code,
|
||||
'specification_name': obj.specification_fixture_id.name,
|
||||
'image': '' if not obj.image else base64.b64encode(obj.image).decode('utf-8'),
|
||||
'number': obj.number,
|
||||
'usable_num': obj.usable_num,
|
||||
'have_been_used_num': obj.have_been_used_num,
|
||||
'scrap_num': obj.scrap_num
|
||||
}
|
||||
fixture_material_list.append(val)
|
||||
kw = json.dumps(fixture_material_list, ensure_ascii=False)
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
logging.info("夹具物料每日同步成功")
|
||||
return '夹具物料注册成功'
|
||||
else:
|
||||
logging.info("没有注册夹具物料信息")
|
||||
except Exception as e:
|
||||
logging.info("夹具物料同步失败:%s" % e)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super(FixtureMaterialSearch, self).create(vals_list)
|
||||
for record in records:
|
||||
if record:
|
||||
record.enroll_fixture_material()
|
||||
return records
|
||||
# @api.model_create_multi
|
||||
# def create(self, vals_list):
|
||||
# records = super(FixtureMaterialSearch, self).create(vals_list)
|
||||
# for record in records:
|
||||
# if record:
|
||||
# record.enroll_fixture_material()
|
||||
# return records
|
||||
|
||||
@@ -33,23 +33,26 @@ def get_suitable_coolant_names(item):
|
||||
|
||||
class ToolDatasync(models.Model):
|
||||
_name = 'sf.tool.datasync'
|
||||
_description = '定时同步所有刀具'
|
||||
_description = '定时同步所有刀具、夹具'
|
||||
|
||||
def _cron_tool_datasync_all(self):
|
||||
try:
|
||||
self.env['stock.lot'].sudo().sync_enroll_tool_material_stock_all()
|
||||
logging.info("刀具物料序列号每日同步成功")
|
||||
|
||||
self.env['stock.lot'].sudo().sync_enroll_fixture_material_stock_all()
|
||||
|
||||
self.env['sf.tool.material.search'].sudo().sync_enroll_tool_material_all()
|
||||
logging.info("刀具物料每日同步成功")
|
||||
|
||||
self.env['sf.fixture.material.search'].sudo().sync_enroll_fixture_material_all()
|
||||
|
||||
self.env['sf.functional.cutting.tool.entity'].sudo().esync_enroll_functional_tool_entity_all()
|
||||
logging.info("功能刀具列表每日同步成功")
|
||||
self.env['sf.functional.tool.warning'].sudo().sync_enroll_functional_tool_warning_all()
|
||||
logging.info("功能刀具预警每日同步成功")
|
||||
self.env['stock.move.line'].sudo().sync_enroll_functional_tool_move_all()
|
||||
logging.info("功能刀具出入库记录每日同步成功")
|
||||
self.env[
|
||||
'sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
|
||||
logging.info("功能刀具安全库存每日同步成功")
|
||||
logging.info("已全部同步完成!!!")
|
||||
# self.env['sf.functional.tool.warning'].sudo().sync_enroll_functional_tool_warning_all()
|
||||
# logging.info("功能刀具预警每日同步成功")
|
||||
# self.env['stock.move.line'].sudo().sync_enroll_functional_tool_move_all()
|
||||
# logging.info("功能刀具出入库记录每日同步成功")
|
||||
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
|
||||
# logging.info("功能刀具安全库存每日同步成功")
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
raise ValidationError("数据错误导致同步失败,请联系管理员")
|
||||
@@ -59,24 +62,25 @@ class StockLot(models.Model):
|
||||
_inherit = 'stock.lot'
|
||||
_description = '刀具物料序列号注册'
|
||||
|
||||
def enroll_tool_material_stock(self):
|
||||
logging.info('调用刀具物料序列号注册接口: enroll_tool_material_stock()')
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + "/api/tool_material_stock/create"
|
||||
objs_all = self.env['stock.lot'].search([('id', '=', self.id), ('active', 'in', [True, False])])
|
||||
self._get_sync_stock_lot(objs_all, str_url, token, headers)
|
||||
# def enroll_tool_material_stock(self):
|
||||
# logging.info('调用刀具物料序列号注册接口: enroll_tool_material_stock()')
|
||||
# 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)
|
||||
# str_url = sf_sync_config['sf_url'] + "/api/tool_material_stock/create"
|
||||
# objs_all = self.env['stock.lot'].search([('id', '=', self.id), ('active', 'in', [True, False])])
|
||||
# self._get_sync_stock_lot(objs_all, str_url, token, headers)
|
||||
|
||||
def sync_enroll_tool_material_stock_all(self):
|
||||
logging.info('调用刀具物料序列号注册接口: sync_enroll_tool_material_stock_all()')
|
||||
logging.info('调用 sync_enroll_tool_material_stock_all 同步接口')
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + "/api/tool_material_stock/create"
|
||||
objs_all = self.env['stock.lot'].search([('rfid', '!=', False)])
|
||||
product_ids = self.env['product.product'].sudo().search([('categ_type', '=', '刀具')]).ids
|
||||
objs_all = self.env['stock.lot'].search([('rfid', '!=', False), ('product_id', 'in', product_ids)])
|
||||
self._get_sync_stock_lot(objs_all, str_url, token, headers)
|
||||
|
||||
def _get_sync_stock_lot(self, objs_all, str_url, token, headers):
|
||||
@@ -95,11 +99,12 @@ class StockLot(models.Model):
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
return '刀具物料序列号注册成功'
|
||||
logging.info("刀具物料序列号每日同步成功")
|
||||
return '刀具(夹具)物料序列号注册成功'
|
||||
else:
|
||||
logging.info("没有注册刀具物料序列号信息")
|
||||
logging.info("没有刀具物料序列号信息")
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("刀具物料序列号同步失败:%s" % e)
|
||||
|
||||
|
||||
class ToolMaterial(models.Model):
|
||||
@@ -108,18 +113,18 @@ class ToolMaterial(models.Model):
|
||||
|
||||
crea_url = '/api/tool_material/create'
|
||||
|
||||
def enroll_tool_material(self):
|
||||
logging.info('调用刀具物料注册接口: enroll_tool_material()')
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + self.crea_url
|
||||
objs_all = self.search([('id', '=', self.id)])
|
||||
self._get_sync_tool_material_search(objs_all, str_url, token, headers)
|
||||
# def enroll_tool_material(self):
|
||||
# logging.info('调用刀具物料注册接口: enroll_tool_material()')
|
||||
# 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)
|
||||
# str_url = sf_sync_config['sf_url'] + self.crea_url
|
||||
# objs_all = self.search([('id', '=', self.id)])
|
||||
# self._get_sync_tool_material_search(objs_all, str_url, token, headers)
|
||||
|
||||
def sync_enroll_tool_material_all(self):
|
||||
logging.info('调用刀具物料注册接口: sync_enroll_tool_material_all()')
|
||||
logging.info('调用 sync_enroll_tool_material_all 同步接口')
|
||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||
token = sf_sync_config['token']
|
||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||
@@ -150,11 +155,14 @@ class ToolMaterial(models.Model):
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
logging.info("刀具物料每日同步成功")
|
||||
return '刀具物料注册成功'
|
||||
else:
|
||||
logging.info('没有注册刀具物料信息')
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("刀具物料同步失败:%s" % e)
|
||||
|
||||
|
||||
|
||||
|
||||
class FunctionalCuttingToolEntity(models.Model):
|
||||
@@ -164,18 +172,18 @@ class FunctionalCuttingToolEntity(models.Model):
|
||||
crea_url = "/api/functional_tool_entity/create"
|
||||
|
||||
# 注册同步功能刀具列表
|
||||
def enroll_functional_tool_entity(self):
|
||||
logging.info('调用功能刀具列表注册接口: enroll_functional_tool_entity()')
|
||||
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)
|
||||
str_url = sf_sync_config['sf_url'] + self.crea_url
|
||||
objs_all = self.env['sf.functional.cutting.tool.entity'].search([('id', '=', self.id)])
|
||||
self._get_sync_functional_cutting_tool_entity(objs_all, str_url, token, headers)
|
||||
# def enroll_functional_tool_entity(self):
|
||||
# logging.info('调用功能刀具列表注册接口: enroll_functional_tool_entity()')
|
||||
# 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)
|
||||
# str_url = sf_sync_config['sf_url'] + self.crea_url
|
||||
# objs_all = self.env['sf.functional.cutting.tool.entity'].search([('id', '=', self.id)])
|
||||
# self._get_sync_functional_cutting_tool_entity(objs_all, str_url, token, headers)
|
||||
|
||||
def esync_enroll_functional_tool_entity_all(self):
|
||||
logging.info('调用功能刀具列表注册接口: esync_enroll_functional_tool_entity_all()')
|
||||
logging.info('调用 esync_enroll_functional_tool_entity_all 同步接口')
|
||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||
token = sf_sync_config['token']
|
||||
sf_secret_key = sf_sync_config['sf_secret_key']
|
||||
@@ -202,6 +210,7 @@ class FunctionalCuttingToolEntity(models.Model):
|
||||
'coarse_middle_thin': item.coarse_middle_thin,
|
||||
'new_former': item.new_former,
|
||||
'tool_loading_length': item.tool_loading_length,
|
||||
'handle_length': item.handle_length,
|
||||
'functional_tool_length': item.functional_tool_length,
|
||||
'effective_length': item.effective_length,
|
||||
'max_lifetime_value': item.max_lifetime_value,
|
||||
@@ -234,11 +243,14 @@ class FunctionalCuttingToolEntity(models.Model):
|
||||
r = requests.post(str_url, json={}, data={'kw': kw, 'token': token}, headers=headers)
|
||||
ret = r.json()
|
||||
if ret.get('code') == 200:
|
||||
logging.info("功能刀具列表每日同步成功")
|
||||
return "功能刀具注册成功"
|
||||
else:
|
||||
logging.info('没有注册功能刀具信息')
|
||||
except Exception as e:
|
||||
logging.info("捕获错误信息:%s" % e)
|
||||
logging.info("功能刀具同步失败:%s" % e)
|
||||
|
||||
|
||||
|
||||
|
||||
class FunctionalToolWarning(models.Model):
|
||||
|
||||
@@ -46,6 +46,8 @@ class ToolMaterial(models.Model):
|
||||
record.scrap_num = scrap_num
|
||||
record.number = usable_num + have_been_used_num + scrap_num
|
||||
|
||||
|
||||
|
||||
@api.model
|
||||
def _read_group_cutting_tool_material_id(self, categories, domain, order):
|
||||
cutting_tool_material_id = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
|
||||
|
||||
Reference in New Issue
Block a user