Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修改机床参数bug

# Conflicts:
#	sf_warehouse/views/shelf_location.xml
This commit is contained in:
qihao.gong@jikimo.com
2024-02-21 16:40:24 +08:00
39 changed files with 1449 additions and 256 deletions

View File

@@ -1,2 +1,3 @@
from . import models
from . import commons
from . import controllers

View File

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

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
import logging
import json
import base64
from odoo import http
from odoo.http import request
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/MaintenanceToolGroups', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_maintenance_tool_groups_Info(self, **kw):
"""
机床刀具组接口
:param kw:
:return:
"""
logging.info('get_maintenance_tool_groups_Info:%s' % kw)
try:
datas = request.httprequest.data
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('DeviceId:%s' % ret)
tool_groups = request.env['sf.tool.groups'].sudo().search([])
res = {'Succeed': True, 'Datas': []}
if tool_groups:
for item in tool_groups:
device_id = ''
for equipment_id in item.equipment_ids:
device_id = '%s,%s' % (device_id, equipment_id.name)
res['Datas'].append({
'GroupName': item.name,
'DeviceId': device_id
})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('get_maintenance_tool_groups_Info error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -256,18 +256,21 @@ class ToolGroups(models.Model):
# ==========机床刀具组接口==========
def _register_tool_groups(self, obj):
create_url = '/AutoDeviceApi/FeedBackOut'
create_url = '/AutoDeviceApi/MaintenanceToolGroups'
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(obj, token, sf_secret_key)
strurl = sf_sync_config['sf_url'] + create_url
device_id = ''
for equipment_id in obj.equipment_ids:
device_id = '%s,%s' % (device_id, equipment_id.name)
name = None
if obj:
for equipment_id in obj.equipment_ids:
device_id = '%s,%s' % (device_id, equipment_id.name)
name = obj.name
val = {
'DeviceId': device_id,
'GroupName': obj.name,
'GroupName': name,
}
kw = json.dumps(val, ensure_ascii=False)
r = requests.post(strurl, json={}, data={'kw': kw, 'token': token}, headers=headers)
@@ -279,7 +282,7 @@ class ToolGroups(models.Model):
# def write(self, vals):
# obj = super().write(vals)
# self._register_tool_groups(obj)
# self._register_tool_groups(self)
# return obj
#
# @api.model_create_multi

View File

@@ -22,6 +22,9 @@ access_sf_processing_technology_admin,sf_processing_technology_admin,model_sf_pr
access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,0
access_sf_supplier_sort_admin,sf_supplier_sort_admin,model_sf_supplier_sort,base.group_system,1,1,1,0
access_sf_production_process_parameter,sf_production_process_parameter,model_sf_production_process_parameter,base.group_user,1,1,1,0
access_sf_production_process_parameter_group_plan_director,sf_production_process_parameter_group_plan_director,model_sf_production_process_parameter,sf_base.group_plan_director,1,0,0,0
access_sf_production_process_parameter_group_purchase_director,sf_production_process_parameter_group_purchase_director,model_sf_production_process_parameter,sf_base.group_purchase_director,1,0,0,0
access_sf_production_process_parameter_group_sale_director,sf_production_process_parameter_group_sale_director,model_sf_production_process_parameter,sf_base.group_sale_director,1,0,0,0
access_sf_production_process_parameter_admin,sf_production_process_parameter_admin,model_sf_production_process_parameter,base.group_system,1,1,1,0
access_sf_production_process_category,sf_production_process_category,model_sf_production_process_category,base.group_user,1,1,1,0
access_sf_production_process_category_admin,sf_production_process_category_admin,model_sf_production_process_category,base.group_system,1,1,1,0
@@ -177,3 +180,9 @@ access_sf_fixture_materials_basic_parameters,sf_fixture_materials_basic_paramete
access_mrp_production_group_sale_salemanager,mrp_production_group_sale_salemanager,mrp.model_mrp_production,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_production_group_sale_director,mrp_production_group_sale_director,mrp.model_mrp_production,sf_base.group_sale_director,1,0,0,0
access_material_apply_group_plan_dispatch,material_apply,model_material_apply,sf_base.group_plan_dispatch,1,0,0,0
access_sf_machine_brand_tags_group_plan_dispatch,sf_machine_brand_tags,model_sf_machine_brand_tags,sf_base.group_plan_dispatch,1,0,0,0
access_ir_actions_act_window_group_plan_dispatch,ir.actions.act_window,base.model_ir_actions_act_window,sf_base.group_plan_dispatch,1,0,0,0
access_ir_actions_act_window_view_group_plan_dispatch,ir.actions.act_window.view,base.model_ir_actions_act_window_view,sf_base.group_plan_dispatch,1,0,0,0
access_sf_supplier_sort_group_plan_dispatch,sf.supplier.sort,model_sf_supplier_sort,sf_base.group_plan_dispatch,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
22 access_sf_supplier_sort sf_supplier_sort model_sf_supplier_sort base.group_user 1 1 1 0
23 access_sf_supplier_sort_admin sf_supplier_sort_admin model_sf_supplier_sort base.group_system 1 1 1 0
24 access_sf_production_process_parameter sf_production_process_parameter model_sf_production_process_parameter base.group_user 1 1 1 0
25 access_sf_production_process_parameter_group_plan_director sf_production_process_parameter_group_plan_director model_sf_production_process_parameter sf_base.group_plan_director 1 0 0 0
26 access_sf_production_process_parameter_group_purchase_director sf_production_process_parameter_group_purchase_director model_sf_production_process_parameter sf_base.group_purchase_director 1 0 0 0
27 access_sf_production_process_parameter_group_sale_director sf_production_process_parameter_group_sale_director model_sf_production_process_parameter sf_base.group_sale_director 1 0 0 0
28 access_sf_production_process_parameter_admin sf_production_process_parameter_admin model_sf_production_process_parameter base.group_system 1 1 1 0
29 access_sf_production_process_category sf_production_process_category model_sf_production_process_category base.group_user 1 1 1 0
30 access_sf_production_process_category_admin sf_production_process_category_admin model_sf_production_process_category base.group_system 1 1 1 0
180
181
182
183
184
185
186
187
188

View File

@@ -33,7 +33,8 @@ class Sf_Bf_Connect(http.Controller):
aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
logging.info('get_bfm_process_or===================================:%s' % order_id.name)
aa.default_code = kw['order_number']
aa.logistics_way = kw['logistics_way']
if kw.get('logistics_way'):
aa.logistics_way = kw['logistics_way']
logging.info('get_bfm_process_order_listaaaaaaaaaaaaaaaaaaaaaaaaaaaa================:%s' % aa.default_code)
for item in bfm_process_order_list:
product = request.env['product.template'].sudo().product_create(product_id, item, order_id,

View File

@@ -21,6 +21,7 @@ class Http(models.AbstractModel):
def _auth_method_sf_token(cls):
# 从headers.environ中获取对方传过来的token,timestamp,加密的校验字符串
datas = request.httprequest.headers.environ
logging.info(datas)
if 'HTTP_TOKEN' in datas:
_logger.info('token:%s' % datas['HTTP_TOKEN'])
# 查询密钥
@@ -40,6 +41,7 @@ class Http(models.AbstractModel):
raise AuthenticationError('请求已过期')
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key)
check_sf_str = hashlib.sha1(check_str.encode('utf-8')).hexdigest()
_logger.info('check_str:%s' % check_sf_str)
if check_sf_str != datas['HTTP_CHECKSTR']:
raise AuthenticationError('数据校验不通过')
else:

View File

@@ -57,7 +57,7 @@ class JdEclp(models.Model):
res = super(JdEclp, self).button_validate()
if self.check_out == 'OUT':
if self.logistics_way != '自提':
if self.logistics_status != '3':
if self.logistics_status != '2':
raise ValidationError('非自提订单,必须先下物流单,并获取物流面单后才可出库!')
return res

View File

@@ -8,14 +8,14 @@
<field name="arch" type="xml">
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
<div>
<h2>bfm环境配置</h2>
<h2>业务平台参数配置</h2>
<div class="row mt16 o_settings_container" id="jd_api">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="bfm_url"/>
<field name="bfm_url"/>
<label for="bfm_url" />
<field name="bfm_url" string="访问地址"/>
</div>
</div>
<!-- </div> -->

View File

@@ -17,6 +17,7 @@
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
'views/mrp_views_menus.xml',
'views/stock_lot_views.xml',
'views/mrp_production_addional_change.xml',
'views/mrp_routing_workcenter_view.xml',
'views/production_line_view.xml',

View File

@@ -20,9 +20,9 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': True, 'Datas': []}
datas = request.httprequest.data
ret = json.loads(datas)
logging.info('RfidCode:%s' % ret)
logging.info('RfidCode:%s' % ret['RfidCode'])
workorder = request.env['mrp.workorder'].sudo().search(
[('production_id.name', '=', 'WH/MO/00071'), ('routing_type', '=', '装夹')])
[('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', '装夹预调')])
if workorder:
for item in workorder:
res['Datas'].append({

View File

@@ -133,6 +133,7 @@ class ResMrpWorkOrder(models.Model):
preset_program_information = fields.Char(string="预调程序信息")
workpiece_delivery_ids = fields.One2many('sf.workpiece.delivery', 'workorder_id', '工件配送')
is_delivery = fields.Boolean('是否配送完成', default=False)
rfid_code = fields.Char('RFID')
@api.onchange('is_ok')
def _onchange_inspection_user_id(self):

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import base64
import qrcode
from collections import defaultdict, namedtuple
import logging
import json
@@ -12,6 +13,7 @@ from odoo.tools import float_compare
from odoo.addons.stock.models.stock_rule import ProcurementException
from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError
from io import BytesIO
class StockRule(models.Model):
@@ -264,6 +266,60 @@ class ProductionLot(models.Model):
return "%s-%s-%03d" % (product.cutting_tool_model_id.code, now, 1)
return "%s-%03d" % (product.name, 1)
qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code')
@api.depends('name')
def _generate_qr_code(self):
for record in self:
# Generate QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(record.name)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
# Encode the image data in base64
image_stream = BytesIO()
qr_image.save(image_stream, format="PNG")
encoded_image = base64.b64encode(image_stream.getvalue())
record.qr_code_image = encoded_image
def print_qr_code(self):
self.ensure_one() # 确保这个方法只为一个记录调用
# if not self.lot_id:
# raise UserError("没有找到序列号。")
# 假设_lot_qr_code方法已经生成了二维码并保存在字段中
qr_code_data = self.qr_code_image
if not qr_code_data:
raise UserError("没有找到二维码数据。")
# 生成下载链接或直接触发下载
# 此处的实现依赖于你的具体需求,以下是触发下载的一种示例
attachment = self.env['ir.attachment'].sudo().create({
'datas': self.qr_code_image,
'type': 'binary',
'description': '二维码图片',
'name': self.name + '.png',
# 'res_id': invoice.id,
# 'res_model': 'stock.picking',
'public': True,
'mimetype': 'application/x-png',
# 'model_name': 'stock.picking',
})
# 返回附件的下载链接
download_url = '/web/content/%s?download=true' % attachment.id
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
return {
'type': 'ir.actions.act_url',
'url': str(base_url) + download_url,
'target': 'self',
}
class StockPicking(models.Model):
_inherit = 'stock.picking'

View File

@@ -3,6 +3,9 @@ access_sf_cnc_processing,sf_cnc_processing,model_sf_cnc_processing,sf_base.group
access_sf_cnc_processing_manager,sf_cnc_processing,model_sf_cnc_processing,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_model_type,sf_model_type,model_sf_model_type,sf_base.group_sf_mrp_user,1,0,0,0
access_sf_model_type_manager,sf_model_type,model_sf_model_type,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_model_type_group_sale_director,sf_model_type_group_sale_director,model_sf_model_type,sf_base.group_sale_director,1,0,0,0
access_sf_model_type_group_purchase_director,sf_model_type_group_purchase_director,model_sf_model_type,sf_base.group_purchase_director,1,0,0,0
access_sf_model_type_group_plan_director,sf_model_type_group_plan_director,model_sf_model_type,sf_base.group_plan_director,1,0,0,0
access_sf_product_model_type_routing_sort,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0
access_sf_product_model_type_routing_sort_manager,sf_product_model_type_routing_sort,model_sf_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_embryo_model_type_routing_sort,sf_embryo_model_type_routing_sort,model_sf_embryo_model_type_routing_sort,sf_base.group_sf_mrp_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_sf_cnc_processing_manager sf_cnc_processing model_sf_cnc_processing sf_base.group_sf_mrp_manager 1 1 1 0
4 access_sf_model_type sf_model_type model_sf_model_type sf_base.group_sf_mrp_user 1 0 0 0
5 access_sf_model_type_manager sf_model_type model_sf_model_type sf_base.group_sf_mrp_manager 1 1 1 0
6 access_sf_model_type_group_sale_director sf_model_type_group_sale_director model_sf_model_type sf_base.group_sale_director 1 0 0 0
7 access_sf_model_type_group_purchase_director sf_model_type_group_purchase_director model_sf_model_type sf_base.group_purchase_director 1 0 0 0
8 access_sf_model_type_group_plan_director sf_model_type_group_plan_director model_sf_model_type sf_base.group_plan_director 1 0 0 0
9 access_sf_product_model_type_routing_sort sf_product_model_type_routing_sort model_sf_product_model_type_routing_sort sf_base.group_sf_mrp_user 1 0 0 0
10 access_sf_product_model_type_routing_sort_manager sf_product_model_type_routing_sort model_sf_product_model_type_routing_sort sf_base.group_sf_mrp_manager 1 1 1 0
11 access_sf_embryo_model_type_routing_sort sf_embryo_model_type_routing_sort model_sf_embryo_model_type_routing_sort sf_base.group_sf_mrp_user 1 0 0 0

View File

@@ -176,6 +176,8 @@
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
<field name="functional_fixture_type_id" force_save="1"
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
<field name="rfid_code"
attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
</group>
<group attrs='{"invisible": [("routing_type","=","获取CNC加工程序")]}'>
<div>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_production_lot_form_generate_qr_code" model="ir.ui.view">
<field name="name">stock.lot.form.quality</field>
<field name="model">stock.lot</field>
<field name="inherit_id" ref="stock.view_production_lot_form"/>
<field name="arch" type="xml">
<xpath expr="//form//sheet//group//group[2]" position="inside">
<field name="qr_code_image" widget="image"/>
</xpath>
<xpath expr="//sheet" position="before">
<header>
<button string="打印二维码" name="print_qr_code" type="object" class="btn-primary"/>
</header>
</xpath>
</field>
</record>
</odoo>

View File

@@ -13,8 +13,8 @@ class ResConfigSettings(models.TransientModel):
token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003')
sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6')
sf_url = fields.Char(string='访问地址', default='https://sf.cs.jikimo.com')
bfm_url = fields.Char(string='业务平台后端访问地址', default='https://bfm.jikimo.com')
agv_url = fields.Char(string='avg访问地址', default='http://IP:PORT/rcms/services/rest')
model_parser_url = fields.Char('特征识别路径')
ftp_host = fields.Char(string='FTP的ip')
ftp_port = fields.Char(string='FTP端口')
ftp_user = fields.Char(string='FTP用户')

View File

@@ -8,8 +8,8 @@
<field name="arch" type="xml">
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
<div>
<h2>同步参数配置</h2>
<div class="row mt16 o_settings_container" id="pay_api">
<h2>云平台参数配置</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
@@ -36,7 +36,7 @@
</div>
<div>
<h2>FTP参数配置</h2>
<div class="row mt16 o_settings_container" id="pay_api">
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
@@ -61,14 +61,14 @@
</div>
</div>
<div>
<h2>业务平台参数配置</h2>
<div class="row mt16 o_settings_container" id="pay_api">
<h2>特征识别参数配置</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="bfm_url" string="访问地址"/>
<field name="bfm_url"/>
<label for="model_parser_url" string="访问地址"/>
<field name="model_parser_url"/>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_production_plan,sf.production.plan,model_sf_production_plan,base.group_user,1,0,0,0
access_sf_production_plan_for_dispatch,sf.production.plan for dispatch,model_sf_production_plan,sf_base.group_plan_dispatch,1,1,1,0
access_sf_production_plan_for_dispatch,sf.production.plan for dispatch,model_sf_production_plan,sf_base.group_plan_dispatch,1,1,0,0
access_sf_action_plan_all_wizard,sf.action.plan.all.wizard,model_sf_action_plan_all_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_plan sf.production.plan model_sf_production_plan base.group_user 1 0 0 0
3 access_sf_production_plan_for_dispatch sf.production.plan for dispatch model_sf_production_plan sf_base.group_plan_dispatch 1 1 1 0 0
4 access_sf_action_plan_all_wizard sf.action.plan.all.wizard model_sf_action_plan_all_wizard base.group_user 1 1 1 1
5

View File

@@ -11,7 +11,7 @@ class QualityCheck(models.Model):
# ==========零件特采接口==========
def _register_tool_groups(self):
create_url = '/AutoDeviceApi/FeedBackOut'
create_url = '/AutoDeviceApi/ModSpecial'
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']

View File

@@ -1,3 +1,6 @@
from . import sale_order
from . import quick_easy_order
from . import auto_quatotion_common
from . import parser_and_calculate_work_time
from . import preload_datas_functions

View File

@@ -0,0 +1,472 @@
import time
# import pandas as pd
from lxml import etree
from collections import Counter
from . import preload_datas_functions as preload
# import preload_datas_functions as preload
class FeatureParser:
"""
解析Feature.xml文件
"""
def __init__(self, xml_file):
self.root = etree.parse(xml_file).getroot()
self.size = self._get_size()
self.holes = self._get_holes()
self.slots = self._get_slot()
self.open_slots = self._get_open_slot()
self.vectors = self._get_vectors()
def _get_size(self):
size = self.root.find('Size')
return {
'length': float(size.get('Length')),
'width': float(size.get('Width')),
'height': float(size.get('Height'))
}
def _get_vectors(self):
vectors = {}
for item in self.root.findall('.//Item'):
vector = item.find('Vector')
if vector is not None:
key = (vector.get('i'), vector.get('j'), vector.get('k'))
vectors[key] = vectors.get(key, 0) + 1
return vectors
def get_vector_counts(self):
return len(self.vectors)
def _get_holes(self):
holes = []
hole_element = self.root.find('Hole')
if hole_element is not None:
for item in self.root.find('Hole').iter('Item'):
hole = {} # 每个hole是一个字典
hole['id'] = int(item.get('ID'))
hole['name'] = item.get('Name')
hole['red'] = int(item.get('Red'))
hole['green'] = int(item.get('Green'))
hole['blue'] = int(item.get('Blue'))
# 处理circles
circles = []
for circle in item.iter('Circle'):
circles.append({
'x': float(circle.get('x')),
'y': float(circle.get('y')),
'z': float(circle.get('z')),
'rad': float(circle.get('rad'))
})
hole['circles'] = circles
# 处理bottom
bottoms = []
for bottom in item.iter('Bottom'):
bottoms.append({
'x': float(bottom.get('x')),
'y': float(bottom.get('y')),
'z': float(bottom.get('z')),
'rad': float(bottom.get('rad'))
})
hole['bottoms'] = bottoms
# 处理vector
for vector in item.iter('Vector'):
hole['vector'] = {
'i': float(vector.get('i')),
'j': float(vector.get('j')),
'k': float(vector.get('k'))
}
# 创建元组并添加到列表中
z_rad_tuples = []
max_z = None
non_zero_rads = set() # 使用set来存储rad值自动去重
for circle in circles:
z = float(circle.get('z'))
rad = float(circle.get('rad'))
if max_z is None or z > max_z:
max_z = z
if rad != 0:
non_zero_rads.add(rad)
for rad in non_zero_rads:
z_rad_tuple = (max_z, rad)
z_rad_tuples.append(z_rad_tuple)
hole['z_rad_tuples'] = z_rad_tuples
holes.append(hole) # 添加到holes列表中
return holes
def _get_slot(self):
"""
获取slot信息
"""
slots = []
slot_a_list = []
slot_element = self.root.find('Slot')
if slot_element is not None:
for item in self.root.find('Slot').iter('Item'):
slot = {}
slot['id'] = int(item.get('ID'))
slot['name'] = item.get('Name')
slot['red'] = int(item.get('Red'))
slot['green'] = int(item.get('Green'))
slot['blue'] = int(item.get('Blue'))
# 获取Volume和Area信息
volume = item.find('Volume')
if volume is not None:
slot['volume'] = float(volume.get('value'))
area = item.find('Area')
if area is not None:
slot['area'] = float(area.get('value'))
slot_a_list.append(slot['area'])
# 处理lines
lines = []
for line in item.iter('Line'):
lines.append({
'type': line.get('Type'), # 'type' : 'line' or 'arc
'x1': float(line.get('x1')),
'y1': float(line.get('y1')),
'z1': float(line.get('z1')),
'x2': float(line.get('x2')),
'y2': float(line.get('y2')),
'z2': float(line.get('z2'))
})
slot['lines'] = lines
# 处理Arc
arcs = []
for arc in item.iter('Arc'):
arcs.append({
'type': arc.get('Type'),
'x1': float(arc.get('x1')),
'y1': float(arc.get('y1')),
'z1': float(arc.get('z1')),
'x2': float(arc.get('x2')),
'y2': float(arc.get('y2')),
'z2': float(arc.get('z2')),
'x3': float(arc.get('x3')),
'y3': float(arc.get('y3')),
'z3': float(arc.get('z3'))
})
slot['arcs'] = arcs
slot['a'] = slot_a_list
slots.append(slot)
return slots
def _get_open_slot(self):
"""
获取open_slot信息
"""
open_slots = []
open_slot_v_list = []
open_slot_element = self.root.find('OpenSlot')
if open_slot_element is not None:
for item in self.root.find('OpenSlot').iter('Item'):
open_slot = {}
open_slot['id'] = int(item.get('ID'))
open_slot['name'] = item.get('Name')
open_slot['red'] = int(item.get('Red'))
open_slot['green'] = int(item.get('Green'))
open_slot['blue'] = int(item.get('Blue'))
# 获取Volume和Area信息
volume = item.find('Volume')
if volume is not None:
open_slot['volume'] = float(volume.get('value'))
area = item.find('Area')
if area is not None:
open_slot['area'] = float(area.get('value'))
# open_slot_v_list.append(round(open_slot['volume'] / open_slot['area'], 3))
open_slot_v_list.append(open_slot['area'])
# 处理lines
lines = []
for line in item.iter('Line'):
lines.append({
'type': line.get('Type'), # 'type' : 'line' or 'arc
'x1': float(line.get('x1')),
'y1': float(line.get('y1')),
'z1': float(line.get('z1')),
'x2': float(line.get('x2')),
'y2': float(line.get('y2')),
'z2': float(line.get('z2'))
})
open_slot['lines'] = lines
# 处理Arc
arcs = []
for arc in item.iter('Arc'):
arcs.append({
'type': arc.get('Type'),
'x1': float(arc.get('x1')),
'y1': float(arc.get('y1')),
'z1': float(arc.get('z1')),
'x2': float(arc.get('x2')),
'y2': float(arc.get('y2')),
'z2': float(arc.get('z2')),
'x3': float(arc.get('x3')),
'y3': float(arc.get('y3')),
'z3': float(arc.get('z3'))
})
open_slot['arcs'] = arcs
open_slot['v'] = open_slot_v_list
open_slots.append(open_slot)
return open_slots
def hole_time(parser):
"""
计算孔的工时
:return:
"""
# 判断是否有孔
if parser.holes is not None:
# 遍历所有的孔,获取孔径和孔深度,然后调用函数查询工时
hole_total_time = 0
nums = 1
expand_hole = ''
j_time = 0
j_hole_nums = 0
hole_nums = 0
for hole in parser.holes:
for z_rad_tuple in hole['z_rad_tuples']:
if (2 * z_rad_tuple[1] * z_rad_tuple[0] <= 3750) and (2 * z_rad_tuple[1] <= 25):
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = preload.get_suitable_hole_working_hours(preload.df_hole_duration,
2 * z_rad_tuple[1],
z_rad_tuple[0])
# if per_time_minute is None:
# raise Exception('孔径为%s深度为%s的孔没有找到对应的工时' % (2 * z_rad_tuple[1], z_rad_tuple[0]))
# print('per_time_minute', per_time_minute)
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif (2 * z_rad_tuple[1] * z_rad_tuple[0] <= 3750) and (2 * z_rad_tuple[1] > 25):
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.0003 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif 3750 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 50000:
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.0003 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif 50000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 100000:
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.00018 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif 100000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 150000:
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.00016 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif 150000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 200000:
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.00015 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
elif 200000 < 2 * z_rad_tuple[1] * z_rad_tuple[0] <= 250000:
hole_nums += 1
# print('z_rad_tuple', z_rad_tuple)
per_time_minute = 0.0002 * 2 * z_rad_tuple[1] * z_rad_tuple[0]
expand_hole_end = 0.6 if expand_hole == '' else 1
per_time = (per_time_minute * 1 * expand_hole_end + j_time * j_hole_nums * expand_hole_end) / 60
hole_total_time += per_time
else:
raise Exception('孔径为%s,深度为%s的孔没有找到对应的工时' % (2 * z_rad_tuple[1], z_rad_tuple[0]))
print('孔工时', round(hole_total_time * nums * 2) / 2)
print('共有%s个孔,其中%s为台阶孔' % (len(parser.holes), hole_nums - len(parser.holes)))
return round(hole_total_time * nums * 2) / 2
else:
return 0
def slot_time(parser):
# 判断是否有槽
if parser.slots is not None:
# 遍历所有的槽,获取槽的长度,然后调用函数查询工时
slot_total_time = 0
nums = 1
finish_time = 0
process_time = 0
process_total_time = 0
slot_a = parser.slots[0]['a']
slot_a_counter = Counter(slot_a)
slot_a_counter_result = dict(slot_a_counter)
for i in slot_a_counter_result:
for slot in parser.slots:
if slot['area'] == i:
# # 计算长度第一条线和第三条线的X轴距离
# length = abs(slot['lines'][0]['y1'] - slot['lines'][2]['y1'])
# # 计算宽度第一条线和第二条线的Y轴距离
# width = abs(slot['lines'][1]['x1'] - slot['lines'][3]['x1'])
# # 计算面积
# area = length * width
# 槽深度
depth = round(slot['volume'] / slot['area'], 3)
# 沟通刀具暂定为12
finish_tool_diameter = 12
if 200 < slot['area'] <= 5000:
finish_time = 0
# 加工穴数待定取得每一个槽的穴数和装夹次数那这个数量目前暂时按1来算待有统计数据之后再说
rough_part_nums = slot_a_counter_result[slot['area']]
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
# 调用函数计算槽的工时
slot_total_time = preload.get_suitable_rough_working_hours(preload.df_rough_duration, depth)
# if nums > 20:
# process_time = round(
# (nums * 0.6 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# elif nums > 10:
# process_time = round(
# (nums * 0.7 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# elif nums > 6:
# process_time = round(
# (nums * 0.8 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# elif nums > 4:
# process_time = round(
# (nums * 0.9 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# elif nums > 1:
# process_time = round(
# (nums * 0.95 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# else:
# process_time = round(
# (nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
# finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
process_time = round(
(nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# print('slot_total_time', slot_total_time)
elif slot['area'] <= 200:
slot_total_time = 0
rough_part_nums = slot_a_counter_result[slot['area']]
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
# 调用函数计算槽的工时
finish_time = preload.get_suitable_finish_working_hours(preload.df_finish_duration, depth,
finish_tool_diameter)
process_time = round(
(nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# print('finish_time', finish_time)
else:
rough_part_nums = slot_a_counter_result[slot['area']]
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
process_time = round(
(nums * 1 * ((0.00016 * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
process_total_time += process_time
print('槽工时', process_total_time)
return process_total_time
else:
return 0
def open_slot_time(parser):
# 判断是否有开口槽
if parser.open_slots is not None:
# 遍历所有的开口槽,获取槽宽和槽长,然后调用函数查询工时
open_slot_total_time = 0
nums = 1
finish_time = 0
open_slot_process_time = 0
open_slot_v = parser.open_slots[0]['v']
counter = Counter(open_slot_v)
result = dict(counter)
transiant_time = 0
for i in result:
for open_slot in parser.open_slots:
if open_slot['area'] == i:
depth = round(open_slot['volume'] / open_slot['area'], 3)
# 沟通刀具暂定为12
finish_tool_diameter = 12
if 200 < open_slot['area'] <= 5000:
finish_time = 0
# 加工穴数待定取得每一个槽的穴数和装夹次数那这个数量目前暂时按1来算待有统计数据之后再说
rough_part_nums = result[open_slot['area']]
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
# 调用函数计算槽的工时
slot_total_time = preload.get_suitable_rough_working_hours(preload.df_rough_duration, depth)
open_slot_process_time = round(
(nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# print('slot_total_time', slot_total_time)
elif open_slot['area'] <= 200:
slot_total_time = 0
rough_part_nums = 1
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
# 调用函数计算槽的工时
finish_time = preload.get_suitable_finish_working_hours(preload.df_finish_duration, depth,
finish_tool_diameter)
open_slot_process_time = round(
(nums * 1 * ((slot_total_time * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
# print('finish_time', finish_time)
else:
rough_part_nums = 1
rough_clamping_times = 1
finishi_part_nums = 1
finish_clamping_times = 1
open_slot_process_time = round(
(nums * 1 * ((0.00016 * rough_part_nums + rough_clamping_times * 20) + (
finish_time * finishi_part_nums + finish_clamping_times * 25)) / 60) * 2) / 2
transiant_time += open_slot_process_time
print('开口槽工时', transiant_time)
return transiant_time
else:
return 0
if __name__ == '__main__':
time1 = time.time()
parser = FeatureParser(
'D:\\ccccccccccccccccccccccccccccccccccccccccccccccc\\aa\\JKM001-260.200.30_FeatureTable.xml')
# print('parser', parser.holes)
# print('parser.slots', parser.slots)
# print('parser.open_slots', parser.open_slots)
print('总工时', hole_time(parser) + slot_time(parser) + open_slot_time(parser))
time2 = time.time()
print('耗时:', time2 - time1)

View File

@@ -0,0 +1,201 @@
import psycopg2
# import pandas as pd
def load_and_convert_data(table_name, column_names):
connection = None
data = []
try:
# connection = psycopg2.connect(user="odoo",
# password="odoo",
# host="localhost",
# port="5432",
# database="www1")
connection = psycopg2.connect(user="odoo",
password="odoo",
host="120.76.195.146",
port="15432",
database="bfm_dev1")
cursor = connection.cursor()
# Construct the query string using the table name passed in
query = f"SELECT {', '.join(column_names)} FROM {table_name};"
cursor.execute(query)
# Fetch all rows from cursor
data = cursor.fetchall()
except (Exception, psycopg2.Error) as error:
print("Error fetching data from PostgreSQL table", error)
finally:
# Always close database connection after work done
if (connection):
cursor.close()
connection.close()
# Convert the list of tuples to DataFrame
# df = pd.DataFrame(data, columns=column_names)
#
# # Convert all string columns to float
# for col in df.columns:
# if df[col].dtype == 'object':
# df[col] = df[col].astype(float)
return 'df'
def get_suitable_hole_working_hours(df, target_diameter, target_depth):
"""
从钻孔、铰孔数据中获取符合要求的最小工时
"""
# df为输入的数据target_diameter为目标孔径target_depth为目标孔深
df_diameter_filtered = df.loc[df['hole_diameter'] >= target_diameter]
if not df_diameter_filtered.empty:
min_diameter = df_diameter_filtered['hole_diameter'].min()
df_depth_filtered = df_diameter_filtered.loc[
(df_diameter_filtered['hole_diameter'] == min_diameter) & (
df_diameter_filtered['hole_depth'] >= target_depth)]
if not df_depth_filtered.empty:
min_depth_row = df_depth_filtered.loc[df_depth_filtered['hole_depth'].idxmin()]
min_working_hours = min_depth_row['working_hours']
return min_working_hours
else:
print("No records found where hole_depth is bigger than the target depth")
return None
else:
print("No records found where hole_diameter is bigger than the target diameter")
return None
def get_suitable_blank_working_hours(df, blank_height, blank_length, blank_width):
"""
从毛坯数据中获取符合要求的最小工时
"""
# df为输入的数据blank_height为目标毛坯高度blank_length为目标毛坯长度blank_width为目标毛坯宽度
df_height_filtered = df.loc[df['blank_height'] >= blank_height]
if not df_height_filtered.empty:
min_height = df_height_filtered['blank_height'].min()
df_length_filtered = df_height_filtered.loc[
(df_height_filtered['blank_height'] == min_height) & (
df_height_filtered['blank_length'] >= blank_length)]
if not df_length_filtered.empty:
min_length_row = df_length_filtered.loc[df_length_filtered['blank_length'].idxmin()]
min_working_hours = min_length_row['working_hours']
return min_working_hours
else:
print("No records found where blank_length is bigger than the target length")
return None
else:
print("No records found where blank_height is bigger than the target height")
return None
def get_suitable_rough_working_hours(df, rough_depth):
"""
从粗加工数据中获取符合要求的最小工时
"""
# df为输入的数据rough_depth为目标粗加工深度
df_depth_filtered = df.loc[df['rough_depth'] >= rough_depth]
if not df_depth_filtered.empty:
min_depth_row = df_depth_filtered.loc[df_depth_filtered['rough_depth'].idxmin()]
min_working_hours = min_depth_row['working_hours']
return min_working_hours
else:
print("No records found where rough_depth is bigger than the target depth")
return None
def get_suitable_finish_working_hours(df, finish_depth, finish_tool_diameter):
"""
从精加工数据中获取符合要求的最小工时
"""
# df为输入的数据finish_depth为目标精加工深度finish_tool_diameter为目标精加工刀具直径
df_depth_filtered = df.loc[df['finish_depth'] >= finish_depth]
if not df_depth_filtered.empty:
min_depth = df_depth_filtered['finish_depth'].min()
df_tool_diameter_filtered = df_depth_filtered.loc[
(df_depth_filtered['finish_depth'] == min_depth) & (
df_depth_filtered['finish_tool_diameter'] >= finish_tool_diameter)]
if not df_tool_diameter_filtered.empty:
min_tool_diameter_row = df_tool_diameter_filtered.loc[
df_tool_diameter_filtered['finish_tool_diameter'].idxmin()]
min_working_hours = min_tool_diameter_row['working_hours']
return min_working_hours
else:
print("No records found where finish_tool_diameter is bigger than the target tool diameter")
return None
else:
print("No records found where finish_depth is bigger than the target depth")
return None
def get_suitable_chamfer_working_hours(df, chamfer_length, chamfer_size):
"""
根据倒角长度获得倒角工时装夹平面耗时clamping_type_plane和装夹斜面耗时clamping_type_slope
"""
# df为输入的数据chamfer_length为目标倒角长度clamping_type_plane为目标装夹平面耗时clamping_type_slope为目标装夹斜面耗时
df_length_filtered = df.loc[df['chamfer_length'] >= chamfer_length]
df_chamfer_size_filtered = df.loc[df['chamfer_size'] >= chamfer_size]
if not df_length_filtered.empty and not df_chamfer_size_filtered.empty:
min_length_row = df_length_filtered.loc[df_length_filtered['chamfer_length'].idxmin()]
min_chamfer_size_row = df_chamfer_size_filtered.loc[df_chamfer_size_filtered['chamfer_size'].idxmin()]
clamping_time = min_length_row['clamping_time']
clamping_type_plane = min_length_row['clamping_type_plane']
clamping_type_slope = min_length_row['clamping_type_slope']
coefficient = min_chamfer_size_row['coefficient']
return clamping_time, clamping_type_plane, clamping_type_slope, coefficient
else:
print("No records found where chamfer_length is bigger than the target length")
return None
df_hole_duration = load_and_convert_data('hole_duration', ['hole_diameter', 'hole_depth', 'working_hours'])
df_j_hole_duration = load_and_convert_data('j_hole_duration', ['hole_diameter', 'hole_depth', 'working_hours'])
df_chamfer_duration = load_and_convert_data('chamfer_duration',
['chamfer_length', 'clamping_time', 'chamfer_size', 'coefficient',
'clamping_type_plane', 'clamping_type_slope'])
df_blank_duration = load_and_convert_data('blank_duration',
['blank_length', 'blank_width', 'blank_height', 'working_hours'])
df_rough_duration = load_and_convert_data('rough_duration', ['rough_depth', 'working_hours'])
df_finish_duration = load_and_convert_data('finish_duration',
['finish_depth', 'finish_tool_diameter', 'working_hours'])
if __name__ == '__main__':
min_working_hours = get_suitable_hole_working_hours(df_hole_duration, 24, 150)
print('min_working_hours', min_working_hours)
min_j_working_hours = get_suitable_hole_working_hours(df_j_hole_duration, 10, 15)
print('min_j_working_hours', min_j_working_hours)
min_blank_working_hours = get_suitable_blank_working_hours(df_blank_duration, 150, 300, 300)
print('min_blank_working_hours', min_blank_working_hours)
min_rough_working_hours = get_suitable_rough_working_hours(df_rough_duration, 49)
print('min_rough_working_hours', min_rough_working_hours)
min_finish_working_hours = get_suitable_finish_working_hours(df_finish_duration, 0.5, 10)
print('min_finish_working_hours', min_finish_working_hours)
clamping_time, clamping_type_plane, clamping_type_slope, coefficient = get_suitable_chamfer_working_hours(
df_chamfer_duration, 10, 1.5)
print('clamping_time', clamping_time)
print('clamping_type_plane', clamping_type_plane)
print('clamping_type_slope', clamping_type_slope)
print('coefficient', coefficient)

View File

@@ -2,17 +2,19 @@ import logging
import base64
import hashlib
import os
import platform
import json
from datetime import datetime
import requests
from OCC.Extend.DataExchange import read_step_file
from OCC.Extend.DataExchange import write_stl_file
from odoo import http
from odoo.http import request
# from OCC.Extend.DataExchange import read_step_file
# from OCC.Extend.DataExchange import write_stl_file
from odoo import models, fields, api
from odoo.modules import get_resource_path
from odoo.exceptions import ValidationError, UserError
from odoo.addons.sf_base.commons.common import Common
from . import parser_and_calculate_work_time as pc
@@ -34,8 +36,8 @@ class QuickEasyOrder(models.Model):
('0.03', '±0.03mm'),
('0.02', '±0.02mm'),
('0.01', '±0.01mm')], string='加工精度', default='0.10')
material_id = fields.Many2one('sf.production.materials', '材料', compute='_compute_material_model', store=True)
material_model_id = fields.Many2one('sf.materials.model', '型号', compute='_compute_material_model', store=True)
material_id = fields.Many2one('sf.production.materials', '材料')
material_model_id = fields.Many2one('sf.materials.model', '型号')
# process_id = fields.Many2one('sf.production.process', string='表面工艺')
parameter_ids = fields.Many2many('sf.production.process.parameter', 'process_item_order_rel', string='可选参数')
quantity = fields.Integer('数量', default=1)
@@ -77,11 +79,11 @@ class QuickEasyOrder(models.Model):
if len(item[2]) > 0:
logging.info('create-attachment:%s' % int(item[2][0]))
attachment = self.env['ir.attachment'].sudo().search([('id', '=', int(item[2][0]))])
base64_data = base64.b64encode(attachment.datas)
base64_datas = base64_data.decode('utf-8')
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
report_path = attachment._full_path(attachment.store_fname)
vals['model_file'] = self.transition_glb_file(report_path, model_code)
# base64_data = base64.b64encode(attachment.datas)
# base64_datas = base64_data.decode('utf-8')
# model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
# report_path = attachment._full_path(attachment.store_fname)
vals['model_file'] = self.model_analyze(attachment)
# logging.info('create-model_file:%s' % len(vals['model_file']))
obj = super(QuickEasyOrder, self).create(vals)
@@ -91,6 +93,147 @@ class QuickEasyOrder(models.Model):
obj.state = '待接单'
return obj
def model_analyze(self,model_attachment):
"""
step模型解析上传模型时转为web可显示的格式
:return:
"""
config = request.env['res.config.settings'].sudo().get_values()
try:
# 获取当前操作系统
os_name = platform.system()
for item in model_attachment:
# 将拿到的3D模型数据存入文件
# 定义文件名和文件的二进制内容
file_name = item.name # 请将这里替换为你的文件名
print('file_name', file_name)
# base64_data = base64.b64encode(item.datas)
# base64_datas = base64_data.decode('utf-8')
binary_content = item.datas # 请将这里替换为你的文件的二进制内容
# binary_content从字符串转为二进制
binary_content = base64.b64decode(binary_content)
# 定义新的文件夹路径
# 根据操作系统不同,文件路径不同
path_header = '/model_parser' if os_name == 'Linux' else 'D:/model_analysis'
# new_folder_path = 'D:/11111final' + '/' + item['name'].split(".")[0]
new_folder_path = path_header + '/' + item.name.rpartition('.')[0]
print('new_folder_path', new_folder_path)
# 检查新的文件夹是否存在,如果不存在,则创建
if not os.path.exists(new_folder_path):
os.makedirs(new_folder_path)
# 定义新的文件路径
new_file_path = os.path.join(new_folder_path, file_name)
# 将二进制内容写入新的文件
with open(new_file_path, 'wb') as f:
f.write(binary_content)
# 检查文件是否已经成功写入
if os.path.exists(new_file_path):
print(f'Successfully wrote binary content to {new_file_path}')
else:
print(f'Failed to write binary content to {new_file_path}')
# 附件
# attachment = request.env['ir.attachment'].sudo().create({
# 'datas': item['data'].encode('utf-8'),
# 'type': 'binary',
# 'description': '模型文件',
# 'name': item['name'],
# 'public': True,
# 'model_name': item['name'],
# })
headers = {'Content-Type': 'application/json'}
# 调用写入宿主机接口
# url_dir = 'http://192.168.50.202:8000/create_and_write_file'
url_dir = config['model_parser_url'] + '/create_and_write_file'
data = {
'folder_path': new_folder_path, # 您想要创建的文件夹路径
'file_path': new_file_path, # 您想要创建的文件名
'content': item['data'] # 您想要写入文件的内容
}
requests.post(url_dir, json=data, headers=headers)
# 调用特征包接口
url = config['model_parser_url'] + '/process_file'
payload = {
'file_path': new_file_path,
'dest_path': new_folder_path,
'back_url': config['bfm_url']
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
print("Request was successful.")
print("Response: ", response.json())
else:
print("Request failed.")
# 特征识别
xml_path = new_folder_path + '/' + item.name.rpartition('.')[0] + '_FeatrueTable.XML'
print('xml_path', xml_path)
parser_obj = pc.FeatureParser(xml_path)
print('parser_obj', parser_obj)
slot = parser_obj.slots
print('slot', slot)
hole = parser_obj.holes
print('hole', hole)
size = parser_obj.size
print('size', size)
open_slot = parser_obj.open_slots
print('open_slot', open_slot)
vector = parser_obj.vectors
print('vector', vector)
print('all parcer', size)
try:
hole_time = pc.hole_time(parser_obj)
print('hole_time', hole_time)
except Exception as e:
return json.dumps({'code': 400, 'msg': '孔尺寸超限', 'error_msg': str(e)})
try:
slot_time = pc.slot_time(parser_obj)
print('slot_time', slot_time)
except Exception as e:
return json.dumps({'code': 400, 'msg': '槽尺寸超限', 'error_msg': str(e)})
try:
open_slot_time = pc.open_slot_time(parser_obj)
print('open_slot_time', open_slot_time)
except Exception as e:
return json.dumps({'code': 400, 'msg': '开口槽尺寸超限', 'error_msg': str(e)})
total_time = hole_time + slot_time + open_slot_time
print(hole_time, slot_time, open_slot_time)
print('total_time', total_time)
ret = {'feature_infos': [{'name': 'all_feature', 'type': '', 'process_time': total_time}],
'boxshape': size, 'slugX': 10.0, 'slugY': 90.0, 'slugZ': 42.0,
'turn_over_times': 2,
'target_faces': ['A', 'B']}
self.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False)
self.model_length = size['length'] # 长 单位mm
self.model_width = size['width'] # 宽
self.model_height = size['height'] # 高
self.model_volume = size['length'] * size['width'] * size['height']
# 附件处理
base64_data = base64.b64encode(item.datas)
base64_datas = base64_data.decode('utf-8')
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
# 读取文件
shapes = read_step_file(new_file_path)
output_file = os.path.join(new_folder_path, str(model_code) + '.stl')
write_stl_file(shapes, output_file, 'binary', 0.03, 0.5)
# 转化为glb
output_glb_file = os.path.join(new_folder_path, str(model_code) + '.glb')
util_path = get_resource_path('jikimo_gateway_api', 'static/util')
# 根据操作系统确定使用 'python' 还是 'python3'
python_cmd = 'python3' if os_name == 'Linux' else 'python'
print('python_cmd', python_cmd)
print('os_name', os_name)
# 使用引号包围路径
cmd = '%s "%s/stl2gltf.py" "%s" "%s" -b' % (python_cmd, util_path, output_file, output_glb_file)
logging.info(cmd)
os.system(cmd)
# 转base64
with open(output_glb_file, 'rb') as fileObj:
image_data = fileObj.read()
base64_data = base64.b64encode(image_data)
return base64_data
except Exception as e:
return UserError('模型自动报价失败,请联系管理员')
# 将attach的datas内容转为glb文件
def transition_glb_file(self, report_path, model_code):
shapes = read_step_file(report_path)
@@ -116,24 +259,7 @@ class QuickEasyOrder(models.Model):
raise ValidationError('只允许上传一个文件')
if item.upload_model_file:
file_attachment_id = item.upload_model_file[0]
# 附件路径
report_path = file_attachment_id._full_path(file_attachment_id.store_fname)
logging.info("模型路径: %s" % report_path)
base64_data = base64.b64encode(file_attachment_id.datas)
base64_datas = base64_data.decode('utf-8')
model_code = hashlib.sha1(base64_datas.encode('utf-8')).hexdigest()
logging.info("模型编码: %s" % model_code)
item.model_file = self.transition_glb_file(report_path, model_code)
ret = self.feature_recognition(report_path, model_code)
logging.info("自动报价返回值: %s" % ret)
boxshape = ret['boxshape'].tolist()
logging.info("自动报价boxshape: %s" % boxshape)
logging.info('自动报价feature_infos:%s' % ret['feature_infos'])
item.model_length = boxshape[0] # 长 单位mm
item.model_width = boxshape[1] # 宽
item.model_height = boxshape[2] # 高
item.model_volume = boxshape[0] * boxshape[1] * boxshape[2]
item.model_feature = json.dumps(ret['feature_infos'], ensure_ascii=False)
item.model_file = self.model_analyze(file_attachment_id)
self._get_price(item)
else:
item.model_file = False

View File

@@ -1,5 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_quick_easy_order,quick_easy_order,model_quick_easy_order,base.group_system,1,1,1,1
access_quick_easy_order,quick_easy_order,model_quick_easy_order,base.group_system,1,1,1,0
access_quick_easy_order_group_sale_salemanager,quick_easy_order_group_sale_salemanager,model_quick_easy_order,sf_base.group_sale_salemanager,1,1,1,0
access_quick_easy_order_group_sale_director,quick_easy_order_group_sale_director,model_quick_easy_order,sf_base.group_sale_director,1,1,1,0
access_sf_auto_quatotion_common,sf_auto_quatotion_common,model_sf_auto_quatotion_common,base.group_system,1,1,1,1
access_sale_order_manager,sale_order_manager,model_sale_order,sf_base.group_sale_salemanager,1,1,1,0
access_sale_order_director,sale_order_director,model_sale_order,sf_base.group_sale_director,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_quick_easy_order quick_easy_order model_quick_easy_order base.group_system 1 1 1 1 0
3 access_quick_easy_order_group_sale_salemanager quick_easy_order_group_sale_salemanager model_quick_easy_order sf_base.group_sale_salemanager 1 1 1 0
4 access_quick_easy_order_group_sale_director quick_easy_order_group_sale_director model_quick_easy_order sf_base.group_sale_director 1 1 1 0
5 access_sf_auto_quatotion_common sf_auto_quatotion_common model_sf_auto_quatotion_common base.group_system 1 1 1 1
6 access_sale_order_manager sale_order_manager model_sale_order sf_base.group_sale_salemanager 1 1 1 0
7 access_sale_order_director sale_order_director model_sale_order sf_base.group_sale_director 1 1 1 0

View File

@@ -47,7 +47,7 @@
</h1>
<group>
<group>
<field name="customer_id" readonly="1" force_save="1"/>
<field name="customer_id" />
<field name="material_id"/>
<field name="material_model_id"/>
<!-- <field name="process_id"/>-->

View File

@@ -8,11 +8,11 @@ from odoo.http import request
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackOut', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/MachineToolLibrary', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_equipment_tool_Info(self, **kw):
"""
机床当前刀库实时信息
机床刀库实时信息
:param kw:
:return:
"""
@@ -31,6 +31,7 @@ class Manufacturing_Connect(http.Controller):
for equipment_tool_id in item.product_template_ids:
functional_tool_id = self.env['sf.functional.cutting.tool.entity'].sudo().search(
[('code', '=', equipment_tool_id.tool_code)])
alarm_time = None
if functional_tool_id.functional_tool_status == '报警':
alarm_time = self.env['sf.functional.tool.warning'].sudo().search(
@@ -54,3 +55,32 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('get_equipment_tool_Info error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/ToolGroups', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_functional_tool_groups_Info(self, **kw):
"""
刀具组接口
:param kw:
:return:
"""
logging.info('get_functional_tool_groups_Info:%s' % kw)
try:
datas = request.httprequest.data
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('DeviceId:%s' % ret)
functional_tools = request.env['sf.functional.cutting.tool.entity'].sudo().search([])
res = {'Succeed': True, 'Datas': []}
if functional_tools:
for item in functional_tools:
res['Datas'].append({
'GroupName': item.tool_groups_id.name,
'ToolId': item.code,
'ToolName': item.name
})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('get_functional_tool_groups_Info error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -180,7 +180,7 @@ class FunctionalCuttingToolEntity(models.Model):
# ==========刀具组接口==========
def _register_functional_tool_groups(self, obj):
create_url = '/AutoDeviceApi/FeedBackOut'
create_url = '/AutoDeviceApi/ToolGroups'
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
@@ -956,18 +956,6 @@ class FunctionalToolAssembly(models.Model):
return functional_tool
return False
def automated_assembly(self):
"""
todo 自动组装
:return:
"""
def automatic_printing_of_QR_code(self):
"""
todo 自动打印二维码
:return:
"""
def assemble_single_print(self):
"""
todo 组装单打印

View File

@@ -11,7 +11,7 @@ class CNCprocessing(models.Model):
# ==========MES装刀指令接口==========
def register_cnc_processing(self, cnc_processing):
create_url = '/AutoDeviceApi/FeedBackOut'
create_url = '/AutoDeviceApi/MESToolLoadingInstruction'
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']

View File

@@ -1,18 +1,18 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_functional_cutting_tool_entity,sf.functional.cutting.tool.entity,model_sf_functional_cutting_tool_entity,sf_base.group_sf_tool_user,1,1,1,1
access_sf_functional_tool_warning,sf.functional.tool.warning,model_sf_functional_tool_warning,sf_base.group_sf_tool_user,1,1,1,1
access_sf_real_time_distribution_of_functional_tools,sf.real.time.distribution.of.functional.tools,model_sf_real_time_distribution_of_functional_tools,sf_base.group_sf_tool_user,1,1,1,1
access_sf_functional_cutting_tool_entity,sf.functional.cutting.tool.entity,model_sf_functional_cutting_tool_entity,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_warning,sf.functional.tool.warning,model_sf_functional_tool_warning,sf_base.group_sf_tool_user,1,1,1,0
access_sf_real_time_distribution_of_functional_tools,sf.real.time.distribution.of.functional.tools,model_sf_real_time_distribution_of_functional_tools,sf_base.group_sf_tool_user,1,1,1,0
access_sf_cam_work_order_program_knife_plan,sf.cam.work.order.program.knife.plan,model_sf_cam_work_order_program_knife_plan,sf_base.group_sf_tool_user,1,1,1,1
access_sf_machine_table_tool_changing_apply,sf.machine.table.tool.changing.apply,model_sf_machine_table_tool_changing_apply,sf_base.group_sf_tool_user,1,1,1,1
access_sf_cam_work_order_program_knife_plan,sf.cam.work.order.program.knife.plan,model_sf_cam_work_order_program_knife_plan,sf_base.group_sf_tool_user,1,1,1,0
access_sf_machine_table_tool_changing_apply,sf.machine.table.tool.changing.apply,model_sf_machine_table_tool_changing_apply,sf_base.group_sf_tool_user,1,1,1,0
access_sf_tool_change_requirement_information,sf.tool.change.requirement.information,model_sf_tool_change_requirement_information,sf_base.group_sf_tool_user,1,1,1,1
access_sf_tool_transfer_request_information,sf.tool.transfer.request.information,model_sf_tool_transfer_request_information,sf_base.group_sf_tool_user,1,1,1,1
access_sf_tool_change_requirement_information,sf.tool.change.requirement.information,model_sf_tool_change_requirement_information,sf_base.group_sf_tool_user,1,1,1,0
access_sf_tool_transfer_request_information,sf.tool.transfer.request.information,model_sf_tool_transfer_request_information,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_assembly,sf.functional.tool.assembly,model_sf_functional_tool_assembly,sf_base.group_sf_tool_user,1,1,1,1
access_sf_functional_tool_assembly_order,sf.functional.tool.assembly.order,model_sf_functional_tool_assembly_order,sf_base.group_sf_tool_user,1,1,1,1
access_sf_tool_material_search,sf.tool.material.search,model_sf_tool_material_search,sf_base.group_sf_tool_user,1,1,1,1
access_sf_functional_tool_assembly,sf.functional.tool.assembly,model_sf_functional_tool_assembly,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_assembly_order,sf.functional.tool.assembly.order,model_sf_functional_tool_assembly_order,sf_base.group_sf_tool_user,1,1,1,0
access_sf_tool_material_search,sf.tool.material.search,model_sf_tool_material_search,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_cutting_tool_entity_group_plan_dispatch,sf.functional.cutting.tool.entity,model_sf_functional_cutting_tool_entity,sf_base.group_plan_dispatch,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_functional_cutting_tool_entity sf.functional.cutting.tool.entity model_sf_functional_cutting_tool_entity sf_base.group_sf_tool_user 1 1 1 1 0
3 access_sf_functional_tool_warning sf.functional.tool.warning model_sf_functional_tool_warning sf_base.group_sf_tool_user 1 1 1 1 0
4 access_sf_real_time_distribution_of_functional_tools sf.real.time.distribution.of.functional.tools model_sf_real_time_distribution_of_functional_tools sf_base.group_sf_tool_user 1 1 1 1 0
5 access_sf_cam_work_order_program_knife_plan sf.cam.work.order.program.knife.plan model_sf_cam_work_order_program_knife_plan sf_base.group_sf_tool_user 1 1 1 1 0
6 access_sf_machine_table_tool_changing_apply sf.machine.table.tool.changing.apply model_sf_machine_table_tool_changing_apply sf_base.group_sf_tool_user 1 1 1 1 0
7 access_sf_tool_change_requirement_information sf.tool.change.requirement.information model_sf_tool_change_requirement_information sf_base.group_sf_tool_user 1 1 1 1 0
8 access_sf_tool_transfer_request_information sf.tool.transfer.request.information model_sf_tool_transfer_request_information sf_base.group_sf_tool_user 1 1 1 1 0
9 access_sf_functional_tool_assembly sf.functional.tool.assembly model_sf_functional_tool_assembly sf_base.group_sf_tool_user 1 1 1 1 0
10 access_sf_functional_tool_assembly_order sf.functional.tool.assembly.order model_sf_functional_tool_assembly_order sf_base.group_sf_tool_user 1 1 1 1 0
11 access_sf_tool_material_search sf.tool.material.search model_sf_tool_material_search sf_base.group_sf_tool_user 1 1 1 1 0
12 access_sf_functional_cutting_tool_entity_group_plan_dispatch sf.functional.cutting.tool.entity model_sf_functional_cutting_tool_entity sf_base.group_plan_dispatch 1 0 0 0
13 access_sf_functional_tool_warning_group_plan_dispatch sf.functional.tool.warning model_sf_functional_tool_warning sf_base.group_plan_dispatch 1 0 0 0
14 access_sf_real_time_distribution_of_functional_tools_group_plan_dispatch sf.real.time.distribution.of.functional.tools model_sf_real_time_distribution_of_functional_tools sf_base.group_plan_dispatch 1 0 0 0
15 access_sf_cam_work_order_program_knife_plan_group_plan_dispatch sf.cam.work.order.program.knife.plan model_sf_cam_work_order_program_knife_plan sf_base.group_plan_dispatch 1 0 0 0
16 access_sf_machine_table_tool_changing_apply_group_plan_dispatch sf.machine.table.tool.changing.apply model_sf_machine_table_tool_changing_apply sf_base.group_plan_dispatch 1 0 0 0
17 access_sf_tool_change_requirement_information_group_plan_dispatch sf.tool.change.requirement.information model_sf_tool_change_requirement_information sf_base.group_plan_dispatch 1 0 0 0
18 access_sf_tool_transfer_request_information_group_plan_dispatch sf.tool.transfer.request.information model_sf_tool_transfer_request_information sf_base.group_plan_dispatch 1 0 0 0

View File

@@ -967,10 +967,10 @@
}"
attrs="{'invisible': [('assemble_status', '!=', '0')]}" groups="sf_base.group_sf_mrp_user"
class="btn-primary"/>
<button string="组装单打印" name="assemble_single_print" type="object"
groups="sf_base.group_sf_mrp_user"
attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"
confirm="是否确认打印组装单"/>
<!-- <button string="组装单打印" name="assemble_single_print" type="object"-->
<!-- groups="sf_base.group_sf_mrp_user"-->
<!-- attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"-->
<!-- confirm="是否确认打印组装单"/>-->
</tree>
</field>
</record>
@@ -1006,14 +1006,10 @@
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
class="btn-primary"/>
<button string="打印二维码" name="automatic_printing_of_QR_code" type="object"
groups="sf_base.group_sf_mrp_user"
attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"
confirm="是否确认打印二维码"/>
<button string="组装单打印" name="assemble_single_print" type="object"
groups="sf_base.group_sf_mrp_user"
attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"
confirm="是否确认打印组装单"/>
<!-- <button string="组装单打印" name="assemble_single_print" type="object"-->
<!-- groups="sf_base.group_sf_mrp_user"-->
<!-- attrs="{'invisible': [('assemble_status', '=', '0')]}" class="btn-primary"-->
<!-- confirm="是否确认打印组装单"/>-->
<field name="assemble_status" widget="statusbar" statusbar_visible="0,1"/>
</header>
<sheet>

View File

@@ -445,6 +445,21 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
if obj.after_assembly_functional_tool_length == 0:
raise ValidationError('组装参数信息【伸出长】不能为0')
@api.constrains('rfid')
def _check_rfid(self):
self.get_rfid()
@api.onchange('rfid')
def _onchange_rfid(self):
self.get_rfid()
def get_rfid(self):
for obj in self:
if obj.rfid:
tool_entity = self.env['sf.functional.cutting.tool.entity'].sudo().search([('rfid', '=', obj.rfid)])
if tool_entity:
raise ValidationError('%s】的rfid已被使用请重新录入' % obj.rfid)
def functional_tool_assembly(self):
"""
功能刀具组装

View File

@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
import datetime
import logging
import base64
import qrcode
import io
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import UserError
@@ -220,30 +223,15 @@ class SfLocation(models.Model):
# return area_type_barcode + self.channel + self.direction + '-' + self.barcode + '-' + i_str + '-' + j_str
class ShelfLocation(models.Model):
_name = 'sf.shelf.location'
_description = '货架货位'
class SfShelf(models.Model):
_name = 'sf.shelf'
_description = '货架'
_order = 'name'
# current_location_id = fields.Many2one('sf.shelf.location', string='当前位置')
# # 目的位置
# destination_location_id = fields.Many2one('sf.shelf.location', string='目的位置')
current_move_ids = fields.One2many('stock.move.line', 'current_location_id', '当前位置调拨单')
destination_move_ids = fields.One2many('stock.move.line', 'destination_location_id', '目标位置调拨单')
storage_time = fields.Datetime('入库时间', compute='_compute_location_status')
@api.depends('location_status')
def _compute_location_status(self):
for record in self:
if record.location_status == '占用':
record.storage_time = datetime.datetime.now()
if record.location_status == '空闲':
record.storage_time = False
if record.location_status == '禁用':
record.storage_time = False
name = fields.Char('名称', required=True, size=20)
barcode = fields.Char('编码', copy=False, size=15)
name = fields.Char('货架名称', required=True, size=20)
barcode = fields.Char('编码', copy=False, size=15, required=True)
# 货位
location_ids = fields.One2many('sf.shelf.location', 'shelf_id', string='货位')
check_state = fields.Selection([
('enable', '启用'),
@@ -253,48 +241,26 @@ class ShelfLocation(models.Model):
def action_check(self):
self.check_state = 'enable'
# 仓库类别selection库区、库位、货位
location_type = fields.Selection([
('货架', '货架'),
('货位', '货位')
], string='存储类型')
# 绑定库区
shelf_location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')])
location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')])
# 产品类别 关联product.category
# product_type = fields.Many2many('product.category', string='产品类别')
shelf_location_id = fields.Many2one('stock.location', string='所属库区')
# picking_product_type = fields.Many2many('stock.picking', string='调拨产品类别', related='location_dest_id.product_type')
# 货架独有字段通道、方向、货架高度m、货架层数、层数容量
channel = fields.Char(string='通道')
channel = fields.Char(string='通道', required=True, size=10)
direction = fields.Selection([
('R', 'R'),
('L', 'L')
], string='方向')
], string='方向', required=True)
shelf_height = fields.Float(string='货架高度(m)')
shelf_layer = fields.Integer(string='货架层数')
layer_capacity = fields.Integer(string='层数容量')
# 货位独有字段:货位状态、产品(关联产品对象)、产品序列号(关联产品序列号对象)
location_status = fields.Selection([
('空闲', '空闲'),
('占用', '占用'),
('禁用', '禁用')
], string='货位状态', default='空闲', readonly=True)
# product_id = fields.Many2one('product.template', string='产品')
product_id = fields.Many2one('product.product', string='产品', compute='_compute_product_id', readonly=True)
product_sn_id = fields.Many2one('stock.lot', string='产品序列号')
# 是否有货位
is_there_area = fields.Boolean(string='是否有货位', compute='_compute_is_there_area', default=False, store=True)
hide_shelf = fields.Boolean(compute='_compute_hide_what', string='隐藏货架')
hide_location = fields.Boolean(compute='_compute_hide_what', string='隐藏货位')
# 修改货位状态为禁用
def action_location_status_disable(self):
self.location_status = '禁用'
# 修改货位状态为空闲
def action_location_status_enable(self):
self.location_status = '空闲'
@api.depends('location_ids')
def _compute_is_there_area(self):
for record in self:
record.is_there_area = bool(record.location_ids)
@api.onchange('shelf_location_id')
def _onchange_shelf_location_id(self):
@@ -307,53 +273,24 @@ class ShelfLocation(models.Model):
for location in all_location:
location.location_id = record.shelf_location_id.id
@api.depends('product_sn_id')
def _compute_product_id(self):
"""
根据产品序列号,获取产品
"""
for record in self:
if record.product_sn_id:
record.sudo().product_id = record.product_sn_id.product_id
record.sudo().location_status = '占用'
else:
record.product_id = False
# record.location_status = '空闲'
@api.depends('location_type')
def _compute_hide_what(self):
"""
根据仓库类别,隐藏不需要的字段
:return:
"""
for record in self:
record.sudo().hide_shelf = False
record.sudo().hide_location = False
if record.location_type and record.location_type == '货架':
record.sudo().hide_shelf = True
elif record.location_type and record.location_type == '货位':
record.sudo().hide_location = True
else:
pass
def create_location(self):
"""
当仓库类型为货架时,自动生成其下面的货位,数量为货架层数*层数容量
"""
if self.location_type == '货架':
for i in range(self.shelf_layer):
for j in range(self.layer_capacity):
location_name = self.name + '-' + str(i + 1) + '' + '-' + str(j + 1) + '位置'
# 检查是否已经有同名的位置存在
existing_location = self.search([('name', '=', location_name)])
if not existing_location:
self.create({
'name': location_name,
'location_id': self.shelf_location_id.id,
'location_type': '货位',
'barcode': self.generate_barcode(i, j),
'location_status': '空闲',
})
area_obj = self.env['sf.shelf.location']
for i in range(self.shelf_layer):
for j in range(self.layer_capacity):
location_name = self.name + '-' + str(i + 1) + '' + '-' + str(j + 1) + '位置'
# 检查是否已经有同名的位置存在
existing_location = area_obj.search([('name', '=', location_name)])
if not existing_location:
area_obj.create({
'name': location_name,
'location_id': self.shelf_location_id.id,
'barcode': self.generate_barcode(i, j),
'location_status': '空闲',
'shelf_id': self.id
})
def generate_barcode(self, i, j):
"""
@@ -367,6 +304,86 @@ class ShelfLocation(models.Model):
return area_type_barcode + self.channel + self.direction + '-' + self.barcode + '-' + i_str + '-' + j_str
class ShelfLocation(models.Model):
_name = 'sf.shelf.location'
_description = '货位'
_order = 'name'
# current_location_id = fields.Many2one('sf.shelf.location', string='当前位置')
# # 目的位置
# destination_location_id = fields.Many2one('sf.shelf.location', string='目的位置')
current_move_ids = fields.One2many('stock.move.line', 'current_location_id', '当前位置调拨单')
destination_move_ids = fields.One2many('stock.move.line', 'destination_location_id', '目标位置调拨单')
storage_time = fields.Datetime('入库时间', compute='_compute_location_status')
@api.depends('location_status')
def _compute_location_status(self):
for record in self:
if record.location_status == '占用':
record.storage_time = datetime.datetime.now()
if record.location_status == '空闲':
record.storage_time = False
if record.location_status == '禁用':
record.storage_time = False
name = fields.Char('货位名称', required=True, size=20)
barcode = fields.Char('货位编码', copy=False, size=15)
# 货架
shelf_id = fields.Many2one('sf.shelf', string='货架')
check_state = fields.Selection([
('enable', '启用'),
('close', '关闭')
], string='审核状态', default='close')
def action_check(self):
self.check_state = 'enable'
# # 仓库类别selection库区、库位、货位
# location_type = fields.Selection([
# ('货架', '货架'),
# ('货位', '货位')
# ], string='存储类型')
# 绑定库区
# shelf_location_id = fields.Many2one('stock.location', string='所属库区', domain=[('location_type', '=', '库区')])
location_id = fields.Many2one('stock.location', string='所属库区')
# 产品类别 关联product.category
# product_type = fields.Many2many('product.category', string='产品类别')
# picking_product_type = fields.Many2many('stock.picking', string='调拨产品类别', related='location_dest_id.product_type')
# 货位独有字段:货位状态、产品(关联产品对象)、产品序列号(关联产品序列号对象)
location_status = fields.Selection([
('空闲', '空闲'),
('占用', '占用'),
('禁用', '禁用')
], string='货位状态', default='空闲', readonly=True)
# product_id = fields.Many2one('product.template', string='产品')
product_id = fields.Many2one('product.product', string='产品', compute='_compute_product_id', readonly=True)
product_sn_id = fields.Many2one('stock.lot', string='产品序列号')
# 修改货位状态为禁用
def action_location_status_disable(self):
self.location_status = '禁用'
# 修改货位状态为空闲
def action_location_status_enable(self):
self.location_status = '空闲'
@api.depends('product_sn_id')
def _compute_product_id(self):
"""
根据产品序列号,获取产品
"""
for record in self:
if record.product_sn_id:
record.sudo().product_id = record.product_sn_id.product_id
record.sudo().location_status = '占用'
else:
record.product_id = False
record.location_status = '空闲'
class Sf_stock_move_line(models.Model):
_inherit = 'stock.move.line'
@@ -375,6 +392,112 @@ class Sf_stock_move_line(models.Model):
# location_dest_id = fields.Many2one('stock.location', string='目标库位')
location_dest_id_product_type = fields.Many2many(related='location_dest_id.product_type')
location_dest_id_value = fields.Integer(compute='_compute_location_dest_id_value', store=True)
# lot_qr_code = fields.Binary(string='二维码', compute='_compute_lot_qr_code', store=True)
lot_qr_code = fields.Binary(string='二维码', compute='_compute_lot_qr_code', store=True)
@api.depends('lot_name')
def _compute_lot_qr_code(self):
for record in self:
if record.lot_id:
# record.lot_qr_code = record.lot_id.lot_qr_code
# 创建一个QRCode对象
qr = qrcode.QRCode(
version=1, # 设置版本, 1-40控制二维码的大小
error_correction=qrcode.constants.ERROR_CORRECT_L, # 设置错误校正等级
box_size=10, # 设置每个格子的像素大小
border=4, # 设置边框的格子宽度
)
# 添加数据
qr.add_data(record.lot_id.name)
qr.make(fit=True)
# 创建二维码图像
img = qr.make_image(fill_color="black", back_color="white")
# 创建一个内存文件
buffer = io.BytesIO()
img.save(buffer, format="PNG") # 将图像保存到内存文件中
# 获取二进制数据
binary_data = buffer.getvalue()
# 使用Base64编码这些二进制数据
data = base64.b64encode(binary_data)
self.lot_qr_code = data
else:
record.lot_qr_code = False
def print_qr_code(self):
self.ensure_one() # 确保这个方法只为一个记录调用
# if not self.lot_id:
# raise UserError("没有找到序列号。")
# 假设_lot_qr_code方法已经生成了二维码并保存在字段中
qr_code_data = self.lot_qr_code
if not qr_code_data:
raise UserError("没有找到二维码数据。")
# 生成下载链接或直接触发下载
# 此处的实现依赖于你的具体需求,以下是触发下载的一种示例
attachment = self.env['ir.attachment'].sudo().create({
'datas': self.lot_qr_code,
'type': 'binary',
'description': '二维码图片',
'name': self.lot_name + '.png',
# 'res_id': invoice.id,
# 'res_model': 'stock.picking',
'public': True,
'mimetype': 'application/x-png',
# 'model_name': 'stock.picking',
})
# 返回附件的下载链接
download_url = '/web/content/%s?download=true' % attachment.id
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
return {
'type': 'ir.actions.act_url',
'url': str(base_url) + download_url,
'target': 'self',
}
# # # 定义一个方法,用于根据序列号生成二维码
# # @api.depends('lot_id')
# def generate_lot_qr_code(self):
# # 创建一个QRCode对象
# qr = qrcode.QRCode(
# version=1, # 设置版本, 1-40控制二维码的大小
# error_correction=qrcode.constants.ERROR_CORRECT_L, # 设置错误校正等级
# box_size=10, # 设置每个格子的像素大小
# border=4, # 设置边框的格子宽度
# )
#
# # 添加数据
# qr.add_data(self.lot_id.name)
# qr.make(fit=True)
#
# # 创建二维码图像
# img = qr.make_image(fill_color="black", back_color="white")
#
# # 创建一个内存文件
# buffer = io.BytesIO()
# img.save(buffer, format="PNG") # 将图像保存到内存文件中
#
# # 获取二进制数据
# binary_data = buffer.getvalue()
#
# # 使用Base64编码这些二进制数据
# data = base64.b64encode(binary_data)
# self.lot_qr_code = data
# attachment = self.env['ir.attachment'].sudo().create({
# 'datas': data,
# 'type': 'binary',
# 'description': '二维码图片',
# 'name': self.lot_id.name + '.png',
# # 'res_id': invoice.id,
# # 'res_model': 'stock.picking',
# 'public': True,
# 'mimetype': 'application/pdf',
# # 'model_name': 'stock.picking',
# })
# def button_test(self):
# print(self.picking_id.name)
@@ -652,3 +775,35 @@ class SfStockScrap(models.Model):
def action_check(self):
self.check_state = 'enable'
class CustomStockMove(models.Model):
_inherit = 'stock.move'
def action_assign_serial_show_details(self):
# 首先执行原有逻辑
result = super(CustomStockMove, self).action_assign_serial_show_details()
# 接着为每个 lot_name 生成二维码
move_lines = self.move_line_ids # 获取当前 stock.move 对应的所有 stock.move.line 记录
for line in move_lines:
if line.lot_name: # 确保 lot_name 存在
qr_data = self.compute_lot_qr_code(line.lot_name)
# 假设 stock.move.line 模型中有一个字段叫做 lot_qr_code 用于存储二维码数据
line.lot_qr_code = qr_data
return result
def compute_lot_qr_code(self, lot_name):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(lot_name)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = io.BytesIO()
img.save(buffer, format="PNG")
binary_data = buffer.getvalue()
data = base64.b64encode(binary_data).decode() # 确保返回的是字符串形式的数据
return data

View File

@@ -1,7 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_shelf_location,sf.shelf.location,model_sf_shelf_location,sf_warehouse.group_sf_stock_user,1,0,0,0
access_sf_shelf_location,sf.shelf.location,model_sf_shelf_location,sf_warehouse.group_sf_stock_manager,1,1,1,0
access_sf_shelf_location_group_sf_stock_user,sf.shelf.location,model_sf_shelf_location,sf_warehouse.group_sf_stock_user,1,0,0,0
access_sf_shelf_location_group_sf_stock_manager,sf.shelf.location,model_sf_shelf_location,sf_warehouse.group_sf_stock_manager,1,1,1,0
access_sf_shelf_group_sf_stock_user,sf.shelf.group.sf.stock.user,model_sf_shelf,sf_warehouse.group_sf_stock_user,1,0,0,0
access_sf_shelf_group_sf_stock_manager,sf.shelf.group.sf.stock.manager,model_sf_shelf,sf_warehouse.group_sf_stock_manager,1,1,1,0
access_procurement_group,procurement.group,stock.model_procurement_group,base.group_user,1,1,1,0
access_stock_warehouse_manager,stock.warehouse.manager,stock.model_stock_warehouse,sf_warehouse.group_sf_stock_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_shelf_location access_sf_shelf_location_group_sf_stock_user sf.shelf.location model_sf_shelf_location sf_warehouse.group_sf_stock_user 1 0 0 0
3 access_sf_shelf_location access_sf_shelf_location_group_sf_stock_manager sf.shelf.location model_sf_shelf_location sf_warehouse.group_sf_stock_manager 1 1 1 0
4 access_procurement_group access_sf_shelf_group_sf_stock_user procurement.group sf.shelf.group.sf.stock.user stock.model_procurement_group model_sf_shelf base.group_user sf_warehouse.group_sf_stock_user 1 1 0 1 0 0
5 access_sf_shelf_group_sf_stock_manager sf.shelf.group.sf.stock.manager model_sf_shelf sf_warehouse.group_sf_stock_manager 1 1 1 0
6 access_procurement_group procurement.group stock.model_procurement_group base.group_user 1 1 1 0
7 access_stock_warehouse_manager stock.warehouse.manager stock.model_stock_warehouse sf_warehouse.group_sf_stock_user 1 1 1 0
8 access_stock_warehouse_user stock.warehouse.user stock.model_stock_warehouse base.group_user 1 0 0 0
9 access_stock_location_partner_manager stock.location.partner.manager stock.model_stock_location base.group_partner_manager 1 0 0 0

View File

@@ -11,7 +11,6 @@
</xpath>
<xpath expr="//field[@name='location_dest_id'][2]" position="after">
<field name="destination_location_id" domain="[
('location_type', '=', '货位'),
('location_id', '=', location_dest_id_value),
('location_status', '=', '空闲')
]"/>
@@ -51,6 +50,22 @@
</xpath>
<xpath expr="//form//sheet//group//group//field[@name='location_dest_id']" position="after">
<field name="destination_location_id" options="{'no_create': False}"/>
</xpath>
<xpath expr="//form//sheet//group//group//field[@name='create_uid']" position="after">
<field name="lot_qr_code" widget="image"/>
</xpath>
</field>
</record>
<record id="sf_view_stock_move_line_operation_tree" model="ir.ui.view">
<field name="name">sf.stock.move.line.operation.tree</field>
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_stock_move_line_operation_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='product_uom_id']" position="after">
<field name="lot_qr_code" widget="image"/>
<button name="print_qr_code" string="打印编码" type="object" class="oe_highlight"/>
</xpath>
</field>
</record>

View File

@@ -1,14 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- 货架视图 -->
<record id="view_sf_shelf" model="ir.ui.view">
<field name="name">Sf Shelf</field>
<field name="model">sf.shelf</field>
<field name="arch" type="xml">
<form string="Sf Shelf">
<header>
<field name="is_there_area" invisible="1"/>
<button string="生成货位" name="create_location" type="object" class="oe_highlight" attrs="{'invisible': [('is_there_area', '=', True)]}"/>
</header>
<sheet>
<group>
<field name="barcode" string="货架编码"/>
<field name="name" string="货架名称"/>
<field name="check_state" string="审核状态"/>
<field name="channel" string="通道"/>
<field name="shelf_location_id" string="所属库区"/>
<field name="direction" string="方向"/>
<field name="shelf_height" string="货架高度(m)"/>
<field name="shelf_layer" string="货架层数"/>
<field name="layer_capacity" string="层数容量"/>
</group>
<field name="location_ids" widget="one2many_list">
<tree string="Shelf Location">
<field name="barcode" string="编码"/>
<field name="name" string="名称"/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="view_sf_shelf_tree" model="ir.ui.view">
<field name="name">Sf Shelf tree</field>
<field name="model">sf.shelf</field>
<field name="arch" type="xml">
<tree string="Sf Shelf">
<field name="barcode" string="货架编码"/>
<field name="name" string="名称"/>
<field name="shelf_location_id" string="所属库区"/>
</tree>
</field>
</record>
<!-- 货架action -->
<record id="sf_shelf_action" model="ir.actions.act_window">
<field name="name">货架</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.shelf</field>
<field name="view_mode">tree,form</field>
<!-- <field name="view_id" ref="view_sf_shelf_tree"/> -->
</record>
<!-- 货架菜单 -->
<menuitem
id="sf_shelf_menu"
name="货架"
parent="stock.menu_warehouse_config"
sequence="19"
action="sf_shelf_action"
groups="sf_warehouse.group_sf_stock_user"/>
<record id="view_shelf_location_tree" model="ir.ui.view">
<field name="name">shelf.location.tree</field>
<field name="model">sf.shelf.location</field>
<field name="arch" type="xml">
<tree string="Shelf Location">
<field name="name" string="名称"/>
<field name="barcode" string="编码"/>
<field name="location_type"/>
<field name="barcode"/>
<field name="name"/>
<field name="location_id"/>
<!-- <field name="check_state" widget="label_selection"-->
<!-- options="{'classes': {'unchecked':'warning','checked': 'success'}}"/>-->
<!-- <button name="action_check" string="审核" type="object"-->
@@ -53,9 +117,9 @@
<field name="model">sf.shelf.location</field>
<field name="arch" type="xml">
<form string="Shelf Location">
<header>
<field name="location_status" invisible="1"/>
<button string="生成货位" name="create_location" type="object" class="oe_highlight"
attrs="{'invisible': [('hide_shelf', '=', False)]}"/>
<button string="禁用货位" name="action_location_status_disable" type="object"
@@ -64,6 +128,11 @@
<button string="启用货位" name="action_location_status_enable" type="object"
class="oe_highlight"
attrs="{'invisible': ['|', ('hide_shelf', '=', True), ('location_status', '!=', '禁用')]}"/>
<button string="禁用货位" name="action_location_status_disable" type="object" class="oe_highlight"
attrs="{'invisible': [('location_status', '!=', '空闲')]}"/>
<button string="启用货位" name="action_location_status_enable" type="object" class="oe_highlight"
attrs="{'invisible': [('location_status', '!=', '禁用')]}"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
@@ -72,7 +141,6 @@
class="oe_stat_button"
context="{'search_default_current_location_id': [active_id]}"
icon="fa-exchange">
<field string="当前位置历史" name="current_move_ids" widget="statinfo"/>
</button>
<button name="%(stock_move_line_action1)d"
@@ -80,36 +148,17 @@
class="oe_stat_button"
context="{'search_default_destination_location_id': [active_id]}"
icon="fa-exchange">
<field string="目标位置历史" name="destination_move_ids" widget="statinfo"/>
</button>
</div>
<group>
<field name="hide_shelf" invisible="1"/>
<field name="hide_location" invisible="1"/>
<field name="name" string="名称"/>
<field name="barcode" string="编码"/>
<field name="location_type"/>
<field name="shelf_location_id" attrs="{'invisible': [('location_type', '=', '货位')]}"/>
<field name="location_id"
attrs="{'readonly': [('location_type', '=', '货位')], 'invisible': [('location_type', '=', '货架')]}"/>
<field name="channel"
attrs="{'invisible': [('hide_shelf', '=', False)], 'required': [('hide_shelf', '!=', False)]}"/>
<field name="direction"
attrs="{'invisible': [('hide_shelf', '=', False)], 'required': [('hide_shelf', '!=', False)]}"/>
<field name="product_sn_id" attrs="{'invisible': [('hide_location', '=', False)]}"/>
<!-- <field name="product_type" widget="many2many_tags"/> -->
<field name="shelf_height"
attrs="{'invisible': [('hide_shelf', '=', False)], 'required': [('hide_shelf', '!=', False)]}"/>
<field name="shelf_layer"
attrs="{'invisible': [('hide_shelf', '=', False)], 'required': [('hide_shelf', '!=', False)]}"/>
<field name="layer_capacity"
attrs="{'invisible': [('hide_shelf', '=', False)], 'required': [('hide_shelf', '!=', False)]}"/>
<!-- <field name="product_id" attrs="{'invisible': [('hide_location', '=', False)], 'required': [('hide_location', '!=', False), ('location_status', '=', '占用')]}"/> -->
<field name="product_id" attrs="{'invisible': [('hide_location', '=', False)]}"/>
<!-- <field name="product_type" attrs="{'invisible': [('hide_location', '=', False)], 'required': [('hide_location', '!=', False), ('location_status', '=', '占用')]}" widget="many2many_tags"/> -->
<field name="location_status"
attrs="{'invisible': [('hide_location', '=', False)], 'required': [('hide_location', '!=', False)]}"/>
<field name="barcode"/>
<field name="name"/>
<field name="shelf_id"/>
<field name="location_id"/>
<field name="product_sn_id"/>
<field name="product_id"/>
<field name="location_status"/>
<field name="storage_time" widget="datetime"/>
</group>
</sheet>
@@ -128,13 +177,13 @@
#{record.location_status.raw_value == '空闲' ? 'kanban_color_1' : ''}
#{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''}
#{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}">
<!-- 标题 -->
<!-- 标题 -->
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title">
<field name="name"/>
</div>
</div>
<!-- 内容 -->
<!-- 内容 -->
<div class="o_kanban_record_bottom">
<field name="location_status"/>
</div>
@@ -145,31 +194,31 @@
</div>
</div>
</t>
<!-- <t t-name="kanban-box"> -->
<!-- <div t-attf-class="oe_kanban_card oe_kanban_global_click -->
<!-- #{record.location_status.raw_value == '空闲' ? 'kanban_color_1' : ''} -->
<!-- #{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''} -->
<!-- #{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}"> -->
<!-- --><!-- 看板内容 -->
<!-- </div> -->
<!-- <div t-attf-class="oe_kanban_card"> -->
<!-- --><!-- 标题 -->
<!-- <div class="o_kanban_card_header"> -->
<!-- <div class="o_kanban_card_header_title"> -->
<!-- <field name="name"/> -->
<!-- </div> -->
<!-- </div> -->
<!-- --><!-- 内容 -->
<!-- <div class="o_kanban_record_bottom"> -->
<!-- <field name="location_status"/> -->
<!-- </div> -->
<!-- <div class="o_kanban_record_bottom"> -->
<!-- <field name="product_sn_id"/> -->
<!-- <span> | </span> -->
<!-- <field name="product_id"/> -->
<!-- </div> -->
<!-- </div> -->
<!-- </t> -->
<!-- <t t-name="kanban-box"> -->
<!-- <div t-attf-class="oe_kanban_card oe_kanban_global_click -->
<!-- #{record.location_status.raw_value == '空闲' ? 'kanban_color_1' : ''} -->
<!-- #{record.location_status.raw_value == '占用' ? 'kanban_color_2' : ''} -->
<!-- #{record.location_status.raw_value == '禁用' ? 'kanban_color_3' : ''}"> -->
<!-- 看板内容 -->
<!-- </div> -->
<!-- <div t-attf-class="oe_kanban_card"> -->
<!-- 标题 -->
<!-- <div class="o_kanban_card_header"> -->
<!-- <div class="o_kanban_card_header_title"> -->
<!-- <field name="name"/> -->
<!-- </div> -->
<!-- </div> -->
<!-- 内容 -->
<!-- <div class="o_kanban_record_bottom"> -->
<!-- <field name="location_status"/> -->
<!-- </div> -->
<!-- <div class="o_kanban_record_bottom"> -->
<!-- <field name="product_sn_id"/> -->
<!-- <span> | </span> -->
<!-- <field name="product_id"/> -->
<!-- </div> -->
<!-- </div> -->
<!-- </t> -->
</templates>
</kanban>
</field>
@@ -180,10 +229,12 @@
<field name="name">shelf.location.search</field>
<field name="model">sf.shelf.location</field>
<field name="arch" type="xml">
<search string="货架货位">
<search string="货位">
<searchpanel class="account_root">
<!-- <field name="location_type" icon="fa-filter"/> -->
<field name="location_id" select="multi" icon="fa-filter"/>
<!-- <field name="location_id" select="multi" icon="fa-filter"/> -->
<field name="location_id" string="所属库区" icon="fa-filter"/>
<field name="shelf_id" string="货架"/>
<!-- <field name="location_status" icon="fa-filter"/> -->
</searchpanel>
</search>
@@ -191,11 +242,11 @@
</record>
<record id="shelf_location_kanban_action_id" model="ir.actions.act_window">
<field name="name">架货</field>
<field name="name">货位看板</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.shelf.location</field>
<field name="view_mode">kanban,form</field>
<field name="domain">[('location_type', '=', '货位'),('check_state','=','enable')]</field>
<!-- <field name="domain">[('check_state','=','enable')]</field> -->
</record>
<!-- <record id="example_action" model="ir.actions.act_window"> -->
@@ -212,7 +263,7 @@
<record id="action_sf_shelf_location" model="ir.actions.act_window">
<field name="name">架货</field>
<field name="name">货位</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.shelf.location</field>
<field name="view_mode">tree,form</field>
@@ -235,15 +286,14 @@
<!-- sequence="50" -->
<!-- action="kanban_action_id"/> -->
<menuitem id="shelf_location_kanban_menu" name="货位看板" parent="stock.menu_stock_root"
sequence="51"
action="shelf_location_kanban_action_id"
groups="sf_warehouse.group_sf_stock_user"/>
<menuitem id="menu_sf_shelf_location" name="货架货位" parent="stock.menu_warehouse_config"
sequence="2"
action="action_sf_shelf_location"
groups="sf_warehouse.group_sf_stock_user"/>
sequence="20"/>
</data>